summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Tridgell <tridge@samba.org>2004-05-10 11:23:50 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 12:51:55 -0500
commit0f581e4af943a7e5dfd71d1c308ac668f287aed3 (patch)
tree023b42ba3ffcd6e3a58f6fe9065ac4c51d7b551e
parent0997d02c50dfe1bd65db89e91705166d29b5ec56 (diff)
downloadsamba-0f581e4af943a7e5dfd71d1c308ac668f287aed3.tar.gz
samba-0f581e4af943a7e5dfd71d1c308ac668f287aed3.tar.bz2
samba-0f581e4af943a7e5dfd71d1c308ac668f287aed3.zip
r623: setUserInfo level 24 (password set) now works in the SAMR server. This includes all
of the password complexity, password history and other password restrictions. (This used to be commit cb070b9084d95cf5178edbef951b75eab62b7220)
-rw-r--r--source4/lib/genrand.c2
-rw-r--r--source4/ntvfs/ipc/vfs_ipc.c5
-rw-r--r--source4/rpc_server/dcerpc_server.c13
-rw-r--r--source4/rpc_server/dcerpc_server.h3
-rw-r--r--source4/rpc_server/samr/dcesrv_samr.c73
-rw-r--r--source4/rpc_server/samr/samdb.c270
-rw-r--r--source4/torture/rpc/samr.c3
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)) {