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