]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamed.c
hostname: add new hostnamectl tool as text client for hostnamed
[thirdparty/systemd.git] / src / hostname / hostnamed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <dlfcn.h>
28
29 #include "util.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
34 #include "virt.h"
35
36 #define INTERFACE \
37 " <interface name=\"org.freedesktop.hostname1\">\n" \
38 " <property name=\"Hostname\" type=\"s\" access=\"read\"/>\n" \
39 " <property name=\"StaticHostname\" type=\"s\" access=\"read\"/>\n" \
40 " <property name=\"PrettyHostname\" type=\"s\" access=\"read\"/>\n" \
41 " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
42 " <method name=\"SetHostname\">\n" \
43 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
44 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
45 " </method>\n" \
46 " <method name=\"SetStaticHostname\">\n" \
47 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
48 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
49 " </method>\n" \
50 " <method name=\"SetPrettyHostname\">\n" \
51 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
52 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
53 " </method>\n" \
54 " <method name=\"SetIconName\">\n" \
55 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57 " </method>\n" \
58 " </interface>\n"
59
60 #define INTROSPECTION \
61 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
62 "<node>\n" \
63 INTERFACE \
64 BUS_PROPERTIES_INTERFACE \
65 BUS_INTROSPECTABLE_INTERFACE \
66 BUS_PEER_INTERFACE \
67 "</node>\n"
68
69 #define INTERFACES_LIST \
70 BUS_GENERIC_INTERFACES_LIST \
71 "org.freedesktop.hostname1\0"
72
73 const char hostname_interface[] _introspect_("hostname1") = INTERFACE;
74
75 enum {
76 PROP_HOSTNAME,
77 PROP_STATIC_HOSTNAME,
78 PROP_PRETTY_HOSTNAME,
79 PROP_ICON_NAME,
80 _PROP_MAX
81 };
82
83 static char *data[_PROP_MAX] = {
84 NULL,
85 NULL,
86 NULL,
87 NULL
88 };
89
90 static usec_t remain_until = 0;
91
92 static void free_data(void) {
93 int p;
94
95 for (p = 0; p < _PROP_MAX; p++) {
96 free(data[p]);
97 data[p] = NULL;
98 }
99 }
100
101 static int read_data(void) {
102 int r;
103
104 free_data();
105
106 data[PROP_HOSTNAME] = gethostname_malloc();
107 if (!data[PROP_HOSTNAME])
108 return -ENOMEM;
109
110 r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
111 if (r < 0 && r != -ENOENT)
112 return r;
113
114 r = parse_env_file("/etc/machine-info", NEWLINE,
115 "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
116 "ICON_NAME", &data[PROP_ICON_NAME],
117 NULL);
118 if (r < 0 && r != -ENOENT)
119 return r;
120
121 return 0;
122 }
123
124 static bool check_nss(void) {
125
126 void *dl;
127
128 if ((dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY))) {
129 dlclose(dl);
130 return true;
131 }
132
133 return false;
134 }
135
136 static const char* fallback_icon_name(void) {
137
138 #if defined(__i386__) || defined(__x86_64__)
139 int r;
140 char *type;
141 unsigned t;
142 #endif
143
144 if (detect_virtualization(NULL) > 0)
145 return "computer-vm";
146
147 #if defined(__i386__) || defined(__x86_64__)
148 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
149 if (r < 0)
150 return NULL;
151
152 r = safe_atou(type, &t);
153 free(type);
154
155 if (r < 0)
156 return NULL;
157
158 /* We only list the really obvious cases here. The DMI data is
159 unreliable enough, so let's not do any additional guesswork
160 on top of that.
161
162 See the SMBIOS Specification 2.7.1 section 7.4.1 for
163 details about the values listed here:
164
165 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
166 */
167
168 switch (t) {
169
170 case 0x3:
171 case 0x4:
172 case 0x6:
173 case 0x7:
174 return "computer-desktop";
175
176 case 0x9:
177 case 0xA:
178 case 0xE:
179 return "computer-laptop";
180
181 case 0x11:
182 case 0x1C:
183 return "computer-server";
184 }
185
186 #endif
187 return NULL;
188 }
189
190 static int write_data_hostname(void) {
191 const char *hn;
192
193 if (isempty(data[PROP_HOSTNAME]))
194 hn = "localhost";
195 else
196 hn = data[PROP_HOSTNAME];
197
198 if (sethostname(hn, strlen(hn)) < 0)
199 return -errno;
200
201 return 0;
202 }
203
204 static int write_data_static_hostname(void) {
205
206 if (isempty(data[PROP_STATIC_HOSTNAME])) {
207
208 if (unlink("/etc/hostname") < 0)
209 return errno == ENOENT ? 0 : -errno;
210
211 return 0;
212 }
213
214 return write_one_line_file_atomic("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
215 }
216
217 static int write_data_other(void) {
218
219 static const char * const name[_PROP_MAX] = {
220 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
221 [PROP_ICON_NAME] = "ICON_NAME"
222 };
223
224 char **l = NULL;
225 int r, p;
226
227 r = load_env_file("/etc/machine-info", &l);
228 if (r < 0 && r != -ENOENT)
229 return r;
230
231 for (p = 2; p < _PROP_MAX; p++) {
232 char *t, **u;
233
234 assert(name[p]);
235
236 if (isempty(data[p])) {
237 strv_env_unset(l, name[p]);
238 continue;
239 }
240
241 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
242 strv_free(l);
243 return -ENOMEM;
244 }
245
246 u = strv_env_set(l, t);
247 free(t);
248 strv_free(l);
249
250 if (!u)
251 return -ENOMEM;
252 l = u;
253 }
254
255 if (strv_isempty(l)) {
256
257 if (unlink("/etc/machine-info") < 0)
258 return errno == ENOENT ? 0 : -errno;
259
260 return 0;
261 }
262
263 r = write_env_file("/etc/machine-info", l);
264 strv_free(l);
265
266 return r;
267 }
268
269 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
270 const char *name;
271
272 assert(i);
273 assert(property);
274
275 if (isempty(data[PROP_ICON_NAME]))
276 name = fallback_icon_name();
277 else
278 name = data[PROP_ICON_NAME];
279
280 return bus_property_append_string(i, property, (void*) name);
281 }
282
283 static const BusProperty bus_hostname_properties[] = {
284 { "Hostname", bus_property_append_string, "s", sizeof(data[0])*PROP_HOSTNAME, true },
285 { "StaticHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_STATIC_HOSTNAME, true },
286 { "PrettyHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_PRETTY_HOSTNAME, true },
287 { "IconName", bus_hostname_append_icon_name, "s", sizeof(data[0])*PROP_ICON_NAME, true },
288 { NULL, }
289 };
290
291 static const BusBoundProperties bps[] = {
292 { "org.freedesktop.hostname1", bus_hostname_properties, data },
293 { NULL, }
294 };
295
296 static DBusHandlerResult hostname_message_handler(
297 DBusConnection *connection,
298 DBusMessage *message,
299 void *userdata) {
300
301
302 DBusMessage *reply = NULL, *changed = NULL;
303 DBusError error;
304 int r;
305
306 assert(connection);
307 assert(message);
308
309 dbus_error_init(&error);
310
311 if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
312 const char *name;
313 dbus_bool_t interactive;
314
315 if (!dbus_message_get_args(
316 message,
317 &error,
318 DBUS_TYPE_STRING, &name,
319 DBUS_TYPE_BOOLEAN, &interactive,
320 DBUS_TYPE_INVALID))
321 return bus_send_error_reply(connection, message, &error, -EINVAL);
322
323 if (isempty(name))
324 name = data[PROP_STATIC_HOSTNAME];
325
326 if (isempty(name))
327 name = "localhost";
328
329 if (!hostname_is_valid(name))
330 return bus_send_error_reply(connection, message, NULL, -EINVAL);
331
332 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
333 char *h;
334
335 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, NULL, &error);
336 if (r < 0)
337 return bus_send_error_reply(connection, message, &error, r);
338
339 h = strdup(name);
340 if (!h)
341 goto oom;
342
343 free(data[PROP_HOSTNAME]);
344 data[PROP_HOSTNAME] = h;
345
346 r = write_data_hostname();
347 if (r < 0) {
348 log_error("Failed to set host name: %s", strerror(-r));
349 return bus_send_error_reply(connection, message, NULL, r);
350 }
351
352 log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
353
354 changed = bus_properties_changed_new(
355 "/org/freedesktop/hostname1",
356 "org.freedesktop.hostname1",
357 "Hostname\0");
358 if (!changed)
359 goto oom;
360 }
361
362 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
363 const char *name;
364 dbus_bool_t interactive;
365
366 if (!dbus_message_get_args(
367 message,
368 &error,
369 DBUS_TYPE_STRING, &name,
370 DBUS_TYPE_BOOLEAN, &interactive,
371 DBUS_TYPE_INVALID))
372 return bus_send_error_reply(connection, message, &error, -EINVAL);
373
374 if (isempty(name))
375 name = NULL;
376
377 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
378
379 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, NULL, &error);
380 if (r < 0)
381 return bus_send_error_reply(connection, message, &error, r);
382
383 if (isempty(name)) {
384 free(data[PROP_STATIC_HOSTNAME]);
385 data[PROP_STATIC_HOSTNAME] = NULL;
386 } else {
387 char *h;
388
389 if (!hostname_is_valid(name))
390 return bus_send_error_reply(connection, message, NULL, -EINVAL);
391
392 h = strdup(name);
393 if (!h)
394 goto oom;
395
396 free(data[PROP_STATIC_HOSTNAME]);
397 data[PROP_STATIC_HOSTNAME] = h;
398 }
399
400 r = write_data_static_hostname();
401 if (r < 0) {
402 log_error("Failed to write static host name: %s", strerror(-r));
403 return bus_send_error_reply(connection, message, NULL, r);
404 }
405
406 log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
407
408 changed = bus_properties_changed_new(
409 "/org/freedesktop/hostname1",
410 "org.freedesktop.hostname1",
411 "StaticHostname\0");
412 if (!changed)
413 goto oom;
414 }
415
416 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
417 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
418
419 const char *name;
420 dbus_bool_t interactive;
421 int k;
422
423 if (!dbus_message_get_args(
424 message,
425 &error,
426 DBUS_TYPE_STRING, &name,
427 DBUS_TYPE_BOOLEAN, &interactive,
428 DBUS_TYPE_INVALID))
429 return bus_send_error_reply(connection, message, &error, -EINVAL);
430
431 if (isempty(name))
432 name = NULL;
433
434 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
435
436 if (!streq_ptr(name, data[k])) {
437
438 /* Since the pretty hostname should always be
439 * changed at the same time as the static one,
440 * use the same policy action for both... */
441
442 r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
443 "org.freedesktop.hostname1.set-static-hostname" :
444 "org.freedesktop.hostname1.set-machine-info", interactive, NULL, &error);
445 if (r < 0)
446 return bus_send_error_reply(connection, message, &error, r);
447
448 if (isempty(name)) {
449 free(data[k]);
450 data[k] = NULL;
451 } else {
452 char *h;
453
454 /* The icon name might ultimately be
455 * used as file name, so better be
456 * safe than sorry */
457 if (k == PROP_ICON_NAME && !filename_is_safe(name))
458 return bus_send_error_reply(connection, message, NULL, -EINVAL);
459 if (k == PROP_PRETTY_HOSTNAME && !string_is_safe(name))
460 return bus_send_error_reply(connection, message, NULL, -EINVAL);
461
462 h = strdup(name);
463 if (!h)
464 goto oom;
465
466 free(data[k]);
467 data[k] = h;
468 }
469
470 r = write_data_other();
471 if (r < 0) {
472 log_error("Failed to write machine info: %s", strerror(-r));
473 return bus_send_error_reply(connection, message, NULL, r);
474 }
475
476 log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
477
478 changed = bus_properties_changed_new(
479 "/org/freedesktop/hostname1",
480 "org.freedesktop.hostname1",
481 k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
482 if (!changed)
483 goto oom;
484 }
485
486 } else
487 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
488
489 if (!(reply = dbus_message_new_method_return(message)))
490 goto oom;
491
492 if (!dbus_connection_send(connection, reply, NULL))
493 goto oom;
494
495 dbus_message_unref(reply);
496 reply = NULL;
497
498 if (changed) {
499
500 if (!dbus_connection_send(connection, changed, NULL))
501 goto oom;
502
503 dbus_message_unref(changed);
504 }
505
506 return DBUS_HANDLER_RESULT_HANDLED;
507
508 oom:
509 if (reply)
510 dbus_message_unref(reply);
511
512 if (changed)
513 dbus_message_unref(changed);
514
515 dbus_error_free(&error);
516
517 return DBUS_HANDLER_RESULT_NEED_MEMORY;
518 }
519
520 static int connect_bus(DBusConnection **_bus) {
521 static const DBusObjectPathVTable hostname_vtable = {
522 .message_function = hostname_message_handler
523 };
524 DBusError error;
525 DBusConnection *bus = NULL;
526 int r;
527
528 assert(_bus);
529
530 dbus_error_init(&error);
531
532 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
533 if (!bus) {
534 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
535 r = -ECONNREFUSED;
536 goto fail;
537 }
538
539 dbus_connection_set_exit_on_disconnect(bus, FALSE);
540
541 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
542 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
543 r = log_oom();
544 goto fail;
545 }
546
547 r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
548 if (dbus_error_is_set(&error)) {
549 log_error("Failed to register name on bus: %s", bus_error_message(&error));
550 r = -EEXIST;
551 goto fail;
552 }
553
554 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
555 log_error("Failed to acquire name.");
556 r = -EEXIST;
557 goto fail;
558 }
559
560 if (_bus)
561 *_bus = bus;
562
563 return 0;
564
565 fail:
566 dbus_connection_close(bus);
567 dbus_connection_unref(bus);
568
569 dbus_error_free(&error);
570
571 return r;
572 }
573
574 int main(int argc, char *argv[]) {
575 int r;
576 DBusConnection *bus = NULL;
577 bool exiting = false;
578
579 log_set_target(LOG_TARGET_AUTO);
580 log_parse_environment();
581 log_open();
582
583 umask(0022);
584
585 if (argc == 2 && streq(argv[1], "--introspect")) {
586 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
587 "<node>\n", stdout);
588 fputs(hostname_interface, stdout);
589 fputs("</node>\n", stdout);
590 return 0;
591 }
592
593 if (argc != 1) {
594 log_error("This program takes no arguments.");
595 r = -EINVAL;
596 goto finish;
597 }
598
599 if (!check_nss())
600 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
601
602 r = read_data();
603 if (r < 0) {
604 log_error("Failed to read hostname data: %s", strerror(-r));
605 goto finish;
606 }
607
608 r = connect_bus(&bus);
609 if (r < 0)
610 goto finish;
611
612 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
613 for (;;) {
614
615 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
616 break;
617
618 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
619 exiting = true;
620 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
621 }
622 }
623
624 r = 0;
625
626 finish:
627 free_data();
628
629 if (bus) {
630 dbus_connection_flush(bus);
631 dbus_connection_close(bus);
632 dbus_connection_unref(bus);
633 }
634
635 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
636 }