chiark / gitweb /
bus: properly handle if new objects are installed in the node tree while we are dispa...
[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 <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_string_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", NULL, &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'", strna(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'", strna(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 &&
557                                     (string_has_cc(name) || chars_intersect(name, "\t")))
558                                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
559                                 if (k == PROP_CHASSIS && !valid_chassis(name))
560                                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
561
562                                 h = strdup(name);
563                                 if (!h)
564                                         goto oom;
565
566                                 free(data[k]);
567                                 data[k] = h;
568                         }
569
570                         r = write_data_other();
571                         if (r < 0) {
572                                 log_error("Failed to write machine info: %s", strerror(-r));
573                                 return bus_send_error_reply(connection, message, NULL, r);
574                         }
575
576                         log_info("Changed %s to '%s'",
577                                  k == PROP_PRETTY_HOSTNAME ? "pretty host name" :
578                                  k == PROP_CHASSIS ? "chassis" : "icon name", strna(data[k]));
579
580                         changed = bus_properties_changed_new(
581                                         "/org/freedesktop/hostname1",
582                                         "org.freedesktop.hostname1",
583                                         k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" :
584                                         k == PROP_CHASSIS ? "Chassis\0" : "IconName\0");
585                         if (!changed)
586                                 goto oom;
587                 }
588
589         } else
590                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
591
592         reply = dbus_message_new_method_return(message);
593         if (!reply)
594                 goto oom;
595
596         if (!bus_maybe_send_reply(connection, message, reply))
597                 goto oom;
598
599         dbus_message_unref(reply);
600         reply = NULL;
601
602         if (changed) {
603
604                 if (!dbus_connection_send(connection, changed, NULL))
605                         goto oom;
606
607                 dbus_message_unref(changed);
608         }
609
610         return DBUS_HANDLER_RESULT_HANDLED;
611
612 oom:
613         if (reply)
614                 dbus_message_unref(reply);
615
616         if (changed)
617                 dbus_message_unref(changed);
618
619         dbus_error_free(&error);
620
621         return DBUS_HANDLER_RESULT_NEED_MEMORY;
622 }
623
624 static int connect_bus(DBusConnection **_bus) {
625         static const DBusObjectPathVTable hostname_vtable = {
626                 .message_function = hostname_message_handler
627         };
628         DBusError error;
629         DBusConnection *bus = NULL;
630         int r;
631
632         assert(_bus);
633
634         dbus_error_init(&error);
635
636         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
637         if (!bus) {
638                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
639                 r = -ECONNREFUSED;
640                 goto fail;
641         }
642
643         dbus_connection_set_exit_on_disconnect(bus, FALSE);
644
645         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
646             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
647                 r = log_oom();
648                 goto fail;
649         }
650
651         r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
652         if (dbus_error_is_set(&error)) {
653                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
654                 r = -EEXIST;
655                 goto fail;
656         }
657
658         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
659                 log_error("Failed to acquire name.");
660                 r = -EEXIST;
661                 goto fail;
662         }
663
664         if (_bus)
665                 *_bus = bus;
666
667         return 0;
668
669 fail:
670         dbus_connection_close(bus);
671         dbus_connection_unref(bus);
672
673         dbus_error_free(&error);
674
675         return r;
676 }
677
678 int main(int argc, char *argv[]) {
679         int r;
680         DBusConnection *bus = NULL;
681         bool exiting = false;
682
683         log_set_target(LOG_TARGET_AUTO);
684         log_parse_environment();
685         log_open();
686
687         umask(0022);
688         label_init("/etc");
689
690         if (argc == 2 && streq(argv[1], "--introspect")) {
691                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
692                       "<node>\n", stdout);
693                 fputs(hostname_interface, stdout);
694                 fputs("</node>\n", stdout);
695                 return 0;
696         }
697
698         if (argc != 1) {
699                 log_error("This program takes no arguments.");
700                 r = -EINVAL;
701                 goto finish;
702         }
703
704         if (!check_nss())
705                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
706
707         r = read_data();
708         if (r < 0) {
709                 log_error("Failed to read hostname data: %s", strerror(-r));
710                 goto finish;
711         }
712
713         r = connect_bus(&bus);
714         if (r < 0)
715                 goto finish;
716
717         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
718         for (;;) {
719
720                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
721                         break;
722
723                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
724                         exiting = true;
725                         bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
726                 }
727         }
728
729         r = 0;
730
731 finish:
732         free_data();
733
734         if (bus) {
735                 dbus_connection_flush(bus);
736                 dbus_connection_close(bus);
737                 dbus_connection_unref(bus);
738         }
739
740         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
741 }