From: Lennart Poettering Date: Tue, 18 Aug 2020 07:56:56 +0000 (+0200) Subject: home: make libpwquality dep a runtime dlopen() one X-Git-Tag: v247-rc1~409^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=679badd7ba5ddcff97ad8603fcf272454ab06663;p=thirdparty%2Fsystemd.git home: make libpwquality dep a runtime dlopen() one Also, let's move the glue for this to src/shared/ so that we later can reuse this in sysemd-firstboot. Given that libpwquality is a more a leaf dependency, let's make it runtime optional, so that downstream distros can downgrade their package deps from Required to Recommended. --- diff --git a/meson.build b/meson.build index 227fe097a81..47b2b5f4b12 100644 --- a/meson.build +++ b/meson.build @@ -2173,8 +2173,7 @@ if conf.get('ENABLE_HOMED') == 1 link_with : [libshared], dependencies : [threads, libcrypt, - libopenssl, - libpwquality], + libopenssl], install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) @@ -2188,8 +2187,7 @@ if conf.get('ENABLE_HOMED') == 1 libcrypt, libopenssl, libp11kit, - libfido2, - libpwquality], + libfido2], install_rpath : rootlibexecdir, install : true, install_dir : rootbindir) diff --git a/src/home/homectl.c b/src/home/homectl.c index 33e262706d2..9d082ef0c05 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -30,6 +30,7 @@ #include "rlimit-util.h" #include "spawn-polkit-agent.h" #include "terminal-util.h" +#include "user-record-pwquality.h" #include "user-record-show.h" #include "user-record-util.h" #include "user-record.h" @@ -1097,7 +1098,7 @@ static int create_home(int argc, char *argv[], void *userdata) { /* If password quality enforcement is disabled, let's at least warn client side */ - r = quality_check_password(hr, hr, &error); + r = user_record_quality_check_password(hr, hr, &error); if (r < 0) log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", bus_error_message(&error, r)); } diff --git a/src/home/homed-home.c b/src/home/homed-home.c index f0c157cb7d8..45c2152531f 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -33,6 +33,7 @@ #include "strv.h" #include "user-record-sign.h" #include "user-record-util.h" +#include "user-record-pwquality.h" #include "user-record.h" #include "user-util.h" @@ -1289,7 +1290,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) { if (h->record->enforce_password_policy == false) log_debug("Password quality check turned off for account, skipping."); else { - r = quality_check_password(h->record, secret, error); + r = user_record_quality_check_password(h->record, secret, error); if (r < 0) return r; } @@ -1640,7 +1641,7 @@ int home_passwd(Home *h, if (c->enforce_password_policy == false) log_debug("Password quality check turned off for account, skipping."); else { - r = quality_check_password(c, merged_secret, error); + r = user_record_quality_check_password(c, merged_secret, error); if (r < 0) return r; } diff --git a/src/home/meson.build b/src/home/meson.build index 797f3a3c6d0..fbf09501c4c 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -50,8 +50,8 @@ systemd_homed_sources = files(''' homed-varlink.c homed-varlink.h homed.c - pwquality-util.c - pwquality-util.h + user-record-pwquality.c + user-record-pwquality.h user-record-sign.c user-record-sign.h user-record-util.c @@ -74,8 +74,8 @@ homectl_sources = files(''' homectl-pkcs11.c homectl-pkcs11.h homectl.c - pwquality-util.c - pwquality-util.h + user-record-pwquality.c + user-record-pwquality.h user-record-util.c user-record-util.h '''.split()) diff --git a/src/home/pwquality-util.c b/src/home/pwquality-util.c deleted file mode 100644 index fbf6f6c8cc9..00000000000 --- a/src/home/pwquality-util.c +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ - -#include - -#if HAVE_PWQUALITY -/* pwquality.h uses size_t but doesn't include sys/types.h on its own */ -#include -#include -#endif - -#include "bus-common-errors.h" -#include "home-util.h" -#include "memory-util.h" -#include "pwquality-util.h" -#include "strv.h" - -#if HAVE_PWQUALITY -DEFINE_TRIVIAL_CLEANUP_FUNC(pwquality_settings_t*, pwquality_free_settings); - -static void pwquality_maybe_disable_dictionary( - pwquality_settings_t *pwq) { - - char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; - const char *path; - int r; - - r = pwquality_get_str_value(pwq, PWQ_SETTING_DICT_PATH, &path); - if (r < 0) { - log_warning("Failed to read libpwquality dictionary path, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL)); - return; - } - - // REMOVE THIS AS SOON AS https://github.com/libpwquality/libpwquality/pull/21 IS MERGED AND RELEASED - if (isempty(path)) - path = "/usr/share/cracklib/pw_dict.pwd.gz"; - - if (isempty(path)) { - log_warning("Weird, no dictionary file configured, ignoring."); - return; - } - - if (access(path, F_OK) >= 0) - return; - - if (errno != ENOENT) { - log_warning_errno(errno, "Failed to check if dictionary file %s exists, ignoring: %m", path); - return; - } - - r = pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, 0); - if (r < 0) { - log_warning("Failed to disable libpwquality dictionary check, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL)); - return; - } -} - -int quality_check_password( - UserRecord *hr, - UserRecord *secret, - sd_bus_error *error) { - - _cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL; - char buf[PWQ_MAX_ERROR_MESSAGE_LEN], **pp; - void *auxerror; - int r; - - assert(hr); - assert(secret); - - pwq = pwquality_default_settings(); - if (!pwq) - return log_oom(); - - r = pwquality_read_config(pwq, NULL, &auxerror); - if (r < 0) - log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuration, ignoring: %s", - pwquality_strerror(buf, sizeof(buf), r, auxerror)); - - pwquality_maybe_disable_dictionary(pwq); - - /* This is a bit more complex than one might think at first. pwquality_check() would like to know the - * old password to make security checks. We support arbitrary numbers of passwords however, hence we - * call the function once for each combination of old and new password. */ - - /* Iterate through all new passwords */ - STRV_FOREACH(pp, secret->password) { - bool called = false; - char **old; - - r = test_password_many(hr->hashed_password, *pp); - if (r < 0) - return r; - if (r == 0) /* This is an old password as it isn't listed in the hashedPassword field, skip it */ - continue; - - /* Check this password against all old passwords */ - STRV_FOREACH(old, secret->password) { - - if (streq(*pp, *old)) - continue; - - r = test_password_many(hr->hashed_password, *old); - if (r < 0) - return r; - if (r > 0) /* This is a new password, not suitable as old password */ - continue; - - r = pwquality_check(pwq, *pp, *old, hr->user_name, &auxerror); - if (r < 0) - return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s", - pwquality_strerror(buf, sizeof(buf), r, auxerror)); - - called = true; - } - - if (called) - continue; - - /* If there are no old passwords, let's call pwquality_check() without any. */ - r = pwquality_check(pwq, *pp, NULL, hr->user_name, &auxerror); - if (r < 0) - return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s", - pwquality_strerror(buf, sizeof(buf), r, auxerror)); - } - - return 0; -} - -#define N_SUGGESTIONS 6 - -int suggest_passwords(void) { - _cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL; - _cleanup_strv_free_erase_ char **suggestions = NULL; - _cleanup_(erase_and_freep) char *joined = NULL; - char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; - void *auxerror; - size_t i; - int r; - - pwq = pwquality_default_settings(); - if (!pwq) - return log_oom(); - - r = pwquality_read_config(pwq, NULL, &auxerror); - if (r < 0) - log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuration, ignoring: %s", - pwquality_strerror(buf, sizeof(buf), r, auxerror)); - - pwquality_maybe_disable_dictionary(pwq); - - suggestions = new0(char*, N_SUGGESTIONS+1); - if (!suggestions) - return log_oom(); - - for (i = 0; i < N_SUGGESTIONS; i++) { - r = pwquality_generate(pwq, 64, suggestions + i); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s", - pwquality_strerror(buf, sizeof(buf), r, NULL)); - } - - joined = strv_join(suggestions, " "); - if (!joined) - return log_oom(); - - log_info("Password suggestions: %s", joined); - return 0; -} - -#else - -int quality_check_password( - UserRecord *hr, - UserRecord *secret, - sd_bus_error *error) { - - assert(hr); - assert(secret); - - return 0; -} - -int suggest_passwords(void) { - return 0; -} -#endif diff --git a/src/home/pwquality-util.h b/src/home/pwquality-util.h deleted file mode 100644 index d61c04c3421..00000000000 --- a/src/home/pwquality-util.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ -#pragma once - -#include "sd-bus.h" -#include "user-record.h" - -int quality_check_password(UserRecord *hr, UserRecord *secret, sd_bus_error *error); - -int suggest_passwords(void); diff --git a/src/home/user-record-pwquality.c b/src/home/user-record-pwquality.c new file mode 100644 index 00000000000..a5d632c7727 --- /dev/null +++ b/src/home/user-record-pwquality.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "bus-common-errors.h" +#include "errno-util.h" +#include "home-util.h" +#include "pwquality-util.h" +#include "strv.h" +#include "user-record-pwquality.h" +#include "user-record-util.h" + +#if HAVE_PWQUALITY + +int user_record_quality_check_password( + UserRecord *hr, + UserRecord *secret, + sd_bus_error *error) { + + _cleanup_(sym_pwquality_free_settingsp) pwquality_settings_t *pwq = NULL; + char buf[PWQ_MAX_ERROR_MESSAGE_LEN], **pp; + void *auxerror; + int r; + + assert(hr); + assert(secret); + + r = pwq_allocate_context(&pwq); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to allocate libpwquality context: %m"); + + /* This is a bit more complex than one might think at first. pwquality_check() would like to know the + * old password to make security checks. We support arbitrary numbers of passwords however, hence we + * call the function once for each combination of old and new password. */ + + /* Iterate through all new passwords */ + STRV_FOREACH(pp, secret->password) { + bool called = false; + char **old; + + r = test_password_many(hr->hashed_password, *pp); + if (r < 0) + return r; + if (r == 0) /* This is an old password as it isn't listed in the hashedPassword field, skip it */ + continue; + + /* Check this password against all old passwords */ + STRV_FOREACH(old, secret->password) { + + if (streq(*pp, *old)) + continue; + + r = test_password_many(hr->hashed_password, *old); + if (r < 0) + return r; + if (r > 0) /* This is a new password, not suitable as old password */ + continue; + + r = sym_pwquality_check(pwq, *pp, *old, hr->user_name, &auxerror); + if (r < 0) + return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); + + called = true; + } + + if (called) + continue; + + /* If there are no old passwords, let's call pwquality_check() without any. */ + r = sym_pwquality_check(pwq, *pp, NULL, hr->user_name, &auxerror); + if (r < 0) + return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); + } + + return 1; +} + +#else + +int user_record_quality_check_password( + UserRecord *hr, + UserRecord *secret, + sd_bus_error *error) { + + return 0; +} + +#endif diff --git a/src/home/user-record-pwquality.h b/src/home/user-record-pwquality.h new file mode 100644 index 00000000000..a37d3691810 --- /dev/null +++ b/src/home/user-record-pwquality.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-bus.h" +#include "user-record.h" + +int user_record_quality_check_password(UserRecord *hr, UserRecord *secret, sd_bus_error *error); diff --git a/src/shared/meson.build b/src/shared/meson.build index 0da733c3fe7..d192a4d8d7f 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -191,6 +191,8 @@ shared_sources = files(''' pretty-print.h ptyfwd.c ptyfwd.h + pwquality-util.c + pwquality-util.h reboot-util.c reboot-util.h resize-fs.c diff --git a/src/shared/pwquality-util.c b/src/shared/pwquality-util.c new file mode 100644 index 00000000000..799c39f32b5 --- /dev/null +++ b/src/shared/pwquality-util.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "dlfcn-util.h" +#include "errno-util.h" +#include "log.h" +#include "macro.h" +#include "memory-util.h" +#include "pwquality-util.h" +#include "strv.h" + +#if HAVE_PWQUALITY + +static void *pwquality_dl = NULL; + +int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); +pwquality_settings_t *(*sym_pwquality_default_settings)(void); +void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); +int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); +int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); +int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); +int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); +const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); + +int dlopen_pwquality(void) { + _cleanup_(dlclosep) void *dl = NULL; + int r; + + if (pwquality_dl) + return 0; /* Already loaded */ + + dl = dlopen("libpwquality.so.1", RTLD_LAZY); + if (!dl) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpwquality support is not installed: %s", dlerror()); + + r = dlsym_many_and_warn( + dl, + LOG_DEBUG, + &sym_pwquality_check, "pwquality_check", + &sym_pwquality_default_settings, "pwquality_default_settings", + &sym_pwquality_free_settings, "pwquality_free_settings", + &sym_pwquality_generate, "pwquality_generate", + &sym_pwquality_get_str_value, "pwquality_get_str_value", + &sym_pwquality_read_config, "pwquality_read_config", + &sym_pwquality_set_int_value, "pwquality_set_int_value", + &sym_pwquality_strerror, "pwquality_strerror", + NULL); + if (r < 0) + return r; + + /* Note that we never release the reference here, because there's no real reason to, after all this + * was traditionally a regular shared library dependency which lives forever too. */ + pwquality_dl = TAKE_PTR(dl); + return 1; +} + +void pwq_maybe_disable_dictionary(pwquality_settings_t *pwq) { + char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; + const char *path; + int r; + + assert(pwq); + + r = sym_pwquality_get_str_value(pwq, PWQ_SETTING_DICT_PATH, &path); + if (r < 0) { + log_debug("Failed to read libpwquality dictionary path, ignoring: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, NULL)); + return; + } + + // REMOVE THIS AS SOON AS https://github.com/libpwquality/libpwquality/pull/21 IS MERGED AND RELEASED + if (isempty(path)) + path = "/usr/share/cracklib/pw_dict.pwd.gz"; + + if (isempty(path)) { + log_debug("Weird, no dictionary file configured, ignoring."); + return; + } + + if (access(path, F_OK) >= 0) + return; + + if (errno != ENOENT) { + log_debug_errno(errno, "Failed to check if dictionary file %s exists, ignoring: %m", path); + return; + } + + r = sym_pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, 0); + if (r < 0) + log_debug("Failed to disable libpwquality dictionary check, ignoring: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, NULL)); +} + +int pwq_allocate_context(pwquality_settings_t **ret) { + _cleanup_(sym_pwquality_free_settingsp) pwquality_settings_t *pwq = NULL; + char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; + void *auxerror; + int r; + + assert(ret); + + r = dlopen_pwquality(); + if (r < 0) + return r; + + pwq = sym_pwquality_default_settings(); + if (!pwq) + return -ENOMEM; + + r = sym_pwquality_read_config(pwq, NULL, &auxerror); + if (r < 0) + log_debug("Failed to read libpwquality configuration, ignoring: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); + + pwq_maybe_disable_dictionary(pwq); + + *ret = TAKE_PTR(pwq); + return 0; +} + +#define N_SUGGESTIONS 6 + +int suggest_passwords(void) { + _cleanup_(sym_pwquality_free_settingsp) pwquality_settings_t *pwq = NULL; + _cleanup_strv_free_erase_ char **suggestions = NULL; + _cleanup_(erase_and_freep) char *joined = NULL; + char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; + size_t i; + int r; + + r = pwq_allocate_context(&pwq); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to allocate libpwquality context: %m"); + + suggestions = new0(char*, N_SUGGESTIONS+1); + if (!suggestions) + return log_oom(); + + for (i = 0; i < N_SUGGESTIONS; i++) { + r = sym_pwquality_generate(pwq, 64, suggestions + i); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s", + sym_pwquality_strerror(buf, sizeof(buf), r, NULL)); + } + + joined = strv_join(suggestions, " "); + if (!joined) + return log_oom(); + + log_info("Password suggestions: %s", joined); + return 1; +} + +#endif diff --git a/src/shared/pwquality-util.h b/src/shared/pwquality-util.h new file mode 100644 index 00000000000..2ef34dabee6 --- /dev/null +++ b/src/shared/pwquality-util.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "macro.h" + +#if HAVE_PWQUALITY +/* pwquality.h uses size_t but doesn't include sys/types.h on its own */ +#include +#include + +extern int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); +extern pwquality_settings_t *(*sym_pwquality_default_settings)(void); +extern void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); +extern int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); +extern int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); +extern int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); +extern int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); +extern const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); + +int dlopen_pwquality(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC(pwquality_settings_t*, sym_pwquality_free_settings); + +void pwq_maybe_disable_dictionary(pwquality_settings_t *pwq); +int pwq_allocate_context(pwquality_settings_t **ret); +int suggest_passwords(void); + +#else + +static inline int suggest_passwords(void) { + return 0; +} + +#endif