chiark / gitweb /
3b19d430b27bca1c67a3ee7a0faf26de169e3883
[elogind.git] / src / hostname / hostnamed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <dlfcn.h>
26
27 #include "util.h"
28 #include "strv.h"
29 #include "def.h"
30 #include "virt.h"
31 #include "env-util.h"
32 #include "fileio-label.h"
33 #include "label.h"
34 #include "bus-util.h"
35 #include "event-util.h"
36
37 enum {
38         PROP_HOSTNAME,
39         PROP_STATIC_HOSTNAME,
40         PROP_PRETTY_HOSTNAME,
41         PROP_ICON_NAME,
42         PROP_CHASSIS,
43         PROP_OS_PRETTY_NAME,
44         PROP_OS_CPE_NAME,
45         _PROP_MAX
46 };
47
48 typedef struct Context {
49         char *data[_PROP_MAX];
50         Hashmap *polkit_registry;
51 } Context;
52
53 static void context_reset(Context *c) {
54         int p;
55
56         assert(c);
57
58         for (p = 0; p < _PROP_MAX; p++) {
59                 free(c->data[p]);
60                 c->data[p] = NULL;
61         }
62 }
63
64 static void context_free(Context *c, sd_bus *bus) {
65         assert(c);
66
67         context_reset(c);
68         bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
69 }
70
71 static int context_read_data(Context *c) {
72         int r;
73
74         assert(c);
75
76         context_reset(c);
77
78         c->data[PROP_HOSTNAME] = gethostname_malloc();
79         if (!c->data[PROP_HOSTNAME])
80                 return -ENOMEM;
81
82         r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
83         if (r < 0 && r != -ENOENT)
84                 return r;
85
86         r = parse_env_file("/etc/machine-info", NEWLINE,
87                            "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
88                            "ICON_NAME", &c->data[PROP_ICON_NAME],
89                            "CHASSIS", &c->data[PROP_CHASSIS],
90                            NULL);
91         if (r < 0 && r != -ENOENT)
92                 return r;
93
94         r = parse_env_file("/etc/os-release", NEWLINE,
95                            "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
96                            "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
97                            NULL);
98         if (r < 0 && r != -ENOENT)
99                 return r;
100
101         return 0;
102 }
103
104 static bool check_nss(void) {
105         void *dl;
106
107         dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
108         if (dl) {
109                 dlclose(dl);
110                 return true;
111         }
112
113         return false;
114 }
115
116 static bool valid_chassis(const char *chassis) {
117
118         assert(chassis);
119
120         return nulstr_contains(
121                         "vm\0"
122                         "container\0"
123                         "desktop\0"
124                         "laptop\0"
125                         "server\0"
126                         "tablet\0"
127                         "handset\0",
128                         chassis);
129 }
130
131 static const char* fallback_chassis(void) {
132         int r;
133         char *type;
134         unsigned t;
135         int v;
136
137         v = detect_virtualization(NULL);
138
139         if (v == VIRTUALIZATION_VM)
140                 return "vm";
141         if (v == VIRTUALIZATION_CONTAINER)
142                 return "container";
143
144         r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
145         if (r < 0)
146                 goto try_dmi;
147
148         r = safe_atou(type, &t);
149         free(type);
150         if (r < 0)
151                 goto try_dmi;
152
153         /* We only list the really obvious cases here as the ACPI data
154          * is not really super reliable.
155          *
156          * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
157          *
158          * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
159          */
160
161         switch(t) {
162
163         case 1:
164         case 3:
165         case 6:
166                 return "desktop";
167
168         case 2:
169                 return "laptop";
170
171         case 4:
172         case 5:
173         case 7:
174                 return "server";
175
176         case 8:
177                 return "tablet";
178         }
179
180 try_dmi:
181         r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
182         if (r < 0)
183                 return NULL;
184
185         r = safe_atou(type, &t);
186         free(type);
187         if (r < 0)
188                 return NULL;
189
190         /* We only list the really obvious cases here. The DMI data is
191            unreliable enough, so let's not do any additional guesswork
192            on top of that.
193
194            See the SMBIOS Specification 2.7.1 section 7.4.1 for
195            details about the values listed here:
196
197            http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
198          */
199
200         switch (t) {
201
202         case 0x3:
203         case 0x4:
204         case 0x6:
205         case 0x7:
206                 return "desktop";
207
208         case 0x8:
209         case 0x9:
210         case 0xA:
211         case 0xE:
212                 return "laptop";
213
214         case 0xB:
215                 return "handset";
216
217         case 0x11:
218         case 0x1C:
219                 return "server";
220         }
221
222         return NULL;
223 }
224
225 static char* context_fallback_icon_name(Context *c) {
226         const char *chassis;
227
228         assert(c);
229
230         if (!isempty(c->data[PROP_CHASSIS]))
231                 return strappend("computer-", c->data[PROP_CHASSIS]);
232
233         chassis = fallback_chassis();
234         if (chassis)
235                 return strappend("computer-", chassis);
236
237         return strdup("computer");
238 }
239
240 static int context_write_data_hostname(Context *c) {
241         const char *hn;
242
243         assert(c);
244
245         if (isempty(c->data[PROP_HOSTNAME]))
246                 hn = "localhost";
247         else
248                 hn = c->data[PROP_HOSTNAME];
249
250         if (sethostname(hn, strlen(hn)) < 0)
251                 return -errno;
252
253         return 0;
254 }
255
256 static int context_write_data_static_hostname(Context *c) {
257
258         assert(c);
259
260         if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
261
262                 if (unlink("/etc/hostname") < 0)
263                         return errno == ENOENT ? 0 : -errno;
264
265                 return 0;
266         }
267         return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
268 }
269
270 static int context_write_data_machine_info(Context *c) {
271
272         static const char * const name[_PROP_MAX] = {
273                 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
274                 [PROP_ICON_NAME] = "ICON_NAME",
275                 [PROP_CHASSIS] = "CHASSIS"
276         };
277
278         _cleanup_strv_free_ char **l = NULL;
279         int r, p;
280
281         assert(c);
282
283         r = load_env_file("/etc/machine-info", NULL, &l);
284         if (r < 0 && r != -ENOENT)
285                 return r;
286
287         for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
288                 char *t, **u;
289
290                 assert(name[p]);
291
292                 if (isempty(c->data[p]))  {
293                         strv_env_unset(l, name[p]);
294                         continue;
295                 }
296
297                 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
298                         return -ENOMEM;
299
300                 u = strv_env_set(l, t);
301                 free(t);
302
303                 if (!u)
304                         return -ENOMEM;
305
306                 strv_free(l);
307                 l = u;
308         }
309
310         if (strv_isempty(l)) {
311
312                 if (unlink("/etc/machine-info") < 0)
313                         return errno == ENOENT ? 0 : -errno;
314
315                 return 0;
316         }
317
318         return write_env_file_label("/etc/machine-info", l);
319 }
320
321 static int property_get_icon_name(
322                 sd_bus *bus,
323                 const char *path,
324                 const char *interface,
325                 const char *property,
326                 sd_bus_message *reply,
327                 void *userdata,
328                 sd_bus_error *error) {
329
330         _cleanup_free_ char *n = NULL;
331         Context *c = userdata;
332         const char *name;
333
334         if (isempty(c->data[PROP_ICON_NAME]))
335                 name = n = context_fallback_icon_name(c);
336         else
337                 name = c->data[PROP_ICON_NAME];
338
339         if (!name)
340                 return -ENOMEM;
341
342         return sd_bus_message_append(reply, "s", name);
343 }
344
345 static int property_get_chassis(
346                 sd_bus *bus,
347                 const char *path,
348                 const char *interface,
349                 const char *property,
350                 sd_bus_message *reply,
351                 void *userdata,
352                 sd_bus_error *error) {
353
354         Context *c = userdata;
355         const char *name;
356
357         if (isempty(c->data[PROP_CHASSIS]))
358                 name = fallback_chassis();
359         else
360                 name = c->data[PROP_CHASSIS];
361
362         return sd_bus_message_append(reply, "s", name);
363 }
364
365 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
366         Context *c = userdata;
367         const char *name;
368         int interactive;
369         char *h;
370         int r;
371
372         r = sd_bus_message_read(m, "sb", &name, &interactive);
373         if (r < 0)
374                 return r;
375
376         if (isempty(name))
377                 name = c->data[PROP_STATIC_HOSTNAME];
378
379         if (isempty(name))
380                 name = "localhost";
381
382         if (!hostname_is_valid(name))
383                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
384
385         if (streq_ptr(name, c->data[PROP_HOSTNAME]))
386                 return sd_bus_reply_method_return(m, NULL);
387
388         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
389         if (r < 0)
390                 return r;
391         if (r == 0)
392                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
393
394         h = strdup(name);
395         if (!h)
396                 return -ENOMEM;
397
398         free(c->data[PROP_HOSTNAME]);
399         c->data[PROP_HOSTNAME] = h;
400
401         r = context_write_data_hostname(c);
402         if (r < 0) {
403                 log_error("Failed to set host name: %s", strerror(-r));
404                 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
405         }
406
407         log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
408
409         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
410
411         return sd_bus_reply_method_return(m, NULL);
412 }
413
414 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
415         Context *c = userdata;
416         const char *name;
417         int interactive;
418         int r;
419
420         r = sd_bus_message_read(m, "sb", &name, &interactive);
421         if (r < 0)
422                 return r;
423
424         if (isempty(name))
425                 name = NULL;
426
427         if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
428                 return sd_bus_reply_method_return(m, NULL);
429
430         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
431         if (r < 0)
432                 return r;
433         if (r == 0)
434                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
435
436         if (isempty(name)) {
437                 free(c->data[PROP_STATIC_HOSTNAME]);
438                 c->data[PROP_STATIC_HOSTNAME] = NULL;
439         } else {
440                 char *h;
441
442                 if (!hostname_is_valid(name))
443                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
444
445                 h = strdup(name);
446                 if (!h)
447                         return -ENOMEM;
448
449                 free(c->data[PROP_STATIC_HOSTNAME]);
450                 c->data[PROP_STATIC_HOSTNAME] = h;
451         }
452
453         r = context_write_data_static_hostname(c);
454         if (r < 0) {
455                 log_error("Failed to write static host name: %s", strerror(-r));
456                 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
457         }
458
459         log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
460
461         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
462
463         return sd_bus_reply_method_return(m, NULL);
464 }
465
466 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb, sd_bus_error *error) {
467         int interactive;
468         const char *name;
469         int r;
470
471         assert(c);
472         assert(bus);
473         assert(m);
474
475         r = sd_bus_message_read(m, "sb", &name, &interactive);
476         if (r < 0)
477                 return r;
478
479         if (isempty(name))
480                 name = NULL;
481
482         if (streq_ptr(name, c->data[prop]))
483                 return sd_bus_reply_method_return(m, NULL);
484
485         /* Since the pretty hostname should always be changed at the
486          * same time as the static one, use the same policy action for
487          * both... */
488
489         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
490                           "org.freedesktop.hostname1.set-static-hostname" :
491                           "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
492         if (r < 0)
493                 return r;
494         if (r == 0)
495                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
496
497         if (isempty(name)) {
498                 free(c->data[prop]);
499                 c->data[prop] = NULL;
500         } else {
501                 char *h;
502
503                 /* The icon name might ultimately be used as file
504                  * name, so better be safe than sorry */
505
506                 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
507                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
508                 if (prop == PROP_PRETTY_HOSTNAME &&
509                     (string_has_cc(name) || chars_intersect(name, "\t")))
510                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
511                 if (prop == PROP_CHASSIS && !valid_chassis(name))
512                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
513
514                 h = strdup(name);
515                 if (!h)
516                         return -ENOMEM;
517
518                 free(c->data[prop]);
519                 c->data[prop] = h;
520         }
521
522         r = context_write_data_machine_info(c);
523         if (r < 0) {
524                 log_error("Failed to write machine info: %s", strerror(-r));
525                 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
526         }
527
528         log_info("Changed %s to '%s'",
529                  prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
530                  prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
531
532         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
533                                        prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
534                                        prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
535
536         return sd_bus_reply_method_return(m, NULL);
537 }
538
539 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
540         return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
541 }
542
543 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
544         return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
545 }
546
547 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
548         return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
549 }
550
551 static const sd_bus_vtable hostname_vtable[] = {
552         SD_BUS_VTABLE_START(0),
553         SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
554         SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
555         SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
556         SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
557         SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
558         SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
559         SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
560         SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
561         SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
562         SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
563         SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
564         SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
565         SD_BUS_VTABLE_END,
566 };
567
568 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
569         _cleanup_bus_unref_ sd_bus *bus = NULL;
570         int r;
571
572         assert(c);
573         assert(event);
574         assert(_bus);
575
576         r = sd_bus_default_system(&bus);
577         if (r < 0) {
578                 log_error("Failed to get system bus connection: %s", strerror(-r));
579                 return r;
580         }
581
582         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
583         if (r < 0) {
584                 log_error("Failed to register object: %s", strerror(-r));
585                 return r;
586         }
587
588         r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
589         if (r < 0) {
590                 log_error("Failed to register name: %s", strerror(-r));
591                 return r;
592         }
593
594         r = sd_bus_attach_event(bus, event, 0);
595         if (r < 0) {
596                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
597                 return r;
598         }
599
600         *_bus = bus;
601         bus = NULL;
602
603         return 0;
604 }
605
606 int main(int argc, char *argv[]) {
607         Context context = {};
608
609         _cleanup_event_unref_ sd_event *event = NULL;
610         _cleanup_bus_unref_ sd_bus *bus = NULL;
611         int r;
612
613         log_set_target(LOG_TARGET_AUTO);
614         log_parse_environment();
615         log_open();
616
617         umask(0022);
618         label_init("/etc");
619
620         if (argc != 1) {
621                 log_error("This program takes no arguments.");
622                 r = -EINVAL;
623                 goto finish;
624         }
625
626         if (!check_nss())
627                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
628
629         if (argc != 1) {
630                 log_error("This program takes no arguments.");
631                 r = -EINVAL;
632                 goto finish;
633         }
634
635         r = sd_event_default(&event);
636         if (r < 0) {
637                 log_error("Failed to allocate event loop: %s", strerror(-r));
638                 goto finish;
639         }
640
641         sd_event_set_watchdog(event, true);
642
643         r = connect_bus(&context, event, &bus);
644         if (r < 0)
645                 goto finish;
646
647         r = context_read_data(&context);
648         if (r < 0) {
649                 log_error("Failed to read hostname and machine information: %s", strerror(-r));
650                 goto finish;
651         }
652
653         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
654         if (r < 0) {
655                 log_error("Failed to run event loop: %s", strerror(-r));
656                 goto finish;
657         }
658
659 finish:
660         context_free(&context, bus);
661
662         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
663 }