chiark / gitweb /
build-sys: add stub makefiles to subdirs
[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 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 #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 DBusHandlerResult hostname_message_handler(
284                 DBusConnection *connection,
285                 DBusMessage *message,
286                 void *userdata) {
287
288         const BusProperty properties[] = {
289                 { "org.freedesktop.hostname1", "Hostname",       bus_property_append_string,    "s", data[PROP_HOSTNAME]},
290                 { "org.freedesktop.hostname1", "StaticHostname", bus_property_append_string,    "s", data[PROP_STATIC_HOSTNAME]},
291                 { "org.freedesktop.hostname1", "PrettyHostname", bus_property_append_string,    "s", data[PROP_PRETTY_HOSTNAME]},
292                 { "org.freedesktop.hostname1", "IconName",       bus_hostname_append_icon_name, "s", data[PROP_ICON_NAME]},
293                 { NULL, NULL, NULL, NULL, NULL }
294         };
295
296         DBusMessage *reply = NULL, *changed = NULL;
297         DBusError error;
298         int r;
299
300         assert(connection);
301         assert(message);
302
303         dbus_error_init(&error);
304
305         if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
306                 const char *name;
307                 dbus_bool_t interactive;
308
309                 if (!dbus_message_get_args(
310                                     message,
311                                     &error,
312                                     DBUS_TYPE_STRING, &name,
313                                     DBUS_TYPE_BOOLEAN, &interactive,
314                                     DBUS_TYPE_INVALID))
315                         return bus_send_error_reply(connection, message, &error, -EINVAL);
316
317                 if (isempty(name))
318                         name = data[PROP_STATIC_HOSTNAME];
319
320                 if (isempty(name))
321                         name = "localhost";
322
323                 if (!hostname_is_valid(name))
324                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
325
326                 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
327                         char *h;
328
329                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, &error);
330                         if (r < 0)
331                                 return bus_send_error_reply(connection, message, &error, r);
332
333                         h = strdup(name);
334                         if (!h)
335                                 goto oom;
336
337                         free(data[PROP_HOSTNAME]);
338                         data[PROP_HOSTNAME] = h;
339
340                         r = write_data_hostname();
341                         if (r < 0) {
342                                 log_error("Failed to set host name: %s", strerror(-r));
343                                 return bus_send_error_reply(connection, message, NULL, r);
344                         }
345
346                         log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
347
348                         changed = bus_properties_changed_new(
349                                         "/org/freedesktop/hostname1",
350                                         "org.freedesktop.hostname1",
351                                         "Hostname\0");
352                         if (!changed)
353                                 goto oom;
354                 }
355
356         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
357                 const char *name;
358                 dbus_bool_t interactive;
359
360                 if (!dbus_message_get_args(
361                                     message,
362                                     &error,
363                                     DBUS_TYPE_STRING, &name,
364                                     DBUS_TYPE_BOOLEAN, &interactive,
365                                     DBUS_TYPE_INVALID))
366                         return bus_send_error_reply(connection, message, &error, -EINVAL);
367
368                 if (isempty(name))
369                         name = NULL;
370
371                 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
372
373                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, &error);
374                         if (r < 0)
375                                 return bus_send_error_reply(connection, message, &error, r);
376
377                         if (isempty(name)) {
378                                 free(data[PROP_STATIC_HOSTNAME]);
379                                 data[PROP_STATIC_HOSTNAME] = NULL;
380                         } else {
381                                 char *h;
382
383                                 if (!hostname_is_valid(name))
384                                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
385
386                                 h = strdup(name);
387                                 if (!h)
388                                         goto oom;
389
390                                 free(data[PROP_STATIC_HOSTNAME]);
391                                 data[PROP_STATIC_HOSTNAME] = h;
392                         }
393
394                         r = write_data_static_hostname();
395                         if (r < 0) {
396                                 log_error("Failed to write static host name: %s", strerror(-r));
397                                 return bus_send_error_reply(connection, message, NULL, r);
398                         }
399
400                         log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
401
402                         changed = bus_properties_changed_new(
403                                         "/org/freedesktop/hostname1",
404                                         "org.freedesktop.hostname1",
405                                         "StaticHostname\0");
406                         if (!changed)
407                                 goto oom;
408                 }
409
410         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
411                    dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
412
413                 const char *name;
414                 dbus_bool_t interactive;
415                 int k;
416
417                 if (!dbus_message_get_args(
418                                     message,
419                                     &error,
420                                     DBUS_TYPE_STRING, &name,
421                                     DBUS_TYPE_BOOLEAN, &interactive,
422                                     DBUS_TYPE_INVALID))
423                         return bus_send_error_reply(connection, message, &error, -EINVAL);
424
425                 if (isempty(name))
426                         name = NULL;
427
428                 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
429
430                 if (!streq_ptr(name, data[k])) {
431
432                         /* Since the pretty hostname should always be
433                          * changed at the same time as the static one,
434                          * use the same policy action for both... */
435
436                         r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
437                                           "org.freedesktop.hostname1.set-static-hostname" :
438                                           "org.freedesktop.hostname1.set-machine-info", interactive, &error);
439                         if (r < 0)
440                                 return bus_send_error_reply(connection, message, &error, r);
441
442                         if (isempty(name)) {
443                                 free(data[k]);
444                                 data[k] = NULL;
445                         } else {
446                                 char *h;
447
448                                 h = strdup(name);
449                                 if (!h)
450                                         goto oom;
451
452                                 free(data[k]);
453                                 data[k] = h;
454                         }
455
456                         r = write_data_other();
457                         if (r < 0) {
458                                 log_error("Failed to write machine info: %s", strerror(-r));
459                                 return bus_send_error_reply(connection, message, NULL, r);
460                         }
461
462                         log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
463
464                         changed = bus_properties_changed_new(
465                                         "/org/freedesktop/hostname1",
466                                         "org.freedesktop.hostname1",
467                                         k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
468                         if (!changed)
469                                 goto oom;
470                 }
471
472         } else
473                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
474
475         if (!(reply = dbus_message_new_method_return(message)))
476                 goto oom;
477
478         if (!dbus_connection_send(connection, reply, NULL))
479                 goto oom;
480
481         dbus_message_unref(reply);
482         reply = NULL;
483
484         if (changed) {
485
486                 if (!dbus_connection_send(connection, changed, NULL))
487                         goto oom;
488
489                 dbus_message_unref(changed);
490         }
491
492         return DBUS_HANDLER_RESULT_HANDLED;
493
494 oom:
495         if (reply)
496                 dbus_message_unref(reply);
497
498         if (changed)
499                 dbus_message_unref(changed);
500
501         dbus_error_free(&error);
502
503         return DBUS_HANDLER_RESULT_NEED_MEMORY;
504 }
505
506 static int connect_bus(DBusConnection **_bus) {
507         static const DBusObjectPathVTable hostname_vtable = {
508                 .message_function = hostname_message_handler
509         };
510         DBusError error;
511         DBusConnection *bus = NULL;
512         int r;
513
514         assert(_bus);
515
516         dbus_error_init(&error);
517
518         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
519         if (!bus) {
520                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
521                 r = -ECONNREFUSED;
522                 goto fail;
523         }
524
525         dbus_connection_set_exit_on_disconnect(bus, FALSE);
526
527         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
528             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
529                 log_error("Not enough memory");
530                 r = -ENOMEM;
531                 goto fail;
532         }
533
534         r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
535         if (dbus_error_is_set(&error)) {
536                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
537                 r = -EEXIST;
538                 goto fail;
539         }
540
541         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
542                 log_error("Failed to acquire name.");
543                 r = -EEXIST;
544                 goto fail;
545         }
546
547         if (_bus)
548                 *_bus = bus;
549
550         return 0;
551
552 fail:
553         dbus_connection_close(bus);
554         dbus_connection_unref(bus);
555
556         dbus_error_free(&error);
557
558         return r;
559 }
560
561 int main(int argc, char *argv[]) {
562         int r;
563         DBusConnection *bus = NULL;
564         bool exiting = false;
565
566         log_set_target(LOG_TARGET_AUTO);
567         log_parse_environment();
568         log_open();
569
570         umask(0022);
571
572         if (argc == 2 && streq(argv[1], "--introspect")) {
573                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
574                       "<node>\n", stdout);
575                 fputs(hostname_interface, stdout);
576                 fputs("</node>\n", stdout);
577                 return 0;
578         }
579
580         if (argc != 1) {
581                 log_error("This program takes no arguments.");
582                 r = -EINVAL;
583                 goto finish;
584         }
585
586         if (!check_nss())
587                 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
588
589         r = read_data();
590         if (r < 0) {
591                 log_error("Failed to read hostname data: %s", strerror(-r));
592                 goto finish;
593         }
594
595         r = connect_bus(&bus);
596         if (r < 0)
597                 goto finish;
598
599         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
600         for (;;) {
601
602                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
603                         break;
604
605                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
606                         exiting = true;
607                         bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
608                 }
609         }
610
611         r = 0;
612
613 finish:
614         free_data();
615
616         if (bus) {
617                 dbus_connection_flush(bus);
618                 dbus_connection_close(bus);
619                 dbus_connection_unref(bus);
620         }
621
622         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
623 }