]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamed.c
hostnamed: expose KernelName and KernelRelease on the bus
[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 #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, "/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 }