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