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