chiark / gitweb /
hostnamed: add reference to SMBIOS specs
[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         static const char * const name[_PROP_MAX] = {
196                 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
197                 [PROP_ICON_NAME] = "ICON_NAME"
198         };
199
200         char **l = NULL;
201         int r, p;
202
203         r = load_env_file("/etc/machine-info", &l);
204         if (r < 0 && r != -ENOENT)
205                 return r;
206
207         for (p = 2; p < _PROP_MAX; p++) {
208                 char *t, **u;
209
210                 assert(name[p]);
211
212                 if (isempty(data[p]))  {
213                         l = strv_env_unset(l, name[p]);
214                         continue;
215                 }
216
217                 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
218                         strv_free(l);
219                         return -ENOMEM;
220                 }
221
222                 u = strv_env_set(l, t);
223                 free(t);
224                 strv_free(l);
225
226                 if (!u)
227                         return -ENOMEM;
228                 l = u;
229         }
230
231         if (strv_isempty(l)) {
232
233                 if (unlink("/etc/machine-info") < 0)
234                         return errno == ENOENT ? 0 : -errno;
235
236                 return 0;
237         }
238
239         r = write_env_file("/etc/machine-info", l);
240         strv_free(l);
241
242         return r;
243 }
244
245 /* This mimics dbus_bus_get_unix_user() */
246 static pid_t get_unix_process_id(
247                 DBusConnection *connection,
248                 const char *name,
249                 DBusError *error) {
250
251         DBusMessage *m = NULL, *reply = NULL;
252         uint32_t pid = 0;
253
254         m = dbus_message_new_method_call(
255                         DBUS_SERVICE_DBUS,
256                         DBUS_PATH_DBUS,
257                         DBUS_INTERFACE_DBUS,
258                         "GetConnectionUnixProcessID");
259         if (!m) {
260                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
261                 goto finish;
262         }
263
264         if (!dbus_message_append_args(
265                             m,
266                             DBUS_TYPE_STRING, &name,
267                             DBUS_TYPE_INVALID)) {
268                 dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
269                 goto finish;
270         }
271
272         reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
273         if (!reply)
274                 goto finish;
275
276         if (dbus_set_error_from_message(error, reply))
277                 goto finish;
278
279         if (!dbus_message_get_args(
280                             reply, error,
281                             DBUS_TYPE_UINT32, &pid,
282                             DBUS_TYPE_INVALID))
283                 goto finish;
284
285 finish:
286         if (m)
287                 dbus_message_unref(m);
288
289         if (reply)
290                 dbus_message_unref(reply);
291
292         return (pid_t) pid;
293 }
294
295 static int verify_polkit(
296                 DBusConnection *c,
297                 DBusMessage *request,
298                 const char *action,
299                 bool interactive,
300                 DBusError *error) {
301
302         DBusMessage *m = NULL, *reply = NULL;
303         const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = "";
304         const char *sender;
305         uint32_t flags = interactive ? 1 : 0;
306         pid_t pid_raw;
307         uint32_t pid_u32;
308         unsigned long long starttime_raw;
309         uint64_t starttime_u64;
310         DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant;
311         int r;
312         dbus_bool_t authorized = FALSE;
313
314         assert(c);
315         assert(request);
316
317         sender = dbus_message_get_sender(request);
318         if (!sender)
319                 return -EINVAL;
320
321         pid_raw = get_unix_process_id(c, sender, error);
322         if (pid_raw == 0)
323                 return -EINVAL;
324
325         r = get_starttime_of_pid(pid_raw, &starttime_raw);
326         if (r < 0)
327                 return r;
328
329         m = dbus_message_new_method_call(
330                         "org.freedesktop.PolicyKit1",
331                         "/org/freedesktop/PolicyKit1/Authority",
332                         "org.freedesktop.PolicyKit1.Authority",
333                         "CheckAuthorization");
334         if (!m)
335                 return -ENOMEM;
336
337         dbus_message_iter_init_append(m, &iter_msg);
338
339         pid_u32 = (uint32_t) pid_raw;
340         starttime_u64 = (uint64_t) starttime_raw;
341
342         if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) ||
343             !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) ||
344             !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) ||
345             !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
346             !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) ||
347             !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) ||
348             !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) ||
349             !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
350             !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
351             !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
352             !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) ||
353             !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) ||
354             !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) ||
355             !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
356             !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
357             !dbus_message_iter_close_container(&iter_struct, &iter_array) ||
358             !dbus_message_iter_close_container(&iter_msg, &iter_struct) ||
359             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) ||
360             !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) ||
361             !dbus_message_iter_close_container(&iter_msg, &iter_array) ||
362             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) ||
363             !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) {
364                 r = -ENOMEM;
365                 goto finish;
366         }
367
368         reply = dbus_connection_send_with_reply_and_block(c, m, -1, error);
369         if (!reply) {
370                 r = -EIO;
371                 goto finish;
372         }
373
374         if (dbus_set_error_from_message(error, reply)) {
375                 r = -EIO;
376                 goto finish;
377         }
378
379         if (!dbus_message_iter_init(reply, &iter_msg) ||
380             dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) {
381                 r = -EIO;
382                 goto finish;
383         }
384
385         dbus_message_iter_recurse(&iter_msg, &iter_struct);
386
387         if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
388                 r = -EIO;
389                 goto finish;
390         }
391
392         dbus_message_iter_get_basic(&iter_struct, &authorized);
393
394         r = authorized ? 0 : -EPERM;
395
396 finish:
397
398         if (m)
399                 dbus_message_unref(m);
400
401         if (reply)
402                 dbus_message_unref(reply);
403
404         return r;
405 }
406
407 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
408         const char *name;
409
410         assert(i);
411         assert(property);
412
413         if (isempty(data[PROP_ICON_NAME]))
414                 name = fallback_icon_name();
415         else
416                 name = data[PROP_ICON_NAME];
417
418         return bus_property_append_string(i, property, (void*) name);
419 }
420
421 static DBusHandlerResult hostname_message_handler(
422                 DBusConnection *connection,
423                 DBusMessage *message,
424                 void *userdata) {
425
426         const BusProperty properties[] = {
427                 { "org.freedesktop.hostname1", "Hostname",       bus_property_append_string,    "s", data[PROP_HOSTNAME]},
428                 { "org.freedesktop.hostname1", "StaticHostname", bus_property_append_string,    "s", data[PROP_STATIC_HOSTNAME]},
429                 { "org.freedesktop.hostname1", "PrettyHostname", bus_property_append_string,    "s", data[PROP_PRETTY_HOSTNAME]},
430                 { "org.freedesktop.hostname1", "IconName",       bus_hostname_append_icon_name, "s", data[PROP_ICON_NAME]},
431                 { NULL, NULL, NULL, NULL, NULL }
432         };
433
434         DBusMessage *reply = NULL, *changed = NULL;
435         DBusError error;
436         int r;
437
438         assert(connection);
439         assert(message);
440
441         dbus_error_init(&error);
442
443         if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
444                 const char *name;
445                 dbus_bool_t interactive;
446
447                 if (!dbus_message_get_args(
448                                     message,
449                                     &error,
450                                     DBUS_TYPE_STRING, &name,
451                                     DBUS_TYPE_BOOLEAN, &interactive,
452                                     DBUS_TYPE_INVALID))
453                         return bus_send_error_reply(connection, message, &error, -EINVAL);
454
455                 if (isempty(name))
456                         name = data[PROP_STATIC_HOSTNAME];
457
458                 if (isempty(name))
459                         name = "localhost";
460
461                 if (!hostname_is_valid(name))
462                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
463
464                 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
465                         char *h;
466
467                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, &error);
468                         if (r < 0)
469                                 return bus_send_error_reply(connection, message, &error, r);
470
471                         h = strdup(name);
472                         if (!h)
473                                 goto oom;
474
475                         free(data[PROP_HOSTNAME]);
476                         data[PROP_HOSTNAME] = h;
477
478                         r = write_data_hostname();
479                         if (r < 0)
480                                 return bus_send_error_reply(connection, message, NULL, r);
481
482                         log_info("Changed host name to '%s'", data[PROP_HOSTNAME]);
483
484                         changed = bus_properties_changed_new(
485                                         "/org/freedesktop/hostname1",
486                                         "org.freedesktop.hostname1",
487                                         "Hostname\0");
488                         if (!changed)
489                                 goto oom;
490                 }
491
492         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
493                 const char *name;
494                 dbus_bool_t interactive;
495
496                 if (!dbus_message_get_args(
497                                     message,
498                                     &error,
499                                     DBUS_TYPE_STRING, &name,
500                                     DBUS_TYPE_BOOLEAN, &interactive,
501                                     DBUS_TYPE_INVALID))
502                         return bus_send_error_reply(connection, message, &error, -EINVAL);
503
504                 if (isempty(name))
505                         name = NULL;
506
507                 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
508
509                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, &error);
510                         if (r < 0)
511                                 return bus_send_error_reply(connection, message, &error, r);
512
513                         if (isempty(name)) {
514                                 free(data[PROP_STATIC_HOSTNAME]);
515                                 data[PROP_STATIC_HOSTNAME] = NULL;
516                         } else {
517                                 char *h;
518
519                                 if (!hostname_is_valid(name))
520                                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
521
522                                 h = strdup(name);
523                                 if (!h)
524                                         goto oom;
525
526                                 free(data[PROP_STATIC_HOSTNAME]);
527                                 data[PROP_STATIC_HOSTNAME] = h;
528                         }
529
530                         r = write_data_static_hostname();
531                         if (r < 0)
532                                 return bus_send_error_reply(connection, message, NULL, r);
533
534                         log_info("Changed static host name to '%s'", data[PROP_HOSTNAME]);
535
536                         changed = bus_properties_changed_new(
537                                         "/org/freedesktop/hostname1",
538                                         "org.freedesktop.hostname1",
539                                         "StaticHostname\0");
540                         if (!changed)
541                                 goto oom;
542                 }
543
544         } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
545                    dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
546
547                 const char *name;
548                 dbus_bool_t interactive;
549                 int k;
550
551                 if (!dbus_message_get_args(
552                                     message,
553                                     &error,
554                                     DBUS_TYPE_STRING, &name,
555                                     DBUS_TYPE_BOOLEAN, &interactive,
556                                     DBUS_TYPE_INVALID))
557                         return bus_send_error_reply(connection, message, &error, -EINVAL);
558
559                 if (isempty(name))
560                         name = NULL;
561
562                 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
563
564                 if (!streq_ptr(name, data[k])) {
565
566                         r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-machine-info", interactive, &error);
567                         if (r < 0)
568                                 return bus_send_error_reply(connection, message, &error, r);
569
570                         if (isempty(name)) {
571                                 free(data[k]);
572                                 data[k] = NULL;
573                         } else {
574                                 char *h;
575
576                                 h = strdup(name);
577                                 if (!h)
578                                         goto oom;
579
580                                 free(data[k]);
581                                 data[k] = h;
582                         }
583
584                         r = write_data_other();
585                         if (r < 0)
586                                 return bus_send_error_reply(connection, message, NULL, r);
587
588                         log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", data[k]);
589
590                         changed = bus_properties_changed_new(
591                                         "/org/freedesktop/hostname1",
592                                         "org.freedesktop.hostname1",
593                                         k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
594                         if (!changed)
595                                 goto oom;
596                 }
597
598         } else
599                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
600
601         if (!(reply = dbus_message_new_method_return(message)))
602                 goto oom;
603
604         if (!dbus_connection_send(connection, reply, NULL))
605                 goto oom;
606
607         dbus_message_unref(reply);
608         reply = NULL;
609
610         if (changed) {
611
612                 if (!dbus_connection_send(connection, changed, NULL))
613                         goto oom;
614
615                 dbus_message_unref(changed);
616         }
617
618         return DBUS_HANDLER_RESULT_HANDLED;
619
620 oom:
621         if (reply)
622                 dbus_message_unref(reply);
623
624         if (changed)
625                 dbus_message_unref(changed);
626
627         dbus_error_free(&error);
628
629         return DBUS_HANDLER_RESULT_NEED_MEMORY;
630 }
631
632 int main(int argc, char *argv[]) {
633         const DBusObjectPathVTable hostname_vtable = {
634                 .message_function = hostname_message_handler
635         };
636
637         DBusConnection *bus = NULL;
638         DBusError error;
639         int r;
640
641         dbus_error_init(&error);
642
643         log_set_target(LOG_TARGET_AUTO);
644         log_parse_environment();
645         log_open();
646
647         if (argc != 1) {
648                 log_error("This program takes no arguments.");
649                 r = -EINVAL;
650                 goto finish;
651         }
652
653         umask(0022);
654
655         r = read_data();
656         if (r < 0) {
657                 log_error("Failed to read hostname data: %s", strerror(-r));
658                 goto finish;
659         }
660
661         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
662         if (!bus) {
663                 log_error("Failed to get system D-Bus connection: %s", error.message);
664                 r = -ECONNREFUSED;
665                 goto finish;
666         }
667
668         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL)) {
669                 log_error("Not enough memory");
670                 r = -ENOMEM;
671                 goto finish;
672         }
673
674         if (dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) < 0) {
675                 log_error("Failed to register name on bus: %s", error.message);
676                 goto finish;
677         }
678
679         while (dbus_connection_read_write_dispatch(bus, -1))
680                 ;
681
682         r = 0;
683
684 finish:
685         free_data();
686
687         if (bus) {
688                 dbus_connection_flush(bus);
689                 dbus_connection_close(bus);
690                 dbus_connection_unref(bus);
691         }
692
693         dbus_error_free(&error);
694
695         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
696 }