]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostnamed.c
dbus: don't hit assert when dumping properties
[thirdparty/systemd.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 }