From 23fa786ca67ed3a32930ff1a7b175ac823db187c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 30 Apr 2020 18:30:56 +0200 Subject: [PATCH 1/3] locale-util: add new helper locale_is_installed() This new helper checks whether the specified locale is installed. It's distinct from locale_is_valid() which just superficially checks if a string looks like something that could be a valid locale. Heavily inspired by @jsynacek's #13964. Replaces: #13964 --- src/basic/locale-util.c | 15 +++++++++++++++ src/basic/locale-util.h | 1 + 2 files changed, 16 insertions(+) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index e4ce289c51..ab5f86f54a 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -254,6 +254,21 @@ bool locale_is_valid(const char *name) { return true; } +int locale_is_installed(const char *name) { + if (!locale_is_valid(name)) + return false; + + if (STR_IN_SET(name, "C", "POSIX")) /* These ones are always OK */ + return true; + + _cleanup_(freelocalep) locale_t loc = + newlocale(LC_ALL_MASK, name, 0); + if (loc == (locale_t) 0) + return errno == ENOMEM ? -ENOMEM : false; + + return true; +} + void init_gettext(void) { setlocale(LC_ALL, ""); textdomain(GETTEXT_PACKAGE); diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index 7d77fa2bda..6bd3772059 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -31,6 +31,7 @@ typedef enum LocaleVariable { int get_locales(char ***l); bool locale_is_valid(const char *name); +int locale_is_installed(const char *name); #define _(String) gettext(String) #define N_(String) String From b45b0a69bb7ef3e6e66d443eae366b6d1c387cab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 30 Apr 2020 18:32:55 +0200 Subject: [PATCH 2/3] test: add test case for locale_is_installed() --- src/test/test-locale-util.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index e7a22797bb..728196be08 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -36,6 +36,28 @@ static void test_locale_is_valid(void) { assert_se(!locale_is_valid("\x01gar\x02 bage\x03")); } +static void test_locale_is_installed(void) { + log_info("/* %s */", __func__); + + /* Always available */ + assert_se(locale_is_installed("POSIX") > 0); + assert_se(locale_is_installed("C") > 0); + + /* Might, or might not be installed. */ + assert_se(locale_is_installed("en_EN.utf8") >= 0); + assert_se(locale_is_installed("fr_FR.utf8") >= 0); + assert_se(locale_is_installed("fr_FR@euro") >= 0); + assert_se(locale_is_installed("fi_FI") >= 0); + + /* Definitely not valid */ + assert_se(locale_is_installed("") == 0); + assert_se(locale_is_installed("/usr/bin/foo") == 0); + assert_se(locale_is_installed("\x01gar\x02 bage\x03") == 0); + + /* Definitely not installed */ + assert_se(locale_is_installed("zz_ZZ") == 0); +} + static void test_keymaps(void) { _cleanup_strv_free_ char **kmaps = NULL; char **p; @@ -98,6 +120,7 @@ static void dump_special_glyphs(void) { int main(int argc, char *argv[]) { test_get_locales(); test_locale_is_valid(); + test_locale_is_installed(); test_keymaps(); dump_special_glyphs(); From a00a78b84e2ab352b3144bfae8bc578d172303be Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 30 Apr 2020 18:32:44 +0200 Subject: [PATCH 3/3] tree-wide: port various bits over to locale_is_installed() --- src/firstboot/firstboot.c | 28 ++++++++----- src/home/homectl.c | 3 ++ src/locale/localed.c | 87 ++++++++++++++++++++++++--------------- src/login/pam_systemd.c | 4 +- 4 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 488e87beae..f9fdf7b1d7 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -205,6 +205,14 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i } } +static bool locale_is_ok(const char *name) { + + if (arg_root) + return locale_is_valid(name); + + return locale_is_installed(name) > 0; +} + static int prompt_locale(void) { _cleanup_strv_free_ char **locales = NULL; int r; @@ -238,7 +246,7 @@ static int prompt_locale(void) { print_welcome(); r = prompt_loop("Please enter system locale name or number", - locales, 60, locale_is_valid, &arg_locale); + locales, 60, locale_is_ok, &arg_locale); if (r < 0) return r; @@ -246,7 +254,7 @@ static int prompt_locale(void) { return 0; r = prompt_loop("Please enter system message locale name or number", - locales, 60, locale_is_valid, &arg_locale_messages); + locales, 60, locale_is_ok, &arg_locale_messages); if (r < 0) return r; @@ -791,10 +799,6 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_LOCALE: - if (!locale_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Locale %s is not valid.", optarg); - r = free_and_strdup(&arg_locale, optarg); if (r < 0) return log_oom(); @@ -802,10 +806,6 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_LOCALE_MESSAGES: - if (!locale_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Locale %s is not valid.", optarg); - r = free_and_strdup(&arg_locale_messages, optarg); if (r < 0) return log_oom(); @@ -927,6 +927,14 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached("Unhandled option"); } + /* We check if the specified locale strings are valid down here, so that we can take --root= into + * account when looking for the locale files. */ + + if (arg_locale && !locale_is_ok(arg_locale)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale); + if (arg_locale_messages && !locale_is_ok(arg_locale_messages)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages); + return 1; } diff --git a/src/home/homectl.c b/src/home/homectl.c index c98bf8b540..e1857b6923 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2807,6 +2807,9 @@ static int parse_argv(int argc, char *argv[]) { if (!locale_is_valid(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", optarg); + if (locale_is_installed(optarg) <= 0) + log_warning("Locale '%s' is not installed, accepting anyway.", optarg); + r = json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", optarg); if (r < 0) return log_error_errno(r, "Failed to set preferredLanguage field: %m"); diff --git a/src/locale/localed.c b/src/locale/localed.c index acdf79fa6b..a6aa3bae8c 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -258,18 +258,57 @@ static int property_get_xkb( return -EINVAL; } +static int process_locale_list_item( + const char *assignment, + char *new_locale[static _VARIABLE_LC_MAX], + sd_bus_error *error) { + + assert(assignment); + assert(new_locale); + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { + const char *name, *e; + + assert_se(name = locale_variable_to_string(p)); + + e = startswith(assignment, name); + if (!e) + continue; + + if (*e != '=') + continue; + + e++; + + if (!locale_is_valid(e)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s is not valid, refusing.", e); + if (locale_is_installed(e) <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale %s not installed, refusing.", e); + if (new_locale[p]) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale variable %s set twice, refusing.", name); + + new_locale[p] = strdup(e); + if (!new_locale[p]) + return -ENOMEM; + + return 0; + } + + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale assignment %s not valid, refusing.", assignment); +} + static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { _cleanup_(locale_variables_freep) char *new_locale[_VARIABLE_LC_MAX] = {}; _cleanup_strv_free_ char **settings = NULL, **l = NULL; Context *c = userdata; bool modified = false; - int interactive, p, r; + int interactive, r; char **i; assert(m); assert(c); - r = bus_message_read_strv_extend(m, &l); + r = sd_bus_message_read_strv(m, &l); if (r < 0) return r; @@ -278,11 +317,13 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er return r; /* If single locale without variable name is provided, then we assume it is LANG=. */ - if (strv_length(l) == 1 && !strchr(*l, '=')) { - if (!locale_is_valid(*l)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); + if (strv_length(l) == 1 && !strchr(l[0], '=')) { + if (!locale_is_valid(l[0])) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid locale specification: %s", l[0]); + if (locale_is_installed(l[0]) <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified locale is not installed: %s", l[0]); - new_locale[VARIABLE_LANG] = strdup(*l); + new_locale[VARIABLE_LANG] = strdup(l[0]); if (!new_locale[VARIABLE_LANG]) return -ENOMEM; @@ -291,31 +332,9 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er /* Check whether a variable is valid */ STRV_FOREACH(i, l) { - bool valid = false; - - for (p = 0; p < _VARIABLE_LC_MAX; p++) { - size_t k; - const char *name; - - name = locale_variable_to_string(p); - assert(name); - - k = strlen(name); - if (startswith(*i, name) && - (*i)[k] == '=' && - locale_is_valid((*i) + k + 1)) { - valid = true; - - new_locale[p] = strdup((*i) + k + 1); - if (!new_locale[p]) - return -ENOMEM; - - break; - } - } - - if (!valid) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); + r = process_locale_list_item(*i, new_locale, error); + if (r < 0) + return r; } /* If LANG was specified, but not LANGUAGE, check if we should @@ -338,7 +357,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er } /* Merge with the current settings */ - for (p = 0; p < _VARIABLE_LC_MAX; p++) + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) if (!isempty(c->locale[p]) && isempty(new_locale[p])) { new_locale[p] = strdup(c->locale[p]); if (!new_locale[p]) @@ -347,7 +366,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er locale_simplify(new_locale); - for (p = 0; p < _VARIABLE_LC_MAX; p++) + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) if (!streq_ptr(c->locale[p], new_locale[p])) { modified = true; break; @@ -372,7 +391,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - for (p = 0; p < _VARIABLE_LC_MAX; p++) + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) free_and_replace(c->locale[p], new_locale[p]); r = locale_write_data(c, &settings); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 84bea21ab7..b07773088e 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -580,9 +580,9 @@ static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool if (pam_getenv(handle, "LANG")) { if (debug) pam_syslog(handle, LOG_DEBUG, "PAM environment variable $LANG already set, not changing based on user record."); - } else if (!locale_is_valid(ur->preferred_language)) { + } else if (locale_is_installed(ur->preferred_language) <= 0) { if (debug) - pam_syslog(handle, LOG_DEBUG, "Preferred language specified in user record is not valid locally, not setting $LANG."); + pam_syslog(handle, LOG_DEBUG, "Preferred language specified in user record is not valid or not installed, not setting $LANG."); } else { _cleanup_free_ char *joined = NULL;