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