chiark / gitweb /
hostnamectl: if somebody invokes 'hostnamectl set-hostname' with a valid internet...
[elogind.git] / src / hostname / hostnamectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 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 <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <locale.h>
27 #include <string.h>
28 #include <sys/timex.h>
29 #include <sys/utsname.h>
30
31 #include "dbus-common.h"
32 #include "util.h"
33 #include "spawn-polkit-agent.h"
34 #include "build.h"
35 #include "hwclock.h"
36 #include "strv.h"
37 #include "sd-id128.h"
38 #include "virt.h"
39 #include "fileio.h"
40
41 static enum transport {
42         TRANSPORT_NORMAL,
43         TRANSPORT_SSH,
44         TRANSPORT_POLKIT
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static const char *arg_host = NULL;
48 static bool arg_set_transient = false;
49 static bool arg_set_pretty = false;
50 static bool arg_set_static = false;
51
52 static void polkit_agent_open_if_enabled(void) {
53
54         /* Open the polkit agent as a child process if necessary */
55
56         if (!arg_ask_password)
57                 return;
58
59         polkit_agent_open();
60 }
61
62 typedef struct StatusInfo {
63         const char *hostname;
64         const char *static_hostname;
65         const char *pretty_hostname;
66         const char *icon_name;
67         const char *chassis;
68 } StatusInfo;
69
70 static void print_status_info(StatusInfo *i) {
71         sd_id128_t mid, bid;
72         int r;
73         const char *id = NULL;
74         _cleanup_free_ char *pretty_name = NULL, *cpe_name = NULL;
75         struct utsname u;
76
77         assert(i);
78
79         printf("   Static hostname: %s\n",
80                strna(i->static_hostname));
81
82         if (!streq_ptr(i->hostname, i->static_hostname))
83                 printf("Transient hostname: %s\n",
84                        strna(i->hostname));
85
86         printf("   Pretty hostname: %s\n"
87                "         Icon name: %s\n"
88                "           Chassis: %s\n",
89                strna(i->pretty_hostname),
90                strna(i->icon_name),
91                strna(i->chassis));
92
93         r = sd_id128_get_machine(&mid);
94         if (r >= 0)
95                 printf("        Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid));
96
97         r = sd_id128_get_boot(&bid);
98         if (r >= 0)
99                 printf("           Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid));
100
101         if (detect_virtualization(&id) > 0)
102                 printf("    Virtualization: %s\n", id);
103
104         r = parse_env_file("/etc/os-release", NEWLINE,
105                            "PRETTY_NAME", &pretty_name,
106                            "CPE_NAME", &cpe_name,
107                            NULL);
108
109         if (!isempty(pretty_name))
110                 printf("  Operating System: %s\n", pretty_name);
111
112         if (!isempty(cpe_name))
113                 printf("       CPE OS Name: %s\n", cpe_name);
114
115         assert_se(uname(&u) >= 0);
116         printf("            Kernel: %s %s\n"
117                "      Architecture: %s\n", u.sysname, u.release, u.machine);
118
119 }
120
121 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
122         assert(name);
123         assert(iter);
124
125         switch (dbus_message_iter_get_arg_type(iter)) {
126
127         case DBUS_TYPE_STRING: {
128                 const char *s;
129
130                 dbus_message_iter_get_basic(iter, &s);
131                 if (!isempty(s)) {
132                         if (streq(name, "Hostname"))
133                                 i->hostname = s;
134                         if (streq(name, "StaticHostname"))
135                                 i->static_hostname = s;
136                         if (streq(name, "PrettyHostname"))
137                                 i->pretty_hostname = s;
138                         if (streq(name, "IconName"))
139                                 i->icon_name = s;
140                         if (streq(name, "Chassis"))
141                                 i->chassis = s;
142                 }
143                 break;
144         }
145         }
146
147         return 0;
148 }
149
150 static int show_status(DBusConnection *bus, char **args, unsigned n) {
151         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
152         const char *interface = "";
153         int r;
154         DBusMessageIter iter, sub, sub2, sub3;
155         StatusInfo info;
156
157         assert(args);
158
159         r = bus_method_call_with_reply(
160                         bus,
161                         "org.freedesktop.hostname1",
162                         "/org/freedesktop/hostname1",
163                         "org.freedesktop.DBus.Properties",
164                         "GetAll",
165                         &reply,
166                         NULL,
167                         DBUS_TYPE_STRING, &interface,
168                         DBUS_TYPE_INVALID);
169         if (r < 0)
170                 return r;
171
172         if (!dbus_message_iter_init(reply, &iter) ||
173             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
174             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
175                 log_error("Failed to parse reply.");
176                 return -EIO;
177         }
178
179         zero(info);
180         dbus_message_iter_recurse(&iter, &sub);
181
182         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
183                 const char *name;
184
185                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
186                         log_error("Failed to parse reply.");
187                         return -EIO;
188                 }
189
190                 dbus_message_iter_recurse(&sub, &sub2);
191
192                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
193                         log_error("Failed to parse reply.");
194                         return -EIO;
195                 }
196
197                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
198                         log_error("Failed to parse reply.");
199                         return -EIO;
200                 }
201
202                 dbus_message_iter_recurse(&sub2, &sub3);
203
204                 r = status_property(name, &sub3, &info);
205                 if (r < 0) {
206                         log_error("Failed to parse reply.");
207                         return r;
208                 }
209
210                 dbus_message_iter_next(&sub);
211         }
212
213         print_status_info(&info);
214         return 0;
215 }
216
217 static char* hostname_simplify(char *s) {
218         char *p, *d;
219
220         for (p = s, d = s; *p; p++) {
221                 if ((*p >= 'a' && *p <= 'z') ||
222                     (*p >= '0' && *p <= '9') ||
223                     *p == '-' || *p == '_')
224                         *(d++) = *p;
225                 else if (*p >= 'A' && *p <= 'Z')
226                         *(d++) = *p - 'A' + 'a';
227                 else if (*p == ' ')
228                         *(d++) = '-';
229         }
230
231         *d = 0;
232
233         strshorten(s, HOST_NAME_MAX);
234         return s;
235 }
236
237 static int set_hostname(DBusConnection *bus, char **args, unsigned n) {
238         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
239         dbus_bool_t interactive = true;
240         _cleanup_free_ char *h = NULL;
241         const char *hostname = args[1];
242         int r;
243
244         assert(args);
245         assert(n == 2);
246
247         polkit_agent_open_if_enabled();
248
249         if (arg_set_pretty) {
250                 const char *p;
251
252                 /* If the passed hostname is already valid, then
253                  * assume the user doesn't know anything about pretty
254                  * hostnames, so let's unset the pretty hostname, and
255                  * just set the passed hostname as static/dynamic
256                  * hostname. */
257
258                 if (hostname_is_valid(hostname))
259                         p = "";
260                 else {
261                         p = hostname;
262
263                         h = strdup(hostname);
264                         if (!h)
265                                 return log_oom();
266
267                         hostname = hostname_simplify(h);
268                 }
269
270                 r = bus_method_call_with_reply(
271                                 bus,
272                                 "org.freedesktop.hostname1",
273                                 "/org/freedesktop/hostname1",
274                                 "org.freedesktop.hostname1",
275                                 "SetPrettyHostname",
276                                 &reply,
277                                 NULL,
278                                 DBUS_TYPE_STRING, &p,
279                                 DBUS_TYPE_BOOLEAN, &interactive,
280                                 DBUS_TYPE_INVALID);
281                 if (r < 0)
282                         return r;
283
284                 dbus_message_unref(reply);
285                 reply = NULL;
286         }
287
288         if (arg_set_static) {
289                 r = bus_method_call_with_reply(
290                                 bus,
291                                 "org.freedesktop.hostname1",
292                                 "/org/freedesktop/hostname1",
293                                 "org.freedesktop.hostname1",
294                                 "SetStaticHostname",
295                                 &reply,
296                                 NULL,
297                                 DBUS_TYPE_STRING, &hostname,
298                                 DBUS_TYPE_BOOLEAN, &interactive,
299                                 DBUS_TYPE_INVALID);
300
301                 if (r < 0)
302                         return r;
303
304                 dbus_message_unref(reply);
305                 reply = NULL;
306         }
307
308         if (arg_set_transient) {
309                 r = bus_method_call_with_reply(
310                                 bus,
311                                 "org.freedesktop.hostname1",
312                                 "/org/freedesktop/hostname1",
313                                 "org.freedesktop.hostname1",
314                                 "SetHostname",
315                                 &reply,
316                                 NULL,
317                                 DBUS_TYPE_STRING, &hostname,
318                                 DBUS_TYPE_BOOLEAN, &interactive,
319                                 DBUS_TYPE_INVALID);
320
321                 if (r < 0)
322                         return r;
323         }
324
325         return 0;
326 }
327
328 static int set_icon_name(DBusConnection *bus, char **args, unsigned n) {
329         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
330         dbus_bool_t interactive = true;
331
332         assert(args);
333         assert(n == 2);
334
335         polkit_agent_open_if_enabled();
336
337         return bus_method_call_with_reply(
338                         bus,
339                         "org.freedesktop.hostname1",
340                         "/org/freedesktop/hostname1",
341                         "org.freedesktop.hostname1",
342                         "SetIconName",
343                         &reply,
344                         NULL,
345                         DBUS_TYPE_STRING, &args[1],
346                         DBUS_TYPE_BOOLEAN, &interactive,
347                         DBUS_TYPE_INVALID);
348 }
349
350 static int set_chassis(DBusConnection *bus, char **args, unsigned n) {
351         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
352         dbus_bool_t interactive = true;
353
354         assert(args);
355         assert(n == 2);
356
357         polkit_agent_open_if_enabled();
358
359         return bus_method_call_with_reply(
360                         bus,
361                         "org.freedesktop.hostname1",
362                         "/org/freedesktop/hostname1",
363                         "org.freedesktop.hostname1",
364                         "SetChassis",
365                         &reply,
366                         NULL,
367                         DBUS_TYPE_STRING, &args[1],
368                         DBUS_TYPE_BOOLEAN, &interactive,
369                         DBUS_TYPE_INVALID);
370 }
371
372 static int help(void) {
373
374         printf("%s [OPTIONS...] COMMAND ...\n\n"
375                "Query or change system hostname.\n\n"
376                "  -h --help              Show this help\n"
377                "     --version           Show package version\n"
378                "     --transient         Only set transient hostname\n"
379                "     --static            Only set static hostname\n"
380                "     --pretty            Only set pretty hostname\n"
381                "     --no-ask-password   Do not prompt for password\n"
382                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
383                "Commands:\n"
384                "  status                 Show current hostname settings\n"
385                "  set-hostname NAME      Set system hostname\n"
386                "  set-icon-name NAME     Set icon name for host\n"
387                "  set-chassis NAME       Set chassis type for host\n",
388                program_invocation_short_name);
389
390         return 0;
391 }
392
393 static int parse_argv(int argc, char *argv[]) {
394
395         enum {
396                 ARG_VERSION = 0x100,
397                 ARG_NO_ASK_PASSWORD,
398                 ARG_SET_TRANSIENT,
399                 ARG_SET_STATIC,
400                 ARG_SET_PRETTY
401         };
402
403         static const struct option options[] = {
404                 { "help",            no_argument,       NULL, 'h'                 },
405                 { "version",         no_argument,       NULL, ARG_VERSION         },
406                 { "transient",       no_argument,       NULL, ARG_SET_TRANSIENT   },
407                 { "static",          no_argument,       NULL, ARG_SET_STATIC      },
408                 { "pretty",          no_argument,       NULL, ARG_SET_PRETTY      },
409                 { "host",            required_argument, NULL, 'H'                 },
410                 { "privileged",      no_argument,       NULL, 'P'                 },
411                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
412                 { NULL,              0,                 NULL, 0                   }
413         };
414
415         int c;
416
417         assert(argc >= 0);
418         assert(argv);
419
420         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
421
422                 switch (c) {
423
424                 case 'h':
425                         help();
426                         return 0;
427
428                 case ARG_VERSION:
429                         puts(PACKAGE_STRING);
430                         puts(SYSTEMD_FEATURES);
431                         return 0;
432
433                 case 'P':
434                         arg_transport = TRANSPORT_POLKIT;
435                         break;
436
437                 case 'H':
438                         arg_transport = TRANSPORT_SSH;
439                         arg_host = optarg;
440                         break;
441
442                 case ARG_SET_TRANSIENT:
443                         arg_set_transient = true;
444                         break;
445
446                 case ARG_SET_PRETTY:
447                         arg_set_pretty = true;
448                         break;
449
450                 case ARG_SET_STATIC:
451                         arg_set_static = true;
452                         break;
453
454                 case ARG_NO_ASK_PASSWORD:
455                         arg_ask_password = false;
456                         break;
457
458                 case '?':
459                         return -EINVAL;
460
461                 default:
462                         log_error("Unknown option code %c", c);
463                         return -EINVAL;
464                 }
465         }
466
467         if (!arg_set_transient && !arg_set_pretty && !arg_set_static)
468                 arg_set_transient = arg_set_pretty = arg_set_static = true;
469
470         return 1;
471 }
472
473 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
474
475         static const struct {
476                 const char* verb;
477                 const enum {
478                         MORE,
479                         LESS,
480                         EQUAL
481                 } argc_cmp;
482                 const int argc;
483                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
484         } verbs[] = {
485                 { "status",        LESS,  1, show_status   },
486                 { "set-hostname",  EQUAL, 2, set_hostname  },
487                 { "set-icon-name", EQUAL, 2, set_icon_name },
488                 { "set-chassis",   EQUAL, 2, set_chassis   },
489         };
490
491         int left;
492         unsigned i;
493
494         assert(argc >= 0);
495         assert(argv);
496         assert(error);
497
498         left = argc - optind;
499
500         if (left <= 0)
501                 /* Special rule: no arguments means "status" */
502                 i = 0;
503         else {
504                 if (streq(argv[optind], "help")) {
505                         help();
506                         return 0;
507                 }
508
509                 for (i = 0; i < ELEMENTSOF(verbs); i++)
510                         if (streq(argv[optind], verbs[i].verb))
511                                 break;
512
513                 if (i >= ELEMENTSOF(verbs)) {
514                         log_error("Unknown operation %s", argv[optind]);
515                         return -EINVAL;
516                 }
517         }
518
519         switch (verbs[i].argc_cmp) {
520
521         case EQUAL:
522                 if (left != verbs[i].argc) {
523                         log_error("Invalid number of arguments.");
524                         return -EINVAL;
525                 }
526
527                 break;
528
529         case MORE:
530                 if (left < verbs[i].argc) {
531                         log_error("Too few arguments.");
532                         return -EINVAL;
533                 }
534
535                 break;
536
537         case LESS:
538                 if (left > verbs[i].argc) {
539                         log_error("Too many arguments.");
540                         return -EINVAL;
541                 }
542
543                 break;
544
545         default:
546                 assert_not_reached("Unknown comparison operator.");
547         }
548
549         if (!bus) {
550                 log_error("Failed to get D-Bus connection: %s", error->message);
551                 return -EIO;
552         }
553
554         return verbs[i].dispatch(bus, argv + optind, left);
555 }
556
557 int main(int argc, char *argv[]) {
558         int r, retval = EXIT_FAILURE;
559         DBusConnection *bus = NULL;
560         DBusError error;
561
562         dbus_error_init(&error);
563
564         setlocale(LC_ALL, "");
565         log_parse_environment();
566         log_open();
567
568         r = parse_argv(argc, argv);
569         if (r < 0)
570                 goto finish;
571         else if (r == 0) {
572                 retval = EXIT_SUCCESS;
573                 goto finish;
574         }
575
576         if (arg_transport == TRANSPORT_NORMAL)
577                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
578         else if (arg_transport == TRANSPORT_POLKIT)
579                 bus_connect_system_polkit(&bus, &error);
580         else if (arg_transport == TRANSPORT_SSH)
581                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
582         else
583                 assert_not_reached("Uh, invalid transport...");
584
585         r = hostnamectl_main(bus, argc, argv, &error);
586         retval = r < 0 ? EXIT_FAILURE : r;
587
588 finish:
589         if (bus) {
590                 dbus_connection_flush(bus);
591                 dbus_connection_close(bus);
592                 dbus_connection_unref(bus);
593         }
594
595         dbus_error_free(&error);
596         dbus_shutdown();
597
598         return retval;
599 }