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