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