chiark / gitweb /
hostnamed: modernizations
[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         _cleanup_strv_free_ 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                         return -ENOMEM;
290
291                 u = strv_env_set(l, t);
292                 free(t);
293
294                 if (!u)
295                         return -ENOMEM;
296
297                 strv_free(l);
298                 l = u;
299         }
300
301         if (strv_isempty(l)) {
302
303                 if (unlink("/etc/machine-info") < 0)
304                         return errno == ENOENT ? 0 : -errno;
305
306                 return 0;
307         }
308
309         return write_env_file_label("/etc/machine-info", l);
310 }
311
312 static int property_get_icon_name(
313                 sd_bus *bus,
314                 const char *path,
315                 const char *interface,
316                 const char *property,
317                 sd_bus_message *reply,
318                 sd_bus_error *error,
319                 void *userdata) {
320
321         _cleanup_free_ char *n = NULL;
322         Context *c = userdata;
323         const char *name;
324         int r;
325
326         if (isempty(c->data[PROP_ICON_NAME]))
327                 name = n = context_fallback_icon_name(c);
328         else
329                 name = c->data[PROP_ICON_NAME];
330
331         if (!name)
332                 return -ENOMEM;
333
334         r = sd_bus_message_append(reply, "s", name);
335         if (r < 0)
336                 return r;
337
338         return 1;
339 }
340
341 static int property_get_chassis(
342                 sd_bus *bus,
343                 const char *path,
344                 const char *interface,
345                 const char *property,
346                 sd_bus_message *reply,
347                 sd_bus_error *error,
348                 void *userdata) {
349
350         Context *c = userdata;
351         const char *name;
352         int r;
353
354         if (isempty(c->data[PROP_CHASSIS]))
355                 name = fallback_chassis();
356         else
357                 name = c->data[PROP_CHASSIS];
358
359         r = sd_bus_message_append(reply, "s", name);
360         if (r < 0)
361                 return r;
362
363         return 1;
364 }
365
366 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
367         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
368         Context *c = userdata;
369         const char *name;
370         int interactive;
371         char *h;
372         int r;
373
374         r = sd_bus_message_read(m, "sb", &name, &interactive);
375         if (r < 0)
376                 return sd_bus_reply_method_errno(m, r, NULL);
377
378         if (isempty(name))
379                 name = c->data[PROP_STATIC_HOSTNAME];
380
381         if (isempty(name))
382                 name = "localhost";
383
384         if (!hostname_is_valid(name))
385                 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
386
387         if (streq_ptr(name, c->data[PROP_HOSTNAME]))
388                 return sd_bus_reply_method_return(m, NULL);
389
390         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, &error, method_set_hostname, c);
391         if (r < 0)
392                 return sd_bus_reply_method_errno(m, r, &error);
393         if (r == 0)
394                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
395
396         h = strdup(name);
397         if (!h)
398                 return log_oom();
399
400         free(c->data[PROP_HOSTNAME]);
401         c->data[PROP_HOSTNAME] = h;
402
403         r = context_write_data_hostname(c);
404         if (r < 0) {
405                 log_error("Failed to set host name: %s", strerror(-r));
406                 return sd_bus_reply_method_errnof(m, r, "Failed to set hostname: %s", strerror(-r));
407         }
408
409         log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
410
411         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
412
413         return sd_bus_reply_method_return(m, NULL);
414 }
415
416 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
417         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
418         Context *c = userdata;
419         const char *name;
420         int interactive;
421         int r;
422
423         r = sd_bus_message_read(m, "sb", &name, &interactive);
424         if (r < 0)
425                 return sd_bus_reply_method_errno(m, r, NULL);
426
427         if (isempty(name))
428                 name = NULL;
429
430         if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
431                 return sd_bus_reply_method_return(m, NULL);
432
433         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, &error, method_set_static_hostname, c);
434         if (r < 0)
435                 return sd_bus_reply_method_errno(m, r, &error);
436         if (r == 0)
437                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
438
439         if (isempty(name)) {
440                 free(c->data[PROP_STATIC_HOSTNAME]);
441                 c->data[PROP_STATIC_HOSTNAME] = NULL;
442         } else {
443                 char *h;
444
445                 if (!hostname_is_valid(name))
446                         return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
447
448                 h = strdup(name);
449                 if (!h)
450                         return log_oom();
451
452                 free(c->data[PROP_STATIC_HOSTNAME]);
453                 c->data[PROP_STATIC_HOSTNAME] = h;
454         }
455
456         r = context_write_data_static_hostname(c);
457         if (r < 0) {
458                 log_error("Failed to write static host name: %s", strerror(-r));
459                 return sd_bus_reply_method_errnof(m, r, "Failed to set static hostname: %s", strerror(-r));
460         }
461
462         log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
463
464         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
465
466         return sd_bus_reply_method_return(m, NULL);
467 }
468
469 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb) {
470         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
471         int interactive;
472         const char *name;
473         int r;
474
475         assert(c);
476         assert(bus);
477         assert(m);
478
479         r = sd_bus_message_read(m, "sb", &name, &interactive);
480         if (r < 0)
481                 return sd_bus_reply_method_errno(m, r, NULL);
482
483         if (isempty(name))
484                 name = NULL;
485
486         if (streq_ptr(name, c->data[prop]))
487                 return sd_bus_reply_method_return(m, NULL);
488
489         /* Since the pretty hostname should always be changed at the
490          * same time as the static one, use the same policy action for
491          * both... */
492
493         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
494                           "org.freedesktop.hostname1.set-static-hostname" :
495                           "org.freedesktop.hostname1.set-machine-info", interactive, &error, cb, c);
496         if (r < 0)
497                 return sd_bus_reply_method_errno(m, r, &error);
498         if (r == 0)
499                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
500
501         if (isempty(name)) {
502                 free(c->data[prop]);
503                 c->data[prop] = NULL;
504         } else {
505                 char *h;
506
507                 /* The icon name might ultimately be used as file
508                  * name, so better be safe than sorry */
509
510                 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
511                         return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
512                 if (prop == PROP_PRETTY_HOSTNAME &&
513                     (string_has_cc(name) || chars_intersect(name, "\t")))
514                         return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
515                 if (prop == PROP_CHASSIS && !valid_chassis(name))
516                         return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
517
518                 h = strdup(name);
519                 if (!h)
520                         return log_oom();
521
522                 free(c->data[prop]);
523                 c->data[prop] = h;
524         }
525
526         r = context_write_data_other(c);
527         if (r < 0) {
528                 log_error("Failed to write machine info: %s", strerror(-r));
529                 return sd_bus_reply_method_errnof(m, r, "Failed to write machine info: %s", strerror(-r));
530         }
531
532         log_info("Changed %s to '%s'",
533                  prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
534                  prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
535
536         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
537                                        prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
538                                        prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
539
540         return sd_bus_reply_method_return(m, NULL);
541 }
542
543 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
544         return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname);
545 }
546
547 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata) {
548         return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name);
549 }
550
551 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata) {
552         return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis);
553 }
554
555 static const sd_bus_vtable hostname_vtable[] = {
556         SD_BUS_VTABLE_START(0),
557         SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
558         SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
559         SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
560         SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
561         SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
562         SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, 0),
563         SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, 0),
564         SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, 0),
565         SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, 0),
566         SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, 0),
567         SD_BUS_VTABLE_END,
568 };
569
570 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
571         _cleanup_bus_unref_ sd_bus *bus = NULL;
572         int r;
573
574         assert(c);
575         assert(event);
576         assert(_bus);
577
578         r = sd_bus_default_system(&bus);
579         if (r < 0) {
580                 log_error("Failed to get system bus connection: %s", strerror(-r));
581                 return r;
582         }
583
584         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
585         if (r < 0) {
586                 log_error("Failed to register object: %s", strerror(-r));
587                 return r;
588         }
589
590         r = sd_bus_request_name(bus, "org.freedesktop.hostname1", SD_BUS_NAME_DO_NOT_QUEUE);
591         if (r < 0) {
592                 log_error("Failed to register name: %s", strerror(-r));
593                 return r;
594         }
595
596         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
597                 log_error("Failed to acquire name.");
598                 return -EEXIST;
599         }
600
601         r = sd_bus_attach_event(bus, event, 0);
602         if (r < 0) {
603                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
604                 return r;
605         }
606
607         *_bus = bus;
608         bus = NULL;
609
610         return 0;
611 }
612
613 int main(int argc, char *argv[]) {
614         Context context = {};
615
616         _cleanup_event_unref_ sd_event *event = NULL;
617         _cleanup_bus_unref_ sd_bus *bus = NULL;
618         int r;
619
620         log_set_target(LOG_TARGET_AUTO);
621         log_parse_environment();
622         log_open();
623
624         umask(0022);
625         label_init("/etc");
626
627         if (argc != 1) {
628                 log_error("This program takes no arguments.");
629                 r = -EINVAL;
630                 goto finish;
631         }
632
633         if (!check_nss())
634                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
635
636         if (argc != 1) {
637                 log_error("This program takes no arguments.");
638                 r = -EINVAL;
639                 goto finish;
640         }
641
642         r = sd_event_default(&event);
643         if (r < 0) {
644                 log_error("Failed to allocate event loop: %s", strerror(-r));
645                 goto finish;
646         }
647
648         r = connect_bus(&context, event, &bus);
649         if (r < 0)
650                 goto finish;
651
652         r = context_read_data(&context);
653         if (r < 0) {
654                 log_error("Failed to read timezone data: %s", strerror(-r));
655                 goto finish;
656         }
657
658         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
659         if (r < 0) {
660                 log_error("Failed to run event loop: %s", strerror(-r));
661                 goto finish;
662         }
663
664         r = 0;
665
666 finish:
667         context_free(&context, bus);
668
669         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
670 }