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