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