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
This commit is contained in:
parent
e3ba6b93de
commit
db994458bb
|
@ -67,6 +67,7 @@ pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
|
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;
|
static libzfs_handle_t *g_zfs;
|
||||||
|
|
||||||
|
@ -160,10 +161,10 @@ pw_free(pw_password_t *pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
static pw_password_t *
|
static pw_password_t *
|
||||||
pw_fetch(pam_handle_t *pamh)
|
pw_fetch(pam_handle_t *pamh, int tok)
|
||||||
{
|
{
|
||||||
const char *token;
|
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,
|
pam_syslog(pamh, LOG_ERR,
|
||||||
"couldn't get password from PAM stack");
|
"couldn't get password from PAM stack");
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
@ -177,13 +178,13 @@ pw_fetch(pam_handle_t *pamh)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const pw_password_t *
|
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) {
|
if (pw == NULL) {
|
||||||
return (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) {
|
if (ret != PAM_SUCCESS) {
|
||||||
pw_free(pw);
|
pw_free(pw);
|
||||||
pam_syslog(pamh, LOG_ERR, "pam_set_data failed");
|
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 *
|
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;
|
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));
|
(const void**)(&authtok));
|
||||||
if (ret == PAM_SUCCESS)
|
if (ret == PAM_SUCCESS)
|
||||||
return (authtok);
|
return (authtok);
|
||||||
if (ret == PAM_NO_MODULE_DATA)
|
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");
|
pam_syslog(pamh, LOG_ERR, "password not available");
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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) {
|
if (ret != PAM_SUCCESS) {
|
||||||
pam_syslog(pamh, LOG_ERR, "clearing password failed");
|
pam_syslog(pamh, LOG_ERR, "clearing password failed");
|
||||||
return (-1);
|
return (-1);
|
||||||
|
@ -686,7 +687,8 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||||
return (PAM_SERVICE_ERR);
|
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) {
|
if (token == NULL) {
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_AUTH_ERR);
|
return (PAM_AUTH_ERR);
|
||||||
|
@ -740,6 +742,8 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_SERVICE_ERR);
|
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) {
|
if (pam_zfs_init(pamh) != 0) {
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
|
@ -751,49 +755,62 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_SERVICE_ERR);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
int key_loaded = is_key_loaded(pamh, dataset);
|
if (!old_token) {
|
||||||
if (key_loaded == -1) {
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"old password from PAM stack is null");
|
||||||
free(dataset);
|
free(dataset);
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_SERVICE_ERR);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
|
if (decrypt_mount(pamh, dataset,
|
||||||
|
old_token->value, B_TRUE) == -1) {
|
||||||
|
pam_syslog(pamh, LOG_ERR,
|
||||||
|
"old token mismatch");
|
||||||
free(dataset);
|
free(dataset);
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
if (! key_loaded) {
|
|
||||||
pam_syslog(pamh, LOG_ERR,
|
|
||||||
"key not loaded, returning try_again");
|
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_PERM_DENIED);
|
return (PAM_PERM_DENIED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
|
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) {
|
if (token == NULL) {
|
||||||
|
pam_syslog(pamh, LOG_ERR, "new password unavailable");
|
||||||
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_SERVICE_ERR);
|
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||||
}
|
|
||||||
if (pam_zfs_init(pamh) != 0) {
|
|
||||||
zfs_key_config_free(&config);
|
|
||||||
return (PAM_SERVICE_ERR);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
char *dataset = zfs_key_config_get_dataset(&config);
|
char *dataset = zfs_key_config_get_dataset(&config);
|
||||||
if (!dataset) {
|
if (!dataset) {
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
|
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||||
|
pw_clear(pamh, PASSWORD_VAR_NAME);
|
||||||
return (PAM_SERVICE_ERR);
|
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);
|
free(dataset);
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
|
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
|
||||||
|
pw_clear(pamh, PASSWORD_VAR_NAME);
|
||||||
return (PAM_SERVICE_ERR);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
|
int changed = change_key(pamh, dataset, token->value);
|
||||||
|
if (!was_loaded) {
|
||||||
|
unmount_unload(pamh, dataset, config.force_unmount);
|
||||||
|
}
|
||||||
free(dataset);
|
free(dataset);
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
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);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -829,7 +846,8 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||||
return (PAM_SUCCESS);
|
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) {
|
if (token == NULL) {
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
return (PAM_SESSION_ERR);
|
return (PAM_SESSION_ERR);
|
||||||
|
@ -853,7 +871,7 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||||
free(dataset);
|
free(dataset);
|
||||||
pam_zfs_free();
|
pam_zfs_free();
|
||||||
zfs_key_config_free(&config);
|
zfs_key_config_free(&config);
|
||||||
if (pw_clear(pamh) == -1) {
|
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
|
||||||
return (PAM_SERVICE_ERR);
|
return (PAM_SERVICE_ERR);
|
||||||
}
|
}
|
||||||
return (PAM_SUCCESS);
|
return (PAM_SUCCESS);
|
||||||
|
|
|
@ -140,7 +140,8 @@ tests = ['umount_unlinked_drain']
|
||||||
tags = ['functional', 'mount']
|
tags = ['functional', 'mount']
|
||||||
|
|
||||||
[tests/functional/pam:Linux]
|
[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']
|
tags = ['functional', 'pam']
|
||||||
|
|
||||||
[tests/functional/procfs:Linux]
|
[tests/functional/procfs:Linux]
|
||||||
|
|
|
@ -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."
|
|
@ -52,7 +52,7 @@ log_must ismounted "$TESTPOOL/pam/${username}"
|
||||||
keystatus available
|
keystatus available
|
||||||
|
|
||||||
# Change user and dataset password to short one.
|
# 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.
|
# Unmount and unload key.
|
||||||
log_must pamtester ${pamservice} ${username} close_session
|
log_must pamtester ${pamservice} ${username} close_session
|
||||||
|
|
Loading…
Reference in New Issue