]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamed.c
localed: move sources to subdirectory
[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 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 #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 DBusHandlerResult hostname_message_handler(
284 DBusConnection *connection,
285 DBusMessage *message,
286 void *userdata) {
287
288 const BusProperty properties[] = {
289 { "org.freedesktop.hostname1", "Hostname", bus_property_append_string, "s", data[PROP_HOSTNAME]},
290 { "org.freedesktop.hostname1", "StaticHostname", bus_property_append_string, "s", data[PROP_STATIC_HOSTNAME]},
291 { "org.freedesktop.hostname1", "PrettyHostname", bus_property_append_string, "s", data[PROP_PRETTY_HOSTNAME]},
292 { "org.freedesktop.hostname1", "IconName", bus_hostname_append_icon_name, "s", data[PROP_ICON_NAME]},
293 { NULL, NULL, NULL, NULL, NULL }
294 };
295
296 DBusMessage *reply = NULL, *changed = NULL;
297 DBusError error;
298 int r;
299
300 assert(connection);
301 assert(message);
302
303 dbus_error_init(&error);
304
305 if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
306 const char *name;
307 dbus_bool_t interactive;
308
309 if (!dbus_message_get_args(
310 message,
311 &error,
312 DBUS_TYPE_STRING, &name,
313 DBUS_TYPE_BOOLEAN, &interactive,
314 DBUS_TYPE_INVALID))
315 return bus_send_error_reply(connection, message, &error, -EINVAL);
316
317 if (isempty(name))
318 name = data[PROP_STATIC_HOSTNAME];
319
320 if (isempty(name))
321 name = "localhost";
322
323 if (!hostname_is_valid(name))
324 return bus_send_error_reply(connection, message, NULL, -EINVAL);
325
326 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
327 char *h;
328
329 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, &error);
330 if (r < 0)
331 return bus_send_error_reply(connection, message, &error, r);
332
333 h = strdup(name);
334 if (!h)
335 goto oom;
336
337 free(data[PROP_HOSTNAME]);
338 data[PROP_HOSTNAME] = h;
339
340 r = write_data_hostname();
341 if (r < 0) {
342 log_error("Failed to set host name: %s", strerror(-r));
343 return bus_send_error_reply(connection, message, NULL, r);
344 }
345
346 log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
347
348 changed = bus_properties_changed_new(
349 "/org/freedesktop/hostname1",
350 "org.freedesktop.hostname1",
351 "Hostname\0");
352 if (!changed)
353 goto oom;
354 }
355
356 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
357 const char *name;
358 dbus_bool_t interactive;
359
360 if (!dbus_message_get_args(
361 message,
362 &error,
363 DBUS_TYPE_STRING, &name,
364 DBUS_TYPE_BOOLEAN, &interactive,
365 DBUS_TYPE_INVALID))
366 return bus_send_error_reply(connection, message, &error, -EINVAL);
367
368 if (isempty(name))
369 name = NULL;
370
371 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
372
373 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, &error);
374 if (r < 0)
375 return bus_send_error_reply(connection, message, &error, r);
376
377 if (isempty(name)) {
378 free(data[PROP_STATIC_HOSTNAME]);
379 data[PROP_STATIC_HOSTNAME] = NULL;
380 } else {
381 char *h;
382
383 if (!hostname_is_valid(name))
384 return bus_send_error_reply(connection, message, NULL, -EINVAL);
385
386 h = strdup(name);
387 if (!h)
388 goto oom;
389
390 free(data[PROP_STATIC_HOSTNAME]);
391 data[PROP_STATIC_HOSTNAME] = h;
392 }
393
394 r = write_data_static_hostname();
395 if (r < 0) {
396 log_error("Failed to write static host name: %s", strerror(-r));
397 return bus_send_error_reply(connection, message, NULL, r);
398 }
399
400 log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
401
402 changed = bus_properties_changed_new(
403 "/org/freedesktop/hostname1",
404 "org.freedesktop.hostname1",
405 "StaticHostname\0");
406 if (!changed)
407 goto oom;
408 }
409
410 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
411 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
412
413 const char *name;
414 dbus_bool_t interactive;
415 int k;
416
417 if (!dbus_message_get_args(
418 message,
419 &error,
420 DBUS_TYPE_STRING, &name,
421 DBUS_TYPE_BOOLEAN, &interactive,
422 DBUS_TYPE_INVALID))
423 return bus_send_error_reply(connection, message, &error, -EINVAL);
424
425 if (isempty(name))
426 name = NULL;
427
428 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
429
430 if (!streq_ptr(name, data[k])) {
431
432 /* Since the pretty hostname should always be
433 * changed at the same time as the static one,
434 * use the same policy action for both... */
435
436 r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
437 "org.freedesktop.hostname1.set-static-hostname" :
438 "org.freedesktop.hostname1.set-machine-info", interactive, &error);
439 if (r < 0)
440 return bus_send_error_reply(connection, message, &error, r);
441
442 if (isempty(name)) {
443 free(data[k]);
444 data[k] = NULL;
445 } else {
446 char *h;
447
448 h = strdup(name);
449 if (!h)
450 goto oom;
451
452 free(data[k]);
453 data[k] = h;
454 }
455
456 r = write_data_other();
457 if (r < 0) {
458 log_error("Failed to write machine info: %s", strerror(-r));
459 return bus_send_error_reply(connection, message, NULL, r);
460 }
461
462 log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
463
464 changed = bus_properties_changed_new(
465 "/org/freedesktop/hostname1",
466 "org.freedesktop.hostname1",
467 k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
468 if (!changed)
469 goto oom;
470 }
471
472 } else
473 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
474
475 if (!(reply = dbus_message_new_method_return(message)))
476 goto oom;
477
478 if (!dbus_connection_send(connection, reply, NULL))
479 goto oom;
480
481 dbus_message_unref(reply);
482 reply = NULL;
483
484 if (changed) {
485
486 if (!dbus_connection_send(connection, changed, NULL))
487 goto oom;
488
489 dbus_message_unref(changed);
490 }
491
492 return DBUS_HANDLER_RESULT_HANDLED;
493
494 oom:
495 if (reply)
496 dbus_message_unref(reply);
497
498 if (changed)
499 dbus_message_unref(changed);
500
501 dbus_error_free(&error);
502
503 return DBUS_HANDLER_RESULT_NEED_MEMORY;
504 }
505
506 static int connect_bus(DBusConnection **_bus) {
507 static const DBusObjectPathVTable hostname_vtable = {
508 .message_function = hostname_message_handler
509 };
510 DBusError error;
511 DBusConnection *bus = NULL;
512 int r;
513
514 assert(_bus);
515
516 dbus_error_init(&error);
517
518 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
519 if (!bus) {
520 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
521 r = -ECONNREFUSED;
522 goto fail;
523 }
524
525 dbus_connection_set_exit_on_disconnect(bus, FALSE);
526
527 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
528 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
529 log_error("Not enough memory");
530 r = -ENOMEM;
531 goto fail;
532 }
533
534 r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
535 if (dbus_error_is_set(&error)) {
536 log_error("Failed to register name on bus: %s", bus_error_message(&error));
537 r = -EEXIST;
538 goto fail;
539 }
540
541 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
542 log_error("Failed to acquire name.");
543 r = -EEXIST;
544 goto fail;
545 }
546
547 if (_bus)
548 *_bus = bus;
549
550 return 0;
551
552 fail:
553 dbus_connection_close(bus);
554 dbus_connection_unref(bus);
555
556 dbus_error_free(&error);
557
558 return r;
559 }
560
561 int main(int argc, char *argv[]) {
562 int r;
563 DBusConnection *bus = NULL;
564 bool exiting = false;
565
566 log_set_target(LOG_TARGET_AUTO);
567 log_parse_environment();
568 log_open();
569
570 umask(0022);
571
572 if (argc == 2 && streq(argv[1], "--introspect")) {
573 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
574 "<node>\n", stdout);
575 fputs(hostname_interface, stdout);
576 fputs("</node>\n", stdout);
577 return 0;
578 }
579
580 if (argc != 1) {
581 log_error("This program takes no arguments.");
582 r = -EINVAL;
583 goto finish;
584 }
585
586 if (!check_nss())
587 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
588
589 r = read_data();
590 if (r < 0) {
591 log_error("Failed to read hostname data: %s", strerror(-r));
592 goto finish;
593 }
594
595 r = connect_bus(&bus);
596 if (r < 0)
597 goto finish;
598
599 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
600 for (;;) {
601
602 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
603 break;
604
605 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
606 exiting = true;
607 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
608 }
609 }
610
611 r = 0;
612
613 finish:
614 free_data();
615
616 if (bus) {
617 dbus_connection_flush(bus);
618 dbus_connection_close(bus);
619 dbus_connection_unref(bus);
620 }
621
622 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
623 }