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