1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-13 17:18:18 +03:00

Merge pull request #31206 from AdrianVovk/user-record-fields

Added some more user record fields
This commit is contained in:
Lennart Poettering 2024-02-14 09:22:23 +01:00 committed by GitHub
commit 64e18af731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 211 additions and 74 deletions

View File

@ -310,11 +310,22 @@ string. The string should be a `tzdata` compatible location string, for
example: `Europe/Berlin`.
`preferredLanguage` → A string indicating the preferred language/locale for the
user. When logging in
user. It is combined with the `additionalLanguages` field to initialize the `$LANG`
and `$LANGUAGE` environment variables on login; see below for more details. This string
should be in a format compatible with the `$LANG` environment variable, for example:
`de_DE.UTF-8`.
`additionalLanguages` → An array of strings indicating the preferred languages/locales
that should be used in the event that translations for the `preferredLanguage` are
missing, listed in order of descending priority. This allows multi-lingual users to
specify all the languages that they know, so software lacking translations in the user's
primary language can try another language that the user knows rather than falling back to
the default English. All entries in this field must be valid locale names, compatible with
the `$LANG` variable, for example: `de_DE.UTF-8`. When logging in
[`pam_systemd`](https://www.freedesktop.org/software/systemd/man/pam_systemd.html)
will automatically initialize the `$LANG` environment variable from this
string. The string hence should be in a format compatible with this environment
variable, for example: `de_DE.UTF8`.
will prepend `preferredLanguage` (if set) to this list (if set), remove duplicates,
and then automatically initialize the `$LANGUAGE` variable with the resulting list.
It will also initialize `$LANG` variable with the first entry in the resulting list.
`niceLevel` → An integer value in the range -20…19. When logging in
[`pam_systemd`](https://www.freedesktop.org/software/systemd/man/pam_systemd.html)
@ -744,7 +755,7 @@ that may be used in this section are identical to the equally named ones in the
`regular` section (i.e. at the top-level object). Specifically, these are:
`iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
`preferredLanguage`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`,
`preferredLanguage`, `additionalLanguages`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`,
`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`,
`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
`mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`,

View File

@ -366,10 +366,11 @@
<varlistentry>
<term><option>--language=</option><replaceable>LANG</replaceable></term>
<listitem><para>Takes a specifier indicating the preferred language of the user. The
<varname>$LANG</varname> environment variable is initialized from this value on login, and thus a
value suitable for this environment variable is accepted here, for example
<option>--language=de_DE.UTF8</option>.</para>
<listitem><para>Takes a comma- or colon-separated list of languages preferred by the user, ordered
by descending priority. The <varname>$LANG</varname> and <varname>$LANGUAGE</varname> environment
variables are initialized from this value on login, and thus values suitible for these environment
variables are accepted here, for example <option>--language=de_DE.UTF-8</option>. This option may
be used more than once, in which case the language lists are concatenated.</para>
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
</varlistentry>

View File

@ -146,6 +146,9 @@ _homectl() {
--cifs-user-name)
comps=$(compgen -A user -- "$cur" )
;;
--language)
comps=$(localectl list-locales 2>/dev/null)
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0

View File

@ -260,7 +260,10 @@ bool locale_is_valid(const char *name) {
if (!filename_is_valid(name))
return false;
if (!string_is_safe(name))
/* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with
* dashes, and variant seems to be alphabetic.
* See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */
if (!in_charset(name, ALPHANUMERICAL "_.-@"))
return false;
return true;

View File

@ -2435,7 +2435,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --shell=PATH Shell for account\n"
" --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n"
" --timezone=TIMEZONE Set a time-zone\n"
" --language=LOCALE Set preferred language\n"
" --language=LOCALE Set preferred languages\n"
" --ssh-authorized-keys=KEYS\n"
" Specify SSH public keys\n"
" --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n"
@ -2547,6 +2547,7 @@ static int help(int argc, char *argv[], void *userdata) {
}
static int parse_argv(int argc, char *argv[]) {
_cleanup_strv_free_ char **arg_languages = NULL;
enum {
ARG_VERSION = 0x100,
@ -3121,26 +3122,46 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_LANGUAGE:
if (isempty(optarg)) {
r = drop_from_identity("language");
case ARG_LANGUAGE: {
const char *p = optarg;
if (isempty(p)) {
r = drop_from_identity("preferredLanguage");
if (r < 0)
return r;
r = drop_from_identity("additionalLanguages");
if (r < 0)
return r;
arg_languages = strv_free(arg_languages);
break;
}
if (!locale_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", optarg);
for (;;) {
_cleanup_free_ char *word = NULL;
if (locale_is_installed(optarg) <= 0)
log_warning("Locale '%s' is not installed, accepting anyway.", optarg);
r = extract_first_word(&p, &word, ",:", 0);
if (r < 0)
return log_error_errno(r, "Failed to parse locale list: %m");
if (r == 0)
break;
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");
if (!locale_is_valid(word))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word);
if (locale_is_installed(word) <= 0)
log_warning("Locale '%s' is not installed, accepting anyway.", word);
r = strv_consume(&arg_languages, TAKE_PTR(word));
if (r < 0)
return log_oom();
strv_uniq(arg_languages);
}
break;
}
case ARG_NOSUID:
case ARG_NODEV:
@ -4021,6 +4042,25 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)
arg_and_resize = true;
if (!strv_isempty(arg_languages)) {
char **additional;
r = json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", arg_languages[0]);
if (r < 0)
return log_error_errno(r, "Failed to update preferred language: %m");
additional = strv_skip(arg_languages, 1);
if (!strv_isempty(additional)) {
r = json_variant_set_field_strv(&arg_identity_extra, "additionalLanguages", additional);
if (r < 0)
return log_error_errno(r, "Failed to update additional language list: %m");
} else {
r = drop_from_identity("additionalLanguages");
if (r < 0)
return r;
}
}
return 1;
}

View File

@ -606,6 +606,7 @@ static int apply_user_record_settings(
bool debug,
uint64_t default_capability_bounding_set,
uint64_t default_capability_ambient_set) {
_cleanup_strv_free_ char **langs = NULL;
int r;
assert(handle);
@ -617,48 +618,25 @@ static int apply_user_record_settings(
}
STRV_FOREACH(i, ur->environment) {
_cleanup_free_ char *n = NULL;
const char *e;
assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */
n = strndup(*i, e - *i);
if (!n)
return pam_log_oom(handle);
if (pam_getenv(handle, n)) {
pam_debug_syslog(handle, debug,
"PAM environment variable $%s already set, not changing based on record.", *i);
continue;
}
r = pam_putenv_and_log(handle, *i, debug);
if (r != PAM_SUCCESS)
return r;
}
if (ur->email_address) {
if (pam_getenv(handle, "EMAIL"))
pam_debug_syslog(handle, debug,
"PAM environment variable $EMAIL already set, not changing based on user record.");
else {
_cleanup_free_ char *joined = NULL;
_cleanup_free_ char *joined = NULL;
joined = strjoin("EMAIL=", ur->email_address);
if (!joined)
return pam_log_oom(handle);
joined = strjoin("EMAIL=", ur->email_address);
if (!joined)
return pam_log_oom(handle);
r = pam_putenv_and_log(handle, joined, debug);
if (r != PAM_SUCCESS)
return r;
}
r = pam_putenv_and_log(handle, joined, debug);
if (r != PAM_SUCCESS)
return r;
}
if (ur->time_zone) {
if (pam_getenv(handle, "TZ"))
pam_debug_syslog(handle, debug,
"PAM environment variable $TZ already set, not changing based on user record.");
else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
pam_debug_syslog(handle, debug,
"Time zone specified in user record is not valid locally, not setting $TZ.");
else {
@ -674,21 +652,38 @@ static int apply_user_record_settings(
}
}
if (ur->preferred_language) {
if (pam_getenv(handle, "LANG"))
pam_debug_syslog(handle, debug,
"PAM environment variable $LANG already set, not changing based on user record.");
else if (locale_is_installed(ur->preferred_language) <= 0)
pam_debug_syslog(handle, debug,
"Preferred language specified in user record is not valid or not installed, not setting $LANG.");
else {
_cleanup_free_ char *joined = NULL;
r = user_record_languages(ur, &langs);
if (r < 0)
pam_syslog_errno(handle, LOG_ERR, r,
"Failed to acquire user's language preferences, ignoring: %m");
else if (strv_isempty(langs))
; /* User has no preference set so we do nothing */
else if (locale_is_installed(langs[0]) <= 0)
pam_debug_syslog(handle, debug,
"Preferred languages specified in user record are not installed locally, not setting $LANG or $LANGUAGE.");
else {
_cleanup_free_ char *lang = NULL;
joined = strjoin("LANG=", ur->preferred_language);
lang = strjoin("LANG=", langs[0]);
if (!lang)
return pam_log_oom(handle);
r = pam_putenv_and_log(handle, lang, debug);
if (r != PAM_SUCCESS)
return r;
if (strv_length(langs) > 1) {
_cleanup_free_ char *joined = NULL, *language = NULL;
joined = strv_join(langs, ":");
if (!joined)
return pam_log_oom(handle);
r = pam_putenv_and_log(handle, joined, debug);
language = strjoin("LANGUAGE=", joined);
if (!language)
return pam_log_oom(handle);
r = pam_putenv_and_log(handle, language, debug);
if (r != PAM_SUCCESS)
return r;
}

View File

@ -23,6 +23,7 @@ const char *user_record_state_color(const char *state) {
}
void user_record_show(UserRecord *hr, bool show_full_group_info) {
_cleanup_strv_free_ char **langs = NULL;
const char *hd, *ip, *shell;
UserStorage storage;
usec_t t;
@ -237,15 +238,15 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->time_zone)
printf(" Time Zone: %s\n", hr->time_zone);
if (hr->preferred_language)
printf(" Language: %s\n", hr->preferred_language);
if (!strv_isempty(hr->environment))
STRV_FOREACH(i, hr->environment) {
printf(i == hr->environment ?
" Environment: %s\n" :
" %s\n", *i);
}
r = user_record_languages(hr, &langs);
if (r < 0) {
errno = -r;
printf(" Languages: (can't acquire: %m)\n");
} else if (!strv_isempty(langs)) {
STRV_FOREACH(i, langs)
printf(i == langs ? " Languages: %s" : ", %s", *i);
printf("\n");
}
if (hr->locked >= 0)
printf(" Locked: %s\n", yes_no(hr->locked));

View File

@ -10,6 +10,7 @@
#include "glyph-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
@ -146,6 +147,7 @@ static UserRecord* user_record_free(UserRecord *h) {
strv_free(h->environment);
free(h->time_zone);
free(h->preferred_language);
strv_free(h->additional_languages);
rlimit_free_all(h->rlimits);
free(h->skeleton_directory);
@ -535,6 +537,62 @@ static int json_dispatch_environment(const char *name, JsonVariant *variant, Jso
return strv_free_and_replace(*l, n);
}
static int json_dispatch_locale(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!locale_is_valid(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_locales(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
_cleanup_strv_free_ char **n = NULL;
char ***l = userdata;
const char *locale;
JsonVariant *e;
int r;
if (json_variant_is_null(variant)) {
*l = strv_free(*l);
return 0;
}
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
if (!json_variant_is_string(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
locale = json_variant_string(e);
if (!locale_is_valid(locale))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name));
r = strv_extend(&n, locale);
if (r < 0)
return json_log_oom(variant, flags);
}
return strv_free_and_replace(*l, n);
}
JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
@ -1171,7 +1229,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
{ "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
@ -1506,7 +1565,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
{ "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
@ -2034,6 +2094,27 @@ uint64_t user_record_capability_ambient_set(UserRecord *h) {
return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
}
int user_record_languages(UserRecord *h, char ***ret) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(h);
assert(ret);
if (h->preferred_language) {
l = strv_new(h->preferred_language);
if (!l)
return -ENOMEM;
}
r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
if (r < 0)
return r;
*ret = TAKE_PTR(l);
return 0;
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);

View File

@ -252,6 +252,7 @@ typedef struct UserRecord {
char **environment;
char *time_zone;
char *preferred_language;
char **additional_languages;
int nice_level;
struct rlimit *rlimits[_RLIMIT_MAX];
@ -415,6 +416,7 @@ AutoResizeMode user_record_auto_resize_mode(UserRecord *h);
uint64_t user_record_rebalance_weight(UserRecord *h);
uint64_t user_record_capability_bounding_set(UserRecord *h);
uint64_t user_record_capability_ambient_set(UserRecord *h);
int user_record_languages(UserRecord *h, char ***ret);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);