chiark / gitweb /
ece5d1d410c0f94f8df06cc9e6c5b800523d5dc4
[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         if(!name)
364                 name = "";
365
366         r = sd_bus_message_append(reply, "s", name);
367         if (r < 0)
368                 return r;
369
370         return 1;
371 }
372
373 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
374         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
375         Context *c = userdata;
376         const char *name;
377         unsigned interactive;
378         char *h;
379         int r;
380
381         r = sd_bus_message_read(m, "sb", &name, &interactive);
382         if (r < 0)
383                 return sd_bus_reply_method_errno(bus, m, r, NULL);
384
385         if (isempty(name))
386                 name = c->data[PROP_STATIC_HOSTNAME];
387
388         if (isempty(name))
389                 name = "localhost";
390
391         if (!hostname_is_valid(name))
392                 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
393
394         if (streq_ptr(name, c->data[PROP_HOSTNAME]))
395                 return sd_bus_reply_method_return(bus, m, NULL);
396
397         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, &error, method_set_hostname, c);
398         if (r < 0)
399                 return sd_bus_reply_method_errno(bus, m, r, &error);
400         if (r == 0)
401                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
402
403         h = strdup(name);
404         if (!h)
405                 return log_oom();
406
407         free(c->data[PROP_HOSTNAME]);
408         c->data[PROP_HOSTNAME] = h;
409
410         r = context_write_data_hostname(c);
411         if (r < 0) {
412                 log_error("Failed to set host name: %s", strerror(-r));
413                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set hostname: %s", strerror(-r));
414         }
415
416         log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
417
418         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
419
420         return sd_bus_reply_method_return(bus, m, NULL);
421 }
422
423 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
424         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
425         Context *c = userdata;
426         const char *name;
427         unsigned interactive;
428         int r;
429
430         r = sd_bus_message_read(m, "sb", &name, &interactive);
431         if (r < 0)
432                 return sd_bus_reply_method_errno(bus, m, r, NULL);
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(bus, 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 sd_bus_reply_method_errno(bus, m, r, &error);
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_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
454
455                 h = strdup(name);
456                 if (!h)
457                         return log_oom();
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_reply_method_errnof(bus, m, 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(bus, 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) {
477         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
478         unsigned interactive;
479         const char *name;
480         int r;
481
482         assert(c);
483         assert(bus);
484         assert(m);
485
486         r = sd_bus_message_read(m, "sb", &name, &interactive);
487         if (r < 0)
488                 return sd_bus_reply_method_errno(bus, m, r, NULL);
489
490         if (isempty(name))
491                 name = NULL;
492
493         if (streq_ptr(name, c->data[prop]))
494                 return sd_bus_reply_method_return(bus, m, NULL);
495
496         /* Since the pretty hostname should always be changed at the
497          * same time as the static one, use the same policy action for
498          * both... */
499
500         r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
501                           "org.freedesktop.hostname1.set-static-hostname" :
502                           "org.freedesktop.hostname1.set-machine-info", interactive, &error, cb, c);
503         if (r < 0)
504                 return sd_bus_reply_method_errno(bus, m, r, &error);
505         if (r == 0)
506                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
507
508         if (isempty(name)) {
509                 free(c->data[prop]);
510                 c->data[prop] = NULL;
511         } else {
512                 char *h;
513
514                 /* The icon name might ultimately be used as file
515                  * name, so better be safe than sorry */
516
517                 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
518                         return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
519                 if (prop == PROP_PRETTY_HOSTNAME &&
520                     (string_has_cc(name) || chars_intersect(name, "\t")))
521                         return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
522                 if (prop == PROP_CHASSIS && !valid_chassis(name))
523                         return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
524
525                 h = strdup(name);
526                 if (!h)
527                         return log_oom();
528
529                 free(c->data[prop]);
530                 c->data[prop] = h;
531         }
532
533         r = context_write_data_other(c);
534         if (r < 0) {
535                 log_error("Failed to write machine info: %s", strerror(-r));
536                 return sd_bus_reply_method_errnof(bus, m, r, "Failed to write machine info: %s", strerror(-r));
537         }
538
539         log_info("Changed %s to '%s'",
540                  prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
541                  prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
542
543         sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
544                                        prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
545                                        prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
546
547         return sd_bus_reply_method_return(bus, m, NULL);
548 }
549
550 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
551         return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname);
552 }
553
554 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata) {
555         return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name);
556 }
557
558 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata) {
559         return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis);
560 }
561
562 static const sd_bus_vtable hostname_vtable[] = {
563         SD_BUS_VTABLE_START(0),
564         SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
565         SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
566         SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
567         SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
568         SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
569         SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, 0),
570         SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, 0),
571         SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, 0),
572         SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, 0),
573         SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, 0),
574         SD_BUS_VTABLE_END,
575 };
576
577 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
578         _cleanup_bus_unref_ sd_bus *bus = NULL;
579         int r;
580
581         assert(c);
582         assert(event);
583         assert(_bus);
584
585         r = sd_bus_open_system(&bus);
586         if (r < 0) {
587                 log_error("Failed to get system bus connection: %s", strerror(-r));
588                 return r;
589         }
590
591         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
592         if (r < 0) {
593                 log_error("Failed to register object: %s", strerror(-r));
594                 return r;
595         }
596
597         r = sd_bus_request_name(bus, "org.freedesktop.hostname1", SD_BUS_NAME_DO_NOT_QUEUE);
598         if (r < 0) {
599                 log_error("Failed to register name: %s", strerror(-r));
600                 return r;
601         }
602
603         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
604                 log_error("Failed to acquire name.");
605                 return -EEXIST;
606         }
607
608         r = sd_bus_attach_event(bus, event, 0);
609         if (r < 0) {
610                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
611                 return r;
612         }
613
614         *_bus = bus;
615         bus = NULL;
616
617         return 0;
618 }
619
620 int main(int argc, char *argv[]) {
621         Context context = {};
622
623         _cleanup_event_unref_ sd_event *event = NULL;
624         _cleanup_bus_unref_ sd_bus *bus = NULL;
625         int r;
626
627         log_set_target(LOG_TARGET_AUTO);
628         log_parse_environment();
629         log_open();
630
631         umask(0022);
632         label_init("/etc");
633
634         if (argc != 1) {
635                 log_error("This program takes no arguments.");
636                 r = -EINVAL;
637                 goto finish;
638         }
639
640         if (!check_nss())
641                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
642
643         if (argc != 1) {
644                 log_error("This program takes no arguments.");
645                 r = -EINVAL;
646                 goto finish;
647         }
648
649         r = sd_event_new(&event);
650         if (r < 0) {
651                 log_error("Failed to allocate event loop: %s", strerror(-r));
652                 goto finish;
653         }
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 timezone data: %s", strerror(-r));
662                 goto finish;
663         }
664
665         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
666         if (r < 0) {
667                 log_error("Failed to run event loop: %s", strerror(-r));
668                 goto finish;
669         }
670
671         r = 0;
672
673 finish:
674         context_free(&context, bus);
675
676         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
677 }