@@ -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+
7174static const char PASSWORD_VAR_NAME [] = "pam_zfs_key_authtok" ;
7275static 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+
757844static int
758845zfs_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" )))
829925PAM_EXTERN int
830926pam_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" )))
8881010PAM_EXTERN int
8891011pam_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+
9821107PAM_EXTERN int
9831108pam_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 );
0 commit comments