Skip to content

Commit 07ae463

Browse files
authored
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>
1 parent 7e33476 commit 07ae463

File tree

12 files changed

+524
-90
lines changed

12 files changed

+524
-90
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
usr/lib/*/security/pam_zfs_key.so
2+
usr/share/man/man8/pam_zfs_key.8
23
usr/share/pam-configs/zfs_key

contrib/pam_zfs_key/pam_zfs_key.c

Lines changed: 190 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,82 @@ zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
754754
return (ret);
755755
}
756756

757+
/*
758+
* Callback type for foreach_dataset.
759+
* Returns 0 on success, -1 on failure.
760+
*/
761+
typedef int (*dataset_callback_t)(pam_handle_t *, zfs_key_config_t *,
762+
const char *, void *);
763+
764+
/*
765+
* Iterate over comma-separated homes prefixes and call callback for each
766+
* existing dataset. Returns number of successful callbacks, or -1 if none
767+
* succeeded.
768+
*/
769+
static int
770+
foreach_dataset(pam_handle_t *pamh, zfs_key_config_t *config,
771+
dataset_callback_t callback, void *data)
772+
{
773+
if (config->homes_prefix == NULL)
774+
return (-1);
775+
776+
/* Check if this is a comma-separated list */
777+
if (strchr(config->homes_prefix, ',') == NULL) {
778+
/* Single home - use existing logic */
779+
char *dataset = zfs_key_config_get_dataset(pamh, config);
780+
if (dataset == NULL)
781+
return (-1);
782+
int ret = callback(pamh, config, dataset, data);
783+
free(dataset);
784+
return (ret == 0 ? 1 : -1);
785+
}
786+
787+
/* Multiple homes - parse and iterate */
788+
pam_syslog(pamh, LOG_DEBUG,
789+
"processing multiple home prefixes: %s", config->homes_prefix);
790+
791+
char *homes_copy = strdup(config->homes_prefix);
792+
if (homes_copy == NULL)
793+
return (-1);
794+
795+
char *saved_prefix = config->homes_prefix;
796+
char *saveptr;
797+
char *token = strtok_r(homes_copy, ",", &saveptr);
798+
int success_count = 0;
799+
boolean_t failed = B_FALSE;
800+
801+
while (token != NULL) {
802+
/* Temporarily set homes_prefix to this single prefix */
803+
config->homes_prefix = token;
804+
char *dataset = zfs_key_config_get_dataset(pamh, config);
805+
if (dataset != NULL) {
806+
pam_syslog(pamh, LOG_DEBUG,
807+
"processing dataset '%s' for prefix '%s'",
808+
dataset, token);
809+
if (callback(pamh, config, dataset, data) == 0) {
810+
success_count++;
811+
} else {
812+
failed = B_TRUE;
813+
pam_syslog(pamh, LOG_WARNING,
814+
"operation failed for dataset '%s'",
815+
dataset);
816+
}
817+
free(dataset);
818+
} else {
819+
pam_syslog(pamh, LOG_DEBUG,
820+
"no dataset found for prefix '%s', skip", token);
821+
}
822+
token = strtok_r(NULL, ",", &saveptr);
823+
}
824+
825+
config->homes_prefix = saved_prefix;
826+
free(homes_copy);
827+
pam_syslog(pamh, LOG_DEBUG,
828+
"processed %d datasets, %s",
829+
success_count, failed ? "with failures" : "all successful");
830+
return (!failed && success_count > 0 ? success_count : -1);
831+
}
832+
757833
static int
758834
zfs_key_config_modify_session_counter(pam_handle_t *pamh,
759835
zfs_key_config_t *config, int delta)
@@ -825,6 +901,15 @@ zfs_key_config_modify_session_counter(pam_handle_t *pamh,
825901
return (counter_value);
826902
}
827903

904+
/* Callback for authentication - verify password works (noop mode) */
905+
static int
906+
auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
907+
const char *dataset, void *data)
908+
{
909+
const char *passphrase = data;
910+
return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
911+
}
912+
828913
__attribute__((visibility("default")))
829914
PAM_EXTERN int
830915
pam_sm_authenticate(pam_handle_t *pamh, int flags,
@@ -857,21 +942,14 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
857942
zfs_key_config_free(&config);
858943
return (PAM_SERVICE_ERR);
859944
}
860-
char *dataset = zfs_key_config_get_dataset(pamh, &config);
861-
if (!dataset) {
862-
pam_zfs_free();
863-
zfs_key_config_free(&config);
864-
return (PAM_SERVICE_ERR);
865-
}
866-
if (decrypt_mount(pamh, &config, dataset, token->value, B_TRUE) == -1) {
867-
free(dataset);
868-
pam_zfs_free();
869-
zfs_key_config_free(&config);
870-
return (PAM_AUTH_ERR);
871-
}
872-
free(dataset);
945+
946+
int ret = foreach_dataset(pamh, &config, auth_callback,
947+
(void *)token->value);
873948
pam_zfs_free();
874949
zfs_key_config_free(&config);
950+
if (ret < 0) {
951+
return (PAM_AUTH_ERR);
952+
}
875953
return (PAM_SUCCESS);
876954
}
877955

@@ -884,6 +962,39 @@ pam_sm_setcred(pam_handle_t *pamh, int flags,
884962
return (PAM_SUCCESS);
885963
}
886964

965+
/* Context for password change callback */
966+
typedef struct {
967+
const char *old_pass;
968+
const char *new_pass;
969+
} chauthtok_ctx_t;
970+
971+
/* Callback for password change */
972+
static int
973+
chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
974+
const char *dataset, void *data)
975+
{
976+
chauthtok_ctx_t *ctx = data;
977+
int was_loaded = is_key_loaded(pamh, dataset);
978+
if (!was_loaded) {
979+
int ret = decrypt_mount(pamh, config, dataset,
980+
ctx->old_pass, B_FALSE);
981+
if (ret == -1) {
982+
pam_syslog(pamh, LOG_ERR,
983+
"failed to load key for '%s' during "
984+
"password change", dataset);
985+
return (-1);
986+
}
987+
}
988+
int ret = change_key(pamh, dataset, ctx->new_pass);
989+
if (ret == -1) {
990+
pam_syslog(pamh, LOG_ERR,
991+
"failed to change key for dataset '%s'", dataset);
992+
}
993+
if (!was_loaded)
994+
unmount_unload(pamh, dataset, config);
995+
return (ret);
996+
}
997+
887998
__attribute__((visibility("default")))
888999
PAM_EXTERN int
8891000
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
@@ -904,34 +1015,27 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
9041015
}
9051016
const pw_password_t *old_token = pw_get(pamh,
9061017
PAM_OLDAUTHTOK, OLD_PASSWORD_VAR_NAME);
907-
{
908-
if (pam_zfs_init(pamh) != 0) {
909-
zfs_key_config_free(&config);
910-
return (PAM_SERVICE_ERR);
911-
}
912-
char *dataset = zfs_key_config_get_dataset(pamh, &config);
913-
if (!dataset) {
914-
pam_zfs_free();
915-
zfs_key_config_free(&config);
916-
return (PAM_SERVICE_ERR);
917-
}
918-
if (!old_token) {
919-
pam_syslog(pamh, LOG_ERR,
920-
"old password from PAM stack is null");
921-
free(dataset);
922-
pam_zfs_free();
923-
zfs_key_config_free(&config);
924-
return (PAM_SERVICE_ERR);
925-
}
926-
if (decrypt_mount(pamh, &config, dataset,
927-
old_token->value, B_TRUE) == -1) {
928-
pam_syslog(pamh, LOG_ERR,
929-
"old token mismatch");
930-
free(dataset);
931-
pam_zfs_free();
932-
zfs_key_config_free(&config);
933-
return (PAM_PERM_DENIED);
934-
}
1018+
1019+
if (!old_token) {
1020+
pam_syslog(pamh, LOG_ERR,
1021+
"old password from PAM stack is null");
1022+
zfs_key_config_free(&config);
1023+
return (PAM_SERVICE_ERR);
1024+
}
1025+
1026+
if (pam_zfs_init(pamh) != 0) {
1027+
zfs_key_config_free(&config);
1028+
return (PAM_SERVICE_ERR);
1029+
}
1030+
1031+
/* First verify old password works for all datasets */
1032+
int ret = foreach_dataset(pamh, &config, auth_callback,
1033+
(void *)old_token->value);
1034+
if (ret < 0) {
1035+
pam_syslog(pamh, LOG_ERR, "old token mismatch");
1036+
pam_zfs_free();
1037+
zfs_key_config_free(&config);
1038+
return (PAM_PERM_DENIED);
9351039
}
9361040

9371041
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
@@ -944,41 +1048,51 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
9441048
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
9451049
return (PAM_SERVICE_ERR);
9461050
}
947-
char *dataset = zfs_key_config_get_dataset(pamh, &config);
948-
if (!dataset) {
949-
pam_zfs_free();
950-
zfs_key_config_free(&config);
951-
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
952-
pw_clear(pamh, PASSWORD_VAR_NAME);
953-
return (PAM_SERVICE_ERR);
954-
}
955-
int was_loaded = is_key_loaded(pamh, dataset);
956-
if (!was_loaded && decrypt_mount(pamh, &config, dataset,
957-
old_token->value, B_FALSE) == -1) {
958-
free(dataset);
959-
pam_zfs_free();
960-
zfs_key_config_free(&config);
1051+
1052+
chauthtok_ctx_t ctx = {
1053+
.old_pass = old_token->value,
1054+
.new_pass = token->value
1055+
};
1056+
1057+
ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
1058+
pam_zfs_free();
1059+
zfs_key_config_free(&config);
1060+
1061+
if (ret < 0) {
9611062
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
9621063
pw_clear(pamh, PASSWORD_VAR_NAME);
9631064
return (PAM_SERVICE_ERR);
9641065
}
965-
int changed = change_key(pamh, dataset, token->value);
966-
if (!was_loaded) {
967-
unmount_unload(pamh, dataset, &config);
968-
}
969-
free(dataset);
970-
pam_zfs_free();
971-
zfs_key_config_free(&config);
1066+
9721067
if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
973-
pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
1068+
pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
9741069
return (PAM_SERVICE_ERR);
9751070
}
9761071
} else {
1072+
pam_zfs_free();
9771073
zfs_key_config_free(&config);
9781074
}
9791075
return (PAM_SUCCESS);
9801076
}
9811077

1078+
/* Callback for session open - decrypt and mount */
1079+
static int
1080+
open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1081+
const char *dataset, void *data)
1082+
{
1083+
const char *passphrase = data;
1084+
return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
1085+
}
1086+
1087+
/* Callback for session close - unmount and unload */
1088+
static int
1089+
close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1090+
const char *dataset, void *data)
1091+
{
1092+
(void) data;
1093+
return (unmount_unload(pamh, dataset, config));
1094+
}
1095+
9821096
PAM_EXTERN int
9831097
pam_sm_open_session(pam_handle_t *pamh, int flags,
9841098
int argc, const char **argv)
@@ -1016,22 +1130,15 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
10161130
zfs_key_config_free(&config);
10171131
return (PAM_SERVICE_ERR);
10181132
}
1019-
char *dataset = zfs_key_config_get_dataset(pamh, &config);
1020-
if (!dataset) {
1021-
pam_zfs_free();
1022-
zfs_key_config_free(&config);
1023-
return (PAM_SERVICE_ERR);
1024-
}
1025-
if (decrypt_mount(pamh, &config, dataset,
1026-
token->value, B_FALSE) == -1) {
1027-
free(dataset);
1028-
pam_zfs_free();
1029-
zfs_key_config_free(&config);
1030-
return (PAM_SERVICE_ERR);
1031-
}
1032-
free(dataset);
1133+
1134+
int ret = foreach_dataset(pamh, &config, open_session_callback,
1135+
(void *)token->value);
10331136
pam_zfs_free();
10341137
zfs_key_config_free(&config);
1138+
1139+
if (ret < 0) {
1140+
return (PAM_SERVICE_ERR);
1141+
}
10351142
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
10361143
return (PAM_SERVICE_ERR);
10371144
}
@@ -1071,20 +1178,15 @@ pam_sm_close_session(pam_handle_t *pamh, int flags,
10711178
zfs_key_config_free(&config);
10721179
return (PAM_SERVICE_ERR);
10731180
}
1074-
char *dataset = zfs_key_config_get_dataset(pamh, &config);
1075-
if (!dataset) {
1076-
pam_zfs_free();
1077-
zfs_key_config_free(&config);
1078-
return (PAM_SESSION_ERR);
1079-
}
1080-
if (unmount_unload(pamh, dataset, &config) == -1) {
1081-
free(dataset);
1082-
pam_zfs_free();
1181+
1182+
int ret = foreach_dataset(pamh, &config,
1183+
close_session_callback, NULL);
1184+
pam_zfs_free();
1185+
1186+
if (ret < 0) {
10831187
zfs_key_config_free(&config);
10841188
return (PAM_SESSION_ERR);
10851189
}
1086-
free(dataset);
1087-
pam_zfs_free();
10881190
}
10891191

10901192
zfs_key_config_free(&config);

man/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ endif
112112

113113
if BUILD_LINUX
114114
dist_man_MANS += \
115+
%D%/man8/pam_zfs_key.8 \
115116
%D%/man8/zfs-unzone.8 \
116117
%D%/man8/zfs-zone.8
117118
endif

0 commit comments

Comments
 (0)