]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
dbus: add some more safety checks before accepting data from bus clients
[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
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "util.h"
29 #include "mkdir.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
34
35 #define INTERFACE \
36 " <interface name=\"org.freedesktop.locale1\">\n" \
37 " <property name=\"Locale\" type=\"as\" access=\"read\"/>\n" \
38 " <property name=\"VConsoleKeymap\" type=\"s\" access=\"read\"/>\n" \
39 " <property name=\"VConsoleKeymapToggle\" type=\"s\" access=\"read\"/>\n" \
40 " <property name=\"X11Layout\" type=\"s\" access=\"read\"/>\n" \
41 " <property name=\"X11Model\" type=\"s\" access=\"read\"/>\n" \
42 " <property name=\"X11Variant\" type=\"s\" access=\"read\"/>\n" \
43 " <property name=\"X11Options\" type=\"s\" access=\"read\"/>\n" \
44 " <method name=\"SetLocale\">\n" \
45 " <arg name=\"locale\" type=\"as\" direction=\"in\"/>\n" \
46 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
47 " </method>\n" \
48 " <method name=\"SetVConsoleKeyboard\">\n" \
49 " <arg name=\"keymap\" type=\"s\" direction=\"in\"/>\n" \
50 " <arg name=\"keymap_toggle\" type=\"s\" direction=\"in\"/>\n" \
51 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
52 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
53 " </method>\n" \
54 " <method name=\"SetX11Keyboard\">\n" \
55 " <arg name=\"layout\" type=\"s\" direction=\"in\"/>\n" \
56 " <arg name=\"model\" type=\"s\" direction=\"in\"/>\n" \
57 " <arg name=\"variant\" type=\"s\" direction=\"in\"/>\n" \
58 " <arg name=\"options\" type=\"s\" direction=\"in\"/>\n" \
59 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
60 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
61 " </method>\n" \
62 " </interface>\n"
63
64 #define INTROSPECTION \
65 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
66 "<node>\n" \
67 INTERFACE \
68 BUS_PROPERTIES_INTERFACE \
69 BUS_INTROSPECTABLE_INTERFACE \
70 BUS_PEER_INTERFACE \
71 "</node>\n"
72
73 #define INTERFACES_LIST \
74 BUS_GENERIC_INTERFACES_LIST \
75 "org.freedesktop.locale1\0"
76
77 const char locale_interface[] _introspect_("locale1") = INTERFACE;
78
79 enum {
80 /* We don't list LC_ALL here on purpose. People should be
81 * using LANG instead. */
82
83 PROP_LANG,
84 PROP_LANGUAGE,
85 PROP_LC_CTYPE,
86 PROP_LC_NUMERIC,
87 PROP_LC_TIME,
88 PROP_LC_COLLATE,
89 PROP_LC_MONETARY,
90 PROP_LC_MESSAGES,
91 PROP_LC_PAPER,
92 PROP_LC_NAME,
93 PROP_LC_ADDRESS,
94 PROP_LC_TELEPHONE,
95 PROP_LC_MEASUREMENT,
96 PROP_LC_IDENTIFICATION,
97 _PROP_MAX
98 };
99
100 static const char * const names[_PROP_MAX] = {
101 [PROP_LANG] = "LANG",
102 [PROP_LANGUAGE] = "LANGUAGE",
103 [PROP_LC_CTYPE] = "LC_CTYPE",
104 [PROP_LC_NUMERIC] = "LC_NUMERIC",
105 [PROP_LC_TIME] = "LC_TIME",
106 [PROP_LC_COLLATE] = "LC_COLLATE",
107 [PROP_LC_MONETARY] = "LC_MONETARY",
108 [PROP_LC_MESSAGES] = "LC_MESSAGES",
109 [PROP_LC_PAPER] = "LC_PAPER",
110 [PROP_LC_NAME] = "LC_NAME",
111 [PROP_LC_ADDRESS] = "LC_ADDRESS",
112 [PROP_LC_TELEPHONE] = "LC_TELEPHONE",
113 [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT",
114 [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
115 };
116
117 static char *data[_PROP_MAX] = {
118 NULL,
119 NULL,
120 NULL,
121 NULL,
122 NULL,
123 NULL,
124 NULL,
125 NULL,
126 NULL,
127 NULL,
128 NULL,
129 NULL,
130 NULL
131 };
132
133 typedef struct State {
134 char *x11_layout, *x11_model, *x11_variant, *x11_options;
135 char *vc_keymap, *vc_keymap_toggle;
136 } State;
137
138 static State state;
139
140 static usec_t remain_until = 0;
141
142 static int free_and_set(char **s, const char *v) {
143 int r;
144 char *t;
145
146 assert(s);
147
148 r = strdup_or_null(isempty(v) ? NULL : v, &t);
149 if (r < 0)
150 return r;
151
152 free(*s);
153 *s = t;
154
155 return 0;
156 }
157
158 static void free_data_locale(void) {
159 int p;
160
161 for (p = 0; p < _PROP_MAX; p++) {
162 free(data[p]);
163 data[p] = NULL;
164 }
165 }
166
167 static void free_data_x11(void) {
168 free(state.x11_layout);
169 free(state.x11_model);
170 free(state.x11_variant);
171 free(state.x11_options);
172
173 state.x11_layout = state.x11_model = state.x11_variant = state.x11_options = NULL;
174 }
175
176 static void free_data_vconsole(void) {
177 free(state.vc_keymap);
178 free(state.vc_keymap_toggle);
179
180 state.vc_keymap = state.vc_keymap_toggle = NULL;
181 }
182
183 static void simplify(void) {
184 int p;
185
186 for (p = 1; p < _PROP_MAX; p++)
187 if (isempty(data[p]) || streq_ptr(data[PROP_LANG], data[p])) {
188 free(data[p]);
189 data[p] = NULL;
190 }
191 }
192
193 static int read_data_locale(void) {
194 int r;
195
196 free_data_locale();
197
198 r = parse_env_file("/etc/locale.conf", NEWLINE,
199 "LANG", &data[PROP_LANG],
200 "LANGUAGE", &data[PROP_LANGUAGE],
201 "LC_CTYPE", &data[PROP_LC_CTYPE],
202 "LC_NUMERIC", &data[PROP_LC_NUMERIC],
203 "LC_TIME", &data[PROP_LC_TIME],
204 "LC_COLLATE", &data[PROP_LC_COLLATE],
205 "LC_MONETARY", &data[PROP_LC_MONETARY],
206 "LC_MESSAGES", &data[PROP_LC_MESSAGES],
207 "LC_PAPER", &data[PROP_LC_PAPER],
208 "LC_NAME", &data[PROP_LC_NAME],
209 "LC_ADDRESS", &data[PROP_LC_ADDRESS],
210 "LC_TELEPHONE", &data[PROP_LC_TELEPHONE],
211 "LC_MEASUREMENT", &data[PROP_LC_MEASUREMENT],
212 "LC_IDENTIFICATION", &data[PROP_LC_IDENTIFICATION],
213 NULL);
214
215 if (r == -ENOENT) {
216 int p;
217
218 /* Fill in what we got passed from systemd. */
219
220 for (p = 0; p < _PROP_MAX; p++) {
221 char *e, *d;
222
223 assert(names[p]);
224
225 e = getenv(names[p]);
226 if (e) {
227 d = strdup(e);
228 if (!d)
229 return -ENOMEM;
230 } else
231 d = NULL;
232
233 free(data[p]);
234 data[p] = d;
235 }
236
237 r = 0;
238 }
239
240 simplify();
241 return r;
242 }
243
244 static void free_data(void) {
245 free_data_locale();
246 free_data_vconsole();
247 free_data_x11();
248 }
249
250 static int read_data_vconsole(void) {
251 int r;
252
253 free_data_vconsole();
254
255 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
256 "KEYMAP", &state.vc_keymap,
257 "KEYMAP_TOGGLE", &state.vc_keymap_toggle,
258 NULL);
259
260 if (r < 0 && r != -ENOENT)
261 return r;
262
263 return 0;
264 }
265
266 static int read_data_x11(void) {
267 FILE *f;
268 char line[LINE_MAX];
269 bool in_section = false;
270
271 free_data_x11();
272
273 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
274 if (!f)
275 return errno == ENOENT ? 0 : -errno;
276
277 while (fgets(line, sizeof(line), f)) {
278 char *l;
279
280 char_array_0(line);
281 l = strstrip(line);
282
283 if (l[0] == 0 || l[0] == '#')
284 continue;
285
286 if (in_section && first_word(l, "Option")) {
287 char **a;
288
289 a = strv_split_quoted(l);
290 if (!a) {
291 fclose(f);
292 return -ENOMEM;
293 }
294
295 if (strv_length(a) == 3) {
296
297 if (streq(a[1], "XkbLayout")) {
298 free(state.x11_layout);
299 state.x11_layout = a[2];
300 a[2] = NULL;
301 } else if (streq(a[1], "XkbModel")) {
302 free(state.x11_model);
303 state.x11_model = a[2];
304 a[2] = NULL;
305 } else if (streq(a[1], "XkbVariant")) {
306 free(state.x11_variant);
307 state.x11_variant = a[2];
308 a[2] = NULL;
309 } else if (streq(a[1], "XkbOptions")) {
310 free(state.x11_options);
311 state.x11_options = a[2];
312 a[2] = NULL;
313 }
314 }
315
316 strv_free(a);
317
318 } else if (!in_section && first_word(l, "Section")) {
319 char **a;
320
321 a = strv_split_quoted(l);
322 if (!a) {
323 fclose(f);
324 return -ENOMEM;
325 }
326
327 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
328 in_section = true;
329
330 strv_free(a);
331 } else if (in_section && first_word(l, "EndSection"))
332 in_section = false;
333 }
334
335 fclose(f);
336
337 return 0;
338 }
339
340 static int read_data(void) {
341 int r, q, p;
342
343 r = read_data_locale();
344 q = read_data_vconsole();
345 p = read_data_x11();
346
347 return r < 0 ? r : q < 0 ? q : p;
348 }
349
350 static int write_data_locale(void) {
351 int r, p;
352 char **l = NULL;
353
354 r = load_env_file("/etc/locale.conf", &l);
355 if (r < 0 && r != -ENOENT)
356 return r;
357
358 for (p = 0; p < _PROP_MAX; p++) {
359 char *t, **u;
360
361 assert(names[p]);
362
363 if (isempty(data[p])) {
364 l = strv_env_unset(l, names[p]);
365 continue;
366 }
367
368 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
369 strv_free(l);
370 return -ENOMEM;
371 }
372
373 u = strv_env_set(l, t);
374 free(t);
375 strv_free(l);
376
377 if (!u)
378 return -ENOMEM;
379
380 l = u;
381 }
382
383 if (strv_isempty(l)) {
384 strv_free(l);
385
386 if (unlink("/etc/locale.conf") < 0)
387 return errno == ENOENT ? 0 : -errno;
388
389 return 0;
390 }
391
392 r = write_env_file("/etc/locale.conf", l);
393 strv_free(l);
394
395 return r;
396 }
397
398 static void push_data(DBusConnection *bus) {
399 char **l_set = NULL, **l_unset = NULL, **t;
400 int c_set = 0, c_unset = 0, p;
401 DBusError error;
402 DBusMessage *m = NULL, *reply = NULL;
403 DBusMessageIter iter, sub;
404
405 dbus_error_init(&error);
406
407 assert(bus);
408
409 l_set = new0(char*, _PROP_MAX);
410 l_unset = new0(char*, _PROP_MAX);
411 if (!l_set || !l_unset) {
412 log_oom();
413 goto finish;
414 }
415
416 for (p = 0; p < _PROP_MAX; p++) {
417 assert(names[p]);
418
419 if (isempty(data[p]))
420 l_unset[c_set++] = (char*) names[p];
421 else {
422 char *s;
423
424 if (asprintf(&s, "%s=%s", names[p], data[p]) < 0) {
425 log_oom();
426 goto finish;
427 }
428
429 l_set[c_unset++] = s;
430 }
431 }
432
433 assert(c_set + c_unset == _PROP_MAX);
434 m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment");
435 if (!m) {
436 log_error("Could not allocate message.");
437 goto finish;
438 }
439
440 dbus_message_iter_init_append(m, &iter);
441
442 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
443 log_oom();
444 goto finish;
445 }
446
447 STRV_FOREACH(t, l_unset)
448 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
449 log_oom();
450 goto finish;
451 }
452
453 if (!dbus_message_iter_close_container(&iter, &sub) ||
454 !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
455 log_oom();
456 goto finish;
457 }
458
459 STRV_FOREACH(t, l_set)
460 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
461 log_oom();
462 goto finish;
463 }
464
465 if (!dbus_message_iter_close_container(&iter, &sub)) {
466 log_oom();
467 goto finish;
468 }
469
470 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
471 if (!reply) {
472 log_error("Failed to set locale information: %s", bus_error_message(&error));
473 goto finish;
474 }
475
476 finish:
477 if (m)
478 dbus_message_unref(m);
479
480 if (reply)
481 dbus_message_unref(reply);
482
483 dbus_error_free(&error);
484
485 strv_free(l_set);
486 free(l_unset);
487 }
488
489 static int write_data_vconsole(void) {
490 int r;
491 char **l = NULL;
492
493 r = load_env_file("/etc/vconsole.conf", &l);
494 if (r < 0 && r != -ENOENT)
495 return r;
496
497 if (isempty(state.vc_keymap))
498 l = strv_env_unset(l, "KEYMAP");
499 else {
500 char *s, **u;
501
502 s = strappend("KEYMAP=", state.vc_keymap);
503 if (!s) {
504 strv_free(l);
505 return -ENOMEM;
506 }
507
508 u = strv_env_set(l, s);
509 free(s);
510 strv_free(l);
511
512 if (!u)
513 return -ENOMEM;
514
515 l = u;
516 }
517
518 if (isempty(state.vc_keymap_toggle))
519 l = strv_env_unset(l, "KEYMAP_TOGGLE");
520 else {
521 char *s, **u;
522
523 s = strappend("KEYMAP_TOGGLE=", state.vc_keymap_toggle);
524 if (!s) {
525 strv_free(l);
526 return -ENOMEM;
527 }
528
529 u = strv_env_set(l, s);
530 free(s);
531 strv_free(l);
532
533 if (!u)
534 return -ENOMEM;
535
536 l = u;
537 }
538
539 if (strv_isempty(l)) {
540 strv_free(l);
541
542 if (unlink("/etc/vconsole.conf") < 0)
543 return errno == ENOENT ? 0 : -errno;
544
545 return 0;
546 }
547
548 r = write_env_file("/etc/vconsole.conf", l);
549 strv_free(l);
550
551 return r;
552 }
553
554 static int write_data_x11(void) {
555 FILE *f;
556 char *temp_path;
557 int r;
558
559 if (isempty(state.x11_layout) &&
560 isempty(state.x11_model) &&
561 isempty(state.x11_variant) &&
562 isempty(state.x11_options)) {
563
564 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
565 return errno == ENOENT ? 0 : -errno;
566
567 return 0;
568 }
569
570 mkdir_parents_label("/etc/X11/xorg.conf.d", 0755);
571
572 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
573 if (r < 0)
574 return r;
575
576 fchmod(fileno(f), 0644);
577
578 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
579 "# manually too freely.\n"
580 "Section \"InputClass\"\n"
581 " Identifier \"system-keyboard\"\n"
582 " MatchIsKeyboard \"on\"\n", f);
583
584 if (!isempty(state.x11_layout))
585 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", state.x11_layout);
586
587 if (!isempty(state.x11_model))
588 fprintf(f, " Option \"XkbModel\" \"%s\"\n", state.x11_model);
589
590 if (!isempty(state.x11_variant))
591 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", state.x11_variant);
592
593 if (!isempty(state.x11_options))
594 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", state.x11_options);
595
596 fputs("EndSection\n", f);
597 fflush(f);
598
599 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
600 r = -errno;
601 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
602 unlink(temp_path);
603 } else
604 r = 0;
605
606 fclose(f);
607 free(temp_path);
608
609 return r;
610 }
611
612 static int load_vconsole_keymap(DBusConnection *bus, DBusError *error) {
613 DBusMessage *m = NULL, *reply = NULL;
614 const char *name = "systemd-vconsole-setup.service", *mode = "replace";
615 int r;
616 DBusError _error;
617
618 assert(bus);
619
620 if (!error) {
621 dbus_error_init(&_error);
622 error = &_error;
623 }
624
625 m = dbus_message_new_method_call(
626 "org.freedesktop.systemd1",
627 "/org/freedesktop/systemd1",
628 "org.freedesktop.systemd1.Manager",
629 "RestartUnit");
630 if (!m) {
631 log_error("Could not allocate message.");
632 r = -ENOMEM;
633 goto finish;
634 }
635
636 if (!dbus_message_append_args(m,
637 DBUS_TYPE_STRING, &name,
638 DBUS_TYPE_STRING, &mode,
639 DBUS_TYPE_INVALID)) {
640 log_error("Could not append arguments to message.");
641 r = -ENOMEM;
642 goto finish;
643 }
644
645 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
646 if (!reply) {
647 log_error("Failed to issue method call: %s", bus_error_message(error));
648 r = -EIO;
649 goto finish;
650 }
651
652 r = 0;
653
654 finish:
655 if (m)
656 dbus_message_unref(m);
657
658 if (reply)
659 dbus_message_unref(reply);
660
661 if (error == &_error)
662 dbus_error_free(error);
663
664 return r;
665 }
666
667 static char *strnulldash(const char *s) {
668 return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
669 }
670
671 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
672 assert(f);
673 assert(n);
674 assert(a);
675
676 for (;;) {
677 char line[LINE_MAX];
678 char *l, **b;
679
680 errno = 0;
681 if (!fgets(line, sizeof(line), f)) {
682
683 if (ferror(f))
684 return errno ? -errno : -EIO;
685
686 return 0;
687 }
688
689 (*n) ++;
690
691 l = strstrip(line);
692 if (l[0] == 0 || l[0] == '#')
693 continue;
694
695 b = strv_split_quoted(l);
696 if (!b)
697 return -ENOMEM;
698
699 if (strv_length(b) < 5) {
700 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
701 strv_free(b);
702 continue;
703
704 }
705
706 *a = b;
707 return 1;
708 }
709 }
710
711 static int convert_vconsole_to_x11(DBusConnection *connection) {
712 bool modified = false;
713
714 assert(connection);
715
716 if (isempty(state.vc_keymap)) {
717
718 modified =
719 !isempty(state.x11_layout) ||
720 !isempty(state.x11_model) ||
721 !isempty(state.x11_variant) ||
722 !isempty(state.x11_options);
723
724 free_data_x11();
725 } else {
726 FILE *f;
727 unsigned n = 0;
728
729 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
730 if (!f)
731 return -errno;
732
733 for (;;) {
734 char **a;
735 int r;
736
737 r = read_next_mapping(f, &n, &a);
738 if (r < 0) {
739 fclose(f);
740 return r;
741 }
742
743 if (r == 0)
744 break;
745
746 if (!streq(state.vc_keymap, a[0])) {
747 strv_free(a);
748 continue;
749 }
750
751 if (!streq_ptr(state.x11_layout, strnulldash(a[1])) ||
752 !streq_ptr(state.x11_model, strnulldash(a[2])) ||
753 !streq_ptr(state.x11_variant, strnulldash(a[3])) ||
754 !streq_ptr(state.x11_options, strnulldash(a[4]))) {
755
756 if (free_and_set(&state.x11_layout, strnulldash(a[1])) < 0 ||
757 free_and_set(&state.x11_model, strnulldash(a[2])) < 0 ||
758 free_and_set(&state.x11_variant, strnulldash(a[3])) < 0 ||
759 free_and_set(&state.x11_options, strnulldash(a[4])) < 0) {
760 strv_free(a);
761 fclose(f);
762 return -ENOMEM;
763 }
764
765 modified = true;
766 }
767
768 strv_free(a);
769 break;
770 }
771
772 fclose(f);
773 }
774
775 if (modified) {
776 dbus_bool_t b;
777 DBusMessage *changed;
778 int r;
779
780 r = write_data_x11();
781 if (r < 0)
782 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
783
784 changed = bus_properties_changed_new(
785 "/org/freedesktop/locale1",
786 "org.freedesktop.locale1",
787 "X11Layout\0"
788 "X11Model\0"
789 "X11Variant\0"
790 "X11Options\0");
791
792 if (!changed)
793 return -ENOMEM;
794
795 b = dbus_connection_send(connection, changed, NULL);
796 dbus_message_unref(changed);
797
798 if (!b)
799 return -ENOMEM;
800 }
801
802 return 0;
803 }
804
805 static int convert_x11_to_vconsole(DBusConnection *connection) {
806 bool modified = false;
807
808 assert(connection);
809
810 if (isempty(state.x11_layout)) {
811
812 modified =
813 !isempty(state.vc_keymap) ||
814 !isempty(state.vc_keymap_toggle);
815
816 free_data_x11();
817 } else {
818 FILE *f;
819 unsigned n = 0;
820 unsigned best_matching = 0;
821 char *new_keymap = NULL;
822
823 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
824 if (!f)
825 return -errno;
826
827 for (;;) {
828 char **a;
829 unsigned matching = 0;
830 int r;
831
832 r = read_next_mapping(f, &n, &a);
833 if (r < 0) {
834 fclose(f);
835 return r;
836 }
837
838 if (r == 0)
839 break;
840
841 /* Determine how well matching this entry is */
842 if (streq_ptr(state.x11_layout, a[1]))
843 /* If we got an exact match, this is best */
844 matching = 10;
845 else {
846 size_t x;
847
848 x = strcspn(state.x11_layout, ",");
849
850 /* We have multiple X layouts, look
851 * for an entry that matches our key
852 * with the everything but the first
853 * layout stripped off. */
854 if (x > 0 &&
855 strlen(a[1]) == x &&
856 strncmp(state.x11_layout, a[1], x) == 0)
857 matching = 5;
858 else {
859 size_t w;
860
861 /* If that didn't work, strip
862 * off the other layouts from
863 * the entry, too */
864
865 w = strcspn(a[1], ",");
866
867 if (x > 0 && x == w &&
868 memcmp(state.x11_layout, a[1], x) == 0)
869 matching = 1;
870 }
871 }
872
873 if (matching > 0 &&
874 streq_ptr(state.x11_model, a[2])) {
875 matching++;
876
877 if (streq_ptr(state.x11_variant, a[3])) {
878 matching++;
879
880 if (streq_ptr(state.x11_options, a[4]))
881 matching++;
882 }
883 }
884
885 /* The best matching entry so far, then let's
886 * save that */
887 if (matching > best_matching) {
888 best_matching = matching;
889
890 free(new_keymap);
891 new_keymap = strdup(a[0]);
892
893 if (!new_keymap) {
894 strv_free(a);
895 fclose(f);
896 return -ENOMEM;
897 }
898 }
899
900 strv_free(a);
901 }
902
903 fclose(f);
904
905 if (!streq_ptr(state.vc_keymap, new_keymap)) {
906 free(state.vc_keymap);
907 state.vc_keymap = new_keymap;
908
909 free(state.vc_keymap_toggle);
910 state.vc_keymap_toggle = NULL;
911
912 modified = true;
913 } else
914 free(new_keymap);
915 }
916
917 if (modified) {
918 dbus_bool_t b;
919 DBusMessage *changed;
920 int r;
921
922 r = write_data_vconsole();
923 if (r < 0)
924 log_error("Failed to set virtual console keymap: %s", strerror(-r));
925
926 changed = bus_properties_changed_new(
927 "/org/freedesktop/locale1",
928 "org.freedesktop.locale1",
929 "VConsoleKeymap\0"
930 "VConsoleKeymapToggle\0");
931
932 if (!changed)
933 return -ENOMEM;
934
935 b = dbus_connection_send(connection, changed, NULL);
936 dbus_message_unref(changed);
937
938 if (!b)
939 return -ENOMEM;
940
941 return load_vconsole_keymap(connection, NULL);
942 }
943
944 return 0;
945 }
946
947 static int append_locale(DBusMessageIter *i, const char *property, void *userdata) {
948 int r, c = 0, p;
949 char **l;
950
951 l = new0(char*, _PROP_MAX+1);
952 if (!l)
953 return -ENOMEM;
954
955 for (p = 0; p < _PROP_MAX; p++) {
956 char *t;
957
958 if (isempty(data[p]))
959 continue;
960
961 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
962 strv_free(l);
963 return -ENOMEM;
964 }
965
966 l[c++] = t;
967 }
968
969 r = bus_property_append_strv(i, property, (void*) l);
970 strv_free(l);
971
972 return r;
973 }
974
975 static const BusProperty bus_locale_properties[] = {
976 { "Locale", append_locale, "as", 0 },
977 { "X11Layout", bus_property_append_string, "s", offsetof(State, x11_layout), true },
978 { "X11Model", bus_property_append_string, "s", offsetof(State, x11_model), true },
979 { "X11Variant", bus_property_append_string, "s", offsetof(State, x11_variant), true },
980 { "X11Options", bus_property_append_string, "s", offsetof(State, x11_options), true },
981 { "VConsoleKeymap", bus_property_append_string, "s", offsetof(State, vc_keymap), true },
982 { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true },
983 { NULL, }
984 };
985
986 static const BusBoundProperties bps[] = {
987 { "org.freedesktop.locale1", bus_locale_properties, &state },
988 { NULL, }
989 };
990
991 static DBusHandlerResult locale_message_handler(
992 DBusConnection *connection,
993 DBusMessage *message,
994 void *userdata) {
995
996 DBusMessage *reply = NULL, *changed = NULL;
997 DBusError error;
998 int r;
999
1000 assert(connection);
1001 assert(message);
1002
1003 dbus_error_init(&error);
1004
1005 if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) {
1006 char **l = NULL, **i;
1007 dbus_bool_t interactive;
1008 DBusMessageIter iter;
1009 bool modified = false;
1010 bool passed[_PROP_MAX];
1011 int p;
1012
1013 if (!dbus_message_iter_init(message, &iter))
1014 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1015
1016 r = bus_parse_strv_iter(&iter, &l);
1017 if (r < 0) {
1018 if (r == -ENOMEM)
1019 goto oom;
1020
1021 return bus_send_error_reply(connection, message, NULL, r);
1022 }
1023
1024 if (!dbus_message_iter_next(&iter) ||
1025 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) {
1026 strv_free(l);
1027 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1028 }
1029
1030 dbus_message_iter_get_basic(&iter, &interactive);
1031
1032 zero(passed);
1033
1034 /* Check whether a variable changed and if so valid */
1035 STRV_FOREACH(i, l) {
1036 bool valid = false;
1037
1038 for (p = 0; p < _PROP_MAX; p++) {
1039 size_t k;
1040
1041 k = strlen(names[p]);
1042 if (startswith(*i, names[p]) &&
1043 (*i)[k] == '=' &&
1044 string_is_safe((*i) + k + 1)) {
1045 valid = true;
1046 passed[p] = true;
1047
1048 if (!streq_ptr(*i + k + 1, data[p]))
1049 modified = true;
1050
1051 break;
1052 }
1053 }
1054
1055 if (!valid) {
1056 strv_free(l);
1057 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1058 }
1059 }
1060
1061 /* Check whether a variable is unset */
1062 if (!modified) {
1063 for (p = 0; p < _PROP_MAX; p++)
1064 if (!isempty(data[p]) && !passed[p]) {
1065 modified = true;
1066 break;
1067 }
1068 }
1069
1070 if (modified) {
1071
1072 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1073 if (r < 0) {
1074 strv_free(l);
1075 return bus_send_error_reply(connection, message, &error, r);
1076 }
1077
1078 STRV_FOREACH(i, l) {
1079 for (p = 0; p < _PROP_MAX; p++) {
1080 size_t k;
1081
1082 k = strlen(names[p]);
1083 if (startswith(*i, names[p]) && (*i)[k] == '=') {
1084 char *t;
1085
1086 t = strdup(*i + k + 1);
1087 if (!t) {
1088 strv_free(l);
1089 goto oom;
1090 }
1091
1092 free(data[p]);
1093 data[p] = t;
1094
1095 break;
1096 }
1097 }
1098 }
1099
1100 strv_free(l);
1101
1102 for (p = 0; p < _PROP_MAX; p++) {
1103 if (passed[p])
1104 continue;
1105
1106 free(data[p]);
1107 data[p] = NULL;
1108 }
1109
1110 simplify();
1111
1112 r = write_data_locale();
1113 if (r < 0) {
1114 log_error("Failed to set locale: %s", strerror(-r));
1115 return bus_send_error_reply(connection, message, NULL, r);
1116 }
1117
1118 push_data(connection);
1119
1120 log_info("Changed locale information.");
1121
1122 changed = bus_properties_changed_new(
1123 "/org/freedesktop/locale1",
1124 "org.freedesktop.locale1",
1125 "Locale\0");
1126 if (!changed)
1127 goto oom;
1128 } else
1129 strv_free(l);
1130
1131 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1132
1133 const char *keymap, *keymap_toggle;
1134 dbus_bool_t convert, interactive;
1135
1136 if (!dbus_message_get_args(
1137 message,
1138 &error,
1139 DBUS_TYPE_STRING, &keymap,
1140 DBUS_TYPE_STRING, &keymap_toggle,
1141 DBUS_TYPE_BOOLEAN, &convert,
1142 DBUS_TYPE_BOOLEAN, &interactive,
1143 DBUS_TYPE_INVALID))
1144 return bus_send_error_reply(connection, message, &error, -EINVAL);
1145
1146 if (isempty(keymap))
1147 keymap = NULL;
1148
1149 if (isempty(keymap_toggle))
1150 keymap_toggle = NULL;
1151
1152 if (!streq_ptr(keymap, state.vc_keymap) ||
1153 !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1154
1155 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
1156 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
1157 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1158
1159 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1160 if (r < 0)
1161 return bus_send_error_reply(connection, message, &error, r);
1162
1163 if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1164 free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1165 goto oom;
1166
1167 r = write_data_vconsole();
1168 if (r < 0) {
1169 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1170 return bus_send_error_reply(connection, message, NULL, r);
1171 }
1172
1173 log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1174
1175 r = load_vconsole_keymap(connection, NULL);
1176 if (r < 0)
1177 log_error("Failed to request keymap reload: %s", strerror(-r));
1178
1179 changed = bus_properties_changed_new(
1180 "/org/freedesktop/locale1",
1181 "org.freedesktop.locale1",
1182 "VConsoleKeymap\0"
1183 "VConsoleKeymapToggle\0");
1184 if (!changed)
1185 goto oom;
1186
1187 if (convert) {
1188 r = convert_vconsole_to_x11(connection);
1189
1190 if (r < 0)
1191 log_error("Failed to convert keymap data: %s", strerror(-r));
1192 }
1193 }
1194
1195 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1196
1197 const char *layout, *model, *variant, *options;
1198 dbus_bool_t convert, interactive;
1199
1200 if (!dbus_message_get_args(
1201 message,
1202 &error,
1203 DBUS_TYPE_STRING, &layout,
1204 DBUS_TYPE_STRING, &model,
1205 DBUS_TYPE_STRING, &variant,
1206 DBUS_TYPE_STRING, &options,
1207 DBUS_TYPE_BOOLEAN, &convert,
1208 DBUS_TYPE_BOOLEAN, &interactive,
1209 DBUS_TYPE_INVALID))
1210 return bus_send_error_reply(connection, message, &error, -EINVAL);
1211
1212 if (isempty(layout))
1213 layout = NULL;
1214
1215 if (isempty(model))
1216 model = NULL;
1217
1218 if (isempty(variant))
1219 variant = NULL;
1220
1221 if (isempty(options))
1222 options = NULL;
1223
1224 if (!streq_ptr(layout, state.x11_layout) ||
1225 !streq_ptr(model, state.x11_model) ||
1226 !streq_ptr(variant, state.x11_variant) ||
1227 !streq_ptr(options, state.x11_options)) {
1228
1229 if ((layout && !string_is_safe(layout)) ||
1230 (model && !string_is_safe(model)) ||
1231 (variant && !string_is_safe(variant)) ||
1232 (options && !string_is_safe(options)))
1233 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1234
1235 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1236 if (r < 0)
1237 return bus_send_error_reply(connection, message, &error, r);
1238
1239 if (free_and_set(&state.x11_layout, layout) < 0 ||
1240 free_and_set(&state.x11_model, model) < 0 ||
1241 free_and_set(&state.x11_variant, variant) < 0 ||
1242 free_and_set(&state.x11_options, options) < 0)
1243 goto oom;
1244
1245 r = write_data_x11();
1246 if (r < 0) {
1247 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1248 return bus_send_error_reply(connection, message, NULL, r);
1249 }
1250
1251 log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
1252
1253 changed = bus_properties_changed_new(
1254 "/org/freedesktop/locale1",
1255 "org.freedesktop.locale1",
1256 "X11Layout\0"
1257 "X11Model\0"
1258 "X11Variant\0"
1259 "X11Options\0");
1260 if (!changed)
1261 goto oom;
1262
1263 if (convert) {
1264 r = convert_x11_to_vconsole(connection);
1265
1266 if (r < 0)
1267 log_error("Failed to convert keymap data: %s", strerror(-r));
1268 }
1269 }
1270 } else
1271 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
1272
1273 if (!(reply = dbus_message_new_method_return(message)))
1274 goto oom;
1275
1276 if (!dbus_connection_send(connection, reply, NULL))
1277 goto oom;
1278
1279 dbus_message_unref(reply);
1280 reply = NULL;
1281
1282 if (changed) {
1283
1284 if (!dbus_connection_send(connection, changed, NULL))
1285 goto oom;
1286
1287 dbus_message_unref(changed);
1288 }
1289
1290 return DBUS_HANDLER_RESULT_HANDLED;
1291
1292 oom:
1293 if (reply)
1294 dbus_message_unref(reply);
1295
1296 if (changed)
1297 dbus_message_unref(changed);
1298
1299 dbus_error_free(&error);
1300
1301 return DBUS_HANDLER_RESULT_NEED_MEMORY;
1302 }
1303
1304 static int connect_bus(DBusConnection **_bus) {
1305 static const DBusObjectPathVTable locale_vtable = {
1306 .message_function = locale_message_handler
1307 };
1308 DBusError error;
1309 DBusConnection *bus = NULL;
1310 int r;
1311
1312 assert(_bus);
1313
1314 dbus_error_init(&error);
1315
1316 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1317 if (!bus) {
1318 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
1319 r = -ECONNREFUSED;
1320 goto fail;
1321 }
1322
1323 dbus_connection_set_exit_on_disconnect(bus, FALSE);
1324
1325 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
1326 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
1327 r = log_oom();
1328 goto fail;
1329 }
1330
1331 r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
1332 if (dbus_error_is_set(&error)) {
1333 log_error("Failed to register name on bus: %s", bus_error_message(&error));
1334 r = -EEXIST;
1335 goto fail;
1336 }
1337
1338 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
1339 log_error("Failed to acquire name.");
1340 r = -EEXIST;
1341 goto fail;
1342 }
1343
1344 if (_bus)
1345 *_bus = bus;
1346
1347 return 0;
1348
1349 fail:
1350 dbus_connection_close(bus);
1351 dbus_connection_unref(bus);
1352
1353 dbus_error_free(&error);
1354
1355 return r;
1356 }
1357
1358 int main(int argc, char *argv[]) {
1359 int r;
1360 DBusConnection *bus = NULL;
1361 bool exiting = false;
1362
1363 log_set_target(LOG_TARGET_AUTO);
1364 log_parse_environment();
1365 log_open();
1366
1367 umask(0022);
1368
1369 if (argc == 2 && streq(argv[1], "--introspect")) {
1370 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1371 "<node>\n", stdout);
1372 fputs(locale_interface, stdout);
1373 fputs("</node>\n", stdout);
1374 return 0;
1375 }
1376
1377 if (argc != 1) {
1378 log_error("This program takes no arguments.");
1379 r = -EINVAL;
1380 goto finish;
1381 }
1382
1383 r = read_data();
1384 if (r < 0) {
1385 log_error("Failed to read locale data: %s", strerror(-r));
1386 goto finish;
1387 }
1388
1389 r = connect_bus(&bus);
1390 if (r < 0)
1391 goto finish;
1392
1393 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1394 for (;;) {
1395
1396 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1397 break;
1398
1399 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1400 exiting = true;
1401 bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
1402 }
1403 }
1404
1405 r = 0;
1406
1407 finish:
1408 free_data();
1409
1410 if (bus) {
1411 dbus_connection_flush(bus);
1412 dbus_connection_close(bus);
1413 dbus_connection_unref(bus);
1414 }
1415
1416 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1417 }