chiark / gitweb /
util: rename write_one_line_file() to write_string_file()
[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", &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_has_cc(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 (!bus_maybe_send_reply(connection, message, reply))
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 }