]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
Merge pull request #1659 from vcaputo/journal_verify_envalid
[thirdparty/systemd.git] / src / locale / localed.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 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 <errno.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "sd-bus.h"
28
29 #include "util.h"
30 #include "mkdir.h"
31 #include "strv.h"
32 #include "def.h"
33 #include "env-util.h"
34 #include "fileio.h"
35 #include "fileio-label.h"
36 #include "bus-util.h"
37 #include "bus-error.h"
38 #include "bus-message.h"
39 #include "event-util.h"
40 #include "locale-util.h"
41 #include "selinux-util.h"
42
43 #ifdef HAVE_XKBCOMMON
44 #include <xkbcommon/xkbcommon.h>
45 #endif
46
47 enum {
48 /* We don't list LC_ALL here on purpose. People should be
49 * using LANG instead. */
50 LOCALE_LANG,
51 LOCALE_LANGUAGE,
52 LOCALE_LC_CTYPE,
53 LOCALE_LC_NUMERIC,
54 LOCALE_LC_TIME,
55 LOCALE_LC_COLLATE,
56 LOCALE_LC_MONETARY,
57 LOCALE_LC_MESSAGES,
58 LOCALE_LC_PAPER,
59 LOCALE_LC_NAME,
60 LOCALE_LC_ADDRESS,
61 LOCALE_LC_TELEPHONE,
62 LOCALE_LC_MEASUREMENT,
63 LOCALE_LC_IDENTIFICATION,
64 _LOCALE_MAX
65 };
66
67 static const char * const names[_LOCALE_MAX] = {
68 [LOCALE_LANG] = "LANG",
69 [LOCALE_LANGUAGE] = "LANGUAGE",
70 [LOCALE_LC_CTYPE] = "LC_CTYPE",
71 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
72 [LOCALE_LC_TIME] = "LC_TIME",
73 [LOCALE_LC_COLLATE] = "LC_COLLATE",
74 [LOCALE_LC_MONETARY] = "LC_MONETARY",
75 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
76 [LOCALE_LC_PAPER] = "LC_PAPER",
77 [LOCALE_LC_NAME] = "LC_NAME",
78 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
79 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
80 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
81 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
82 };
83
84 typedef struct Context {
85 char *locale[_LOCALE_MAX];
86
87 char *x11_layout;
88 char *x11_model;
89 char *x11_variant;
90 char *x11_options;
91
92 char *vc_keymap;
93 char *vc_keymap_toggle;
94
95 Hashmap *polkit_registry;
96 } Context;
97
98 static const char* nonempty(const char *s) {
99 return isempty(s) ? NULL : s;
100 }
101
102 static bool startswith_comma(const char *s, const char *prefix) {
103 const char *t;
104
105 return s && (t = startswith(s, prefix)) && (*t == ',');
106 }
107
108 static void context_free_x11(Context *c) {
109 c->x11_layout = mfree(c->x11_layout);
110 c->x11_options = mfree(c->x11_options);
111 c->x11_model = mfree(c->x11_model);
112 c->x11_variant = mfree(c->x11_variant);
113 }
114
115 static void context_free_vconsole(Context *c) {
116 c->vc_keymap = mfree(c->vc_keymap);
117 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
118 }
119
120 static void context_free_locale(Context *c) {
121 int p;
122
123 for (p = 0; p < _LOCALE_MAX; p++)
124 c->locale[p] = mfree(c->locale[p]);
125 }
126
127 static void context_free(Context *c) {
128 context_free_locale(c);
129 context_free_x11(c);
130 context_free_vconsole(c);
131
132 bus_verify_polkit_async_registry_free(c->polkit_registry);
133 };
134
135 static void locale_simplify(Context *c) {
136 int p;
137
138 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
139 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
140 c->locale[p] = mfree(c->locale[p]);
141 }
142
143 static int locale_read_data(Context *c) {
144 int r;
145
146 context_free_locale(c);
147
148 r = parse_env_file("/etc/locale.conf", NEWLINE,
149 "LANG", &c->locale[LOCALE_LANG],
150 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
151 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
152 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
153 "LC_TIME", &c->locale[LOCALE_LC_TIME],
154 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
155 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
156 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
157 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
158 "LC_NAME", &c->locale[LOCALE_LC_NAME],
159 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
160 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
161 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
162 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
163 NULL);
164
165 if (r == -ENOENT) {
166 int p;
167
168 /* Fill in what we got passed from systemd. */
169 for (p = 0; p < _LOCALE_MAX; p++) {
170 assert(names[p]);
171
172 r = free_and_strdup(&c->locale[p],
173 nonempty(getenv(names[p])));
174 if (r < 0)
175 return r;
176 }
177
178 r = 0;
179 }
180
181 locale_simplify(c);
182 return r;
183 }
184
185 static int vconsole_read_data(Context *c) {
186 int r;
187
188 context_free_vconsole(c);
189
190 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
191 "KEYMAP", &c->vc_keymap,
192 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
193 NULL);
194
195 if (r < 0 && r != -ENOENT)
196 return r;
197
198 return 0;
199 }
200
201 static int x11_read_data(Context *c) {
202 _cleanup_fclose_ FILE *f;
203 char line[LINE_MAX];
204 bool in_section = false;
205 int r;
206
207 context_free_x11(c);
208
209 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
210 if (!f)
211 return errno == ENOENT ? 0 : -errno;
212
213 while (fgets(line, sizeof(line), f)) {
214 char *l;
215
216 char_array_0(line);
217 l = strstrip(line);
218
219 if (l[0] == 0 || l[0] == '#')
220 continue;
221
222 if (in_section && first_word(l, "Option")) {
223 _cleanup_strv_free_ char **a = NULL;
224
225 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
226 if (r < 0)
227 return r;
228
229 if (strv_length(a) == 3) {
230 char **p = NULL;
231
232 if (streq(a[1], "XkbLayout"))
233 p = &c->x11_layout;
234 else if (streq(a[1], "XkbModel"))
235 p = &c->x11_model;
236 else if (streq(a[1], "XkbVariant"))
237 p = &c->x11_variant;
238 else if (streq(a[1], "XkbOptions"))
239 p = &c->x11_options;
240
241 if (p) {
242 free(*p);
243 *p = a[2];
244 a[2] = NULL;
245 }
246 }
247
248 } else if (!in_section && first_word(l, "Section")) {
249 _cleanup_strv_free_ char **a = NULL;
250
251 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
252 if (r < 0)
253 return -ENOMEM;
254
255 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
256 in_section = true;
257
258 } else if (in_section && first_word(l, "EndSection"))
259 in_section = false;
260 }
261
262 return 0;
263 }
264
265 static int context_read_data(Context *c) {
266 int r, q, p;
267
268 r = locale_read_data(c);
269 q = vconsole_read_data(c);
270 p = x11_read_data(c);
271
272 return r < 0 ? r : q < 0 ? q : p;
273 }
274
275 static int locale_write_data(Context *c, char ***settings) {
276 int r, p;
277 _cleanup_strv_free_ char **l = NULL;
278
279 /* Set values will be returned as strv in *settings on success. */
280
281 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
282 if (r < 0 && r != -ENOENT)
283 return r;
284
285 for (p = 0; p < _LOCALE_MAX; p++) {
286 _cleanup_free_ char *t = NULL;
287 char **u;
288
289 assert(names[p]);
290
291 if (isempty(c->locale[p])) {
292 l = strv_env_unset(l, names[p]);
293 continue;
294 }
295
296 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
297 return -ENOMEM;
298
299 u = strv_env_set(l, t);
300 if (!u)
301 return -ENOMEM;
302
303 strv_free(l);
304 l = u;
305 }
306
307 if (strv_isempty(l)) {
308 if (unlink("/etc/locale.conf") < 0)
309 return errno == ENOENT ? 0 : -errno;
310
311 return 0;
312 }
313
314 r = write_env_file_label("/etc/locale.conf", l);
315 if (r < 0)
316 return r;
317
318 *settings = l;
319 l = NULL;
320 return 0;
321 }
322
323 static int locale_update_system_manager(Context *c, sd_bus *bus) {
324 _cleanup_free_ char **l_unset = NULL;
325 _cleanup_strv_free_ char **l_set = NULL;
326 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
327 sd_bus_error error = SD_BUS_ERROR_NULL;
328 unsigned c_set, c_unset, p;
329 int r;
330
331 assert(bus);
332
333 l_unset = new0(char*, _LOCALE_MAX);
334 if (!l_unset)
335 return -ENOMEM;
336
337 l_set = new0(char*, _LOCALE_MAX);
338 if (!l_set)
339 return -ENOMEM;
340
341 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
342 assert(names[p]);
343
344 if (isempty(c->locale[p]))
345 l_unset[c_set++] = (char*) names[p];
346 else {
347 char *s;
348
349 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
350 return -ENOMEM;
351
352 l_set[c_unset++] = s;
353 }
354 }
355
356 assert(c_set + c_unset == _LOCALE_MAX);
357 r = sd_bus_message_new_method_call(bus, &m,
358 "org.freedesktop.systemd1",
359 "/org/freedesktop/systemd1",
360 "org.freedesktop.systemd1.Manager",
361 "UnsetAndSetEnvironment");
362 if (r < 0)
363 return r;
364
365 r = sd_bus_message_append_strv(m, l_unset);
366 if (r < 0)
367 return r;
368
369 r = sd_bus_message_append_strv(m, l_set);
370 if (r < 0)
371 return r;
372
373 r = sd_bus_call(bus, m, 0, &error, NULL);
374 if (r < 0)
375 log_error_errno(r, "Failed to update the manager environment: %m");
376
377 return 0;
378 }
379
380 static int vconsole_write_data(Context *c) {
381 int r;
382 _cleanup_strv_free_ char **l = NULL;
383
384 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
385 if (r < 0 && r != -ENOENT)
386 return r;
387
388 if (isempty(c->vc_keymap))
389 l = strv_env_unset(l, "KEYMAP");
390 else {
391 _cleanup_free_ char *s = NULL;
392 char **u;
393
394 s = strappend("KEYMAP=", c->vc_keymap);
395 if (!s)
396 return -ENOMEM;
397
398 u = strv_env_set(l, s);
399 if (!u)
400 return -ENOMEM;
401
402 strv_free(l);
403 l = u;
404 }
405
406 if (isempty(c->vc_keymap_toggle))
407 l = strv_env_unset(l, "KEYMAP_TOGGLE");
408 else {
409 _cleanup_free_ char *s = NULL;
410 char **u;
411
412 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
413 if (!s)
414 return -ENOMEM;
415
416 u = strv_env_set(l, s);
417 if (!u)
418 return -ENOMEM;
419
420 strv_free(l);
421 l = u;
422 }
423
424 if (strv_isempty(l)) {
425 if (unlink("/etc/vconsole.conf") < 0)
426 return errno == ENOENT ? 0 : -errno;
427
428 return 0;
429 }
430
431 return write_env_file_label("/etc/vconsole.conf", l);
432 }
433
434 static int x11_write_data(Context *c) {
435 _cleanup_fclose_ FILE *f = NULL;
436 _cleanup_free_ char *temp_path = NULL;
437 int r;
438
439 if (isempty(c->x11_layout) &&
440 isempty(c->x11_model) &&
441 isempty(c->x11_variant) &&
442 isempty(c->x11_options)) {
443
444 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
445 return errno == ENOENT ? 0 : -errno;
446
447 return 0;
448 }
449
450 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
451
452 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
453 if (r < 0)
454 return r;
455
456 fchmod(fileno(f), 0644);
457
458 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
459 "# manually too freely.\n"
460 "Section \"InputClass\"\n"
461 " Identifier \"system-keyboard\"\n"
462 " MatchIsKeyboard \"on\"\n", f);
463
464 if (!isempty(c->x11_layout))
465 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
466
467 if (!isempty(c->x11_model))
468 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
469
470 if (!isempty(c->x11_variant))
471 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
472
473 if (!isempty(c->x11_options))
474 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
475
476 fputs("EndSection\n", f);
477
478 r = fflush_and_check(f);
479 if (r < 0)
480 goto fail;
481
482 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 r = -errno;
484 goto fail;
485 }
486
487 return 0;
488
489 fail:
490 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
491
492 if (temp_path)
493 (void) unlink(temp_path);
494
495 return r;
496 }
497
498 static int vconsole_reload(sd_bus *bus) {
499 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
500 int r;
501
502 assert(bus);
503
504 r = sd_bus_call_method(bus,
505 "org.freedesktop.systemd1",
506 "/org/freedesktop/systemd1",
507 "org.freedesktop.systemd1.Manager",
508 "RestartUnit",
509 &error,
510 NULL,
511 "ss", "systemd-vconsole-setup.service", "replace");
512
513 if (r < 0)
514 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
515 return r;
516 }
517
518 static const char* strnulldash(const char *s) {
519 return isempty(s) || streq(s, "-") ? NULL : s;
520 }
521
522 static int read_next_mapping(const char* filename,
523 unsigned min_fields, unsigned max_fields,
524 FILE *f, unsigned *n, char ***a) {
525 assert(f);
526 assert(n);
527 assert(a);
528
529 for (;;) {
530 char line[LINE_MAX];
531 char *l, **b;
532 int r;
533 size_t length;
534
535 errno = 0;
536 if (!fgets(line, sizeof(line), f)) {
537
538 if (ferror(f))
539 return errno ? -errno : -EIO;
540
541 return 0;
542 }
543
544 (*n) ++;
545
546 l = strstrip(line);
547 if (l[0] == 0 || l[0] == '#')
548 continue;
549
550 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
551 if (r < 0)
552 return r;
553
554 length = strv_length(b);
555 if (length < min_fields || length > max_fields) {
556 log_error("Invalid line %s:%u, ignoring.", filename, *n);
557 strv_free(b);
558 continue;
559
560 }
561
562 *a = b;
563 return 1;
564 }
565 }
566
567 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
568 bool modified = false;
569
570 assert(bus);
571
572 if (isempty(c->vc_keymap)) {
573
574 modified =
575 !isempty(c->x11_layout) ||
576 !isempty(c->x11_model) ||
577 !isempty(c->x11_variant) ||
578 !isempty(c->x11_options);
579
580 context_free_x11(c);
581 } else {
582 _cleanup_fclose_ FILE *f = NULL;
583 unsigned n = 0;
584
585 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
586 if (!f)
587 return -errno;
588
589 for (;;) {
590 _cleanup_strv_free_ char **a = NULL;
591 int r;
592
593 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
594 if (r < 0)
595 return r;
596 if (r == 0)
597 break;
598
599 if (!streq(c->vc_keymap, a[0]))
600 continue;
601
602 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
603 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
604 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
605 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
606
607 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
608 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
609 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
610 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
611 return -ENOMEM;
612
613 modified = true;
614 }
615
616 break;
617 }
618 }
619
620 if (modified) {
621 int r;
622
623 r = x11_write_data(c);
624 if (r < 0)
625 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
626
627 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
628 strempty(c->x11_layout),
629 strempty(c->x11_model),
630 strempty(c->x11_variant),
631 strempty(c->x11_options));
632
633 sd_bus_emit_properties_changed(bus,
634 "/org/freedesktop/locale1",
635 "org.freedesktop.locale1",
636 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
637 } else
638 log_debug("X11 keyboard layout was not modified.");
639
640 return 0;
641 }
642
643 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
644 const char *dir;
645 _cleanup_free_ char *n;
646
647 if (x11_variant)
648 n = strjoin(x11_layout, "-", x11_variant, NULL);
649 else
650 n = strdup(x11_layout);
651 if (!n)
652 return -ENOMEM;
653
654 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
655 _cleanup_free_ char *p = NULL, *pz = NULL;
656 bool uncompressed;
657
658 p = strjoin(dir, "xkb/", n, ".map", NULL);
659 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
660 if (!p || !pz)
661 return -ENOMEM;
662
663 uncompressed = access(p, F_OK) == 0;
664 if (uncompressed || access(pz, F_OK) == 0) {
665 log_debug("Found converted keymap %s at %s",
666 n, uncompressed ? p : pz);
667
668 *new_keymap = n;
669 n = NULL;
670 return 1;
671 }
672 }
673
674 return 0;
675 }
676
677 static int find_legacy_keymap(Context *c, char **new_keymap) {
678 _cleanup_fclose_ FILE *f;
679 unsigned n = 0;
680 unsigned best_matching = 0;
681 int r;
682
683 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
684 if (!f)
685 return -errno;
686
687 for (;;) {
688 _cleanup_strv_free_ char **a = NULL;
689 unsigned matching = 0;
690
691 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
692 if (r < 0)
693 return r;
694 if (r == 0)
695 break;
696
697 /* Determine how well matching this entry is */
698 if (streq_ptr(c->x11_layout, a[1]))
699 /* If we got an exact match, this is best */
700 matching = 10;
701 else {
702 /* We have multiple X layouts, look for an
703 * entry that matches our key with everything
704 * but the first layout stripped off. */
705 if (startswith_comma(c->x11_layout, a[1]))
706 matching = 5;
707 else {
708 char *x;
709
710 /* If that didn't work, strip off the
711 * other layouts from the entry, too */
712 x = strndupa(a[1], strcspn(a[1], ","));
713 if (startswith_comma(c->x11_layout, x))
714 matching = 1;
715 }
716 }
717
718 if (matching > 0) {
719 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
720 matching++;
721
722 if (streq_ptr(c->x11_variant, a[3])) {
723 matching++;
724
725 if (streq_ptr(c->x11_options, a[4]))
726 matching++;
727 }
728 }
729 }
730
731 /* The best matching entry so far, then let's save that */
732 if (matching >= MAX(best_matching, 1u)) {
733 log_debug("Found legacy keymap %s with score %u",
734 a[0], matching);
735
736 if (matching > best_matching) {
737 best_matching = matching;
738
739 r = free_and_strdup(new_keymap, a[0]);
740 if (r < 0)
741 return r;
742 }
743 }
744 }
745
746 if (best_matching < 10 && c->x11_layout) {
747 /* The best match is only the first part of the X11
748 * keymap. Check if we have a converted map which
749 * matches just the first layout.
750 */
751 char *l, *v = NULL, *converted;
752
753 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
754 if (c->x11_variant)
755 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
756 r = find_converted_keymap(l, v, &converted);
757 if (r < 0)
758 return r;
759 if (r > 0) {
760 free(*new_keymap);
761 *new_keymap = converted;
762 }
763 }
764
765 return 0;
766 }
767
768 static int find_language_fallback(const char *lang, char **language) {
769 _cleanup_fclose_ FILE *f = NULL;
770 unsigned n = 0;
771
772 assert(language);
773
774 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
775 if (!f)
776 return -errno;
777
778 for (;;) {
779 _cleanup_strv_free_ char **a = NULL;
780 int r;
781
782 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
783 if (r <= 0)
784 return r;
785
786 if (streq(lang, a[0])) {
787 assert(strv_length(a) == 2);
788 *language = a[1];
789 a[1] = NULL;
790 return 1;
791 }
792 }
793
794 assert_not_reached("should not be here");
795 }
796
797 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
798 bool modified = false;
799 int r;
800
801 assert(bus);
802
803 if (isempty(c->x11_layout)) {
804
805 modified =
806 !isempty(c->vc_keymap) ||
807 !isempty(c->vc_keymap_toggle);
808
809 context_free_x11(c);
810 } else {
811 char *new_keymap = NULL;
812
813 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
814 if (r < 0)
815 return r;
816 else if (r == 0) {
817 r = find_legacy_keymap(c, &new_keymap);
818 if (r < 0)
819 return r;
820 }
821
822 if (!streq_ptr(c->vc_keymap, new_keymap)) {
823 free(c->vc_keymap);
824 c->vc_keymap = new_keymap;
825 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
826 modified = true;
827 } else
828 free(new_keymap);
829 }
830
831 if (modified) {
832 r = vconsole_write_data(c);
833 if (r < 0)
834 log_error_errno(r, "Failed to set virtual console keymap: %m");
835
836 log_info("Changed virtual console keymap to '%s' toggle '%s'",
837 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
838
839 sd_bus_emit_properties_changed(bus,
840 "/org/freedesktop/locale1",
841 "org.freedesktop.locale1",
842 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
843
844 return vconsole_reload(bus);
845 } else
846 log_debug("Virtual console keymap was not modified.");
847
848 return 0;
849 }
850
851 static int property_get_locale(
852 sd_bus *bus,
853 const char *path,
854 const char *interface,
855 const char *property,
856 sd_bus_message *reply,
857 void *userdata,
858 sd_bus_error *error) {
859
860 Context *c = userdata;
861 _cleanup_strv_free_ char **l = NULL;
862 int p, q;
863
864 l = new0(char*, _LOCALE_MAX+1);
865 if (!l)
866 return -ENOMEM;
867
868 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
869 char *t;
870
871 if (isempty(c->locale[p]))
872 continue;
873
874 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
875 return -ENOMEM;
876
877 l[q++] = t;
878 }
879
880 return sd_bus_message_append_strv(reply, l);
881 }
882
883 static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
884 Context *c = userdata;
885 _cleanup_strv_free_ char **l = NULL;
886 char **i;
887 const char *lang = NULL;
888 int interactive;
889 bool modified = false;
890 bool have[_LOCALE_MAX] = {};
891 int p;
892 int r;
893
894 assert(m);
895 assert(c);
896
897 r = bus_message_read_strv_extend(m, &l);
898 if (r < 0)
899 return r;
900
901 r = sd_bus_message_read_basic(m, 'b', &interactive);
902 if (r < 0)
903 return r;
904
905 /* Check whether a variable changed and if it is valid */
906 STRV_FOREACH(i, l) {
907 bool valid = false;
908
909 for (p = 0; p < _LOCALE_MAX; p++) {
910 size_t k;
911
912 k = strlen(names[p]);
913 if (startswith(*i, names[p]) &&
914 (*i)[k] == '=' &&
915 locale_is_valid((*i) + k + 1)) {
916 valid = true;
917 have[p] = true;
918
919 if (p == LOCALE_LANG)
920 lang = (*i) + k + 1;
921
922 if (!streq_ptr(*i + k + 1, c->locale[p]))
923 modified = true;
924
925 break;
926 }
927 }
928
929 if (!valid)
930 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
931 }
932
933 /* If LANG was specified, but not LANGUAGE, check if we should
934 * set it based on the language fallback table. */
935 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
936 _cleanup_free_ char *language = NULL;
937
938 assert(lang);
939
940 (void) find_language_fallback(lang, &language);
941 if (language) {
942 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
943 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
944 r = strv_extendf(&l, "LANGUAGE=%s", language);
945 if (r < 0)
946 return r;
947
948 have[LOCALE_LANGUAGE] = true;
949 modified = true;
950 }
951 }
952 }
953
954 /* Check whether a variable is unset */
955 if (!modified)
956 for (p = 0; p < _LOCALE_MAX; p++)
957 if (!isempty(c->locale[p]) && !have[p]) {
958 modified = true;
959 break;
960 }
961
962 if (modified) {
963 _cleanup_strv_free_ char **settings = NULL;
964
965 r = bus_verify_polkit_async(
966 m,
967 CAP_SYS_ADMIN,
968 "org.freedesktop.locale1.set-locale",
969 NULL,
970 interactive,
971 UID_INVALID,
972 &c->polkit_registry,
973 error);
974 if (r < 0)
975 return r;
976 if (r == 0)
977 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
978
979 STRV_FOREACH(i, l)
980 for (p = 0; p < _LOCALE_MAX; p++) {
981 size_t k;
982
983 k = strlen(names[p]);
984 if (startswith(*i, names[p]) && (*i)[k] == '=') {
985 r = free_and_strdup(&c->locale[p], *i + k + 1);
986 if (r < 0)
987 return r;
988 break;
989 }
990 }
991
992 for (p = 0; p < _LOCALE_MAX; p++) {
993 if (have[p])
994 continue;
995
996 c->locale[p] = mfree(c->locale[p]);
997 }
998
999 locale_simplify(c);
1000
1001 r = locale_write_data(c, &settings);
1002 if (r < 0) {
1003 log_error_errno(r, "Failed to set locale: %m");
1004 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
1005 }
1006
1007 locale_update_system_manager(c, sd_bus_message_get_bus(m));
1008
1009 if (settings) {
1010 _cleanup_free_ char *line;
1011
1012 line = strv_join(settings, ", ");
1013 log_info("Changed locale to %s.", strnull(line));
1014 } else
1015 log_info("Changed locale to unset.");
1016
1017 (void) sd_bus_emit_properties_changed(
1018 sd_bus_message_get_bus(m),
1019 "/org/freedesktop/locale1",
1020 "org.freedesktop.locale1",
1021 "Locale", NULL);
1022 } else
1023 log_debug("Locale settings were not modified.");
1024
1025
1026 return sd_bus_reply_method_return(m, NULL);
1027 }
1028
1029 static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1030 Context *c = userdata;
1031 const char *keymap, *keymap_toggle;
1032 int convert, interactive;
1033 int r;
1034
1035 assert(m);
1036 assert(c);
1037
1038 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1039 if (r < 0)
1040 return r;
1041
1042 if (isempty(keymap))
1043 keymap = NULL;
1044
1045 if (isempty(keymap_toggle))
1046 keymap_toggle = NULL;
1047
1048 if (!streq_ptr(keymap, c->vc_keymap) ||
1049 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1050
1051 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1052 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1053 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1054
1055 r = bus_verify_polkit_async(
1056 m,
1057 CAP_SYS_ADMIN,
1058 "org.freedesktop.locale1.set-keyboard",
1059 NULL,
1060 interactive,
1061 UID_INVALID,
1062 &c->polkit_registry,
1063 error);
1064 if (r < 0)
1065 return r;
1066 if (r == 0)
1067 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1068
1069 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1070 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1071 return -ENOMEM;
1072
1073 r = vconsole_write_data(c);
1074 if (r < 0) {
1075 log_error_errno(r, "Failed to set virtual console keymap: %m");
1076 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1077 }
1078
1079 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1080 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1081
1082 r = vconsole_reload(sd_bus_message_get_bus(m));
1083 if (r < 0)
1084 log_error_errno(r, "Failed to request keymap reload: %m");
1085
1086 (void) sd_bus_emit_properties_changed(
1087 sd_bus_message_get_bus(m),
1088 "/org/freedesktop/locale1",
1089 "org.freedesktop.locale1",
1090 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1091
1092 if (convert) {
1093 r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m));
1094 if (r < 0)
1095 log_error_errno(r, "Failed to convert keymap data: %m");
1096 }
1097 }
1098
1099 return sd_bus_reply_method_return(m, NULL);
1100 }
1101
1102 #ifdef HAVE_XKBCOMMON
1103 _printf_(3, 0)
1104 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1105 const char *fmt;
1106
1107 fmt = strjoina("libxkbcommon: ", format);
1108 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1109 }
1110
1111 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1112 const struct xkb_rule_names rmlvo = {
1113 .model = model,
1114 .layout = layout,
1115 .variant = variant,
1116 .options = options,
1117 };
1118 struct xkb_context *ctx = NULL;
1119 struct xkb_keymap *km = NULL;
1120 int r;
1121
1122 /* compile keymap from RMLVO information to check out its validity */
1123
1124 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1125 if (!ctx) {
1126 r = -ENOMEM;
1127 goto exit;
1128 }
1129
1130 xkb_context_set_log_fn(ctx, log_xkb);
1131
1132 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1133 if (!km) {
1134 r = -EINVAL;
1135 goto exit;
1136 }
1137
1138 r = 0;
1139
1140 exit:
1141 xkb_keymap_unref(km);
1142 xkb_context_unref(ctx);
1143 return r;
1144 }
1145 #else
1146 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1147 return 0;
1148 }
1149 #endif
1150
1151 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1152 Context *c = userdata;
1153 const char *layout, *model, *variant, *options;
1154 int convert, interactive;
1155 int r;
1156
1157 assert(m);
1158 assert(c);
1159
1160 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1161 if (r < 0)
1162 return r;
1163
1164 if (isempty(layout))
1165 layout = NULL;
1166
1167 if (isempty(model))
1168 model = NULL;
1169
1170 if (isempty(variant))
1171 variant = NULL;
1172
1173 if (isempty(options))
1174 options = NULL;
1175
1176 if (!streq_ptr(layout, c->x11_layout) ||
1177 !streq_ptr(model, c->x11_model) ||
1178 !streq_ptr(variant, c->x11_variant) ||
1179 !streq_ptr(options, c->x11_options)) {
1180
1181 if ((layout && !string_is_safe(layout)) ||
1182 (model && !string_is_safe(model)) ||
1183 (variant && !string_is_safe(variant)) ||
1184 (options && !string_is_safe(options)))
1185 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1186
1187 r = bus_verify_polkit_async(
1188 m,
1189 CAP_SYS_ADMIN,
1190 "org.freedesktop.locale1.set-keyboard",
1191 NULL,
1192 interactive,
1193 UID_INVALID,
1194 &c->polkit_registry,
1195 error);
1196 if (r < 0)
1197 return r;
1198 if (r == 0)
1199 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1200
1201 r = verify_xkb_rmlvo(model, layout, variant, options);
1202 if (r < 0) {
1203 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1204 strempty(model), strempty(layout), strempty(variant), strempty(options));
1205 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1206 }
1207
1208 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1209 free_and_strdup(&c->x11_model, model) < 0 ||
1210 free_and_strdup(&c->x11_variant, variant) < 0 ||
1211 free_and_strdup(&c->x11_options, options) < 0)
1212 return -ENOMEM;
1213
1214 r = x11_write_data(c);
1215 if (r < 0) {
1216 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1217 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1218 }
1219
1220 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1221 strempty(c->x11_layout),
1222 strempty(c->x11_model),
1223 strempty(c->x11_variant),
1224 strempty(c->x11_options));
1225
1226 (void) sd_bus_emit_properties_changed(
1227 sd_bus_message_get_bus(m),
1228 "/org/freedesktop/locale1",
1229 "org.freedesktop.locale1",
1230 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1231
1232 if (convert) {
1233 r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
1234 if (r < 0)
1235 log_error_errno(r, "Failed to convert keymap data: %m");
1236 }
1237 }
1238
1239 return sd_bus_reply_method_return(m, NULL);
1240 }
1241
1242 static const sd_bus_vtable locale_vtable[] = {
1243 SD_BUS_VTABLE_START(0),
1244 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1245 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1246 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1247 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1248 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1249 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1250 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1251 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1252 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1253 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1254 SD_BUS_VTABLE_END
1255 };
1256
1257 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1258 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1259 int r;
1260
1261 assert(c);
1262 assert(event);
1263 assert(_bus);
1264
1265 r = sd_bus_default_system(&bus);
1266 if (r < 0)
1267 return log_error_errno(r, "Failed to get system bus connection: %m");
1268
1269 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1270 if (r < 0)
1271 return log_error_errno(r, "Failed to register object: %m");
1272
1273 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1274 if (r < 0)
1275 return log_error_errno(r, "Failed to register name: %m");
1276
1277 r = sd_bus_attach_event(bus, event, 0);
1278 if (r < 0)
1279 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1280
1281 *_bus = bus;
1282 bus = NULL;
1283
1284 return 0;
1285 }
1286
1287 int main(int argc, char *argv[]) {
1288 _cleanup_(context_free) Context context = {};
1289 _cleanup_event_unref_ sd_event *event = NULL;
1290 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1291 int r;
1292
1293 log_set_target(LOG_TARGET_AUTO);
1294 log_parse_environment();
1295 log_open();
1296
1297 umask(0022);
1298 mac_selinux_init("/etc");
1299
1300 if (argc != 1) {
1301 log_error("This program takes no arguments.");
1302 r = -EINVAL;
1303 goto finish;
1304 }
1305
1306 r = sd_event_default(&event);
1307 if (r < 0) {
1308 log_error_errno(r, "Failed to allocate event loop: %m");
1309 goto finish;
1310 }
1311
1312 sd_event_set_watchdog(event, true);
1313
1314 r = connect_bus(&context, event, &bus);
1315 if (r < 0)
1316 goto finish;
1317
1318 r = context_read_data(&context);
1319 if (r < 0) {
1320 log_error_errno(r, "Failed to read locale data: %m");
1321 goto finish;
1322 }
1323
1324 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1325 if (r < 0) {
1326 log_error_errno(r, "Failed to run event loop: %m");
1327 goto finish;
1328 }
1329
1330 finish:
1331 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1332 }