]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamed.c
honor SELinux labels, when creating and writing config files
[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 <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <dlfcn.h>
28
29 #include "util.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
34 #include "virt.h"
35 #include "env-util.h"
36 #include "fileio-label.h"
37 #include "label.h"
38
39 #define INTERFACE \
40 " <interface name=\"org.freedesktop.hostname1\">\n" \
41 " <property name=\"Hostname\" type=\"s\" access=\"read\"/>\n" \
42 " <property name=\"StaticHostname\" type=\"s\" access=\"read\"/>\n" \
43 " <property name=\"PrettyHostname\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
45 " <property name=\"Chassis\" type=\"s\" access=\"read\"/>\n" \
46 " <method name=\"SetHostname\">\n" \
47 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
48 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
49 " </method>\n" \
50 " <method name=\"SetStaticHostname\">\n" \
51 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
52 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
53 " </method>\n" \
54 " <method name=\"SetPrettyHostname\">\n" \
55 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57 " </method>\n" \
58 " <method name=\"SetIconName\">\n" \
59 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
60 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
61 " </method>\n" \
62 " <method name=\"SetChassis\">\n" \
63 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
64 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
65 " </method>\n" \
66 " </interface>\n"
67
68 #define INTROSPECTION \
69 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
70 "<node>\n" \
71 INTERFACE \
72 BUS_PROPERTIES_INTERFACE \
73 BUS_INTROSPECTABLE_INTERFACE \
74 BUS_PEER_INTERFACE \
75 "</node>\n"
76
77 #define INTERFACES_LIST \
78 BUS_GENERIC_INTERFACES_LIST \
79 "org.freedesktop.hostname1\0"
80
81 const char hostname_interface[] _introspect_("hostname1") = INTERFACE;
82
83 enum {
84 PROP_HOSTNAME,
85 PROP_STATIC_HOSTNAME,
86 PROP_PRETTY_HOSTNAME,
87 PROP_ICON_NAME,
88 PROP_CHASSIS,
89 _PROP_MAX
90 };
91
92 static char *data[_PROP_MAX] = {
93 NULL,
94 NULL,
95 NULL,
96 NULL,
97 NULL
98 };
99
100 static usec_t remain_until = 0;
101
102 static void free_data(void) {
103 int p;
104
105 for (p = 0; p < _PROP_MAX; p++) {
106 free(data[p]);
107 data[p] = NULL;
108 }
109 }
110
111 static int read_data(void) {
112 int r;
113
114 free_data();
115
116 data[PROP_HOSTNAME] = gethostname_malloc();
117 if (!data[PROP_HOSTNAME])
118 return -ENOMEM;
119
120 r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
121 if (r < 0 && r != -ENOENT)
122 return r;
123
124 r = parse_env_file("/etc/machine-info", NEWLINE,
125 "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
126 "ICON_NAME", &data[PROP_ICON_NAME],
127 "CHASSIS", &data[PROP_CHASSIS],
128 NULL);
129 if (r < 0 && r != -ENOENT)
130 return r;
131
132 return 0;
133 }
134
135 static bool check_nss(void) {
136 void *dl;
137
138 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
139 if (dl) {
140 dlclose(dl);
141 return true;
142 }
143
144 return false;
145 }
146
147 static bool valid_chassis(const char *chassis) {
148
149 assert(chassis);
150
151 return nulstr_contains(
152 "vm\0"
153 "container\0"
154 "desktop\0"
155 "laptop\0"
156 "server\0"
157 "tablet\0"
158 "handset\0",
159 chassis);
160 }
161
162 static const char* fallback_chassis(void) {
163 int r;
164 char *type;
165 unsigned t;
166 Virtualization v;
167
168 v = detect_virtualization(NULL);
169
170 if (v == VIRTUALIZATION_VM)
171 return "vm";
172 if (v == VIRTUALIZATION_CONTAINER)
173 return "container";
174
175 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
176 if (r < 0)
177 goto try_dmi;
178
179 r = safe_atou(type, &t);
180 free(type);
181 if (r < 0)
182 goto try_dmi;
183
184 /* We only list the really obvious cases here as the ACPI data
185 * is not really super reliable.
186 *
187 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
188 *
189 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
190 */
191
192 switch(t) {
193
194 case 1:
195 case 3:
196 case 6:
197 return "desktop";
198
199 case 2:
200 return "laptop";
201
202 case 4:
203 case 5:
204 case 7:
205 return "server";
206
207 case 8:
208 return "tablet";
209 }
210
211 try_dmi:
212 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
213 if (r < 0)
214 return NULL;
215
216 r = safe_atou(type, &t);
217 free(type);
218 if (r < 0)
219 return NULL;
220
221 /* We only list the really obvious cases here. The DMI data is
222 unreliable enough, so let's not do any additional guesswork
223 on top of that.
224
225 See the SMBIOS Specification 2.7.1 section 7.4.1 for
226 details about the values listed here:
227
228 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
229 */
230
231 switch (t) {
232
233 case 0x3:
234 case 0x4:
235 case 0x6:
236 case 0x7:
237 return "desktop";
238
239 case 0x8:
240 case 0x9:
241 case 0xA:
242 case 0xE:
243 return "laptop";
244
245 case 0xB:
246 return "handset";
247
248 case 0x11:
249 case 0x1C:
250 return "server";
251 }
252
253 return NULL;
254 }
255
256 static char* fallback_icon_name(void) {
257 const char *chassis;
258
259 if (!isempty(data[PROP_CHASSIS]))
260 return strappend("computer-", data[PROP_CHASSIS]);
261
262 chassis = fallback_chassis();
263 if (chassis)
264 return strappend("computer-", chassis);
265
266 return strdup("computer");
267 }
268
269 static int write_data_hostname(void) {
270 const char *hn;
271
272 if (isempty(data[PROP_HOSTNAME]))
273 hn = "localhost";
274 else
275 hn = data[PROP_HOSTNAME];
276
277 if (sethostname(hn, strlen(hn)) < 0)
278 return -errno;
279
280 return 0;
281 }
282
283 static int write_data_static_hostname(void) {
284
285 if (isempty(data[PROP_STATIC_HOSTNAME])) {
286
287 if (unlink("/etc/hostname") < 0)
288 return errno == ENOENT ? 0 : -errno;
289
290 return 0;
291 }
292 return write_one_line_file_atomic_label("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
293 }
294
295 static int write_data_other(void) {
296
297 static const char * const name[_PROP_MAX] = {
298 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
299 [PROP_ICON_NAME] = "ICON_NAME",
300 [PROP_CHASSIS] = "CHASSIS"
301 };
302
303 char **l = NULL;
304 int r, p;
305
306 r = load_env_file("/etc/machine-info", &l);
307 if (r < 0 && r != -ENOENT)
308 return r;
309
310 for (p = 2; p < _PROP_MAX; p++) {
311 char *t, **u;
312
313 assert(name[p]);
314
315 if (isempty(data[p])) {
316 strv_env_unset(l, name[p]);
317 continue;
318 }
319
320 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
321 strv_free(l);
322 return -ENOMEM;
323 }
324
325 u = strv_env_set(l, t);
326 free(t);
327 strv_free(l);
328
329 if (!u)
330 return -ENOMEM;
331 l = u;
332 }
333
334 if (strv_isempty(l)) {
335
336 if (unlink("/etc/machine-info") < 0)
337 return errno == ENOENT ? 0 : -errno;
338
339 return 0;
340 }
341
342 r = write_env_file_label("/etc/machine-info", l);
343 strv_free(l);
344
345 return r;
346 }
347
348 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
349 const char *name;
350 _cleanup_free_ char *n = NULL;
351
352 assert(i);
353 assert(property);
354
355 if (isempty(data[PROP_ICON_NAME]))
356 name = n = fallback_icon_name();
357 else
358 name = data[PROP_ICON_NAME];
359
360 return bus_property_append_string(i, property, (void*) name);
361 }
362
363 static int bus_hostname_append_chassis(DBusMessageIter *i, const char *property, void *userdata) {
364 const char *name;
365
366 assert(i);
367 assert(property);
368
369 if (isempty(data[PROP_CHASSIS]))
370 name = fallback_chassis();
371 else
372 name = data[PROP_CHASSIS];
373
374 return bus_property_append_string(i, property, (void*) name);
375 }
376
377 static const BusProperty bus_hostname_properties[] = {
378 { "Hostname", bus_property_append_string, "s", sizeof(data[0])*PROP_HOSTNAME, true },
379 { "StaticHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_STATIC_HOSTNAME, true },
380 { "PrettyHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_PRETTY_HOSTNAME, true },
381 { "IconName", bus_hostname_append_icon_name, "s", sizeof(data[0])*PROP_ICON_NAME, true },
382 { "Chassis", bus_hostname_append_chassis, "s", sizeof(data[0])*PROP_CHASSIS, true },
383 { NULL, }
384 };
385
386 static const BusBoundProperties bps[] = {
387 { "org.freedesktop.hostname1", bus_hostname_properties, data },
388 { NULL, }
389 };
390
391 static DBusHandlerResult hostname_message_handler(
392 DBusConnection *connection,
393 DBusMessage *message,
394 void *userdata) {
395
396
397 DBusMessage *reply = NULL, *changed = NULL;
398 DBusError error;
399 int r;
400
401 assert(connection);
402 assert(message);
403
404 dbus_error_init(&error);
405
406 if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
407 const char *name;
408 dbus_bool_t interactive;
409
410 if (!dbus_message_get_args(
411 message,
412 &error,
413 DBUS_TYPE_STRING, &name,
414 DBUS_TYPE_BOOLEAN, &interactive,
415 DBUS_TYPE_INVALID))
416 return bus_send_error_reply(connection, message, &error, -EINVAL);
417
418 if (isempty(name))
419 name = data[PROP_STATIC_HOSTNAME];
420
421 if (isempty(name))
422 name = "localhost";
423
424 if (!hostname_is_valid(name))
425 return bus_send_error_reply(connection, message, NULL, -EINVAL);
426
427 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
428 char *h;
429
430 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, NULL, &error);
431 if (r < 0)
432 return bus_send_error_reply(connection, message, &error, r);
433
434 h = strdup(name);
435 if (!h)
436 goto oom;
437
438 free(data[PROP_HOSTNAME]);
439 data[PROP_HOSTNAME] = h;
440
441 r = write_data_hostname();
442 if (r < 0) {
443 log_error("Failed to set host name: %s", strerror(-r));
444 return bus_send_error_reply(connection, message, NULL, r);
445 }
446
447 log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
448
449 changed = bus_properties_changed_new(
450 "/org/freedesktop/hostname1",
451 "org.freedesktop.hostname1",
452 "Hostname\0");
453 if (!changed)
454 goto oom;
455 }
456
457 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
458 const char *name;
459 dbus_bool_t interactive;
460
461 if (!dbus_message_get_args(
462 message,
463 &error,
464 DBUS_TYPE_STRING, &name,
465 DBUS_TYPE_BOOLEAN, &interactive,
466 DBUS_TYPE_INVALID))
467 return bus_send_error_reply(connection, message, &error, -EINVAL);
468
469 if (isempty(name))
470 name = NULL;
471
472 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
473
474 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, NULL, &error);
475 if (r < 0)
476 return bus_send_error_reply(connection, message, &error, r);
477
478 if (isempty(name)) {
479 free(data[PROP_STATIC_HOSTNAME]);
480 data[PROP_STATIC_HOSTNAME] = NULL;
481 } else {
482 char *h;
483
484 if (!hostname_is_valid(name))
485 return bus_send_error_reply(connection, message, NULL, -EINVAL);
486
487 h = strdup(name);
488 if (!h)
489 goto oom;
490
491 free(data[PROP_STATIC_HOSTNAME]);
492 data[PROP_STATIC_HOSTNAME] = h;
493 }
494
495 r = write_data_static_hostname();
496 if (r < 0) {
497 log_error("Failed to write static host name: %s", strerror(-r));
498 return bus_send_error_reply(connection, message, NULL, r);
499 }
500
501 log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
502
503 changed = bus_properties_changed_new(
504 "/org/freedesktop/hostname1",
505 "org.freedesktop.hostname1",
506 "StaticHostname\0");
507 if (!changed)
508 goto oom;
509 }
510
511 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
512 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName") ||
513 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetChassis")) {
514
515 const char *name;
516 dbus_bool_t interactive;
517 int k;
518
519 if (!dbus_message_get_args(
520 message,
521 &error,
522 DBUS_TYPE_STRING, &name,
523 DBUS_TYPE_BOOLEAN, &interactive,
524 DBUS_TYPE_INVALID))
525 return bus_send_error_reply(connection, message, &error, -EINVAL);
526
527 if (isempty(name))
528 name = NULL;
529
530 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME :
531 streq(dbus_message_get_member(message), "SetChassis") ? PROP_CHASSIS : PROP_ICON_NAME;
532
533 if (!streq_ptr(name, data[k])) {
534
535 /* Since the pretty hostname should always be
536 * changed at the same time as the static one,
537 * use the same policy action for both... */
538
539 r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
540 "org.freedesktop.hostname1.set-static-hostname" :
541 "org.freedesktop.hostname1.set-machine-info", interactive, NULL, &error);
542 if (r < 0)
543 return bus_send_error_reply(connection, message, &error, r);
544
545 if (isempty(name)) {
546 free(data[k]);
547 data[k] = NULL;
548 } else {
549 char *h;
550
551 /* The icon name might ultimately be
552 * used as file name, so better be
553 * safe than sorry */
554 if (k == PROP_ICON_NAME && !filename_is_safe(name))
555 return bus_send_error_reply(connection, message, NULL, -EINVAL);
556 if (k == PROP_PRETTY_HOSTNAME && !string_is_safe(name))
557 return bus_send_error_reply(connection, message, NULL, -EINVAL);
558 if (k == PROP_CHASSIS && !valid_chassis(name))
559 return bus_send_error_reply(connection, message, NULL, -EINVAL);
560
561 h = strdup(name);
562 if (!h)
563 goto oom;
564
565 free(data[k]);
566 data[k] = h;
567 }
568
569 r = write_data_other();
570 if (r < 0) {
571 log_error("Failed to write machine info: %s", strerror(-r));
572 return bus_send_error_reply(connection, message, NULL, r);
573 }
574
575 log_info("Changed %s to '%s'",
576 k == PROP_PRETTY_HOSTNAME ? "pretty host name" :
577 k == PROP_CHASSIS ? "chassis" : "icon name", strempty(data[k]));
578
579 changed = bus_properties_changed_new(
580 "/org/freedesktop/hostname1",
581 "org.freedesktop.hostname1",
582 k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" :
583 k == PROP_CHASSIS ? "Chassis\0" : "IconName\0");
584 if (!changed)
585 goto oom;
586 }
587
588 } else
589 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
590
591 reply = dbus_message_new_method_return(message);
592 if (!reply)
593 goto oom;
594
595 if (!dbus_connection_send(connection, reply, NULL))
596 goto oom;
597
598 dbus_message_unref(reply);
599 reply = NULL;
600
601 if (changed) {
602
603 if (!dbus_connection_send(connection, changed, NULL))
604 goto oom;
605
606 dbus_message_unref(changed);
607 }
608
609 return DBUS_HANDLER_RESULT_HANDLED;
610
611 oom:
612 if (reply)
613 dbus_message_unref(reply);
614
615 if (changed)
616 dbus_message_unref(changed);
617
618 dbus_error_free(&error);
619
620 return DBUS_HANDLER_RESULT_NEED_MEMORY;
621 }
622
623 static int connect_bus(DBusConnection **_bus) {
624 static const DBusObjectPathVTable hostname_vtable = {
625 .message_function = hostname_message_handler
626 };
627 DBusError error;
628 DBusConnection *bus = NULL;
629 int r;
630
631 assert(_bus);
632
633 dbus_error_init(&error);
634
635 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
636 if (!bus) {
637 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
638 r = -ECONNREFUSED;
639 goto fail;
640 }
641
642 dbus_connection_set_exit_on_disconnect(bus, FALSE);
643
644 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
645 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
646 r = log_oom();
647 goto fail;
648 }
649
650 r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
651 if (dbus_error_is_set(&error)) {
652 log_error("Failed to register name on bus: %s", bus_error_message(&error));
653 r = -EEXIST;
654 goto fail;
655 }
656
657 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
658 log_error("Failed to acquire name.");
659 r = -EEXIST;
660 goto fail;
661 }
662
663 if (_bus)
664 *_bus = bus;
665
666 return 0;
667
668 fail:
669 dbus_connection_close(bus);
670 dbus_connection_unref(bus);
671
672 dbus_error_free(&error);
673
674 return r;
675 }
676
677 int main(int argc, char *argv[]) {
678 int r;
679 DBusConnection *bus = NULL;
680 bool exiting = false;
681
682 log_set_target(LOG_TARGET_AUTO);
683 log_parse_environment();
684 log_open();
685
686 umask(0022);
687 label_init("/etc");
688
689 if (argc == 2 && streq(argv[1], "--introspect")) {
690 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
691 "<node>\n", stdout);
692 fputs(hostname_interface, stdout);
693 fputs("</node>\n", stdout);
694 return 0;
695 }
696
697 if (argc != 1) {
698 log_error("This program takes no arguments.");
699 r = -EINVAL;
700 goto finish;
701 }
702
703 if (!check_nss())
704 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
705
706 r = read_data();
707 if (r < 0) {
708 log_error("Failed to read hostname data: %s", strerror(-r));
709 goto finish;
710 }
711
712 r = connect_bus(&bus);
713 if (r < 0)
714 goto finish;
715
716 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
717 for (;;) {
718
719 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
720 break;
721
722 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
723 exiting = true;
724 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
725 }
726 }
727
728 r = 0;
729
730 finish:
731 free_data();
732
733 if (bus) {
734 dbus_connection_flush(bus);
735 dbus_connection_close(bus);
736 dbus_connection_unref(bus);
737 }
738
739 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
740 }