Browse Source

Resolves: #216983
Summary: Make random password generation work with policies
Changes: 1) Generate a password that meets the current password syntax rules.
2) Report errors when Min8Bit is set or MinCategories > 4

Noriko Hosoi 19 years ago
parent
commit
99ce798de4
1 changed files with 213 additions and 13 deletions
  1. 213 13
      ldap/servers/slapd/passwd_extop.c

+ 213 - 13
ldap/servers/slapd/passwd_extop.c

@@ -205,33 +205,223 @@ static int passwd_modify_userpassword(Slapi_Entry *targetEntry, const char *newP
 	return ret;
 }
 
-/* Generate a new random password */
-static int passwd_modify_generate_passwd(char **genpasswd)
+/* Generate a new, basic random password */
+static int passwd_modify_generate_basic_passwd( int passlen, char **genpasswd )
 {
-	unsigned char data[ LDAP_EXTOP_PASSMOD_RANDOM_BYTES ];
-	char enc[ 1 + LDIF_BASE64_LEN( LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN ) ];
+	unsigned char *data = NULL;
+	char *enc = NULL;
+	int datalen = LDAP_EXTOP_PASSMOD_RANDOM_BYTES;
+	int enclen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN + 1;
 
-	if (genpasswd == NULL) {
+	if ( genpasswd == NULL ) {
 		return LDAP_OPERATIONS_ERROR;
 	}
 
+	if ( passlen > 0 ) {
+		datalen = passlen * 3 / 4 + 1;
+		enclen = datalen * 4; /* allocate the large enough space */
+	}
+
+	data = (unsigned char *)slapi_ch_calloc( datalen, 1 );
+	enc = (char *)slapi_ch_calloc( enclen, 1 );
+
 	/* get random bytes from NSS */
-	PK11_GenerateRandom( data, LDAP_EXTOP_PASSMOD_RANDOM_BYTES );
+	PK11_GenerateRandom( data, datalen );
 
 	/* b64 encode the random bytes to get a password made up
-         * of printable characters. ldif_base64_encode() will
+	 * of printable characters. ldif_base64_encode() will
 	 * zero-terminate the string */
-	(void)ldif_base64_encode( data, enc, LDAP_EXTOP_PASSMOD_RANDOM_BYTES, -1 );
+	(void)ldif_base64_encode( data, enc, passlen, -1 );
+
+	/* This will get freed by the caller */
+	*genpasswd = slapi_ch_malloc( 1 + passlen );
+
+	/* trim the password to the proper length */
+	PL_strncpyz( *genpasswd, enc, passlen + 1 );
+
+	slapi_ch_free( (void **)&data );
+	slapi_ch_free_string( &enc );
+
+	return LDAP_SUCCESS;
+}
+
+/* Generate a new, password-policy-based random password */
+static int passwd_modify_generate_policy_passwd( passwdPolicy *pwpolicy,
+											char **genpasswd, char **errMesg )
+{
+	unsigned char *data = NULL;
+	int passlen = 0;
+	int tmplen = 0;
+	enum {
+		idx_minuppers = 0,
+		idx_minlowers,
+		idx_mindigits,
+		idx_minspecials,
+		idx_end
+	}; 
+	int my_policy[idx_end];
+	struct {
+		int chr_start;
+		int chr_range;
+	} chr_table[] = { /* NOTE: the above enum order */
+		{ 65, 26 }, /* [ A - Z ] */
+		{ 97, 26 }, /* [ a - z ] */
+		{ 48, 10 }, /* [ 0 - 9 ] */
+		{ 58,  7 }  /* [ : - @ ] */
+	};
+#define gen_policy_pw_getchar(n, idx) \
+( chr_table[(idx)].chr_start + (n) % chr_table[(idx)].chr_range )
+	int i;
+
+	if ( genpasswd == NULL ) {
+		return LDAP_OPERATIONS_ERROR;
+	}
+
+	my_policy[idx_mindigits] = pwpolicy->pw_mindigits;
+	my_policy[idx_minuppers] = pwpolicy->pw_minuppers;
+	my_policy[idx_minlowers] = pwpolicy->pw_minlowers;
+	my_policy[idx_minspecials] = pwpolicy->pw_minspecials;
+
+	/* if only minalphas is set, divide it into minuppers and minlowers. */
+	if ( pwpolicy->pw_minalphas > 0 &&
+		 ( my_policy[idx_minuppers] == 0 && my_policy[idx_minlowers] == 0 )) {
+		unsigned int x = (unsigned int)time(NULL);
+		my_policy[idx_minuppers] = slapi_rand_r(&x) % pwpolicy->pw_minalphas;
+		my_policy[idx_minlowers] = pwpolicy->pw_minalphas - my_policy[idx_minuppers];
+	}
+
+	if ( pwpolicy->pw_mincategories ) {
+		int categories = 0;
+		for ( i = 0; i < idx_end; i++ ) {
+			if ( my_policy[i] > 0 ) {
+				categories++;
+			}
+		}
+		if ( pwpolicy->pw_mincategories > categories ) {
+			categories = pwpolicy->pw_mincategories;
+			for ( i = 0; i < idx_end; i++ ) {
+				if ( my_policy[i] == 0 ) {
+					/* force to add a policy to match the pw_mincategories */
+					my_policy[i] = 1; 
+				}
+				if ( --categories == 0 ) {
+					break;
+				}
+			}
+			if ( categories > 0 ) {
+				/* password generator does not support passwordMin8Bit */
+    			LDAPDebug( LDAP_DEBUG_ANY,
+					"Unable to generate a password that meets the current "
+					"password syntax rules.  A minimum categories setting "
+					"of %d is not supported with random password generation.\n",
+					pwpolicy->pw_mincategories, 0, 0 );
+				*errMesg = "Unable to generate new random password.  Please contact the Administrator.";
+				return LDAP_CONSTRAINT_VIOLATION;
+			}
+		}
+	}
+
+	/* get the password length */
+	tmplen = 0;
+	for ( i = 0; i < idx_end; i++ ) {
+		tmplen += my_policy[i];
+	}
+	passlen = tmplen;
+	if ( passlen < pwpolicy->pw_minlength ) {
+		passlen = pwpolicy->pw_minlength;
+	}
+	if ( passlen < LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN ) {
+		passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
+	}
+
+	data = (unsigned char *)slapi_ch_calloc( passlen, 1 );
+
+	/* get random bytes from NSS */
+	PK11_GenerateRandom( data, passlen );
+
+	/* if password length is longer the sum of my_policy's,
+	   let them share the burden */
+	if ( passlen > tmplen ) {
+		unsigned int x = (unsigned int)time(NULL);
+		int delta = passlen - tmplen;
+		for ( i = 0; i < delta; i++ ) {
+			my_policy[(x = slapi_rand_r(&x)) % idx_end]++;
+		}
+	}
 
 	/* This will get freed by the caller */
-	*genpasswd = slapi_ch_malloc( 1 + LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN);
+	*genpasswd = slapi_ch_malloc( 1 + passlen );
+
+	for ( i = 0; i < passlen; i++ ) {
+		int idx = data[i] % idx_end;
+		int isfirst = 1;
+		/* choose a category based on the random value */
+		while ( my_policy[idx] <= 0 ) {
+			if ( ++idx == idx_end ) {
+				idx = 0; /* if no rule is found, default is uppercase */
+				if ( !isfirst ) {
+					break;
+				}
+				isfirst = 0;
+			}
+		}
+		my_policy[idx]--;
+		(*genpasswd)[i] = gen_policy_pw_getchar(data[i], idx);
+	}
+	(*genpasswd)[passlen] = '\0';
 
-	/* trim the password to the proper length. */
-	PL_strncpyz( *genpasswd, enc, 1 + LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN );
+	slapi_ch_free( (void **)&data );
 
 	return LDAP_SUCCESS;
 }
 
+/* Generate a new random password */
+static int passwd_modify_generate_passwd( passwdPolicy *pwpolicy,
+										  char **genpasswd, char **errMesg )
+{
+	int minalphalen = 0;
+	int passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
+	int rval = LDAP_SUCCESS;
+
+	if ( genpasswd == NULL ) {
+		return LDAP_OPERATIONS_ERROR;
+	}
+	if ( pwpolicy->pw_min8bit > 0 ) {
+    	LDAPDebug( LDAP_DEBUG_ANY, "Unable to generate a password that meets "
+						"the current password syntax rules.  8-bit syntax "
+						"restrictions are not supported with random password "
+						"generation.\n", 0, 0, 0 );
+		*errMesg = "Unable to generate new random password.  Please contact the Administrator.";
+		return LDAP_CONSTRAINT_VIOLATION;
+	}
+
+	if ( pwpolicy->pw_minalphas || pwpolicy->pw_minuppers || 
+		 pwpolicy->pw_minlowers || pwpolicy->pw_mindigits ||
+		 pwpolicy->pw_minspecials || pwpolicy->pw_maxrepeats ||
+		 pwpolicy->pw_mincategories > 2 ) {
+		rval = passwd_modify_generate_policy_passwd( pwpolicy, genpasswd,
+													 errMesg );
+	} else {
+		/* find out the minimum length to fulfill the passwd policy 
+		   requirements */
+		minalphalen = pwpolicy->pw_minuppers + pwpolicy->pw_minlowers;
+		if ( minalphalen < pwpolicy->pw_minalphas ) {
+			minalphalen = pwpolicy->pw_minalphas;
+		}
+		passlen = minalphalen + pwpolicy->pw_mindigits + 
+				  pwpolicy->pw_minspecials + pwpolicy->pw_min8bit;
+		if ( passlen < pwpolicy->pw_minlength ) {
+			passlen = pwpolicy->pw_minlength;
+		}
+		if ( passlen < LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN ) {
+			passlen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN;
+		}
+		rval = passwd_modify_generate_basic_passwd( passlen, genpasswd );
+	}
+
+	return rval;
+}
+
 
 /* Password Modify Extended operation plugin function */
 int
@@ -415,12 +605,22 @@ parse_req_done:
 	 * a random one and return it to the user in a response.
 	 */
 	if (newPasswd == NULL || *newPasswd == '\0') {
+		passwdPolicy *pwpolicy;
+		int rval;
 		/* Do a free of newPasswd here to be safe, otherwise we may leak 1 byte */
 		slapi_ch_free_string( &newPasswd );
 
+
+		pwpolicy = new_passwdPolicy( pb, dn );
+
 		/* Generate a new password */
-		if (passwd_modify_generate_passwd( &newPasswd ) != LDAP_SUCCESS) {
-			errMesg = "Error generating new password.\n";
+		rval = passwd_modify_generate_passwd( pwpolicy, &newPasswd, &errMesg );
+
+		delete_passwdPolicy(&pwpolicy);
+
+		if (rval != LDAP_SUCCESS) {
+			if (!errMesg)
+				errMesg = "Error generating new password.\n";
 			rc = LDAP_OPERATIONS_ERROR;
 			goto free_and_return;
 		}