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