From fc8e3ffb5f261e7efdcbcef46b1f13c3b5599730 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 10 May 2010 17:36:54 +0200 Subject: s4:password_hash - Implement password restrictions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on the Patch from Matthias Dieter Wallnöfer . metze --- source4/dsdb/samdb/ldb_modules/password_hash.c | 195 +++++++++++++++++++++++++ 1 file changed, 195 insertions(+) (limited to 'source4') diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 3433fdbe17..08aa545937 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -116,6 +116,7 @@ struct setup_password_fields_io { const char *sAMAccountName; const char *user_principal_name; bool is_computer; + uint32_t restrictions; } u; /* new credentials and old given credentials */ @@ -1462,6 +1463,179 @@ static int setup_password_fields(struct setup_password_fields_io *io) return LDB_SUCCESS; } +static int check_password_restrictions(struct setup_password_fields_io *io) +{ + struct ldb_context *ldb; + int ret; + enum samr_ValidationStatus stat; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* First check the old password is correct, for password changes */ + if (!io->ac->pwd_reset && !io->ac->change_old_pw_checked) { + /* we need to old nt or lm hash given by the client */ + if (!io->og.nt_hash && !io->og.lm_hash) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "You need to provide the old password " + "in order to change your password!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (io->og.nt_hash) { + if (!io->o.nt_hash) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "There's no old nt_hash, which is needed " + "in order to change your password!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* The password modify through the NT hash is encouraged + and has no problems at all */ + if (memcmp(io->og.nt_hash->hash, io->o.nt_hash->hash, 16) != 0) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "The old password specified doesn't match!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } else if (io->og.lm_hash) { + struct loadparm_context *lp_ctx = + (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"); + + if (!lp_lanman_auth(lp_ctx)) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "The password change through the LM hash is deactivated!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (!io->o.lm_hash) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "There's no old lm_hash, which is needed " + "in order to change your password!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (memcmp(io->og.lm_hash->hash, io->o.lm_hash->hash, 16) != 0) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "The old password specified doesn't match!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + } + + if (io->u.restrictions == 0) { + /* FIXME: Is this right? */ + return LDB_SUCCESS; + } + + /* + * Fundamental password checks done by the call "samdb_check_password". + * It is also in use by "dcesrv_samr_ValidatePassword". + */ + stat = samdb_check_password(io->n.cleartext_utf8, + io->ac->status->domain_data.pwdProperties, + io->ac->status->domain_data.minPwdLength); + switch (stat) { + case SAMR_VALIDATION_STATUS_SUCCESS: + /* perfect -> proceed! */ + break; + + case SAMR_VALIDATION_STATUS_PWD_TOO_SHORT: + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "the password is too short. It should be equal or longer than %i characters!", + io->ac->status->domain_data.minPwdLength); + + io->ac->status->reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT; + return LDB_ERR_CONSTRAINT_VIOLATION; + + case SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH: + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "the password does not meet the complexity criterias!"); + io->ac->status->reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX; + + return LDB_ERR_CONSTRAINT_VIOLATION; + + default: + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "the password doesn't fit by a certain reason!"); + + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (io->ac->pwd_reset) { + return LDB_SUCCESS; + } + + if (io->n.nt_hash) { + uint32_t i; + + /* checks the NT hash password history */ + for (i = 0; i < io->o.nt_history_len; i++) { + ret = memcmp(io->n.nt_hash, io->o.nt_history[i].hash, 16); + if (ret == 0) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "the password was already used (in history)!"); + + io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; + + return LDB_ERR_CONSTRAINT_VIOLATION; + } + } + } + + if (io->n.lm_hash) { + uint32_t i; + + /* checks the LM hash password history */ + for (i = 0; i < io->o.lm_history_len; i++) { + ret = memcmp(io->n.nt_hash, io->o.lm_history[i].hash, 16); + if (ret == 0) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "the password was already used (in history)!"); + + io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; + + return LDB_ERR_CONSTRAINT_VIOLATION; + } + } + } + + /* are all password changes disallowed? */ + if (io->ac->status->domain_data.pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "password changes disabled!"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* can this user change the password? */ + if (io->u.userAccountControl & UF_PASSWD_CANT_CHANGE) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "password can't be changed on this account!"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* Password minimum age: yes, this is a minus. The ages are in negative 100nsec units! */ + if (io->u.pwdLastSet - io->ac->status->domain_data.minPwdAge > io->g.last_set) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "password is too young to change!"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + return LDB_SUCCESS; +} + static int setup_io(struct ph_context *ac, const struct ldb_message *orig_msg, const struct ldb_message *searched_msg, @@ -1498,6 +1672,17 @@ static int setup_io(struct ph_context *ac, return LDB_ERR_CONSTRAINT_VIOLATION; } + /* Only non-trust accounts have restrictions (possibly this test is the + * wrong way around, but we like to be restrictive if possible */ + io->u.restrictions = !(io->u.userAccountControl + & (UF_INTERDOMAIN_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT + | UF_SERVER_TRUST_ACCOUNT)); + + if ((io->u.userAccountControl & UF_PASSWD_NOTREQD) != 0) { + /* see [MS-ADTS] 2.2.15 */ + io->u.restrictions = 0; + } + ret = samdb_msg_find_old_and_new_ldb_val(orig_msg, "userPassword", &io->n.cleartext_utf8, &io->og.cleartext_utf8); if (ret != LDB_SUCCESS) { @@ -2113,6 +2298,11 @@ static int password_hash_add_do_add(struct ph_context *ac) return ret; } + ret = check_password_restrictions(&io); + if (ret != LDB_SUCCESS) { + return ret; + } + if (io.g.nt_hash) { ret = samdb_msg_add_hash(ldb, ac, msg, "unicodePwd", io.g.nt_hash); @@ -2508,6 +2698,11 @@ static int password_hash_mod_do_mod(struct ph_context *ac) return ret; } + ret = check_password_restrictions(&io); + if (ret != LDB_SUCCESS) { + return ret; + } + /* make sure we replace all the old attributes */ ret = ldb_msg_add_empty(msg, "unicodePwd", LDB_FLAG_MOD_REPLACE, NULL); ret = ldb_msg_add_empty(msg, "dBCSPwd", LDB_FLAG_MOD_REPLACE, NULL); -- cgit