chiark / gitweb /
util: include `stdarg.h`
[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
28 #include "util.h"
29 #include "strv.h"
30 #include "dbus-common.h"
31
32 #define INTROSPECTION                                                   \
33         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
34         "<node>\n"                                                      \
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         BUS_PROPERTIES_INTERFACE                                        \
58         BUS_INTROSPECTABLE_INTERFACE                                    \
59         BUS_PEER_INTERFACE                                              \
60         "</node>\n"
61
62 #define INTERFACES_LIST                         \
63         BUS_GENERIC_INTERFACES_LIST             \
64         "org.freedesktop.hostname1\0"
65
66 enum {
67         PROP_HOSTNAME,
68         PROP_STATIC_HOSTNAME,
69         PROP_PRETTY_HOSTNAME,
70         PROP_ICON_NAME,
71         _PROP_MAX
72 };
73
74 static char *data[_PROP_MAX] = {
75         NULL,
76         NULL,
77         NULL,
78         NULL
79 };
80
81 static void free_data(void) {
82         int p;
83
84         for (p = 0; p < _PROP_MAX; p++) {
85                 free(data[p]);
86                 data[p] = NULL;
87         }
88 }
89
90 static int read_data(void) {
91         int r;
92
93         free_data();
94
95         data[PROP_HOSTNAME] = gethostname_malloc();
96         if (!data[PROP_HOSTNAME])
97                 return -ENOMEM;
98
99         r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
100         if (r < 0 && r != -ENOENT)
101                 return r;
102
103         r = parse_env_file("/etc/machine-info", NEWLINE,
104                            "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
105                            "ICON_NAME", &data[PROP_ICON_NAME],
106                            NULL);
107         if (r < 0 && r != -ENOENT)
108                 return r;
109
110         return 0;
111 }
112
113 static const char* fallback_icon_name(void) {
114
115 #if defined(__i386__) || defined(__x86_64__)
116         int r;
117         char *type;
118         unsigned t;
119 #endif
120
121         if (detect_virtualization(NULL) > 0)
122                 return "computer-vm";
123
124 #if defined(__i386__) || defined(__x86_64__)
125         r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
126         if (r < 0)
127                 return NULL;
128
129         r = safe_atou(type, &t);
130         free(type);
131
132         if (r < 0)
133                 return NULL;
134
135         /* We only list the really obvious cases here. The DMI data is
136            unreliable enough, so let's not do any additional guesswork
137            on top of that.
138
139            See the SMBIOS Specification 2.7.1 section 7.4.1 for
140            details about the values listed here:
141
142            http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
143          */
144
145         switch (t) {
146
147         case 0x3:
148         case 0x4:
149         case 0x6:
150         case 0x7:
151                 return "computer-desktop";
152
153         case 0x9:
154         case 0xA:
155         case 0xE:
156                 return "computer-laptop";
157
158         case 0x11:
159         case 0x1C:
160                 return "computer-server";
161         }
162
163 #endif
164         return NULL;
165 }
166
167 static int write_data_hostname(void) {
168         const char *hn;
169
170         if (isempty(data[PROP_HOSTNAME]))
171                 hn = "localhost";
172         else
173                 hn = data[PROP_HOSTNAME];
174
175         if (sethostname(hn, strlen(hn)) < 0)
176                 return -errno;
177
178         return 0;
179 }
180
181 static int write_data_static_hostname(void) {
182
183         if (isempty(data[PROP_STATIC_HOSTNAME])) {
184
185                 if (unlink("/etc/hostname") < 0)
186                         return errno == ENOENT ? 0 : -errno;
187
188                 return 0;
189         }
190
191         return write_one_line_file("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
192 }
193
194 static int write_data_other(void) {
195
196         static const char * const name[_PROP_MAX] = {
197                 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
198                 [PROP_ICON_NAME] = "ICON_NAME"
199         };
200
201         char **l = NULL;
202         int r, p;
203
204         r = load_env_file("/etc/machine-info", &l);
205         if (r < 0 && r != -ENOENT)
206                 return r;
207
208         for (p = 2; p < _PROP_MAX; p++) {
209                 char *t, **u;
210
211                 assert(name[p]);
212
213                 if (isempty(data[p]))  {
214                         l = strv_env_unset(l, name[p]);
215                         continue;
216                 }
217
218                 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
219                         strv_free(l);
220                         return -ENOMEM;
221                 }
222
223                 u = strv_env_set(l, t);
224                 free(t);
225                 strv_free(l);
226
227                 if (!u)
228                         return -ENOMEM;
229                 l = u;
230         }
231
232         if (strv_isempty(l)) {
233
234                 if (unlink("/etc/machine-info") < 0)
235                         return errno == ENOENT ? 0 : -errno;
236
237                 return 0;
238         }
239
240         r = write_env_file("/etc/machine-info", l);
241         strv_free(l);
242
243         return r;
244 }
245
246 /* This mimics dbus_bus_get_unix_user() */
247 static pid_t get_unix_process_id(
248                 DBusConnection *connection,
249                 const char *name,
250                 DBusError *error) {
251
252         DBusMessage *m = NULL, *reply = NULL;
253         uint32_t pid = 0;
254
255         m = dbus_message_new_method_call(
256                         DBUS_SERVICE_DBUS,
257                         DBUS_PATH_DBUS,
258                         DBUS_INTERFACE_DBUS,
259                         "GetConnectionUnixProcessID");
260         if (!m) {
261                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
262                 goto finish;
263         }
264
265         if (!dbus_message_append_args(
266                             m,
267                             DBUS_TYPE_STRING, &name,
268                             DBUS_TYPE_INVALID)) {
269                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
270                 goto finish;
271         }
272
273         reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
274         if (!reply)
275                 goto finish;
276
277         if (dbus_set_error_from_message(error, reply))
278                 goto finish;
279
280         if (!dbus_message_get_args(
281                             reply, error,
282                             DBUS_TYPE_UINT32, &pid,
283                             DBUS_TYPE_INVALID))
284                 goto finish;
285
286 finish:
287         if (m)
288                 dbus_message_unref(m);
289
290         if (reply)
291                 dbus_message_unref(reply);
292
293         return (pid_t) pid;
294 }
295
296 static int verify_polkit(
297                 DBusConnection *c,
298                 DBusMessage *request,
299                 const char *action,
300                 bool interactive,
301                 DBusError *error) {
302
303         DBusMessage *m = NULL, *reply = NULL;
304         const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = "";
305         const char *sender;
306         uint32_t flags = interactive ? 1 : 0;
307         pid_t pid_raw;
308         uint32_t pid_u32;
309         unsigned long long starttime_raw;
310         uint64_t starttime_u64;
311         DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant;
312         int r;
313         dbus_bool_t authorized = FALSE;
314
315         assert(c);
316         assert(request);
317
318         sender = dbus_message_get_sender(request);
319         if (!sender)
320                 return -EINVAL;
321
322         pid_raw = get_unix_process_id(c, sender, error);
323         if (pid_raw == 0)
324                 return -EINVAL;
325
326         r = get_starttime_of_pid(pid_raw, &starttime_raw);
327         if (r < 0)
328                 return r;
329
330         m = dbus_message_new_method_call(
331                         "org.freedesktop.PolicyKit1",
332                         "/org/freedesktop/PolicyKit1/Authority",
333                         "org.freedesktop.PolicyKit1.Authority",
334                         "CheckAuthorization");
335         if (!m)
336                 return -ENOMEM;
337
338         dbus_message_iter_init_append(m, &iter_msg);
339
340         pid_u32 = (uint32_t) pid_raw;
341         starttime_u64 = (uint64_t) starttime_raw;
342
343         if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) ||
344             !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) ||
345             !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) ||
346             !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
347             !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) ||
348             !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) ||
349             !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) ||
350             !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
351             !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
352             !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
353             !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) ||
354             !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) ||
355             !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) ||
356             !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
357             !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
358             !dbus_message_iter_close_container(&iter_struct, &iter_array) ||
359             !dbus_message_iter_close_container(&iter_msg, &iter_struct) ||
360             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) ||
361             !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) ||
362             !dbus_message_iter_close_container(&iter_msg, &iter_array) ||
363             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) ||
364             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) {
365                 r = -ENOMEM;
366                 goto finish;
367         }
368
369         reply = dbus_connection_send_with_reply_and_block(c, m, -1, error);
370         if (!reply) {
371                 r = -EIO;
372                 goto finish;
373         }
374
375         if (dbus_set_error_from_message(error, reply)) {
376                 r = -EIO;
377                 goto finish;
378         }
379
380         if (!dbus_message_iter_init(reply, &iter_msg) ||
381             dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) {
382                 r = -EIO;
383                 goto finish;
384         }
385
386         dbus_message_iter_recurse(&iter_msg, &iter_struct);
387
388         if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
389                 r = -EIO;
390                 goto finish;
391         }
392
393         dbus_message_iter_get_basic(&iter_struct, &authorized);
394
395         r = authorized ? 0 : -EPERM;
396
397 finish:
398
399         if (m)
400                 dbus_message_unref(m);
401
402         if (reply)
403                 dbus_message_unref(reply);
404
405         return r;
406 }
407
408 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
409         const char *name;
410
411         assert(i);
412         assert(property);
413
414         if (isempty(data[PROP_ICON_NAME]))
415                 name = fallback_icon_name();
416         else
417                 name = data[PROP_ICON_NAME];
418
419         return bus_property_append_string(i, property, (void*) name);
420 }
421
422 static DBusHandlerResult hostname_message_handler(
423                 DBusConnection *connection,
424                 DBusMessage *message,
425                 void *userdata) {
426
427         const BusProperty properties[] = {
428                 { "org.freedesktop.hostname1", "Hostname",       bus_property_append_string,    "s", data[PROP_HOSTNAME]},
429                 { "org.freedesktop.hostname1", "StaticHostname", bus_property_append_string,    "s", data[PROP_STATIC_HOSTNAME]},
430                 { "org.freedesktop.hostname1", "PrettyHostname", bus_property_append_string,    "s", data[PROP_PRETTY_HOSTNAME]},
431                 { "org.freedesktop.hostname1", "IconName",       bus_hostname_append_icon_name, "s", data[PROP_ICON_NAME]},
432                 { NULL, NULL, NULL, NULL, NULL }
433         };
434
435         DBusMessage *reply = NULL, *changed = NULL;
436         DBusError error;
437         int r;
438
439         assert(connection);
440         assert(message);
441
442         dbus_error_init(&error);
443
444         if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
445                 const char *name;
446                 dbus_bool_t interactive;
447
448                 if (!dbus_message_get_args(
449                                     message,
450                                     &error,
451                                     DBUS_TYPE_STRING, &name,
452                                     DBUS_TYPE_BOOLEAN, &interactive,
453                                     DBUS_TYPE_INVALID))
454                         return bus_send_error_reply(connection, message, &error, -EINVAL);
455
456                 if (isempty(name))
457                         name = data[PROP_STATIC_HOSTNAME];
458
459                 if (isempty(name))
460                         name = "localhost";
461
462                 if (!hostname_is_valid(name))
463                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
464
465                 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
466                         char *h;
467
468                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, &error);
469                         if (r < 0)
470                                 return bus_send_error_reply(connection, message, &error, r);
471
472                         h = strdup(name);
473                         if (!h)
474                                 goto oom;
475
476                         free(data[PROP_HOSTNAME]);
477                         data[PROP_HOSTNAME] = h;
478
479                         r = write_data_hostname();
480                         if (r < 0) {
481                                 log_error("Failed to set host name: %s", strerror(-r));
482                                 return bus_send_error_reply(connection, message, NULL, r);
483                         }
484
485                         log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
486
487                         changed = bus_properties_changed_new(
488                                         "/org/freedesktop/hostname1",
489                                         "org.freedesktop.hostname1",
490                                         "Hostname\0");
491                         if (!changed)
492                                 goto oom;
493                 }
494
495         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
496                 const char *name;
497                 dbus_bool_t interactive;
498
499                 if (!dbus_message_get_args(
500                                     message,
501                                     &error,
502                                     DBUS_TYPE_STRING, &name,
503                                     DBUS_TYPE_BOOLEAN, &interactive,
504                                     DBUS_TYPE_INVALID))
505                         return bus_send_error_reply(connection, message, &error, -EINVAL);
506
507                 if (isempty(name))
508                         name = NULL;
509
510                 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
511
512                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, &error);
513                         if (r < 0)
514                                 return bus_send_error_reply(connection, message, &error, r);
515
516                         if (isempty(name)) {
517                                 free(data[PROP_STATIC_HOSTNAME]);
518                                 data[PROP_STATIC_HOSTNAME] = NULL;
519                         } else {
520                                 char *h;
521
522                                 if (!hostname_is_valid(name))
523                                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
524
525                                 h = strdup(name);
526                                 if (!h)
527                                         goto oom;
528
529                                 free(data[PROP_STATIC_HOSTNAME]);
530                                 data[PROP_STATIC_HOSTNAME] = h;
531                         }
532
533                         r = write_data_static_hostname();
534                         if (r < 0) {
535                                 log_error("Failed to write static host name: %s", strerror(-r));
536                                 return bus_send_error_reply(connection, message, NULL, r);
537                         }
538
539                         log_info("Changed static host name to '%s'", strempty(data[PROP_HOSTNAME]));
540
541                         changed = bus_properties_changed_new(
542                                         "/org/freedesktop/hostname1",
543                                         "org.freedesktop.hostname1",
544                                         "StaticHostname\0");
545                         if (!changed)
546                                 goto oom;
547                 }
548
549         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
550                    dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
551
552                 const char *name;
553                 dbus_bool_t interactive;
554                 int k;
555
556                 if (!dbus_message_get_args(
557                                     message,
558                                     &error,
559                                     DBUS_TYPE_STRING, &name,
560                                     DBUS_TYPE_BOOLEAN, &interactive,
561                                     DBUS_TYPE_INVALID))
562                         return bus_send_error_reply(connection, message, &error, -EINVAL);
563
564                 if (isempty(name))
565                         name = NULL;
566
567                 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
568
569                 if (!streq_ptr(name, data[k])) {
570
571                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-machine-info", interactive, &error);
572                         if (r < 0)
573                                 return bus_send_error_reply(connection, message, &error, r);
574
575                         if (isempty(name)) {
576                                 free(data[k]);
577                                 data[k] = NULL;
578                         } else {
579                                 char *h;
580
581                                 h = strdup(name);
582                                 if (!h)
583                                         goto oom;
584
585                                 free(data[k]);
586                                 data[k] = h;
587                         }
588
589                         r = write_data_other();
590                         if (r < 0) {
591                                 log_error("Failed to write machine info: %s", strerror(-r));
592                                 return bus_send_error_reply(connection, message, NULL, r);
593                         }
594
595                         log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
596
597                         changed = bus_properties_changed_new(
598                                         "/org/freedesktop/hostname1",
599                                         "org.freedesktop.hostname1",
600                                         k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
601                         if (!changed)
602                                 goto oom;
603                 }
604
605         } else
606                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
607
608         if (!(reply = dbus_message_new_method_return(message)))
609                 goto oom;
610
611         if (!dbus_connection_send(connection, reply, NULL))
612                 goto oom;
613
614         dbus_message_unref(reply);
615         reply = NULL;
616
617         if (changed) {
618
619                 if (!dbus_connection_send(connection, changed, NULL))
620                         goto oom;
621
622                 dbus_message_unref(changed);
623         }
624
625         return DBUS_HANDLER_RESULT_HANDLED;
626
627 oom:
628         if (reply)
629                 dbus_message_unref(reply);
630
631         if (changed)
632                 dbus_message_unref(changed);
633
634         dbus_error_free(&error);
635
636         return DBUS_HANDLER_RESULT_NEED_MEMORY;
637 }
638
639 int main(int argc, char *argv[]) {
640         const DBusObjectPathVTable hostname_vtable = {
641                 .message_function = hostname_message_handler
642         };
643
644         DBusConnection *bus = NULL;
645         DBusError error;
646         int r;
647
648         dbus_error_init(&error);
649
650         log_set_target(LOG_TARGET_AUTO);
651         log_parse_environment();
652         log_open();
653
654         if (argc != 1) {
655                 log_error("This program takes no arguments.");
656                 r = -EINVAL;
657                 goto finish;
658         }
659
660         umask(0022);
661
662         r = read_data();
663         if (r < 0) {
664                 log_error("Failed to read hostname data: %s", strerror(-r));
665                 goto finish;
666         }
667
668         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
669         if (!bus) {
670                 log_error("Failed to get system D-Bus connection: %s", error.message);
671                 r = -ECONNREFUSED;
672                 goto finish;
673         }
674
675         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL)) {
676                 log_error("Not enough memory");
677                 r = -ENOMEM;
678                 goto finish;
679         }
680
681         if (dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) < 0) {
682                 log_error("Failed to register name on bus: %s", error.message);
683                 r = -EEXIST;
684                 goto finish;
685         }
686
687         while (dbus_connection_read_write_dispatch(bus, -1))
688                 ;
689
690         r = 0;
691
692 finish:
693         free_data();
694
695         if (bus) {
696                 dbus_connection_flush(bus);
697                 dbus_connection_close(bus);
698                 dbus_connection_unref(bus);
699         }
700
701         dbus_error_free(&error);
702
703         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
704 }