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