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