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