]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
polkit: don't spawn local client if we access a remote system
[thirdparty/systemd.git] / src / locale / localectl.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 Copyright 2013 Kay Sievers
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <locale.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <string.h>
29 #include <ftw.h>
30 #include <sys/mman.h>
31 #include <fcntl.h>
32
33 #include "sd-bus.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "bus-message.h"
37 #include "util.h"
38 #include "spawn-polkit-agent.h"
39 #include "build.h"
40 #include "strv.h"
41 #include "pager.h"
42 #include "set.h"
43 #include "path-util.h"
44 #include "utf8.h"
45
46 static bool arg_no_pager = false;
47 static bool arg_ask_password = true;
48 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
49 static char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54 if (arg_no_pager)
55 return;
56
57 pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62 /* Open the polkit agent as a child process if necessary */
63 if (!arg_ask_password)
64 return;
65
66 if (arg_transport != BUS_TRANSPORT_LOCAL)
67 return;
68
69 polkit_agent_open();
70 }
71
72 typedef struct StatusInfo {
73 char **locale;
74 const char *vconsole_keymap;
75 const char *vconsole_keymap_toggle;
76 const char *x11_layout;
77 const char *x11_model;
78 const char *x11_variant;
79 const char *x11_options;
80 } StatusInfo;
81
82 static void print_status_info(StatusInfo *i) {
83 assert(i);
84
85 if (strv_isempty(i->locale))
86 puts(" System Locale: n/a\n");
87 else {
88 char **j;
89
90 printf(" System Locale: %s\n", i->locale[0]);
91 STRV_FOREACH(j, i->locale + 1)
92 printf(" %s\n", *j);
93 }
94
95 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
96 if (!isempty(i->vconsole_keymap_toggle))
97 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
98
99 printf(" X11 Layout: %s\n", strna(i->x11_layout));
100 if (!isempty(i->x11_model))
101 printf(" X11 Model: %s\n", i->x11_model);
102 if (!isempty(i->x11_variant))
103 printf(" X11 Variant: %s\n", i->x11_variant);
104 if (!isempty(i->x11_options))
105 printf(" X11 Options: %s\n", i->x11_options);
106 }
107
108 static int show_status(sd_bus *bus, char **args, unsigned n) {
109 StatusInfo info = {};
110 static const struct bus_properties_map map[] = {
111 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
112 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
113 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
114 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
115 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
116 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
117 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
118 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
119 {}
120 };
121 int r;
122
123 assert(bus);
124
125 r = bus_map_all_properties(bus,
126 "org.freedesktop.locale1",
127 "/org/freedesktop/locale1",
128 map,
129 &info);
130 if (r < 0)
131 goto fail;
132
133 print_status_info(&info);
134
135 fail:
136 strv_free(info.locale);
137 return r;
138 }
139
140 static int set_locale(sd_bus *bus, char **args, unsigned n) {
141 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
142 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
143 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
144 int r;
145
146 assert(bus);
147 assert(args);
148
149 polkit_agent_open_if_enabled();
150
151 r = sd_bus_message_new_method_call(bus,
152 "org.freedesktop.locale1",
153 "/org/freedesktop/locale1",
154 "org.freedesktop.locale1",
155 "SetLocale", &m);
156 if (r < 0)
157 return r;
158
159 r = sd_bus_message_append_strv(m, args + 1);
160 if (r < 0)
161 return r;
162
163 r = sd_bus_message_append(m, "b", arg_ask_password);
164 if (r < 0)
165 return r;
166
167 r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, NULL);
168 if (r < 0) {
169 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
170 return r;
171 }
172
173 return 0;
174 }
175
176 static int add_locales_from_archive(Set *locales) {
177 /* Stolen from glibc... */
178
179 struct locarhead {
180 uint32_t magic;
181 /* Serial number. */
182 uint32_t serial;
183 /* Name hash table. */
184 uint32_t namehash_offset;
185 uint32_t namehash_used;
186 uint32_t namehash_size;
187 /* String table. */
188 uint32_t string_offset;
189 uint32_t string_used;
190 uint32_t string_size;
191 /* Table with locale records. */
192 uint32_t locrectab_offset;
193 uint32_t locrectab_used;
194 uint32_t locrectab_size;
195 /* MD5 sum hash table. */
196 uint32_t sumhash_offset;
197 uint32_t sumhash_used;
198 uint32_t sumhash_size;
199 };
200
201 struct namehashent {
202 /* Hash value of the name. */
203 uint32_t hashval;
204 /* Offset of the name in the string table. */
205 uint32_t name_offset;
206 /* Offset of the locale record. */
207 uint32_t locrec_offset;
208 };
209
210 const struct locarhead *h;
211 const struct namehashent *e;
212 const void *p = MAP_FAILED;
213 _cleanup_close_ int fd = -1;
214 size_t sz = 0;
215 struct stat st;
216 unsigned i;
217 int r;
218
219 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
220 if (fd < 0) {
221 if (errno != ENOENT)
222 log_error("Failed to open locale archive: %m");
223 r = -errno;
224 goto finish;
225 }
226
227 if (fstat(fd, &st) < 0) {
228 log_error("fstat() failed: %m");
229 r = -errno;
230 goto finish;
231 }
232
233 if (!S_ISREG(st.st_mode)) {
234 log_error("Archive file is not regular");
235 r = -EBADMSG;
236 goto finish;
237 }
238
239 if (st.st_size < (off_t) sizeof(struct locarhead)) {
240 log_error("Archive has invalid size");
241 r = -EBADMSG;
242 goto finish;
243 }
244
245 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
246 if (p == MAP_FAILED) {
247 log_error("Failed to map archive: %m");
248 r = -errno;
249 goto finish;
250 }
251
252 h = (const struct locarhead *) p;
253 if (h->magic != 0xde020109 ||
254 h->namehash_offset + h->namehash_size > st.st_size ||
255 h->string_offset + h->string_size > st.st_size ||
256 h->locrectab_offset + h->locrectab_size > st.st_size ||
257 h->sumhash_offset + h->sumhash_size > st.st_size) {
258 log_error("Invalid archive file.");
259 r = -EBADMSG;
260 goto finish;
261 }
262
263 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
264 for (i = 0; i < h->namehash_size; i++) {
265 char *z;
266
267 if (e[i].locrec_offset == 0)
268 continue;
269
270 if (!utf8_is_valid((char*) p + e[i].name_offset))
271 continue;
272
273 z = strdup((char*) p + e[i].name_offset);
274 if (!z) {
275 r = log_oom();
276 goto finish;
277 }
278
279 r = set_consume(locales, z);
280 if (r < 0) {
281 log_error("Failed to add locale: %s", strerror(-r));
282 goto finish;
283 }
284 }
285
286 r = 0;
287
288 finish:
289 if (p != MAP_FAILED)
290 munmap((void*) p, sz);
291
292 return r;
293 }
294
295 static int add_locales_from_libdir (Set *locales) {
296 _cleanup_closedir_ DIR *dir;
297 struct dirent *entry;
298 int r;
299
300 dir = opendir("/usr/lib/locale");
301 if (!dir) {
302 log_error("Failed to open locale directory: %m");
303 return -errno;
304 }
305
306 errno = 0;
307 while ((entry = readdir(dir))) {
308 char *z;
309
310 if (entry->d_type != DT_DIR)
311 continue;
312
313 if (ignore_file(entry->d_name))
314 continue;
315
316 z = strdup(entry->d_name);
317 if (!z)
318 return log_oom();
319
320 r = set_consume(locales, z);
321 if (r < 0 && r != -EEXIST) {
322 log_error("Failed to add locale: %s", strerror(-r));
323 return r;
324 }
325
326 errno = 0;
327 }
328
329 if (errno > 0) {
330 log_error("Failed to read locale directory: %m");
331 return -errno;
332 }
333
334 return 0;
335 }
336
337 static int list_locales(sd_bus *bus, char **args, unsigned n) {
338 _cleanup_set_free_ Set *locales;
339 _cleanup_strv_free_ char **l = NULL;
340 int r;
341
342 locales = set_new(string_hash_func, string_compare_func);
343 if (!locales)
344 return log_oom();
345
346 r = add_locales_from_archive(locales);
347 if (r < 0 && r != -ENOENT)
348 return r;
349
350 r = add_locales_from_libdir(locales);
351 if (r < 0)
352 return r;
353
354 l = set_get_strv(locales);
355 if (!l)
356 return log_oom();
357
358 strv_sort(l);
359
360 pager_open_if_enabled();
361
362 strv_print(l);
363
364 return 0;
365 }
366
367 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
368 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
369 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
370 const char *map, *toggle_map;
371 int r;
372
373 assert(bus);
374 assert(args);
375
376 if (n > 3) {
377 log_error("Too many arguments.");
378 return -EINVAL;
379 }
380
381 polkit_agent_open_if_enabled();
382
383 map = args[1];
384 toggle_map = n > 2 ? args[2] : "";
385
386 r = sd_bus_call_method(
387 bus,
388 "org.freedesktop.locale1",
389 "/org/freedesktop/locale1",
390 "org.freedesktop.locale1",
391 "SetVConsoleKeyboard",
392 &error,
393 NULL,
394 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
395 if (r < 0)
396 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
397
398 return r;
399 }
400
401 static Set *keymaps = NULL;
402
403 static int nftw_cb(
404 const char *fpath,
405 const struct stat *sb,
406 int tflag,
407 struct FTW *ftwbuf) {
408
409 char *p, *e;
410 int r;
411
412 if (tflag != FTW_F)
413 return 0;
414
415 if (!endswith(fpath, ".map") &&
416 !endswith(fpath, ".map.gz"))
417 return 0;
418
419 p = strdup(path_get_file_name(fpath));
420 if (!p)
421 return log_oom();
422
423 e = endswith(p, ".map");
424 if (e)
425 *e = 0;
426
427 e = endswith(p, ".map.gz");
428 if (e)
429 *e = 0;
430
431 r = set_consume(keymaps, p);
432 if (r < 0 && r != -EEXIST) {
433 log_error("Can't add keymap: %s", strerror(-r));
434 return r;
435 }
436
437 return 0;
438 }
439
440 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
441 _cleanup_strv_free_ char **l = NULL;
442
443 keymaps = set_new(string_hash_func, string_compare_func);
444 if (!keymaps)
445 return log_oom();
446
447 nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
448 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
449 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
450 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
451
452 l = set_get_strv(keymaps);
453 if (!l) {
454 set_free_free(keymaps);
455 return log_oom();
456 }
457
458 set_free(keymaps);
459
460 if (strv_isempty(l)) {
461 log_error("Couldn't find any console keymaps.");
462 return -ENOENT;
463 }
464
465 strv_sort(l);
466
467 pager_open_if_enabled();
468
469 strv_print(l);
470
471 return 0;
472 }
473
474 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
475 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
476 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
477 const char *layout, *model, *variant, *options;
478 int r;
479
480 assert(bus);
481 assert(args);
482
483 if (n > 5) {
484 log_error("Too many arguments.");
485 return -EINVAL;
486 }
487
488 polkit_agent_open_if_enabled();
489
490 layout = args[1];
491 model = n > 2 ? args[2] : "";
492 variant = n > 3 ? args[3] : "";
493 options = n > 4 ? args[4] : "";
494
495 r = sd_bus_call_method(
496 bus,
497 "org.freedesktop.locale1",
498 "/org/freedesktop/locale1",
499 "org.freedesktop.locale1",
500 "SetX11Keyboard",
501 &error,
502 NULL,
503 "ssssbb", layout, model, variant, options,
504 arg_convert, arg_ask_password);
505 if (r < 0)
506 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
507
508 return r;
509 }
510
511 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
512 _cleanup_fclose_ FILE *f = NULL;
513 _cleanup_strv_free_ char **list = NULL;
514 char line[LINE_MAX];
515 enum {
516 NONE,
517 MODELS,
518 LAYOUTS,
519 VARIANTS,
520 OPTIONS
521 } state = NONE, look_for;
522 int r;
523
524 if (n > 2) {
525 log_error("Too many arguments.");
526 return -EINVAL;
527 }
528
529 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
530 if (!f) {
531 log_error("Failed to open keyboard mapping list. %m");
532 return -errno;
533 }
534
535 if (streq(args[0], "list-x11-keymap-models"))
536 look_for = MODELS;
537 else if (streq(args[0], "list-x11-keymap-layouts"))
538 look_for = LAYOUTS;
539 else if (streq(args[0], "list-x11-keymap-variants"))
540 look_for = VARIANTS;
541 else if (streq(args[0], "list-x11-keymap-options"))
542 look_for = OPTIONS;
543 else
544 assert_not_reached("Wrong parameter");
545
546 FOREACH_LINE(line, f, break) {
547 char *l, *w;
548
549 l = strstrip(line);
550
551 if (isempty(l))
552 continue;
553
554 if (l[0] == '!') {
555 if (startswith(l, "! model"))
556 state = MODELS;
557 else if (startswith(l, "! layout"))
558 state = LAYOUTS;
559 else if (startswith(l, "! variant"))
560 state = VARIANTS;
561 else if (startswith(l, "! option"))
562 state = OPTIONS;
563 else
564 state = NONE;
565
566 continue;
567 }
568
569 if (state != look_for)
570 continue;
571
572 w = l + strcspn(l, WHITESPACE);
573
574 if (n > 1) {
575 char *e;
576
577 if (*w == 0)
578 continue;
579
580 *w = 0;
581 w++;
582 w += strspn(w, WHITESPACE);
583
584 e = strchr(w, ':');
585 if (!e)
586 continue;
587
588 *e = 0;
589
590 if (!streq(w, args[1]))
591 continue;
592 } else
593 *w = 0;
594
595 r = strv_extend(&list, l);
596 if (r < 0)
597 return log_oom();
598 }
599
600 if (strv_isempty(list)) {
601 log_error("Couldn't find any entries.");
602 return -ENOENT;
603 }
604
605 strv_sort(list);
606 strv_uniq(list);
607
608 pager_open_if_enabled();
609
610 strv_print(list);
611 return 0;
612 }
613
614 static int help(void) {
615
616 printf("%s [OPTIONS...] COMMAND ...\n\n"
617 "Query or change system locale and keyboard settings.\n\n"
618 " -h --help Show this help\n"
619 " --version Show package version\n"
620 " --no-convert Don't convert keyboard mappings\n"
621 " --no-pager Do not pipe output into a pager\n"
622 " --no-ask-password Do not prompt for password\n"
623 " -H --host=[USER@]HOST Operate on remote host\n"
624 " -M --machine=CONTAINER Operate on local container\n\n"
625 "Commands:\n"
626 " status Show current locale settings\n"
627 " set-locale LOCALE... Set system locale\n"
628 " list-locales Show known locales\n"
629 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
630 " list-keymaps Show known virtual console keyboard mappings\n"
631 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
632 " Set X11 keyboard mapping\n"
633 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
634 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
635 " list-x11-keymap-variants [LAYOUT]\n"
636 " Show known X11 keyboard mapping variants\n"
637 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
638 program_invocation_short_name);
639
640 return 0;
641 }
642
643 static int parse_argv(int argc, char *argv[]) {
644
645 enum {
646 ARG_VERSION = 0x100,
647 ARG_NO_PAGER,
648 ARG_NO_CONVERT,
649 ARG_NO_ASK_PASSWORD
650 };
651
652 static const struct option options[] = {
653 { "help", no_argument, NULL, 'h' },
654 { "version", no_argument, NULL, ARG_VERSION },
655 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
656 { "host", required_argument, NULL, 'H' },
657 { "machine", required_argument, NULL, 'M' },
658 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
659 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
660 {}
661 };
662
663 int c;
664
665 assert(argc >= 0);
666 assert(argv);
667
668 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
669
670 switch (c) {
671
672 case 'h':
673 return help();
674
675 case ARG_VERSION:
676 puts(PACKAGE_STRING);
677 puts(SYSTEMD_FEATURES);
678 return 0;
679
680 case ARG_NO_CONVERT:
681 arg_convert = false;
682 break;
683
684 case ARG_NO_PAGER:
685 arg_no_pager = true;
686 break;
687
688 case ARG_NO_ASK_PASSWORD:
689 arg_ask_password = false;
690 break;
691
692 case 'H':
693 arg_transport = BUS_TRANSPORT_REMOTE;
694 arg_host = optarg;
695 break;
696
697 case 'M':
698 arg_transport = BUS_TRANSPORT_CONTAINER;
699 arg_host = optarg;
700 break;
701
702 case '?':
703 return -EINVAL;
704
705 default:
706 assert_not_reached("Unhandled option");
707 }
708 }
709
710 return 1;
711 }
712
713 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
714
715 static const struct {
716 const char* verb;
717 const enum {
718 MORE,
719 LESS,
720 EQUAL
721 } argc_cmp;
722 const int argc;
723 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
724 } verbs[] = {
725 { "status", LESS, 1, show_status },
726 { "set-locale", MORE, 2, set_locale },
727 { "list-locales", EQUAL, 1, list_locales },
728 { "set-keymap", MORE, 2, set_vconsole_keymap },
729 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
730 { "set-x11-keymap", MORE, 2, set_x11_keymap },
731 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
732 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
733 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
734 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
735 };
736
737 int left;
738 unsigned i;
739
740 assert(argc >= 0);
741 assert(argv);
742
743 left = argc - optind;
744
745 if (left <= 0)
746 /* Special rule: no arguments means "status" */
747 i = 0;
748 else {
749 if (streq(argv[optind], "help")) {
750 help();
751 return 0;
752 }
753
754 for (i = 0; i < ELEMENTSOF(verbs); i++)
755 if (streq(argv[optind], verbs[i].verb))
756 break;
757
758 if (i >= ELEMENTSOF(verbs)) {
759 log_error("Unknown operation %s", argv[optind]);
760 return -EINVAL;
761 }
762 }
763
764 switch (verbs[i].argc_cmp) {
765
766 case EQUAL:
767 if (left != verbs[i].argc) {
768 log_error("Invalid number of arguments.");
769 return -EINVAL;
770 }
771
772 break;
773
774 case MORE:
775 if (left < verbs[i].argc) {
776 log_error("Too few arguments.");
777 return -EINVAL;
778 }
779
780 break;
781
782 case LESS:
783 if (left > verbs[i].argc) {
784 log_error("Too many arguments.");
785 return -EINVAL;
786 }
787
788 break;
789
790 default:
791 assert_not_reached("Unknown comparison operator.");
792 }
793
794 return verbs[i].dispatch(bus, argv + optind, left);
795 }
796
797 int main(int argc, char*argv[]) {
798 _cleanup_bus_unref_ sd_bus *bus = NULL;
799 int r;
800
801 setlocale(LC_ALL, "");
802 log_parse_environment();
803 log_open();
804
805 r = parse_argv(argc, argv);
806 if (r <= 0)
807 goto finish;
808
809 r = bus_open_transport(arg_transport, arg_host, false, &bus);
810 if (r < 0) {
811 log_error("Failed to create bus connection: %s", strerror(-r));
812 goto finish;
813 }
814
815 r = localectl_main(bus, argc, argv);
816
817 finish:
818 pager_close();
819
820 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
821 }