summaryrefslogtreecommitdiff
path: root/source4
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 /source4
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)
Diffstat (limited to 'source4')
-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)) {