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