diff options
-rw-r--r-- | source4/libcli/auth/credentials.c | 61 | ||||
-rw-r--r-- | source4/rpc_server/netlogon/dcerpc_netlogon.c | 141 | ||||
-rw-r--r-- | source4/rpc_server/samr/dcesrv_samr.c | 4 | ||||
-rw-r--r-- | source4/rpc_server/samr/samdb.c | 102 | ||||
-rw-r--r-- | source4/torture/rpc/netlogon.c | 2 |
5 files changed, 252 insertions, 58 deletions
diff --git a/source4/libcli/auth/credentials.c b/source4/libcli/auth/credentials.c index 7d56f26b11..5fa9d5ac4a 100644 --- a/source4/libcli/auth/credentials.c +++ b/source4/libcli/auth/credentials.c @@ -69,8 +69,6 @@ static void creds_step(struct creds_CredentialState *creds) { struct netr_Credential time_cred; - creds->sequence += 2; - DEBUG(5,("\tseed %08x:%08x\n", IVAL(creds->seed.data, 0), IVAL(creds->seed.data, 4))); @@ -98,6 +96,7 @@ static void creds_step(struct creds_CredentialState *creds) creds->seed = time_cred; } + /* DES encrypt a 16 byte password buffer using the session key */ @@ -109,6 +108,16 @@ void creds_des_encrypt(struct creds_CredentialState *creds, struct netr_Password } /* + DES decrypt a 16 byte password buffer using the session key +*/ +void creds_des_decrypt(struct creds_CredentialState *creds, struct netr_Password *pass) +{ + struct netr_Password tmp; + cred_hash3(tmp.data, pass->data, creds->session_key, 0); + *pass = tmp; +} + +/* ARCFOUR encrypt/decrypt a password buffer using the session key */ void creds_arcfour_crypt(struct creds_CredentialState *creds, char *data, size_t len) @@ -138,13 +147,30 @@ void creds_client_init(struct creds_CredentialState *creds, const uint8 machine_password[16], struct netr_Credential *initial_credential) { - creds_init(creds, client_challenge, server_challenge, machine_password); creds->sequence = time(NULL); + creds_init(creds, client_challenge, server_challenge, machine_password); *initial_credential = creds->client; } /* + step the credentials to the next element in the chain, updating the + current client and server credentials and the seed + + produce the next authenticator in the sequence ready to send to + the server +*/ +void creds_client_authenticator(struct creds_CredentialState *creds, + struct netr_Authenticator *next) +{ + creds->sequence += 2; + creds_step(creds); + + next->cred = creds->client; + next->timestamp = creds->sequence; +} + +/* check that a credentials reply from a server is correct */ BOOL creds_client_check(struct creds_CredentialState *creds, @@ -158,19 +184,6 @@ BOOL creds_client_check(struct creds_CredentialState *creds, return True; } -/* - produce the next authenticator in the sequence ready to send to - the server -*/ -void creds_client_authenticator(struct creds_CredentialState *creds, - struct netr_Authenticator *next) -{ - creds_step(creds); - - next->cred = creds->client; - next->timestamp = creds->sequence; -} - /***************************************************************** The above functions are common to the client and server interface @@ -207,3 +220,19 @@ BOOL creds_server_check(const struct creds_CredentialState *creds, return True; } +BOOL creds_server_step_check(struct creds_CredentialState *creds, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator) +{ + /* Should we check that this is increasing? */ + creds->sequence = received_authenticator->timestamp; + creds_step(creds); + if (creds_server_check(creds, &received_authenticator->cred)) { + return_authenticator->cred = creds->server; + return_authenticator->timestamp = creds->sequence; + return True; + } else { + ZERO_STRUCTP(return_authenticator); + return False; + } +} diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 87945ffef0..853a8b39e9 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -175,7 +175,7 @@ static NTSTATUS netr_ServerAuthenticateInternals(struct server_pipe_state *pipe_ account_name); if (num_records == 0) { - DEBUG(3,("Couldn't find user [%s] in passdb file.\n", + DEBUG(3,("Couldn't find user [%s] in samdb.\n", account_name)); samdb_close(sam_ctx); return NT_STATUS_NO_SUCH_USER; @@ -227,7 +227,7 @@ static NTSTATUS netr_ServerAuthenticateInternals(struct server_pipe_state *pipe_ samdb_close(sam_ctx); if (!pipe_state->creds) { - pipe_state->creds = talloc_p(mem_ctx, struct creds_CredentialState); + pipe_state->creds = talloc_p(pipe_state->mem_ctx, struct creds_CredentialState); if (!pipe_state->creds) { return NT_STATUS_NO_MEMORY; } @@ -297,6 +297,18 @@ static NTSTATUS netr_ServerAuthenticate2(struct dcesrv_call_state *dce_call, TAL r->out.negotiate_flags); } +static BOOL netr_creds_server_step_check(struct server_pipe_state *pipe_state, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator) +{ + if (!pipe_state->authenticated) { + return False; + } + return creds_server_step_check(pipe_state->creds, + received_authenticator, + return_authenticator); +} + /* netr_ServerPasswordSet @@ -314,10 +326,133 @@ static NTSTATUS netr_ServerAuthenticate2(struct dcesrv_call_state *dce_call, TAL static NTSTATUS netr_ServerPasswordSet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct netr_ServerPasswordSet *r) { + struct server_pipe_state *pipe_state = dce_call->conn->private; - DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); + void *sam_ctx; + int num_records; + int num_records_domain; + int ret; + int i; + struct ldb_message **msgs; + struct ldb_message **msgs_domain; + NTSTATUS nt_status; + struct samr_Hash newNtHash; + struct ldb_message mod, *msg_set_pw = &mod; + const char *domain_dn; + struct dom_sid *domain_sid; + + const char *attrs[] = {"objectSid", NULL + }; + + const char **domain_attrs = attrs; + ZERO_STRUCT(mod); + + if (!netr_creds_server_step_check(pipe_state, &r->in.credential, &r->out.return_authenticator)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (!pipe_state) { + DEBUG(1, ("No challange requested by client, cannot authenticate\n")); + return NT_STATUS_ACCESS_DENIED; + } + + sam_ctx = samdb_connect(); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + /* pull the user attributes */ + num_records = samdb_search(sam_ctx, mem_ctx, NULL, &msgs, attrs, + "(&(sAMAccountName=%s)(objectclass=user))", + pipe_state->account_name); + + if (num_records == 0) { + DEBUG(3,("Couldn't find user [%s] in samdb.\n", + pipe_state->account_name)); + samdb_close(sam_ctx); + return NT_STATUS_NO_SUCH_USER; + } + + if (num_records > 1) { + DEBUG(1,("Found %d records matching user [%s]\n", num_records, + pipe_state->account_name)); + samdb_close(sam_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + domain_sid = dom_sid_parse_talloc(mem_ctx, + samdb_result_string(msgs[0], + "objectSid", + NULL)); + if (!domain_sid) { + samdb_close(sam_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + sid_split_rid(domain_sid, NULL); + + /* find the domain's DN */ + num_records_domain = samdb_search(sam_ctx, mem_ctx, NULL, + &msgs_domain, domain_attrs, + "(&(objectSid=%s)(objectclass=domain))", + dom_sid_string(mem_ctx, domain_sid)); + + if (num_records_domain == 0) { + DEBUG(3,("check_sam_security: Couldn't find domain [%s] in passdb file.\n", + dom_sid_string(mem_ctx, domain_sid))); + samdb_close(sam_ctx); + return NT_STATUS_NO_SUCH_USER; + } + + if (num_records_domain > 1) { + DEBUG(1,("Found %d records matching domain [%s]\n", num_records_domain, dom_sid_string(mem_ctx, domain_sid))); + samdb_close(sam_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + domain_dn = msgs_domain[0]->dn; + + mod.dn = talloc_strdup(mem_ctx, msgs[0]->dn); + if (!mod.dn) { + samdb_close(sam_ctx); + return NT_STATUS_NO_MEMORY; + } + creds_des_decrypt(pipe_state->creds, &r->in.new_password); + + memcpy(newNtHash.hash, r->in.new_password.data, sizeof(newNtHash.hash)); + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + nt_status = samdb_set_password(sam_ctx, mem_ctx, + msgs[0]->dn, domain_dn, + msg_set_pw, + NULL, /* Don't have plaintext */ + NULL, &newNtHash, + False /* This is not considered a password change */); + + if (!NT_STATUS_IS_OK(nt_status)) { + samdb_close(sam_ctx); + return nt_status; + } + /* mark all the message elements as LDB_FLAG_MOD_REPLACE, + unless they are already marked with some other flag */ + for (i=0;i<mod.num_elements;i++) { + if (mod.elements[i].flags == 0) { + mod.elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + } + + ret = samdb_modify(sam_ctx, mem_ctx, msg_set_pw); + if (ret != 0) { + /* we really need samdb.c to return NTSTATUS */ + + samdb_close(sam_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + samdb_close(sam_ctx); + return NT_STATUS_OK; } diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c index 847b30e71c..62be2cd262 100644 --- a/source4/rpc_server/samr/dcesrv_samr.c +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -1687,7 +1687,9 @@ static NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, so the domain password policy can be used */ return samdb_set_password(a_state->sam_ctx, mem_ctx, a_state->account_dn, a_state->domain_state->domain_dn, - msg, new_pass, False /* This is a password set, not change */); + msg, new_pass, + NULL, NULL, + False /* This is a password set, not change */); } /* diff --git a/source4/rpc_server/samr/samdb.c b/source4/rpc_server/samr/samdb.c index 2489dae684..76de26222d 100644 --- a/source4/rpc_server/samr/samdb.c +++ b/source4/rpc_server/samr/samdb.c @@ -840,9 +840,12 @@ static BOOL samdb_password_complexity_ok(const char *pass) password restrictions */ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, - const char *user_dn, const char *domain_dn, - struct ldb_message *mod, const char *new_pass, - BOOL user_change) + const char *user_dn, const char *domain_dn, + struct ldb_message *mod, + const char *new_pass, + struct samr_Hash *lmNewHash, + struct samr_Hash *ntNewHash, + BOOL user_change) { const char * const user_attrs[] = { "userAccountControl", "lmPwdHistory", "ntPwdHistory", "unicodePwd", @@ -857,15 +860,14 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, uint_t userAccountControl, badPwdCount; struct samr_Hash *lmPwdHistory, *ntPwdHistory, lmPwdHash, ntPwdHash; struct samr_Hash *new_lmPwdHistory, *new_ntPwdHistory; - struct samr_Hash lmNewHash, ntNewHash; - uint_t lmPwdHistory_len, ntPwdHistory_len; + struct samr_Hash local_lmNewHash, local_ntNewHash; + int lmPwdHistory_len, ntPwdHistory_len; struct ldb_message **res; int count; time_t now = time(NULL); NTTIME now_nt; double now_double; int i; - BOOL lm_hash_ok; /* we need to know the time to compute password age */ unix_to_nt_time(&now_nt, now); @@ -897,9 +899,25 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0); minPwdAge = samdb_result_double(res[0], "minPwdAge", 0); - /* compute the new nt and lm hashes */ - lm_hash_ok = E_deshash(new_pass, lmNewHash.hash); - E_md4hash(new_pass, ntNewHash.hash); + if (new_pass) { + /* check the various password restrictions */ + if (minPwdLength > str_charnum(new_pass)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* possibly check password complexity */ + if (pwdProperties & DOMAIN_PASSWORD_COMPLEX && + !samdb_password_complexity_ok(new_pass)) { + 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 (user_change) { /* are all password changes disallowed? */ @@ -919,10 +937,10 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, /* check the immediately past password */ if (pwdHistoryLength > 0) { - if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) { + if (lmNewHash && memcmp(lmNewHash->hash, lmPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } - if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) { + if (ntNewHash && memcmp(ntNewHash->hash, ntPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } @@ -932,51 +950,45 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, ntPwdHistory_len = MIN(ntPwdHistory_len, pwdHistoryLength); if (pwdHistoryLength > 0) { - if (unicodePwd && strcmp(unicodePwd, new_pass) == 0) { + if (unicodePwd && new_pass && strcmp(unicodePwd, new_pass) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } - if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) { + if (lmNewHash && memcmp(lmNewHash->hash, lmPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } - if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) { + if (ntNewHash && memcmp(ntNewHash->hash, ntPwdHash.hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } - for (i=0;lm_hash_ok && i<lmPwdHistory_len;i++) { - if (memcmp(lmNewHash.hash, lmPwdHistory[i].hash, 16) == 0) { + for (i=0; lmNewHash && i<lmPwdHistory_len;i++) { + if (memcmp(lmNewHash->hash, lmPwdHistory[i].hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } - for (i=0;i<ntPwdHistory_len;i++) { - if (memcmp(ntNewHash.hash, ntPwdHistory[i].hash, 16) == 0) { + for (i=0; ntNewHash && i<ntPwdHistory_len;i++) { + if (memcmp(ntNewHash->hash, ntPwdHistory[i].hash, 16) == 0) { return NT_STATUS_PASSWORD_RESTRICTION; } } } - /* check the various password restrictions */ - if (minPwdLength > str_charnum(new_pass)) { - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* possibly check password complexity */ - if (pwdProperties & DOMAIN_PASSWORD_COMPLEX && - !samdb_password_complexity_ok(new_pass)) { - 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 (lm_hash_ok) { - CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "lmPwdHash", lmNewHash)); + 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")); } - CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "ntPwdHash", ntNewHash)); - if ((pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) && + 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")); + } + + if (new_pass && (pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) && (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) { CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod, "unicodePwd", new_pass)); @@ -1009,17 +1021,31 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx, for (i=0;i<MIN(pwdHistoryLength-1, ntPwdHistory_len);i++) { new_ntPwdHistory[i+1] = ntPwdHistory[i]; } - new_lmPwdHistory[0] = lmNewHash; - new_ntPwdHistory[0] = ntNewHash; + + /* Don't store 'long' passwords in the LM history, + but make sure to 'expire' one password off the other end */ + if (lmNewHash) { + new_lmPwdHistory[0] = *lmNewHash; + } else { + ZERO_STRUCT(new_lmPwdHistory[0]); + } + lmPwdHistory_len = MIN(lmPwdHistory_len + 1, pwdHistoryLength); + + if (ntNewHash) { + new_ntPwdHistory[0] = *ntNewHash; + } else { + ZERO_STRUCT(new_ntPwdHistory[0]); + } + ntPwdHistory_len = MIN(ntPwdHistory_len + 1, pwdHistoryLength); CHECK_RET(samdb_msg_add_hashes(ctx, mem_ctx, mod, "lmPwdHistory", new_lmPwdHistory, - MIN(pwdHistoryLength, lmPwdHistory_len+1))); + lmPwdHistory_len)); + CHECK_RET(samdb_msg_add_hashes(ctx, mem_ctx, mod, "ntPwdHistory", new_ntPwdHistory, - MIN(pwdHistoryLength, ntPwdHistory_len+1))); - + ntPwdHistory_len)); return NT_STATUS_OK; } diff --git a/source4/torture/rpc/netlogon.c b/source4/torture/rpc/netlogon.c index 42d79e154c..b4fbcdf01d 100644 --- a/source4/torture/rpc/netlogon.c +++ b/source4/torture/rpc/netlogon.c @@ -1045,6 +1045,8 @@ static BOOL test_SetPassword(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) password = generate_random_str(mem_ctx, 8); E_md4hash(password, r.in.new_password.data); + creds_des_encrypt(&creds, &r.in.new_password); + /* by changing the machine password twice we test the credentials chaining fully */ printf("Testing a second ServerPasswordSet on machine account\n"); |