@@ -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+
757833static int
758834zfs_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" )))
829914PAM_EXTERN int
830915pam_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" )))
888999PAM_EXTERN int
8891000pam_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+
9821096PAM_EXTERN int
9831097pam_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 );
0 commit comments