chiark / gitweb /
sd-bus: add API to check if a client has privileges
[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 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 }