Added support for multiple homes in pam_zfs_key module (#18084)

This implemented support for having multiple datasets unlocked and
mounted when a session is opened.
Example: `homes=rpool/home,tank/users`

Extra unit tests have been added

A man page documents have been added `man 8 pam_zfs_key`. A few
references to the new man page have also been added in other documents.

Signed-off-by: Dennis Vestergaard Værum <github@varum.dk>
Reviewed-by: Tony Hutter <hutter2@llnl.gov>
Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de>
This commit is contained in:
Dennis Værum 2026-02-04 01:09:10 +01:00 committed by GitHub
parent 7e33476a7c
commit 07ae463d1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 528 additions and 94 deletions

View File

@ -1,2 +1,3 @@
usr/lib/*/security/pam_zfs_key.so
usr/share/man/man8/pam_zfs_key.8
usr/share/pam-configs/zfs_key

View File

@ -754,6 +754,82 @@ zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
return (ret);
}
/*
* Callback type for foreach_dataset.
* Returns 0 on success, -1 on failure.
*/
typedef int (*dataset_callback_t)(pam_handle_t *, zfs_key_config_t *,
const char *, void *);
/*
* Iterate over comma-separated homes prefixes and call callback for each
* existing dataset. Returns number of successful callbacks, or -1 if none
* succeeded.
*/
static int
foreach_dataset(pam_handle_t *pamh, zfs_key_config_t *config,
dataset_callback_t callback, void *data)
{
if (config->homes_prefix == NULL)
return (-1);
/* Check if this is a comma-separated list */
if (strchr(config->homes_prefix, ',') == NULL) {
/* Single home - use existing logic */
char *dataset = zfs_key_config_get_dataset(pamh, config);
if (dataset == NULL)
return (-1);
int ret = callback(pamh, config, dataset, data);
free(dataset);
return (ret == 0 ? 1 : -1);
}
/* Multiple homes - parse and iterate */
pam_syslog(pamh, LOG_DEBUG,
"processing multiple home prefixes: %s", config->homes_prefix);
char *homes_copy = strdup(config->homes_prefix);
if (homes_copy == NULL)
return (-1);
char *saved_prefix = config->homes_prefix;
char *saveptr;
char *token = strtok_r(homes_copy, ",", &saveptr);
int success_count = 0;
boolean_t failed = B_FALSE;
while (token != NULL) {
/* Temporarily set homes_prefix to this single prefix */
config->homes_prefix = token;
char *dataset = zfs_key_config_get_dataset(pamh, config);
if (dataset != NULL) {
pam_syslog(pamh, LOG_DEBUG,
"processing dataset '%s' for prefix '%s'",
dataset, token);
if (callback(pamh, config, dataset, data) == 0) {
success_count++;
} else {
failed = B_TRUE;
pam_syslog(pamh, LOG_WARNING,
"operation failed for dataset '%s'",
dataset);
}
free(dataset);
} else {
pam_syslog(pamh, LOG_DEBUG,
"no dataset found for prefix '%s', skip", token);
}
token = strtok_r(NULL, ",", &saveptr);
}
config->homes_prefix = saved_prefix;
free(homes_copy);
pam_syslog(pamh, LOG_DEBUG,
"processed %d datasets, %s",
success_count, failed ? "with failures" : "all successful");
return (!failed && success_count > 0 ? success_count : -1);
}
static int
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
zfs_key_config_t *config, int delta)
@ -825,6 +901,15 @@ zfs_key_config_modify_session_counter(pam_handle_t *pamh,
return (counter_value);
}
/* Callback for authentication - verify password works (noop mode) */
static int
auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
const char *passphrase = data;
return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
}
__attribute__((visibility("default")))
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
@ -857,21 +942,14 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (decrypt_mount(pamh, &config, dataset, token->value, B_TRUE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_AUTH_ERR);
}
free(dataset);
int ret = foreach_dataset(pamh, &config, auth_callback,
(void *)token->value);
pam_zfs_free();
zfs_key_config_free(&config);
if (ret < 0) {
return (PAM_AUTH_ERR);
}
return (PAM_SUCCESS);
}
@ -884,6 +962,39 @@ pam_sm_setcred(pam_handle_t *pamh, int flags,
return (PAM_SUCCESS);
}
/* Context for password change callback */
typedef struct {
const char *old_pass;
const char *new_pass;
} chauthtok_ctx_t;
/* Callback for password change */
static int
chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
chauthtok_ctx_t *ctx = data;
int was_loaded = is_key_loaded(pamh, dataset);
if (!was_loaded) {
int ret = decrypt_mount(pamh, config, dataset,
ctx->old_pass, B_FALSE);
if (ret == -1) {
pam_syslog(pamh, LOG_ERR,
"failed to load key for '%s' during "
"password change", dataset);
return (-1);
}
}
int ret = change_key(pamh, dataset, ctx->new_pass);
if (ret == -1) {
pam_syslog(pamh, LOG_ERR,
"failed to change key for dataset '%s'", dataset);
}
if (!was_loaded)
unmount_unload(pamh, dataset, config);
return (ret);
}
__attribute__((visibility("default")))
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
@ -904,34 +1015,27 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
}
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);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
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);
}
if (decrypt_mount(pamh, &config, dataset,
old_token->value, B_TRUE) == -1) {
pam_syslog(pamh, LOG_ERR,
"old token mismatch");
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_PERM_DENIED);
}
if (!old_token) {
pam_syslog(pamh, LOG_ERR,
"old password from PAM stack is null");
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (pam_zfs_init(pamh) != 0) {
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
/* First verify old password works for all datasets */
int ret = foreach_dataset(pamh, &config, auth_callback,
(void *)old_token->value);
if (ret < 0) {
pam_syslog(pamh, LOG_ERR, "old token mismatch");
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_PERM_DENIED);
}
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
@ -944,41 +1048,51 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &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);
}
int was_loaded = is_key_loaded(pamh, dataset);
if (!was_loaded && decrypt_mount(pamh, &config, 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);
}
free(dataset);
chauthtok_ctx_t ctx = {
.old_pass = old_token->value,
.new_pass = token->value
};
ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
pam_zfs_free();
zfs_key_config_free(&config);
if (ret < 0) {
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
pw_clear(pamh, PASSWORD_VAR_NAME);
return (PAM_SERVICE_ERR);
}
if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
return (PAM_SERVICE_ERR);
}
} else {
pam_zfs_free();
zfs_key_config_free(&config);
}
return (PAM_SUCCESS);
}
/* Callback for session open - decrypt and mount */
static int
open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
const char *passphrase = data;
return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
}
/* Callback for session close - unmount and unload */
static int
close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
const char *dataset, void *data)
{
(void) data;
return (unmount_unload(pamh, dataset, config));
}
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
@ -1016,22 +1130,15 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
if (decrypt_mount(pamh, &config, dataset,
token->value, B_FALSE) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
free(dataset);
int ret = foreach_dataset(pamh, &config, open_session_callback,
(void *)token->value);
pam_zfs_free();
zfs_key_config_free(&config);
if (ret < 0) {
return (PAM_SERVICE_ERR);
}
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
return (PAM_SERVICE_ERR);
}
@ -1071,20 +1178,15 @@ pam_sm_close_session(pam_handle_t *pamh, int flags,
zfs_key_config_free(&config);
return (PAM_SERVICE_ERR);
}
char *dataset = zfs_key_config_get_dataset(pamh, &config);
if (!dataset) {
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
}
if (unmount_unload(pamh, dataset, &config) == -1) {
free(dataset);
pam_zfs_free();
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
}
free(dataset);
int ret = foreach_dataset(pamh, &config,
close_session_callback, NULL);
pam_zfs_free();
if (ret < 0) {
zfs_key_config_free(&config);
return (PAM_SESSION_ERR);
}
}
zfs_key_config_free(&config);

View File

@ -112,6 +112,7 @@ endif
if BUILD_LINUX
dist_man_MANS += \
%D%/man8/pam_zfs_key.8 \
%D%/man8/zfs-unzone.8 \
%D%/man8/zfs-zone.8
endif

221
man/man8/pam_zfs_key.8 Normal file
View File

@ -0,0 +1,221 @@
.\" SPDX-License-Identifier: BSD-3-Clause
.\"
.\" Copyright (c) 2020, Felix Dörre
.\" All rights reserved.
.\"
.Dd December 24, 2025
.Dt PAM_ZFS_KEY 8
.Os
.
.Sh NAME
.Nm pam_zfs_key
.Nd PAM module for ZFS encryption key management
.Sh SYNOPSIS
.Nm pam_zfs_key.so
.Op Ar options
.
.Sh DESCRIPTION
.Nm
is a PAM module that automatically manages encryption keys for ZFS
datasets during user authentication and session management.
When a user logs in, the module uses their password to unlock their encrypted
home directory.
When the last session closes, the module unmounts the dataset and unloads
the key.
.Pp
The module tracks active sessions using reference counting to support multiple
simultaneous logins from the same user.
.Ss Multiple Home Prefixes
When configured with multiple home prefixes, the module attempts operations
on all matching datasets.
Operations that succeed are not rolled back if others fail.
The module returns success only if all operations succeed.
.Pp
For example, with datasets 1, 2, 3 where dataset 2 fails:
.Bl -bullet -compact
.It
Auth/session: datasets 1 and 3 are unlocked and mounted, dataset 2 is not.
.It
Password change: datasets 1 and 3 have the new password,
dataset 2 retains the old.
.El
.Pp
With
.Sy required ,
login fails even though datasets 1 and 3 succeeded.
With
.Sy optional ,
login proceeds.
For password changes, datasets 1 and 3 are updated while dataset 2
retains the old password.
With
.Sy required ,
the user sees an error.
With
.Sy optional ,
the user sees success and may not notice the inconsistency.
Either way, passwords are left out of sync.
.Pp
Errors are logged to syslog.
Use
.Xr zfs-change-key 8
to resync passwords after partial failure.
.
.Sh OPTIONS
.Bl -tag -width "mount_recursively"
.It Sy homes Ns = Ns Ar path Ns Oo , Ns Ar path2 Ns ... Oc
Comma-separated list of dataset prefixes where user home directories
are located.
The module constructs the full dataset path as
.Ar prefix Ns / Ns Ar username .
Default:
.Sy zroot/home
on
.Fx ,
.Sy rpool/home
on Linux.
.It Sy runstatedir Ns = Ns Ar path
Directory for storing session reference counts.
Default:
.Pa /var/run/pam_zfs_key .
.It Sy uid_min Ns = Ns Ar uid
Minimum user ID for which the module will operate.
Default: 1000.
.It Sy uid_max Ns = Ns Ar uid
Maximum user ID for which the module will operate.
Default: MAXUID.
.It Sy nounmount
Do not unmount datasets or unload encryption keys when sessions close.
Datasets remain mounted and keys remain loaded.
.It Sy forceunmount
Force unmount datasets even if busy
.Pq Dv MS_FORCE .
.It Sy recursive_homes
Recursively search for encrypted datasets under the homes prefix.
.It Sy mount_recursively
Mount and unmount child datasets recursively.
.It Sy prop_mountpoint
Find the user's dataset by matching the dataset's
.Sy mountpoint
property to the user's home directory from
.Pa /etc/passwd ,
instead of constructing the dataset name as
.Ar prefix Ns / Ns Ar username .
.El
.
.Sh FILES
.Bl -tag -width Pa
.It Pa /var/run/pam_zfs_key/ Ns Ar uid
Session reference count files tracking active logins per user.
.El
.
.Sh EXAMPLES
.Ss Example 1: Basic Configuration
Add to
.Pa /etc/pam.d/system-auth :
.Bd -literal -offset indent
auth optional pam_zfs_key.so
password optional pam_zfs_key.so
session optional pam_zfs_key.so
.Ed
.Pp
This configuration uses default settings.
User home datasets are expected at
.Sy zroot/home/ Ns Ar username
on
.Fx
or
.Sy rpool/home/ Ns Ar username
on Linux.
.
.Ss Example 2: Custom Home Directory Prefix
.Bd -literal -offset indent
auth optional pam_zfs_key.so homes=tank/users
password optional pam_zfs_key.so homes=tank/users
session optional pam_zfs_key.so homes=tank/users
.Ed
.Pp
Looks for user datasets at
.Sy tank/users/ Ns Ar username .
.
.Ss Example 3: Multiple Dataset Prefixes
.Bd -literal -offset indent
session optional pam_zfs_key.so homes=rpool/home,tank/users
.Ed
.Pp
Searches for user datasets in both
.Sy rpool/home
and
.Sy tank/users .
.
.Ss Example 4: Keep Datasets Mounted
.Bd -literal -offset indent
session optional pam_zfs_key.so nounmount
.Ed
.Pp
Leaves datasets mounted and keys loaded when sessions close.
Useful for systems with background processes accessing the home directory.
.
.Ss Example 5: Recursive Mounting
.Bd -literal -offset indent
session optional pam_zfs_key.so mount_recursively
.Ed
.Pp
Mounts child datasets recursively, useful when user data is organized
hierarchically like
.Sy rpool/home/alice/documents
and
.Sy rpool/home/alice/photos .
.
.Ss Example 6: Creating an Encrypted Home Dataset
.Bd -literal -offset indent
# zfs create -o encryption=on \e
-o keyformat=passphrase \e
-o keylocation=prompt \e
-o canmount=on \e
-o mountpoint=/home/alice \e
rpool/home/alice
.Ed
.Pp
The user's login password must match the dataset passphrase for automatic
unlocking to work.
The dataset must have a ZFS-managed mountpoint (not legacy) and
.Sy canmount Ns = Ns Sy on
for automatic mounting.
.
.Ss Example 7: Multiple Homes with Password Sync Check
.Bd -literal -offset indent
auth optional pam_zfs_key.so homes=rpool/home,tank/home
password required pam_zfs_key.so homes=rpool/home,tank/home
session optional pam_zfs_key.so homes=rpool/home,tank/home
.Ed
.Pp
Login proceeds even if some datasets are unavailable.
Password changes fail if any dataset cannot be updated, ensuring
the user is notified of sync issues.
See
.Sx Multiple Home Prefixes
for failure behavior.
.
.Sh SEE ALSO
.Xr pam 8 ,
.Xr zfs-change-key 8 ,
.Xr zfs-load-key 8 ,
.Xr zfs-mount 8
.
.Sh NOTES
.Bl -bullet -compact
.It
Only works with datasets using
.Sy keyformat Ns = Ns Sy passphrase .
.It
Datasets must have
.Sy keylocation Ns = Ns Sy prompt .
.It
Datasets with
.Sy mountpoint Ns = Ns Sy legacy ,
.Sy canmount Ns = Ns Sy off ,
or
.Sy canmount Ns = Ns Sy noauto
will have keys loaded but not be automatically mounted.
.El

View File

@ -92,6 +92,9 @@ will ask for the key and mount the dataset
see
.Xr zfs-mount 8
.Pc .
For automated key management during user login,
.Xr pam_zfs_key 8
can load keys and mount encrypted home directories on systems with PAM support.
Once the key is loaded the
.Sy keystatus
property will become
@ -301,5 +304,6 @@ written.
.
.Sh SEE ALSO
.Xr zfsprops 7 ,
.Xr pam_zfs_key 8 ,
.Xr zfs-create 8 ,
.Xr zfs-set 8

View File

@ -110,6 +110,9 @@ on each encryption root before mounting it.
Note that if a filesystem has
.Sy keylocation Ns = Ns Sy prompt ,
this will cause the terminal to interactively block after asking for the key.
On systems with PAM support,
.Xr pam_zfs_key 8
can automate this process during user login.
.It Fl v
Report mount progress.
.It Fl f
@ -138,3 +141,6 @@ The command can also be given a path to a ZFS file system mount point on the
system.
.El
.El
.
.Sh SEE ALSO
.Xr pam_zfs_key 8

View File

@ -805,6 +805,7 @@ don't wait.
.Xr exportfs 8 ,
.Xr mount 8 ,
.Xr net 8 ,
.Xr pam_zfs_key 8 ,
.Xr selinux 8 ,
.Xr zfs-allow 8 ,
.Xr zfs-bookmark 8 ,

View File

@ -51,4 +51,62 @@ references 0
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir}"
echo "testpass" | pamtester ${pamservice} ${username} open_session
references 1
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh available
echo "testpass" | pamtester ${pamservice} ${username} open_session
references 2
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh available
log_must pamtester ${pamservice} ${username} close_session
references 1
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh available
log_must pamtester ${pamservice} ${username} close_session
references 0
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
# Test a 'homes' with many entries
allhomes="$TESTPOOL/pam-multi-home1"
for i in {2..$PAM_MULTI_HOME_COUNT} ; do
allhomes="$allhomes,$TESTPOOL/pam-multi-home$i"
done
genconfig "homes=$allhomes runstatedir=${runstatedir}"
echo "testpass" | pamtester ${pamservice} ${username} open_session
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
references 1
log_must ismounted "$TESTPOOL/pam-multi-home$i/${username}"
keystatus_mh available $i
done
log_must pamtester ${pamservice} ${username} close_session
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
references 0
log_mustnot ismounted "$TESTPOOL/pam-multi-home$i/${username}"
keystatus_mh unavailable $i
done
log_pass "done."

View File

@ -29,28 +29,39 @@ fi
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir}"
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir}"
printf "testpass\nsecondpass\nsecondpass\n" | pamtester -v ${pamservice} ${username} chauthtok
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
echo "secondpass" | pamtester ${pamservice} ${username} open_session
references 1
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh 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 ismounted "$TESTPOOL/pam-multi-home/${username}"
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh available
log_must pamtester ${pamservice} ${username} close_session
references 0
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
log_pass "done."

View File

@ -29,28 +29,40 @@ fi
log_mustnot ismounted "$TESTPOOL/pam/${username}"
keystatus unavailable
log_mustnot ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh unavailable
genconfig "homes=$TESTPOOL/pam runstatedir=${runstatedir} nounmount"
genconfig "homes=$TESTPOOL/pam,$TESTPOOL/pam-multi-home runstatedir=${runstatedir} nounmount"
echo "testpass" | pamtester ${pamservice} ${username} open_session
references 1
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
keystatus_mh available
echo "testpass" | pamtester ${pamservice} ${username} open_session
references 2
keystatus available
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus_mh available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
log_must pamtester ${pamservice} ${username} close_session
references 1
keystatus available
log_must ismounted "$TESTPOOL/pam/${username}"
keystatus_mh available
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
log_must pamtester ${pamservice} ${username} close_session
references 0
keystatus available
keystatus_mh available
log_must ismounted "$TESTPOOL/pam/${username}"
log_must ismounted "$TESTPOOL/pam-multi-home/${username}"
log_must zfs unmount "$TESTPOOL/pam/${username}"
log_must zfs unmount "$TESTPOOL/pam-multi-home/${username}"
log_must zfs unload-key "$TESTPOOL/pam/${username}"
log_must zfs unload-key "$TESTPOOL/pam-multi-home/${username}"
log_pass "done."

View File

@ -30,6 +30,7 @@ DISK=${DISKS%% *}
create_pool $TESTPOOL "$DISK"
log_must zfs create -o mountpoint="$TESTDIR" "$TESTPOOL/pam"
log_must zfs create -o mountpoint="$TESTDIR-multi-home" "$TESTPOOL/pam-multi-home"
log_must add_group pamtestgroup
log_must add_user pamtestgroup ${username}
log_must mkdir -p "$runstatedir"
@ -37,5 +38,15 @@ log_must mkdir -p "$runstatedir"
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam/${username}"
log_must zfs unmount "$TESTPOOL/pam/${username}"
log_must zfs unload-key "$TESTPOOL/pam/${username}"
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam-multi-home/${username}"
log_must zfs unmount "$TESTPOOL/pam-multi-home/${username}"
log_must zfs unload-key "$TESTPOOL/pam-multi-home/${username}"
for i in {1..$PAM_MULTI_HOME_COUNT} ; do
log_must zfs create -o mountpoint="$TESTDIR-multi-home$i" "$TESTPOOL/pam-multi-home$i"
echo "testpass" | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase -o keylocation=prompt "$TESTPOOL/pam-multi-home$i/${username}"
log_must zfs unmount "$TESTPOOL/pam-multi-home$i/${username}"
log_must zfs unload-key "$TESTPOOL/pam-multi-home$i/${username}"
done
log_pass

View File

@ -28,11 +28,17 @@ runstatedir="${TESTDIR}_run"
pammodule="@pammoduledir@/pam_zfs_key.so"
pamservice="pam_zfs_key_test"
pamconfig="/etc/pam.d/${pamservice}"
PAM_MULTI_HOME_COUNT=20
function keystatus {
log_must [ "$(get_prop keystatus "$TESTPOOL/pam/${username}")" = "$1" ]
}
function keystatus_mh {
typeset suffix="${2:-}"
log_must [ "$(get_prop keystatus "$TESTPOOL/pam-multi-home${suffix}/${username}")" = "$1" ]
}
function genconfig {
printf '%s\trequired\tpam_permit.so\n%s\toptional\t%s\t%s\n' \
password password "$pammodule" "$1" \