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