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