diff options
-rw-r--r-- | source4/lib/genrand.c | 2 | ||||
-rw-r--r-- | source4/ntvfs/ipc/vfs_ipc.c | 5 | ||||
-rw-r--r-- | source4/rpc_server/dcerpc_server.c | 13 | ||||
-rw-r--r-- | source4/rpc_server/dcerpc_server.h | 3 | ||||
-rw-r--r-- | source4/rpc_server/samr/dcesrv_samr.c | 73 | ||||
-rw-r--r-- | source4/rpc_server/samr/samdb.c | 270 | ||||
-rw-r--r-- | source4/torture/rpc/samr.c | 3 |
7 files changed, 354 insertions, 15 deletions
diff --git a/source4/lib/genrand.c b/source4/lib/genrand.c index 1423419961..f891ac883a 100644 --- a/source4/lib/genrand.c +++ b/source4/lib/genrand.c @@ -246,7 +246,7 @@ void generate_random_buffer( unsigned char *out, int len, BOOL do_reseed_now) /* very basic password quality checker */ -static BOOL check_password_quality(const char *s) +BOOL check_password_quality(const char *s) { int has_digit=0, has_capital=0, has_lower=0; while (*s) { diff --git a/source4/ntvfs/ipc/vfs_ipc.c b/source4/ntvfs/ipc/vfs_ipc.c index cf1d21bf33..1c02e8fadc 100644 --- a/source4/ntvfs/ipc/vfs_ipc.c +++ b/source4/ntvfs/ipc/vfs_ipc.c @@ -252,6 +252,11 @@ static NTSTATUS ipc_open_generic(struct request_context *req, const char *fname, *ps = p; + /* tell the RPC layer the transport session key */ + if (req->user_ctx->vuser) { + dcesrv_set_session_key(p->dce_conn, req->user_ctx->vuser->session_key); + } + return NT_STATUS_OK; } diff --git a/source4/rpc_server/dcerpc_server.c b/source4/rpc_server/dcerpc_server.c index 463cb4fe39..22a6e1e625 100644 --- a/source4/rpc_server/dcerpc_server.c +++ b/source4/rpc_server/dcerpc_server.c @@ -270,16 +270,25 @@ NTSTATUS dcesrv_endpoint_connect(struct dcesrv_context *dce_ctx, (*p)->partial_input = data_blob(NULL, 0); (*p)->auth_state.ntlmssp_state = NULL; (*p)->auth_state.auth_info = NULL; + (*p)->session_key = data_blob(NULL, 0); return NT_STATUS_OK; } /* + set the transport level session key +*/ +void dcesrv_set_session_key(struct dcesrv_connection *p, DATA_BLOB key) +{ + p->session_key = data_blob_talloc(p->mem_ctx, key.data, key.length); +} + +/* search and connect to a dcerpc endpoint */ NTSTATUS dcesrv_endpoint_search_connect(struct dcesrv_context *dce_ctx, - const struct dcesrv_ep_description *ep_description, - struct dcesrv_connection **dce_conn_p) + const struct dcesrv_ep_description *ep_description, + struct dcesrv_connection **dce_conn_p) { NTSTATUS status; const struct dcesrv_endpoint *ep; diff --git a/source4/rpc_server/dcerpc_server.h b/source4/rpc_server/dcerpc_server.h index 3afcc25576..5f61167906 100644 --- a/source4/rpc_server/dcerpc_server.h +++ b/source4/rpc_server/dcerpc_server.h @@ -130,6 +130,9 @@ struct dcesrv_connection { /* the current authentication state */ struct dcesrv_auth auth_state; + + /* the transport level session key, if any */ + DATA_BLOB session_key; }; diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c index 6412192127..1d64fad5ba 100644 --- a/source4/rpc_server/samr/dcesrv_samr.c +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -1628,6 +1628,35 @@ static NTSTATUS samr_QueryUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CT } +/* + set password via a samr_CryptPassword buffer + this will in the 'msg' with modify operations that will update the user + password when applied +*/ +static NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, + struct samr_account_state *state, TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct samr_CryptPassword *pwbuf) +{ + char new_pass[512]; + uint32 new_pass_len; + DATA_BLOB session_key = dce_call->conn->session_key; + + SamOEMhashBlob(pwbuf->data, 516, &session_key); + + if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), + &new_pass_len, STR_UNICODE)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + return samdb_set_password(state->sam_ctx, mem_ctx, + state->basedn, state->domain_state->basedn, + msg, new_pass); +} + /* samr_SetUserInfo */ @@ -1638,6 +1667,7 @@ static NTSTATUS samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX struct samr_account_state *state; struct ldb_message mod, *msg = &mod; int i, ret; + NTSTATUS status = NT_STATUS_OK; DCESRV_PULL_HANDLE(h, r->in.handle, SAMR_HANDLE_USER); @@ -1703,30 +1733,51 @@ static NTSTATUS samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX case 21: #define IFSET(bit) if (bit & r->in.info->info21.fields_present) - IFSET(SAMR_FIELD_NAME) SET_STRING(msg, info21.full_name.name, "displayName"); - IFSET(SAMR_FIELD_DESCRIPTION) SET_STRING(msg, info21.description.name, "description"); - IFSET(SAMR_FIELD_COMMENT) SET_STRING(msg, info21.comment.name, "comment"); - IFSET(SAMR_FIELD_LOGON_SCRIPT) SET_STRING(msg, info21.logon_script.name, "scriptPath"); - IFSET(SAMR_FIELD_PROFILE) SET_STRING(msg, info21.profile.name, "profilePath"); - IFSET(SAMR_FIELD_WORKSTATION) SET_STRING(msg, info21.workstations.name, "userWorkstations"); - IFSET(SAMR_FIELD_LOGON_HOURS) SET_LHOURS(msg, info21.logon_hours, "logonHours"); - IFSET(SAMR_FIELD_CALLBACK) SET_STRING(msg, info21.callback.name, "userParameters"); - IFSET(SAMR_FIELD_COUNTRY_CODE) SET_UINT(msg, info21.country_code, "countryCode"); - IFSET(SAMR_FIELD_CODE_PAGE) SET_UINT(msg, info21.code_page, "codePage"); + IFSET(SAMR_FIELD_NAME) + SET_STRING(msg, info21.full_name.name, "displayName"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info21.description.name, "description"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info21.comment.name, "comment"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info21.logon_script.name, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE) + SET_STRING(msg, info21.profile.name, "profilePath"); + IFSET(SAMR_FIELD_WORKSTATION) + SET_STRING(msg, info21.workstations.name, "userWorkstations"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info21.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_CALLBACK) + SET_STRING(msg, info21.callback.name, "userParameters"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info21.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info21.code_page, "codePage"); break; + /* the set password levels are handled separately */ + case 24: + status = samr_set_password(dce_call, state, mem_ctx, msg, + &r->in.info->info24.password); + break; + + default: /* many info classes are not valid for SetUserInfo */ return NT_STATUS_INVALID_INFO_CLASS; } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ for (i=0;i<mod.num_elements;i++) { mod.elements[i].flags = LDB_FLAG_MOD_REPLACE; } /* modify the samdb record */ - ret = samdb_modify(state->sam_ctx, mem_ctx, &mod); + ret = samdb_modify(state->sam_ctx, mem_ctx, msg); if (ret != 0) { /* we really need samdb.c to return NTSTATUS */ return NT_STATUS_UNSUCCESSFUL; diff --git a/source4/rpc_server/samr/samdb.c b/source4/rpc_server/samr/samdb.c index c6a85176da..5f59ce1883 100644 --- a/source4/rpc_server/samr/samdb.c +++ b/source4/rpc_server/samr/samdb.c @@ -372,6 +372,52 @@ NTTIME samdb_result_force_pwd_change(void *ctx, TALLOC_CTX *mem_ctx, } /* + pull a samr_Hash structutre from a result set. +*/ +struct samr_Hash samdb_result_hash(struct ldb_message *msg, const char *attr) +{ + struct samr_Hash hash; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + ZERO_STRUCT(hash); + if (val) { + memcpy(hash.hash, val->data, MIN(val->length, 16)); + } + return hash; +} + +/* + pull an array of samr_Hash structutres from a result set. +*/ +uint_t samdb_result_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr, struct samr_Hash **hashes) +{ + uint_t count = 0; + const struct ldb_val *val = ldb_msg_find_ldb_val(msg, attr); + int i; + + *hashes = NULL; + if (!val) { + return 0; + } + count = val->length / 16; + if (count == 0) { + return 0; + } + + *hashes = talloc_array_p(mem_ctx, struct samr_Hash, count); + if (! *hashes) { + return 0; + } + + for (i=0;i<count;i++) { + memcpy((*hashes)[i].hash, (i*16)+(char *)val->data, 16); + } + + return count; +} + + +/* pull a samr_LogonHours structutre from a result set. */ struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_message *msg, const char *attr) @@ -591,6 +637,55 @@ int samdb_msg_add_uint(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, } /* + add a double element to a message (actually a large integer) +*/ +int samdb_msg_add_double(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, double v) +{ + const char *s = talloc_asprintf(mem_ctx, "%.0f", v); + return samdb_msg_add_string(ctx, mem_ctx, msg, attr_name, s); +} + +/* + add a samr_Hash element to a message +*/ +int samdb_msg_add_hash(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct samr_Hash hash) +{ + struct samdb_context *sam_ctx = ctx; + struct ldb_val val; + val.data = talloc(mem_ctx, 16); + val.length = 16; + if (!val.data) { + return -1; + } + memcpy(val.data, hash.hash, 16); + ldb_set_alloc(sam_ctx->ldb, samdb_alloc, mem_ctx); + return ldb_msg_add_value(sam_ctx->ldb, msg, attr_name, &val); +} + +/* + add a samr_Hash array to a message +*/ +int samdb_msg_add_hashes(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char *attr_name, struct samr_Hash *hashes, uint_t count) +{ + struct samdb_context *sam_ctx = ctx; + struct ldb_val val; + int i; + val.data = talloc(mem_ctx, count*16); + val.length = count*16; + if (!val.data) { + return -1; + } + for (i=0;i<count;i++) { + memcpy(i*16 + (char *)val.data, hashes[i].hash, 16); + } + ldb_set_alloc(sam_ctx->ldb, samdb_alloc, mem_ctx); + return ldb_msg_add_value(sam_ctx->ldb, msg, attr_name, &val); +} + +/* add a acct_flags element to a message */ int samdb_msg_add_acct_flags(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg, @@ -682,3 +777,178 @@ int samdb_modify(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg) ldb_set_alloc(sam_ctx->ldb, samdb_alloc, mem_ctx); return ldb_modify(sam_ctx->ldb, msg); } + +/* + 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 +*/ +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) +{ + const char * const user_attrs[] = { "userAccountControl", "lmPwdHistory", + "ntPwdHistory", "unicodePwd", + "lmPwdHash", "ntPwdHash", "badPwdCount", + NULL }; + const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength", + "maxPwdAge", "minPwdAge", + "minPwdLength", "pwdLastSet", NULL }; + const char *unicodePwd; + double minPwdAge, pwdLastSet; + uint_t minPwdLength, pwdProperties, pwdHistoryLength; + 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 ldb_message **res; + int count; + time_t now = time(NULL); + NTTIME now_nt; + double now_double; + int i; + + /* we need to know the time to compute password age */ + unix_to_nt_time(&now_nt, now); + now_double = nttime_to_double_nt(now_nt); + + /* pull all the user parameters */ + count = samdb_search(ctx, mem_ctx, NULL, &res, user_attrs, "dn=%s", user_dn); + if (count != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + unicodePwd = samdb_result_string(res[0], "unicodePwd", NULL); + userAccountControl = samdb_result_uint(res[0], "userAccountControl", 0); + badPwdCount = samdb_result_uint(res[0], "badPwdCount", 0); + lmPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "lmPwdHistory", &lmPwdHistory); + ntPwdHistory_len = samdb_result_hashes(mem_ctx, res[0], + "ntPwdHistory", &ntPwdHistory); + lmPwdHash = samdb_result_hash(res[0], "lmPwdHash"); + ntPwdHash = samdb_result_hash(res[0], "ntPwdHash"); + pwdLastSet = samdb_result_double(res[0], "pwdLastSet", 0); + + /* pull the domain parameters */ + count = samdb_search(ctx, mem_ctx, NULL, &res, domain_attrs, "dn=%s", domain_dn); + if (count != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + 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_double(res[0], "minPwdAge", 0); + + /* are all password changes disallowed? */ + if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* can this user change password? */ + if (userAccountControl & UF_PASSWD_CANT_CHANGE) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* check the various password restrictions */ + if (minPwdLength > str_charnum(new_pass)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* yes, this is a minus. The ages are in negative 100nsec units! */ + if (pwdLastSet - minPwdAge > now_double) { + 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 */ + E_deshash(new_pass, lmNewHash.hash); + E_md4hash(new_pass, ntNewHash.hash); + + /* check the immediately past password */ + if (pwdHistoryLength > 0 && + (memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0 || + memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0)) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + /* check the password history */ + lmPwdHistory_len = MIN(lmPwdHistory_len, pwdHistoryLength); + ntPwdHistory_len = MIN(ntPwdHistory_len, pwdHistoryLength); + + if (pwdHistoryLength > 0) { + if (strcmp(unicodePwd, new_pass) == 0 || + memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0 || + memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + } + + for (i=0;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) { + 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 */ + CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "lmPwdHash", lmNewHash)); + CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "ntPwdHash", ntNewHash)); + + if ((pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) && + (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) { + CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod, + "unicodePwd", new_pass)); + } + + if (pwdHistoryLength > 0) { + new_lmPwdHistory = talloc_array_p(mem_ctx, struct samr_Hash, + pwdHistoryLength); + if (!new_lmPwdHistory) { + return NT_STATUS_NO_MEMORY; + } + new_ntPwdHistory = talloc_array_p(mem_ctx, struct samr_Hash, + pwdHistoryLength); + if (!new_ntPwdHistory) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<MIN(pwdHistoryLength-1, lmPwdHistory_len);i++) { + new_lmPwdHistory[i+1] = lmPwdHistory[i]; + } + for (i=0;i<MIN(pwdHistoryLength-1, ntPwdHistory_len);i++) { + new_ntPwdHistory[i+1] = ntPwdHistory[i]; + } + new_lmPwdHistory[0] = lmNewHash; + new_ntPwdHistory[0] = ntNewHash; + + CHECK_RET(samdb_msg_add_hashes(ctx, mem_ctx, mod, + "lmPwdHistory", + new_lmPwdHistory, + MIN(pwdHistoryLength, lmPwdHistory_len+1))); + CHECK_RET(samdb_msg_add_hashes(ctx, mem_ctx, mod, + "ntPwdHistory", + new_ntPwdHistory, + MIN(pwdHistoryLength, ntPwdHistory_len+1))); + } + + CHECK_RET(samdb_msg_add_double(ctx, mem_ctx, mod, "pwdLastSet", now_double)); + + return NT_STATUS_OK; +} diff --git a/source4/torture/rpc/samr.c b/source4/torture/rpc/samr.c index dc6a1a27dd..4e11cd3995 100644 --- a/source4/torture/rpc/samr.c +++ b/source4/torture/rpc/samr.c @@ -350,7 +350,8 @@ static BOOL test_SetUserPass(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, s.in.level = 24; encode_pw_buffer(u.info24.password.data, newpass, STR_UNICODE); - u.info24.pw_len = strlen(newpass); + /* w2k3 ignores this length */ + u.info24.pw_len = str_charnum(newpass)*2; status = dcerpc_fetch_session_key(p, &session_key); if (!NT_STATUS_IS_OK(status)) { |