diff options
Diffstat (limited to 'source4/dsdb')
-rw-r--r-- | source4/dsdb/samdb/samdb.c | 317 | ||||
-rw-r--r-- | source4/dsdb/samdb/samdb.h | 1 |
2 files changed, 318 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/samdb.c b/source4/dsdb/samdb/samdb.c index 0f6e57c9cf..35de578f38 100644 --- a/source4/dsdb/samdb/samdb.c +++ b/source4/dsdb/samdb/samdb.c @@ -27,6 +27,9 @@ #include "lib/ldb/include/ldb.h" #include "lib/ldb/include/ldb_errors.h" #include "libcli/security/proto.h" +#include "auth/credentials/credentials.h" +#include "libcli/auth/proto.h" +#include "libcli/ldap/ldap.h" #include "system/time.h" #include "system/filesys.h" #include "db_wrap.h" @@ -1036,3 +1039,317 @@ failed: talloc_free(tmp_ctx); return NULL; } + +/* + check that a password is sufficiently complex +*/ +static BOOL samdb_password_complexity_ok(const char *pass) +{ + return check_password_quality(pass); +} + + + +/* + set the user password using plaintext, obeying any user or domain + password restrictions + + note that this function doesn't actually store the result in the + database, it just fills in the "mod" structure with ldb modify + elements to setup the correct change when samdb_replace() is + called. This allows the caller to combine the change with other + changes (as is needed by some of the set user info levels) + + The caller should probably have a transaction wrapping this +*/ +_PUBLIC_ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, + const struct ldb_dn *user_dn, + const struct ldb_dn *domain_dn, + struct ldb_message *mod, + const char *new_pass, + struct samr_Password *lmNewHash, + struct samr_Password *ntNewHash, + BOOL user_change, + BOOL restrictions, + enum samr_RejectReason *reject_reason, + struct samr_DomInfo1 **_dominfo) +{ + const char * const user_attrs[] = { "userAccountControl", "sambaLMPwdHistory", + "sambaNTPwdHistory", + "lmPwdHash", "ntPwdHash", + "objectSid", + "pwdLastSet", NULL }; + const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength", + "maxPwdAge", "minPwdAge", + "minPwdLength", NULL }; + NTTIME pwdLastSet; + int64_t minPwdAge; + uint_t minPwdLength, pwdProperties, pwdHistoryLength; + uint_t userAccountControl; + struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory, *lmPwdHash, *ntPwdHash; + struct samr_Password local_lmNewHash, local_ntNewHash; + int sambaLMPwdHistory_len, sambaNTPwdHistory_len; + struct dom_sid *domain_sid; + struct ldb_message **res; + int count; + time_t now = time(NULL); + NTTIME now_nt; + int i; + + /* we need to know the time to compute password age */ + unix_to_nt_time(&now_nt, now); + + /* pull all the user parameters */ + count = gendb_search_dn(ctx, mem_ctx, user_dn, &res, user_attrs); + if (count != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0); + sambaLMPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "sambaLMPwdHistory", &sambaLMPwdHistory); + sambaNTPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "sambaNTPwdHistory", &sambaNTPwdHistory); + lmPwdHash = samdb_result_hash(mem_ctx, res[0], "lmPwdHash"); + ntPwdHash = samdb_result_hash(mem_ctx, res[0], "ntPwdHash"); + pwdLastSet = samdb_result_uint64(res[0], "pwdLastSet", 0); + + if (domain_dn) { + /* pull the domain parameters */ + count = gendb_search_dn(ctx, mem_ctx, domain_dn, &res, domain_attrs); + if (count != 1) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + } else { + /* work out the domain sid, and pull the domain from there */ + domain_sid = samdb_result_sid_prefix(mem_ctx, res[0], "objectSid"); + if (domain_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + count = gendb_search(ctx, mem_ctx, NULL, &res, domain_attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, domain_sid)); + if (count != 1) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + } + + pwdProperties = samdb_result_uint(res[0], "pwdProperties", 0); + pwdHistoryLength = samdb_result_uint(res[0], "pwdHistoryLength", 0); + minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0); + minPwdAge = samdb_result_int64(res[0], "minPwdAge", 0); + + if (_dominfo) { + struct samr_DomInfo1 *dominfo; + /* on failure we need to fill in the reject reasons */ + dominfo = talloc(mem_ctx, struct samr_DomInfo1); + if (dominfo == NULL) { + return NT_STATUS_NO_MEMORY; + } + dominfo->min_password_length = minPwdLength; + dominfo->password_properties = pwdProperties; + dominfo->password_history_length = pwdHistoryLength; + dominfo->max_password_age = minPwdAge; + dominfo->min_password_age = minPwdAge; + *_dominfo = dominfo; + } + + if (new_pass) { + /* check the various password restrictions */ + if (restrictions && minPwdLength > strlen_m(new_pass)) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_TOO_SHORT; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* possibly check password complexity */ + if (restrictions && pwdProperties & DOMAIN_PASSWORD_COMPLEX && + !samdb_password_complexity_ok(new_pass)) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_COMPLEXITY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* compute the new nt and lm hashes */ + if (E_deshash(new_pass, local_lmNewHash.hash)) { + lmNewHash = &local_lmNewHash; + } + E_md4hash(new_pass, local_ntNewHash.hash); + ntNewHash = &local_ntNewHash; + } + + if (restrictions && user_change) { + /* are all password changes disallowed? */ + if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_OTHER; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* can this user change password? */ + if (userAccountControl & UF_PASSWD_CANT_CHANGE) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_OTHER; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* yes, this is a minus. The ages are in negative 100nsec units! */ + if (pwdLastSet - minPwdAge > now_nt) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_OTHER; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* check the immediately past password */ + if (pwdHistoryLength > 0) { + if (lmNewHash && lmPwdHash && memcmp(lmNewHash->hash, lmPwdHash->hash, 16) == 0) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_COMPLEXITY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + if (ntNewHash && ntPwdHash && memcmp(ntNewHash->hash, ntPwdHash->hash, 16) == 0) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_COMPLEXITY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + } + + /* check the password history */ + sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len, pwdHistoryLength); + sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len, pwdHistoryLength); + + for (i=0; lmNewHash && i<sambaLMPwdHistory_len;i++) { + if (memcmp(lmNewHash->hash, sambaLMPwdHistory[i].hash, 16) == 0) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_COMPLEXITY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + } + for (i=0; ntNewHash && i<sambaNTPwdHistory_len;i++) { + if (memcmp(ntNewHash->hash, sambaNTPwdHistory[i].hash, 16) == 0) { + if (reject_reason) { + *reject_reason = SAMR_REJECT_COMPLEXITY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + } + } + +#define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0) + + /* the password is acceptable. Start forming the new fields */ + if (new_pass) { + /* if we know the cleartext, then only set it. + * Modules in ldb will set all the appropriate + * hashes */ + CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod, + "sambaPassword", new_pass)); + } else { + /* We don't have the cleartext, so delete the old one + * and set what we have of the hashes */ + CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "sambaPassword")); + + if (lmNewHash) { + CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "lmPwdHash", lmNewHash)); + } else { + CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "lmPwdHash")); + } + + if (ntNewHash) { + CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "ntPwdHash", ntNewHash)); + } else { + CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "ntPwdHash")); + } + } + + return NT_STATUS_OK; +} + + +/* + set the user password using plaintext, obeying any user or domain + password restrictions + + This wrapper function takes a SID as input, rather than a user DN, + and actually performs the password change + +*/ +_PUBLIC_ NTSTATUS samdb_set_password_sid(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + const char *new_pass, + struct samr_Password *lmNewHash, + struct samr_Password *ntNewHash, + BOOL user_change, + BOOL restrictions, + enum samr_RejectReason *reject_reason, + struct samr_DomInfo1 **_dominfo) +{ + NTSTATUS nt_status; + struct ldb_dn *user_dn; + struct ldb_message *msg; + int ret; + + ret = ldb_transaction_start(ctx); + if (ret) { + DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(ctx))); + return NT_STATUS_TRANSACTION_ABORTED; + } + + user_dn = samdb_search_dn(ctx, mem_ctx, NULL, + "(&(objectSid=%s)(objectClass=user))", + ldap_encode_ndr_dom_sid(mem_ctx, user_sid)); + if (!user_dn) { + ldb_transaction_cancel(ctx); + DEBUG(3, ("samdb_set_password_sid: SID %s not found in samdb, returning NO_SUCH_USER\n", + dom_sid_string(mem_ctx, user_sid))); + return NT_STATUS_NO_SUCH_USER; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + ldb_transaction_cancel(ctx); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(msg, user_dn); + if (!msg->dn) { + ldb_transaction_cancel(ctx); + return NT_STATUS_NO_MEMORY; + } + + nt_status = samdb_set_password(ctx, mem_ctx, + user_dn, NULL, + msg, new_pass, + lmNewHash, ntNewHash, + user_change, /* This is a password set, not change */ + restrictions, /* run restriction tests */ + reject_reason, _dominfo); + if (!NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_cancel(ctx); + return nt_status; + } + + /* modify the samdb record */ + ret = samdb_replace(ctx, mem_ctx, msg); + if (ret != 0) { + ldb_transaction_cancel(ctx); + return NT_STATUS_ACCESS_DENIED; + } + + ret = ldb_transaction_commit(ctx); + if (ret != 0) { + DEBUG(0,("Failed to commit transaction to change password on %s: %s\n", + ldb_dn_linearize(mem_ctx, msg->dn), + ldb_errstring(ctx))); + return NT_STATUS_TRANSACTION_ABORTED; + } + return NT_STATUS_OK; +} diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 292307f423..36bff2f7ff 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -28,6 +28,7 @@ struct drsuapi_DsNameInfo1; #include "librpc/gen_ndr/security.h" #include "lib/ldb/include/ldb.h" +#include "librpc/gen_ndr/samr.h" #include "dsdb/samdb/samdb_proto.h" #endif /* __SAMDB_H__ */ |