aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVal Packett <val@packett.cool>2023-05-06 01:17:12 +0000
committerBrian Behlendorf <behlendorf1@llnl.gov>2023-06-01 00:01:11 +0000
commitdb994458bbee99968827d88afd823d36ef82af28 (patch)
treee68c0d2b219e058f243a48d92678809553d7eef2
parente3ba6b93de32bc76e9e616472af1c7aafea585a5 (diff)
downloadsrc-db994458bbee99968827d88afd823d36ef82af28.tar.gz
src-db994458bbee99968827d88afd823d36ef82af28.zip
PAM: support password changes even when not mounted
There's usually no requirement that a user be logged in for changing their password, so let's not be surprising here. We need to use the fetch_lazy mechanism for the old password to avoid a double prompt for it, so that mechanism is now generalized a bit. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Felix Dörre <felix@dogcraft.de> Signed-off-by: Val Packett <val@packett.cool> Closes #14834
-rw-r--r--contrib/pam_zfs_key/pam_zfs_key.c70
-rw-r--r--tests/runfiles/linux.run3
-rwxr-xr-xtests/zfs-tests/tests/functional/pam/pam_change_unmounted.ksh55
-rwxr-xr-xtests/zfs-tests/tests/functional/pam/pam_short_password.ksh2
4 files changed, 102 insertions, 28 deletions
diff --git a/contrib/pam_zfs_key/pam_zfs_key.c b/contrib/pam_zfs_key/pam_zfs_key.c
index 6b7a41fa1739..08a8640669b3 100644
--- a/contrib/pam_zfs_key/pam_zfs_key.c
+++ b/contrib/pam_zfs_key/pam_zfs_key.c
@@ -67,6 +67,7 @@ pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
#include <sys/mman.h>
static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
+static const char OLD_PASSWORD_VAR_NAME[] = "pam_zfs_key_oldauthtok";
static libzfs_handle_t *g_zfs;
@@ -160,10 +161,10 @@ pw_free(pw_password_t *pw)
}
static pw_password_t *
-pw_fetch(pam_handle_t *pamh)
+pw_fetch(pam_handle_t *pamh, int tok)
{
const char *token;
- if (pam_get_authtok(pamh, PAM_AUTHTOK, &token, NULL) != PAM_SUCCESS) {
+ if (pam_get_authtok(pamh, tok, &token, NULL) != PAM_SUCCESS) {
pam_syslog(pamh, LOG_ERR,
"couldn't get password from PAM stack");
return (NULL);
@@ -177,13 +178,13 @@ pw_fetch(pam_handle_t *pamh)
}
static const pw_password_t *
-pw_fetch_lazy(pam_handle_t *pamh)
+pw_fetch_lazy(pam_handle_t *pamh, int tok, const char *var_name)
{
- pw_password_t *pw = pw_fetch(pamh);
+ pw_password_t *pw = pw_fetch(pamh, tok);
if (pw == NULL) {
return (NULL);
}
- int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, pw, destroy_pw);
+ int ret = pam_set_data(pamh, var_name, pw, destroy_pw);
if (ret != PAM_SUCCESS) {
pw_free(pw);
pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
@@ -193,23 +194,23 @@ pw_fetch_lazy(pam_handle_t *pamh)
}
static const pw_password_t *
-pw_get(pam_handle_t *pamh)
+pw_get(pam_handle_t *pamh, int tok, const char *var_name)
{
const pw_password_t *authtok = NULL;
- int ret = pam_get_data(pamh, PASSWORD_VAR_NAME,
+ int ret = pam_get_data(pamh, var_name,
(const void**)(&authtok));
if (ret == PAM_SUCCESS)
return (authtok);
if (ret == PAM_NO_MODULE_DATA)
- return (pw_fetch_lazy(pamh));
+ return (pw_fetch_lazy(pamh, tok, var_name));
pam_syslog(pamh, LOG_ERR, "password not available");
return (NULL);
}
static int
-pw_clear(pam_handle_t *pamh)
+pw_clear(pam_handle_t *pamh, const char *var_name)
{
- int ret = pam_set_data(pamh, PASSWORD_VAR_NAME, NULL, NULL);
+ int ret = pam_set_data(pamh, var_name, NULL, NULL);
if (ret != PAM_SUCCESS) {
pam_syslog(pamh, LOG_ERR, "clearing password failed");
return (-1);
@@ -686,7 +687,8 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
return (PAM_SERVICE_ERR);
}
- const pw_password_t *token = pw_fetch_lazy(pamh);
+ const pw_password_t *token = pw_fetch_lazy(pamh,
+ PAM_AUTHTOK, PASSWORD_VAR_NAME);
if (token == NULL) {
zfs_key_config_free(&config);
return (PAM_AUTH_ERR);
@@ -740,6 +742,8 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
+ const pw_password_t *old_token = pw_get(pamh,
+ PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
{
if (pam_zfs_init(pamh) != 0) {
zfs_key_config_free(&config);
@@ -751,49 +755,62 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
- int key_loaded = is_key_loaded(pamh, dataset);
- if (key_loaded == -1) {
+ if (!old_token) {
+ pam_syslog(pamh, LOG_ERR,
+ "old password from PAM stack is null");
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
- free(dataset);
- pam_zfs_free();
- if (! key_loaded) {
+ if (decrypt_mount(pamh, dataset,
+ old_token->value, B_TRUE) == -1) {
pam_syslog(pamh, LOG_ERR,
- "key not loaded, returning try_again");
+ "old token mismatch");
+ free(dataset);
+ pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_PERM_DENIED);
}
}
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
- const pw_password_t *token = pw_get(pamh);
+ const pw_password_t *token = pw_get(pamh, PAM_AUTHTOK,
+ PASSWORD_VAR_NAME);
if (token == NULL) {
+ pam_syslog(pamh, LOG_ERR, "new password unavailable");
+ pam_zfs_free();
zfs_key_config_free(&config);
- return (PAM_SERVICE_ERR);
- }
- if (pam_zfs_init(pamh) != 0) {
- zfs_key_config_free(&config);
+ pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(&config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
+ pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
+ pw_clear(pamh, PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
- if (change_key(pamh, dataset, token->value) == -1) {
+ int was_loaded = is_key_loaded(pamh, dataset);
+ if (!was_loaded && decrypt_mount(pamh, dataset,
+ old_token->value, B_FALSE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
+ pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
+ pw_clear(pamh, PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
+ int changed = change_key(pamh, dataset, token->value);
+ if (!was_loaded) {
+ unmount_unload(pamh, dataset, config.force_unmount);
+ }
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
- if (pw_clear(pamh) == -1) {
+ if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
+ pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
return (PAM_SERVICE_ERR);
}
} else {
@@ -829,7 +846,8 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
return (PAM_SUCCESS);
}
- const pw_password_t *token = pw_get(pamh);
+ const pw_password_t *token = pw_get(pamh,
+ PAM_AUTHTOK, PASSWORD_VAR_NAME);
if (token == NULL) {
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
@@ -853,7 +871,7 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
- if (pw_clear(pamh) == -1) {
+ if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
return (PAM_SERVICE_ERR);
}
return (PAM_SUCCESS);
diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run
index 97fc250a7cbf..618eeb934017 100644
--- a/tests/runfiles/linux.run
+++ b/tests/runfiles/linux.run
@@ -140,7 +140,8 @@ tests = ['umount_unlinked_drain']
tags = ['functional', 'mount']
[tests/functional/pam:Linux]
-tests = ['pam_basic', 'pam_nounmount', 'pam_recursive', 'pam_short_password']
+tests = ['pam_basic', 'pam_change_unmounted', 'pam_nounmount', 'pam_recursive',
+ 'pam_short_password']
tags = ['functional', 'pam']
[tests/functional/procfs:Linux]
diff --git a/tests/zfs-tests/tests/functional/pam/pam_change_unmounted.ksh b/tests/zfs-tests/tests/functional/pam/pam_change_unmounted.ksh
new file mode 100755
index 000000000000..91b202f7609d
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/pam/pam_change_unmounted.ksh
@@ -0,0 +1,55 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+. $STF_SUITE/tests/functional/pam/utilities.kshlib
+
+if [ -n "$ASAN_OPTIONS" ]; then
+ export LD_PRELOAD=$(ldd "$(command -v zfs)" | awk '/libasan\.so/ {print $3}')
+fi
+
+log_mustnot ismounted "$TESTPOOL/pam/${username}"
+keystatus unavailable
+
+genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir}"
+
+printf "testpass\nsecondpass\nsecondpass\n" | pamtester -v ${pamservice} ${username} chauthtok
+
+log_mustnot ismounted "$TESTPOOL/pam/${username}"
+keystatus unavailable
+
+echo "secondpass" | pamtester ${pamservice} ${username} open_session
+references 1
+log_must ismounted "$TESTPOOL/pam/${username}"
+keystatus available
+
+printf "secondpass\ntestpass\ntestpass\n" | pamtester -v ${pamservice} ${username} chauthtok
+
+log_must ismounted "$TESTPOOL/pam/${username}"
+log_must ismounted "$TESTPOOL/pam/${username}"
+keystatus available
+
+log_must pamtester ${pamservice} ${username} close_session
+references 0
+log_mustnot ismounted "$TESTPOOL/pam/${username}"
+keystatus unavailable
+
+log_pass "done."
diff --git a/tests/zfs-tests/tests/functional/pam/pam_short_password.ksh b/tests/zfs-tests/tests/functional/pam/pam_short_password.ksh
index 443e07d7f003..079608583a72 100755
--- a/tests/zfs-tests/tests/functional/pam/pam_short_password.ksh
+++ b/tests/zfs-tests/tests/functional/pam/pam_short_password.ksh
@@ -52,7 +52,7 @@ log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
# Change user and dataset password to short one.
-printf "short\nshort\n" | pamtester ${pamservice} ${username} chauthtok
+printf "testpass\nshort\nshort\n" | pamtester -v ${pamservice} ${username} chauthtok
# Unmount and unload key.
log_must pamtester ${pamservice} ${username} close_session