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