Skip to content

Commit 3c5b105

Browse files
committed
Added support for multiple homes in pam_zfs_key module
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 <[email protected]>
1 parent 648a9a2 commit 3c5b105

File tree

12 files changed

+504
-90
lines changed

12 files changed

+504
-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: 201 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pam_syslog(pam_handle_t *pamh, int loglevel, const char *fmt, ...)
6868

6969
#include <sys/mman.h>
7070

71+
/* Maximum number of home prefixes supported in comma-separated homes= */
72+
#define MAX_HOMES_PREFIXES 16
73+
7174
static const char PASSWORD_VAR_NAME[] = "pam_zfs_key_authtok";
7275
static const char OLD_PASSWORD_VAR_NAME[] = "pam_zfs_key_oldauthtok";
7376

@@ -754,6 +757,90 @@ zfs_key_config_get_dataset(pam_handle_t *pamh, zfs_key_config_t *config)
754757
return (ret);
755758
}
756759

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

915+
/* Callback for authentication - verify password works (noop mode) */
916+
static int
917+
auth_callback(pam_handle_t *pamh, zfs_key_config_t *config,
918+
const char *dataset, void *data)
919+
{
920+
const char *passphrase = data;
921+
return (decrypt_mount(pamh, config, dataset, passphrase, B_TRUE));
922+
}
923+
828924
__attribute__((visibility("default")))
829925
PAM_EXTERN int
830926
pam_sm_authenticate(pam_handle_t *pamh, int flags,
@@ -857,21 +953,14 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags,
857953
zfs_key_config_free(&config);
858954
return (PAM_SERVICE_ERR);
859955
}
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);
956+
957+
int ret = foreach_dataset(pamh, &config, auth_callback,
958+
(void *)token->value);
873959
pam_zfs_free();
874960
zfs_key_config_free(&config);
961+
if (ret < 0) {
962+
return (PAM_AUTH_ERR);
963+
}
875964
return (PAM_SUCCESS);
876965
}
877966

@@ -884,6 +973,39 @@ pam_sm_setcred(pam_handle_t *pamh, int flags,
884973
return (PAM_SUCCESS);
885974
}
886975

976+
/* Context for password change callback */
977+
typedef struct {
978+
const char *old_pass;
979+
const char *new_pass;
980+
} chauthtok_ctx_t;
981+
982+
/* Callback for password change */
983+
static int
984+
chauthtok_callback(pam_handle_t *pamh, zfs_key_config_t *config,
985+
const char *dataset, void *data)
986+
{
987+
chauthtok_ctx_t *ctx = data;
988+
int was_loaded = is_key_loaded(pamh, dataset);
989+
if (!was_loaded) {
990+
int ret = decrypt_mount(pamh, config, dataset,
991+
ctx->old_pass, B_FALSE);
992+
if (ret == -1) {
993+
pam_syslog(pamh, LOG_ERR,
994+
"failed to load key for '%s' during "
995+
"password change", dataset);
996+
return (-1);
997+
}
998+
}
999+
int ret = change_key(pamh, dataset, ctx->new_pass);
1000+
if (ret == -1) {
1001+
pam_syslog(pamh, LOG_ERR,
1002+
"failed to change key for dataset '%s'", dataset);
1003+
}
1004+
if (!was_loaded)
1005+
unmount_unload(pamh, dataset, config);
1006+
return (ret);
1007+
}
1008+
8871009
__attribute__((visibility("default")))
8881010
PAM_EXTERN int
8891011
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
@@ -904,34 +1026,27 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
9041026
}
9051027
const pw_password_t *old_token = pw_get(pamh,
9061028
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-
}
1029+
1030+
if (!old_token) {
1031+
pam_syslog(pamh, LOG_ERR,
1032+
"old password from PAM stack is null");
1033+
zfs_key_config_free(&config);
1034+
return (PAM_SERVICE_ERR);
1035+
}
1036+
1037+
if (pam_zfs_init(pamh) != 0) {
1038+
zfs_key_config_free(&config);
1039+
return (PAM_SERVICE_ERR);
1040+
}
1041+
1042+
/* First verify old password works for all datasets */
1043+
int ret = foreach_dataset(pamh, &config, auth_callback,
1044+
(void *)old_token->value);
1045+
if (ret < 0) {
1046+
pam_syslog(pamh, LOG_ERR, "old token mismatch");
1047+
pam_zfs_free();
1048+
zfs_key_config_free(&config);
1049+
return (PAM_PERM_DENIED);
9351050
}
9361051

9371052
if ((flags & PAM_UPDATE_AUTHTOK) != 0) {
@@ -944,41 +1059,51 @@ pam_sm_chauthtok(pam_handle_t *pamh, int flags,
9441059
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
9451060
return (PAM_SERVICE_ERR);
9461061
}
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);
1062+
1063+
chauthtok_ctx_t ctx = {
1064+
.old_pass = old_token->value,
1065+
.new_pass = token->value
1066+
};
1067+
1068+
ret = foreach_dataset(pamh, &config, chauthtok_callback, &ctx);
1069+
pam_zfs_free();
1070+
zfs_key_config_free(&config);
1071+
1072+
if (ret < 0) {
9611073
pw_clear(pamh, OLD_PASSWORD_VAR_NAME);
9621074
pw_clear(pamh, PASSWORD_VAR_NAME);
9631075
return (PAM_SERVICE_ERR);
9641076
}
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);
1077+
9721078
if (pw_clear(pamh, OLD_PASSWORD_VAR_NAME) == -1 ||
973-
pw_clear(pamh, PASSWORD_VAR_NAME) == -1 || changed == -1) {
1079+
pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
9741080
return (PAM_SERVICE_ERR);
9751081
}
9761082
} else {
1083+
pam_zfs_free();
9771084
zfs_key_config_free(&config);
9781085
}
9791086
return (PAM_SUCCESS);
9801087
}
9811088

1089+
/* Callback for session open - decrypt and mount */
1090+
static int
1091+
open_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1092+
const char *dataset, void *data)
1093+
{
1094+
const char *passphrase = data;
1095+
return (decrypt_mount(pamh, config, dataset, passphrase, B_FALSE));
1096+
}
1097+
1098+
/* Callback for session close - unmount and unload */
1099+
static int
1100+
close_session_callback(pam_handle_t *pamh, zfs_key_config_t *config,
1101+
const char *dataset, void *data)
1102+
{
1103+
(void) data;
1104+
return (unmount_unload(pamh, dataset, config));
1105+
}
1106+
9821107
PAM_EXTERN int
9831108
pam_sm_open_session(pam_handle_t *pamh, int flags,
9841109
int argc, const char **argv)
@@ -1016,22 +1141,15 @@ pam_sm_open_session(pam_handle_t *pamh, int flags,
10161141
zfs_key_config_free(&config);
10171142
return (PAM_SERVICE_ERR);
10181143
}
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);
1144+
1145+
int ret = foreach_dataset(pamh, &config, open_session_callback,
1146+
(void *)token->value);
10331147
pam_zfs_free();
10341148
zfs_key_config_free(&config);
1149+
1150+
if (ret < 0) {
1151+
return (PAM_SERVICE_ERR);
1152+
}
10351153
if (pw_clear(pamh, PASSWORD_VAR_NAME) == -1) {
10361154
return (PAM_SERVICE_ERR);
10371155
}
@@ -1071,20 +1189,15 @@ pam_sm_close_session(pam_handle_t *pamh, int flags,
10711189
zfs_key_config_free(&config);
10721190
return (PAM_SERVICE_ERR);
10731191
}
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();
1192+
1193+
int ret = foreach_dataset(pamh, &config,
1194+
close_session_callback, NULL);
1195+
pam_zfs_free();
1196+
1197+
if (ret < 0) {
10831198
zfs_key_config_free(&config);
10841199
return (PAM_SESSION_ERR);
10851200
}
1086-
free(dataset);
1087-
pam_zfs_free();
10881201
}
10891202

10901203
zfs_key_config_free(&config);

man/Makefile.am

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

112112
if BUILD_LINUX
113113
dist_man_MANS += \
114+
%D%/man8/pam_zfs_key.8 \
114115
%D%/man8/zfs-unzone.8 \
115116
%D%/man8/zfs-zone.8
116117
endif

0 commit comments

Comments
 (0)