diff options
author | Andrew Bartlett <abartlet@samba.org> | 2008-10-16 12:48:16 +1100 |
---|---|---|
committer | Andrew Bartlett <abartlet@samba.org> | 2008-10-16 12:48:16 +1100 |
commit | 7c88ea8aadfc2be0726cbe555543cfab8804c470 (patch) | |
tree | 56f84c94f69e06880c6ad416117f0a1d7e7f5882 | |
parent | fc54ca014b21b655b643697475c6a0c05e773fc4 (diff) | |
download | samba-7c88ea8aadfc2be0726cbe555543cfab8804c470.tar.gz samba-7c88ea8aadfc2be0726cbe555543cfab8804c470.tar.bz2 samba-7c88ea8aadfc2be0726cbe555543cfab8804c470.zip |
Create a 'straight paper path' for UTF16 passwords.
This uses a virtual attribute 'clearTextPassword' (name chosen to
match references in MS-SAMR) that contains the length-limited blob
containing an allegidly UTF16 password. This ensures we do no
validation or filtering of the password before we get a chance to MD4
it. We can then do the required munging into UTF8, and in future
implement the rules Microsoft has provided us with for invalid inputs.
All layers in the process now deal with the strings as length-limited
inputs, incluing the krb5 string2key calls.
This commit also includes a small change to samdb_result_passwords()
to ensure that LM passwords are not returned to the application logic
if LM authentication is disabled.
The objectClass module has been modified to allow the
clearTextPassword attribute to pass down the stack.
Andrew Bartlett
-rw-r--r-- | source4/auth/ntlm/auth_sam.c | 2 | ||||
-rw-r--r-- | source4/dsdb/common/util.c | 88 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/objectclass.c | 12 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/password_hash.c | 195 | ||||
-rw-r--r-- | source4/kdc/kpasswdd.c | 37 | ||||
-rw-r--r-- | source4/libcli/auth/smbencrypt.c | 33 | ||||
-rw-r--r-- | source4/librpc/idl/drsblobs.idl | 2 | ||||
-rw-r--r-- | source4/rpc_server/lsa/dcesrv_lsa.c | 21 | ||||
-rw-r--r-- | source4/rpc_server/netlogon/dcerpc_netlogon.c | 27 | ||||
-rw-r--r-- | source4/rpc_server/samr/samr_password.c | 88 |
10 files changed, 322 insertions, 183 deletions
diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c index ac36d4f1ef..78429106f6 100644 --- a/source4/auth/ntlm/auth_sam.c +++ b/source4/auth/ntlm/auth_sam.c @@ -248,7 +248,7 @@ static NTSTATUS authsam_authenticate(struct auth_context *auth_context, } } - nt_status = samdb_result_passwords(mem_ctx, msgs[0], &lm_pwd, &nt_pwd); + nt_status = samdb_result_passwords(mem_ctx, auth_context->lp_ctx, msgs[0], &lm_pwd, &nt_pwd); NT_STATUS_NOT_OK_RETURN(nt_status); nt_status = authsam_password_ok(auth_context, mem_ctx, diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 5b93efb316..0a87c5ffe1 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -26,6 +26,7 @@ #include "ldb.h" #include "ldb_errors.h" #include "../lib/util/util_ldb.h" +#include "../lib/crypto/crypto.h" #include "dsdb/samdb/samdb.h" #include "libcli/security/security.h" #include "librpc/gen_ndr/ndr_security.h" @@ -571,7 +572,7 @@ uint_t samdb_result_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg, return count; } -NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct ldb_message *msg, +NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct ldb_message *msg, struct samr_Password **lm_pwd, struct samr_Password **nt_pwd) { struct samr_Password *lmPwdHash, *ntPwdHash; @@ -587,14 +588,21 @@ NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct ldb_message *msg, } } if (lm_pwd) { - int num_lm; - num_lm = samdb_result_hashes(mem_ctx, msg, "dBCSPwd", &lmPwdHash); - if (num_lm == 0) { - *lm_pwd = NULL; - } else if (num_lm > 1) { - return NT_STATUS_INTERNAL_DB_CORRUPTION; + /* Ensure that if we have turned off LM + * authentication, that we never use the LM hash, even + * if we store it */ + if (lp_lanman_auth(lp_ctx)) { + int num_lm; + num_lm = samdb_result_hashes(mem_ctx, msg, "dBCSPwd", &lmPwdHash); + if (num_lm == 0) { + *lm_pwd = NULL; + } else if (num_lm > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + *lm_pwd = &lmPwdHash[0]; + } } else { - *lm_pwd = &lmPwdHash[0]; + *lm_pwd = NULL; } } return NT_STATUS_OK; @@ -1531,7 +1539,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, struct ldb_dn *user_dn, struct ldb_dn *domain_dn, struct ldb_message *mod, - const char *new_pass, + const DATA_BLOB *new_password, struct samr_Password *lmNewHash, struct samr_Password *ntNewHash, bool user_change, @@ -1632,40 +1640,47 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, *_dominfo = dominfo; } - if (restrictions && new_pass) { - + if (restrictions && new_password) { + char *new_pass; + /* check the various password restrictions */ - if (restrictions && minPwdLength > strlen_m(new_pass)) { + if (restrictions && minPwdLength > utf16_len_n(new_password->data, (new_password->length / 2))) { if (reject_reason) { *reject_reason = SAMR_REJECT_TOO_SHORT; } return NT_STATUS_PASSWORD_RESTRICTION; } + + /* Create the NT hash */ + mdfour(local_ntNewHash.hash, new_password->data, new_password->length); - /* possibly check password complexity */ - if (restrictions && pwdProperties & DOMAIN_PASSWORD_COMPLEX && - !samdb_password_complexity_ok(new_pass)) { - if (reject_reason) { - *reject_reason = SAMR_REJECT_COMPLEXITY; + ntNewHash = &local_ntNewHash; + + /* Only check complexity if we can convert it at all. Assuming unconvertable passwords are 'strong' */ + if (convert_string_talloc(mem_ctx, lp_iconv_convenience(ldb_get_opaque(ctx, "loadparm")), + CH_UTF16, CH_UNIX, + new_password->data, new_password->length, + (void **)&new_pass) != -1) { + + + /* 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; } - return NT_STATUS_PASSWORD_RESTRICTION; - } - - /* compute the new nt and lm hashes */ - if (E_deshash(new_pass, local_lmNewHash.hash)) { - lmNewHash = &local_lmNewHash; - } - if (!E_md4hash(new_pass, local_ntNewHash.hash)) { - /* If we can't convert this password to UCS2, then we should not accept it */ - if (reject_reason) { - *reject_reason = SAMR_REJECT_OTHER; + + /* compute the new lm hashes (for checking history - case insenitivly!) */ + if (E_deshash(new_pass, local_lmNewHash.hash)) { + lmNewHash = &local_lmNewHash; } - return NT_STATUS_PASSWORD_RESTRICTION; + } - ntNewHash = &local_ntNewHash; } - if (user_change) { + if (restrictions && user_change) { /* are all password changes disallowed? */ if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { if (reject_reason) { @@ -1731,16 +1746,15 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, #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. + if (new_password) { + /* if we know the cleartext UTF16 password, then set it. * Modules in ldb will set all the appropriate * hashes */ - CHECK_RET(samdb_msg_add_string(ctx, mem_ctx, mod, - "userPassword", new_pass)); + CHECK_RET(ldb_msg_add_value(mod, "clearTextPassword", new_password, NULL)); } 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, "userPassword")); + CHECK_RET(samdb_msg_add_delete(ctx, mem_ctx, mod, "clearTextPassword")); if (lmNewHash) { CHECK_RET(samdb_msg_add_hash(ctx, mem_ctx, mod, "dBCSPwd", lmNewHash)); @@ -1769,7 +1783,7 @@ NTSTATUS samdb_set_password(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, */ NTSTATUS samdb_set_password_sid(struct ldb_context *ctx, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, - const char *new_pass, + const DATA_BLOB *new_pass, struct samr_Password *lmNewHash, struct samr_Password *ntNewHash, bool user_change, diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c index e610c358f6..7d00851792 100644 --- a/source4/dsdb/samdb/ldb_modules/objectclass.c +++ b/source4/dsdb/samdb/ldb_modules/objectclass.c @@ -382,11 +382,17 @@ static int fix_attributes(struct ldb_context *ldb, const struct dsdb_schema *sch int i; for (i=0; i < msg->num_elements; i++) { const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, msg->elements[i].name); + /* Add in a very special case for 'clearTextPassword', + * which is used for internal processing only, and is + * not presented in the schema */ if (!attribute) { - ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name); - return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE; + if (strcasecmp(msg->elements[i].name, "clearTextPassword") != 0) { + ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name); + return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE; + } + } else { + msg->elements[i].name = attribute->lDAPDisplayName; } - msg->elements[i].name = attribute->lDAPDisplayName; } return LDB_SUCCESS; diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index e36de3c5c4..c4451d1355 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -109,7 +109,8 @@ struct setup_password_fields_io { /* new credentials */ struct { - const char *cleartext; + const struct ldb_val *cleartext_utf8; + const struct ldb_val *cleartext_utf16; struct samr_Password *nt_hash; struct samr_Password *lm_hash; } n; @@ -144,6 +145,9 @@ struct setup_password_fields_io { } g; }; +/* Get the NT hash, and fill it in as an entry in the password history, + and specify it into io->g.nt_hash */ + static int setup_nt_fields(struct setup_password_fields_io *io) { uint32_t i; @@ -181,6 +185,9 @@ static int setup_nt_fields(struct setup_password_fields_io *io) return LDB_SUCCESS; } +/* Get the LANMAN hash, and fill it in as an entry in the password history, + and specify it into io->g.lm_hash */ + static int setup_lm_fields(struct setup_password_fields_io *io) { uint32_t i; @@ -220,6 +227,10 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) Principal *salt_principal; krb5_salt salt; krb5_keyblock key; + krb5_data cleartext_data; + + cleartext_data.data = io->n.cleartext_utf8->data; + cleartext_data.length = io->n.cleartext_utf8->length; /* Many, many thanks to lukeh@padl.com for this * algorithm, described in his Nov 10 2004 mail to @@ -314,11 +325,11 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of * the salt and the cleartext password */ - krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context, - ENCTYPE_AES256_CTS_HMAC_SHA1_96, - io->n.cleartext, - salt, - &key); + krb5_ret = krb5_string_to_key_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + cleartext_data, + salt, + &key); if (krb5_ret) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_kerberos_keys: " @@ -339,11 +350,11 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) * create ENCTYPE_AES128_CTS_HMAC_SHA1_96 key out of * the salt and the cleartext password */ - krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context, - ENCTYPE_AES128_CTS_HMAC_SHA1_96, - io->n.cleartext, - salt, - &key); + krb5_ret = krb5_string_to_key_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, + cleartext_data, + salt, + &key); if (krb5_ret) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_kerberos_keys: " @@ -364,11 +375,11 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) * create ENCTYPE_DES_CBC_MD5 key out of * the salt and the cleartext password */ - krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context, - ENCTYPE_DES_CBC_MD5, - io->n.cleartext, - salt, - &key); + krb5_ret = krb5_string_to_key_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_DES_CBC_MD5, + cleartext_data, + salt, + &key); if (krb5_ret) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_kerberos_keys: " @@ -389,11 +400,11 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) * create ENCTYPE_DES_CBC_CRC key out of * the salt and the cleartext password */ - krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context, - ENCTYPE_DES_CBC_CRC, - io->n.cleartext, - salt, - &key); + krb5_ret = krb5_string_to_key_data_salt(io->smb_krb5_context->krb5_context, + ENCTYPE_DES_CBC_CRC, + cleartext_data, + salt, + &key); if (krb5_ret) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_kerberos_keys: " @@ -648,7 +659,6 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, DATA_BLOB dns_domain; DATA_BLOB dns_domain_l; DATA_BLOB dns_domain_u; - DATA_BLOB cleartext; DATA_BLOB digest; DATA_BLOB delim; DATA_BLOB backslash; @@ -929,8 +939,6 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, dns_domain_l = data_blob_string_const(io->domain->dns_domain); dns_domain_u = data_blob_string_const(io->domain->realm); - cleartext = data_blob_string_const(io->n.cleartext); - digest = data_blob_string_const("Digest"); delim = data_blob_string_const(":"); @@ -956,7 +964,7 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, MD5Update(&md5, wdigest[i].realm->data, wdigest[i].realm->length); } MD5Update(&md5, delim.data, delim.length); - MD5Update(&md5, cleartext.data, cleartext.length); + MD5Update(&md5, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length); MD5Final(pdb->hashes[i].hash, &md5); } @@ -1011,7 +1019,7 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) ZERO_STRUCT(zero16); ZERO_STRUCT(names); - if (!io->n.cleartext) { + if (!io->n.cleartext_utf8) { /* * when we don't have a cleartext password * we can't setup a supplementalCredential value @@ -1193,7 +1201,7 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) if (pc) { *nc = "CLEARTEXT"; - pcb.cleartext = io->n.cleartext; + pcb.cleartext = *io->n.cleartext_utf16; ndr_err = ndr_push_struct_blob(&pcb_blob, io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), @@ -1285,58 +1293,97 @@ static int setup_password_fields(struct setup_password_fields_io *io) { bool ok; int ret; - + ssize_t converted_pw_len; + /* * refuse the change if someone want to change the cleartext * and supply his own hashes at the same time... */ - if (io->n.cleartext && (io->n.nt_hash || io->n.lm_hash)) { + if ((io->n.cleartext_utf8 || io->n.cleartext_utf16) && (io->n.nt_hash || io->n.lm_hash)) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_password_fields: " "it's only allowed to set the cleartext password or the password hashes"); return LDB_ERR_UNWILLING_TO_PERFORM; } - - if (io->n.cleartext) { - struct samr_Password *hash; - - hash = talloc(io->ac, struct samr_Password); - if (!hash) { + + if (io->n.cleartext_utf8 && io->n.cleartext_utf16) { + ldb_asprintf_errstring(io->ac->module->ldb, + "setup_password_fields: " + "it's only allowed to set the cleartext password as userPassword or clearTextPasssword, not both at once"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + if (io->n.cleartext_utf8) { + char **cleartext_utf16_str; + struct ldb_val *cleartext_utf16_blob; + io->n.cleartext_utf16 = cleartext_utf16_blob = talloc(io->ac, struct ldb_val); + if (!io->n.cleartext_utf16) { ldb_oom(io->ac->module->ldb); return LDB_ERR_OPERATIONS_ERROR; } - - /* compute the new nt hash */ - ok = E_md4hash(io->n.cleartext, hash->hash); - if (ok) { - io->n.nt_hash = hash; - } else { + converted_pw_len = convert_string_talloc(io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), + CH_UTF8, CH_UTF16, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_utf16_str); + if (converted_pw_len == -1) { ldb_asprintf_errstring(io->ac->module->ldb, "setup_password_fields: " - "failed to generate nthash from cleartext password"); + "failed to generate UTF16 password from cleartext UTF8 password"); + return LDB_ERR_OPERATIONS_ERROR; + } + *cleartext_utf16_blob = data_blob_const(cleartext_utf16_str, converted_pw_len); + } else if (io->n.cleartext_utf16) { + char *cleartext_utf8_str; + struct ldb_val *cleartext_utf8_blob; + io->n.cleartext_utf8 = cleartext_utf8_blob = talloc(io->ac, struct ldb_val); + if (!io->n.cleartext_utf8) { + ldb_oom(io->ac->module->ldb); return LDB_ERR_OPERATIONS_ERROR; } + converted_pw_len = convert_string_talloc(io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), + CH_UTF16, CH_UTF8, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length, + (void **)&cleartext_utf8_str); + if (converted_pw_len == -1) { + /* We can't bail out entirely, as these unconvertable passwords are frustratingly valid */ + io->n.cleartext_utf8 = NULL; + talloc_free(cleartext_utf8_blob); + } + *cleartext_utf8_blob = data_blob_const(cleartext_utf8_str, converted_pw_len); } - - if (io->n.cleartext) { - struct samr_Password *hash; - - hash = talloc(io->ac, struct samr_Password); - if (!hash) { + if (io->n.cleartext_utf16) { + struct samr_Password *nt_hash; + nt_hash = talloc(io->ac, struct samr_Password); + if (!nt_hash) { ldb_oom(io->ac->module->ldb); return LDB_ERR_OPERATIONS_ERROR; } + io->n.nt_hash = nt_hash; - /* compute the new lm hash */ - ok = E_deshash(io->n.cleartext, hash->hash); - if (ok) { - io->n.lm_hash = hash; - } else { - talloc_free(hash->hash); - } + /* compute the new nt hash */ + mdfour(nt_hash->hash, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length); } - if (io->n.cleartext) { + if (io->n.cleartext_utf8) { + struct samr_Password *lm_hash; + char *cleartext_unix; + converted_pw_len = convert_string_talloc(io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), + CH_UTF8, CH_UNIX, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_unix); + if (converted_pw_len != -1) { + lm_hash = talloc(io->ac, struct samr_Password); + if (!lm_hash) { + ldb_oom(io->ac->module->ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* compute the new lm hash. */ + ok = E_deshash((char *)cleartext_unix, lm_hash->hash); + if (ok) { + io->n.lm_hash = lm_hash; + } else { + talloc_free(lm_hash->hash); + } + } + ret = setup_kerberos_keys(io); if (ret != 0) { return ret; @@ -1560,6 +1607,7 @@ static int password_hash_add(struct ldb_module *module, struct ldb_request *req) { struct ph_context *ac; struct ldb_message_element *sambaAttr; + struct ldb_message_element *clearTextPasswordAttr; struct ldb_message_element *ntAttr; struct ldb_message_element *lmAttr; int ret; @@ -1591,6 +1639,7 @@ static int password_hash_add(struct ldb_module *module, struct ldb_request *req) * or LM hashes, then we don't need to make any changes. */ sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword"); + clearTextPasswordAttr = ldb_msg_find_element(req->op.mod.message, "clearTextPassword"); ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd"); lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd"); @@ -1611,6 +1660,10 @@ static int password_hash_add(struct ldb_module *module, struct ldb_request *req) ldb_set_errstring(module->ldb, "mupltiple values for userPassword not allowed!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; } + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values > 1) { + ldb_set_errstring(module->ldb, "mupltiple values for clearTextPassword not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } if (ntAttr && (ntAttr->num_values > 1)) { ldb_set_errstring(module->ldb, "mupltiple values for unicodePwd not allowed!\n"); @@ -1626,6 +1679,11 @@ static int password_hash_add(struct ldb_module *module, struct ldb_request *req) return LDB_ERR_CONSTRAINT_VIOLATION; } + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values == 0) { + ldb_set_errstring(module->ldb, "clearTextPassword must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (ntAttr && (ntAttr->num_values == 0)) { ldb_set_errstring(module->ldb, "unicodePwd must have a value!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; @@ -1687,12 +1745,14 @@ static int password_hash_add_do_add(struct ph_context *ac) { io.u.user_principal_name = samdb_result_string(msg, "userPrincipalName", NULL); io.u.is_computer = ldb_msg_check_string_attribute(msg, "objectClass", "computer"); - io.n.cleartext = samdb_result_string(msg, "userPassword", NULL); + io.n.cleartext_utf8 = ldb_msg_find_ldb_val(msg, "userPassword"); + io.n.cleartext_utf16 = ldb_msg_find_ldb_val(msg, "clearTextPassword"); io.n.nt_hash = samdb_result_hash(io.ac, msg, "unicodePwd"); io.n.lm_hash = samdb_result_hash(io.ac, msg, "dBCSPwd"); /* remove attributes */ - if (io.n.cleartext) ldb_msg_remove_attr(msg, "userPassword"); + if (io.n.cleartext_utf8) ldb_msg_remove_attr(msg, "userPassword"); + if (io.n.cleartext_utf16) ldb_msg_remove_attr(msg, "clearTextPassword"); if (io.n.nt_hash) ldb_msg_remove_attr(msg, "unicodePwd"); if (io.n.lm_hash) ldb_msg_remove_attr(msg, "dBCSPwd"); ldb_msg_remove_attr(msg, "pwdLastSet"); @@ -1772,6 +1832,7 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r { struct ph_context *ac; struct ldb_message_element *sambaAttr; + struct ldb_message_element *clearTextAttr; struct ldb_message_element *ntAttr; struct ldb_message_element *lmAttr; struct ldb_message *msg; @@ -1802,13 +1863,16 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r } sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword"); + clearTextAttr = ldb_msg_find_element(req->op.mod.message, "clearTextPassword"); ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd"); lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd"); - /* If no part of this touches the userPassword OR unicodePwd and/or dBCSPwd, then we don't - * need to make any changes. For password changes/set there should - * be a 'delete' or a 'modify' on this attribute. */ - if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) { + /* If no part of this touches the userPassword OR + * clearTextPassword OR unicodePwd and/or dBCSPwd, then we + * don't need to make any changes. For password changes/set + * there should be a 'delete' or a 'modify' on this + * attribute. */ + if ((!sambaAttr) && (!clearTextAttr) && (!ntAttr) && (!lmAttr)) { return ldb_next_request(module, req); } @@ -1817,6 +1881,9 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r if (sambaAttr && (sambaAttr->num_values > 1)) { return LDB_ERR_CONSTRAINT_VIOLATION; } + if (clearTextAttr && (clearTextAttr->num_values > 1)) { + return LDB_ERR_CONSTRAINT_VIOLATION; + } if (ntAttr && (ntAttr->num_values > 1)) { return LDB_ERR_CONSTRAINT_VIOLATION; } @@ -1839,6 +1906,7 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r /* - remove any modification to the password from the first commit * we will make the real modification later */ if (sambaAttr) ldb_msg_remove_attr(msg, "userPassword"); + if (clearTextAttr) ldb_msg_remove_attr(msg, "clearTextPassword"); if (ntAttr) ldb_msg_remove_attr(msg, "unicodePwd"); if (lmAttr) ldb_msg_remove_attr(msg, "dBCSPwd"); @@ -2028,7 +2096,8 @@ static int password_hash_mod_do_mod(struct ph_context *ac) { io.u.user_principal_name = samdb_result_string(searched_msg, "userPrincipalName", NULL); io.u.is_computer = ldb_msg_check_string_attribute(searched_msg, "objectClass", "computer"); - io.n.cleartext = samdb_result_string(orig_msg, "userPassword", NULL); + io.n.cleartext_utf8 = ldb_msg_find_ldb_val(orig_msg, "userPassword"); + io.n.cleartext_utf16 = ldb_msg_find_ldb_val(orig_msg, "clearTextPassword"); io.n.nt_hash = samdb_result_hash(io.ac, orig_msg, "unicodePwd"); io.n.lm_hash = samdb_result_hash(io.ac, orig_msg, "dBCSPwd"); diff --git a/source4/kdc/kpasswdd.c b/source4/kdc/kpasswdd.c index d662844c4e..1336b0157e 100644 --- a/source4/kdc/kpasswdd.c +++ b/source4/kdc/kpasswdd.c @@ -175,7 +175,7 @@ static bool kpasswd_make_pwchange_reply(struct kdc_server *kdc, static bool kpasswdd_change_password(struct kdc_server *kdc, TALLOC_CTX *mem_ctx, struct auth_session_info *session_info, - const char *password, + const DATA_BLOB *password, DATA_BLOB *reply) { NTSTATUS status; @@ -219,6 +219,8 @@ static bool kpasswd_process_request(struct kdc_server *kdc, DATA_BLOB *reply) { struct auth_session_info *session_info; + ssize_t pw_len; + if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security, &session_info))) { return kpasswdd_make_error_reply(kdc, mem_ctx, @@ -230,12 +232,20 @@ static bool kpasswd_process_request(struct kdc_server *kdc, switch (version) { case KRB5_KPASSWD_VERS_CHANGEPW: { - char *password = talloc_strndup(mem_ctx, (const char *)input->data, input->length); - if (!password) { + DATA_BLOB password; + pw_len = convert_string_talloc(mem_ctx, lp_iconv_convenience(kdc->task->lp_ctx), + CH_UTF8, CH_UTF16, + (const char *)input->data, + input->length, + (void **)&password.data); + + if (pw_len == -1) { return false; } + password.length = pw_len; + return kpasswdd_change_password(kdc, mem_ctx, session_info, - password, reply); + &password, reply); break; } case KRB5_KPASSWD_VERS_SETPW: @@ -248,7 +258,7 @@ static bool kpasswd_process_request(struct kdc_server *kdc, krb5_context context = kdc->smb_krb5_context->krb5_context; ChangePasswdDataMS chpw; - char *password; + DATA_BLOB password; krb5_principal principal; char *set_password_on_princ; @@ -271,13 +281,18 @@ static bool kpasswd_process_request(struct kdc_server *kdc, reply); } - password = talloc_strndup(mem_ctx, - (const char *)chpw.newpasswd.data, - chpw.newpasswd.length); - if (!password) { + pw_len = convert_string_talloc(mem_ctx, lp_iconv_convenience(kdc->task->lp_ctx), + CH_UTF8, CH_UTF16, + (const char *)chpw.newpasswd.data, + chpw.newpasswd.length, + (void **)&password.data); + if (pw_len == -1) { free_ChangePasswdDataMS(&chpw); return false; } + + password.length = pw_len; + if ((chpw.targname && !chpw.targrealm) || (!chpw.targname && chpw.targrealm)) { return kpasswdd_make_error_reply(kdc, mem_ctx, @@ -306,7 +321,7 @@ static bool kpasswd_process_request(struct kdc_server *kdc, } else { free_ChangePasswdDataMS(&chpw); return kpasswdd_change_password(kdc, mem_ctx, session_info, - password, reply); + &password, reply); } free_ChangePasswdDataMS(&chpw); @@ -371,7 +386,7 @@ static bool kpasswd_process_request(struct kdc_server *kdc, /* Admin password set */ status = samdb_set_password(samdb, mem_ctx, set_password_on_dn, NULL, - msg, password, NULL, NULL, + msg, &password, NULL, NULL, false, /* this is not a user password change */ &reject_reason, &dominfo); } diff --git a/source4/libcli/auth/smbencrypt.c b/source4/libcli/auth/smbencrypt.c index 3af7e45002..6eb8767140 100644 --- a/source4/libcli/auth/smbencrypt.c +++ b/source4/libcli/auth/smbencrypt.c @@ -536,9 +536,40 @@ bool decode_pw_buffer(uint8_t in_buffer[516], char *new_pwrd, #ifdef DEBUG_PASSWORD DEBUG(100,("decode_pw_buffer: new_pwrd: ")); dump_data(100, (const uint8_t *)new_pwrd, converted_pw_len); - DEBUG(100,("multibyte len:%d\n", converted_pw_len)); + DEBUG(100,("multibyte len:%d\n", (int)converted_pw_len)); DEBUG(100,("original char len:%d\n", byte_len/2)); #endif return true; } + +/*********************************************************** + decode a password buffer + *new_pw_size is the length in bytes of the extracted unicode password +************************************************************/ +bool extract_pw_from_buffer(TALLOC_CTX *mem_ctx, + uint8_t in_buffer[516], DATA_BLOB *new_pass) +{ + int byte_len=0; + + /* The length of the new password is in the last 4 bytes of the data buffer. */ + + byte_len = IVAL(in_buffer, 512); + +#ifdef DEBUG_PASSWORD + dump_data(100, in_buffer, 516); +#endif + + /* Password cannot be longer than the size of the password buffer */ + if ( (byte_len < 0) || (byte_len > 512)) { + return false; + } + + *new_pass = data_blob_talloc(mem_ctx, &in_buffer[512 - byte_len], byte_len); + + if (!*new_pass->data) { + return false; + } + + return true; +} diff --git a/source4/librpc/idl/drsblobs.idl b/source4/librpc/idl/drsblobs.idl index 4274d2000a..087f0c982d 100644 --- a/source4/librpc/idl/drsblobs.idl +++ b/source4/librpc/idl/drsblobs.idl @@ -332,7 +332,7 @@ interface drsblobs { ); typedef [public] struct { - [flag(STR_NOTERM|NDR_REMAINING)] string cleartext; + [flag(NDR_REMAINING)] DATA_BLOB cleartext; } package_PrimaryCLEARTEXTBlob; void decode_PrimaryCLEARTEXT( diff --git a/source4/rpc_server/lsa/dcesrv_lsa.c b/source4/rpc_server/lsa/dcesrv_lsa.c index 9cda7d0d89..4c596f1f03 100644 --- a/source4/rpc_server/lsa/dcesrv_lsa.c +++ b/source4/rpc_server/lsa/dcesrv_lsa.c @@ -968,19 +968,14 @@ static NTSTATUS dcesrv_lsa_CreateTrustedDomain_base(struct dcesrv_call_state *dc mem_ctx, msg_user, "unicodePwd", &auth_struct.incoming.current[i]->AuthInfo.nt4owf.password); } else if (auth_struct.incoming.current[i]->AuthType == TRUST_AUTH_TYPE_CLEAR) { - struct samr_Password hash; -/* - . We cannot do this, as windows chooses to send in random passwords here, that won't convert to UTF8 - samdb_msg_add_string(trusted_domain_state->policy->sam_ldb, - mem_ctx, msg_user, "userPassword", - auth_struct.incoming.current->array[i].AuthInfo.clear.password); -*/ - mdfour(hash.hash, auth_struct.incoming.current[i]->AuthInfo.clear.password, - auth_struct.incoming.current[i]->AuthInfo.clear.size); - samdb_msg_add_hash(trusted_domain_state->policy->sam_ldb, - mem_ctx, msg_user, "unicodePwd", - &hash); - } + DATA_BLOB new_password = data_blob_const(auth_struct.incoming.current[i]->AuthInfo.clear.password, + auth_struct.incoming.current[i]->AuthInfo.clear.size); + ret = ldb_msg_add_value(msg_user, "clearTextPassword", &new_password, NULL); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(policy_state->sam_ldb); + return NT_STATUS_NO_MEMORY; + } + } } } diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 470c27a075..9d4c897892 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -107,7 +107,7 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_ca if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { char *encoded_account = ldb_binary_encode_string(mem_ctx, r->in.account_name); - char *flatname; + const char *flatname; if (!encoded_account) { return NT_STATUS_NO_MEMORY; } @@ -370,7 +370,7 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call creds->sid, NULL, /* Don't have plaintext */ NULL, &r->in.new_password, - false, /* This is not considered a password change */ + true, /* Password change */ NULL, NULL); return nt_status; } @@ -385,15 +385,14 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal struct creds_CredentialState *creds; struct ldb_context *sam_ctx; NTSTATUS nt_status; - char new_pass[512]; - bool ret; + DATA_BLOB new_password; struct samr_CryptPassword password_buf; nt_status = dcesrv_netr_creds_server_step_check(dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, r->in.computer_name, mem_ctx, - &r->in.credential, &r->out.return_authenticator, - &creds); + &r->in.credential, &r->out.return_authenticator, + &creds); NT_STATUS_NOT_OK_RETURN(nt_status); sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(mem_ctx, dce_call->conn->dce_ctx->lp_ctx)); @@ -402,22 +401,20 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal } memcpy(password_buf.data, r->in.new_password.data, 512); - SIVAL(password_buf.data,512,r->in.new_password.length); + SIVAL(password_buf.data, 512, r->in.new_password.length); creds_arcfour_crypt(creds, password_buf.data, 516); - ret = decode_pw_buffer(password_buf.data, new_pass, sizeof(new_pass), - STR_UNICODE); - if (!ret) { - DEBUG(3,("netr_ServerPasswordSet2: failed to decode password buffer\n")); - return NT_STATUS_ACCESS_DENIED; + if (!extract_pw_from_buffer(mem_ctx, password_buf.data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; } - + /* Using the sid for the account as the key, set the password */ nt_status = samdb_set_password_sid(sam_ctx, mem_ctx, creds->sid, - new_pass, /* we have plaintext */ + &new_password, /* we have plaintext */ NULL, NULL, - false, /* This is not considered a password change */ + true, /* Password change */ NULL, NULL); return nt_status; } diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c index 8a855a7bdb..336720ecc7 100644 --- a/source4/rpc_server/samr/samr_password.c +++ b/source4/rpc_server/samr/samr_password.c @@ -86,7 +86,8 @@ NTSTATUS dcesrv_samr_ChangePasswordUser(struct dcesrv_call_state *dce_call, } msg = res[0]; - status = samdb_result_passwords(mem_ctx, msg, &lm_pwd, &nt_pwd); + status = samdb_result_passwords(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, + msg, &lm_pwd, &nt_pwd); if (!NT_STATUS_IS_OK(status) || !lm_pwd || !nt_pwd) { ldb_transaction_cancel(sam_ctx); return NT_STATUS_WRONG_PASSWORD; @@ -183,8 +184,8 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, struct samr_OemChangePasswordUser2 *r) { NTSTATUS status; - char new_pass[512]; - uint32_t new_pass_len; + DATA_BLOB new_password; + char *new_pass; struct samr_CryptPassword *pwbuf = r->in.password; struct ldb_context *sam_ctx; struct ldb_dn *user_dn; @@ -231,7 +232,8 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, user_dn = res[0]->dn; - status = samdb_result_passwords(mem_ctx, res[0], &lm_pwd, NULL); + status = samdb_result_passwords(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, + res[0], &lm_pwd, NULL); if (!NT_STATUS_IS_OK(status) || !lm_pwd) { ldb_transaction_cancel(sam_ctx); return NT_STATUS_WRONG_PASSWORD; @@ -242,15 +244,18 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, arcfour_crypt_blob(pwbuf->data, 516, &lm_pwd_blob); data_blob_free(&lm_pwd_blob); - if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), - STR_ASCII)) { + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { ldb_transaction_cancel(sam_ctx); DEBUG(3,("samr: failed to decode password buffer\n")); return NT_STATUS_WRONG_PASSWORD; } - - /* check LM verifier */ - if (lm_pwd == NULL) { + + if (convert_string_talloc(mem_ctx, lp_iconv_convenience(dce_call->conn->dce_ctx->lp_ctx), + CH_DOS, CH_UNIX, + (const char *)new_password.data, + new_password.length, + (void **)&new_pass) == -1) { + DEBUG(3,("samr: failed to convert incoming password buffer to unix charset\n")); ldb_transaction_cancel(sam_ctx); return NT_STATUS_WRONG_PASSWORD; } @@ -278,7 +283,7 @@ NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, * due to password policies */ status = samdb_set_password(sam_ctx, mem_ctx, user_dn, NULL, - mod, new_pass, + mod, &new_password, NULL, NULL, true, /* this is a user password change */ NULL, @@ -320,7 +325,7 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, struct samr_ChangePasswordUser3 *r) { NTSTATUS status; - char new_pass[512]; + DATA_BLOB new_password; struct ldb_context *sam_ctx = NULL; struct ldb_dn *user_dn; int ret; @@ -369,7 +374,8 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, user_dn = res[0]->dn; - status = samdb_result_passwords(mem_ctx, res[0], &lm_pwd, &nt_pwd); + status = samdb_result_passwords(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, + res[0], &lm_pwd, &nt_pwd); if (!NT_STATUS_IS_OK(status) ) { goto failed; } @@ -384,40 +390,49 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, arcfour_crypt_blob(r->in.nt_password->data, 516, &nt_pwd_blob); data_blob_free(&nt_pwd_blob); - if (!decode_pw_buffer(r->in.nt_password->data, new_pass, sizeof(new_pass), - STR_UNICODE)) { + if (!extract_pw_from_buffer(mem_ctx, r->in.nt_password->data, &new_password)) { + ldb_transaction_cancel(sam_ctx); DEBUG(3,("samr: failed to decode password buffer\n")); - status = NT_STATUS_WRONG_PASSWORD; - goto failed; + return NT_STATUS_WRONG_PASSWORD; } - + if (r->in.nt_verifier == NULL) { status = NT_STATUS_WRONG_PASSWORD; goto failed; } /* check NT verifier */ - E_md4hash(new_pass, new_nt_hash); + mdfour(new_nt_hash, new_password.data, new_password.length); + E_old_pw_hash(new_nt_hash, nt_pwd->hash, nt_verifier.hash); if (memcmp(nt_verifier.hash, r->in.nt_verifier->hash, 16) != 0) { status = NT_STATUS_WRONG_PASSWORD; goto failed; } - /* check LM verifier */ + /* check LM verifier (really not needed as we just checked the + * much stronger NT hash, but the RPC-SAMR test checks for + * this) */ if (lm_pwd && r->in.lm_verifier != NULL) { - E_deshash(new_pass, new_lm_hash); - E_old_pw_hash(new_nt_hash, lm_pwd->hash, lm_verifier.hash); - if (memcmp(lm_verifier.hash, r->in.lm_verifier->hash, 16) != 0) { - status = NT_STATUS_WRONG_PASSWORD; - goto failed; + char *new_pass; + if (convert_string_talloc(mem_ctx, lp_iconv_convenience(dce_call->conn->dce_ctx->lp_ctx), + CH_UTF16, CH_UNIX, + (const char *)new_password.data, + new_password.length, + (void **)&new_pass) != -1) { + E_deshash(new_pass, new_lm_hash); + E_old_pw_hash(new_nt_hash, lm_pwd->hash, lm_verifier.hash); + if (memcmp(lm_verifier.hash, r->in.lm_verifier->hash, 16) != 0) { + status = NT_STATUS_WRONG_PASSWORD; + goto failed; + } } } - mod = ldb_msg_new(mem_ctx); if (mod == NULL) { - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto failed; } mod->dn = ldb_dn_copy(mod, user_dn); @@ -430,7 +445,7 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, * due to password policies */ status = samdb_set_password(sam_ctx, mem_ctx, user_dn, NULL, - mod, new_pass, + mod, &new_password, NULL, NULL, true, /* this is a user password change */ &reason, @@ -517,7 +532,7 @@ NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, struct samr_CryptPassword *pwbuf) { NTSTATUS nt_status; - char new_pass[512]; + DATA_BLOB new_password; DATA_BLOB session_key = data_blob(NULL, 0); nt_status = dcesrv_fetch_session_key(dce_call->conn, &session_key); @@ -527,17 +542,16 @@ NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, arcfour_crypt_blob(pwbuf->data, 516, &session_key); - if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), - STR_UNICODE)) { + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { 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(sam_ctx, mem_ctx, account_dn, domain_dn, - msg, new_pass, + msg, &new_password, NULL, NULL, false, /* This is a password set, not change */ NULL, NULL); @@ -557,8 +571,7 @@ NTSTATUS samr_set_password_ex(struct dcesrv_call_state *dce_call, struct samr_CryptPasswordEx *pwbuf) { NTSTATUS nt_status; - char new_pass[512]; - uint32_t new_pass_len; + DATA_BLOB new_password; DATA_BLOB co_session_key; DATA_BLOB session_key = data_blob(NULL, 0); struct MD5Context ctx; @@ -580,17 +593,16 @@ NTSTATUS samr_set_password_ex(struct dcesrv_call_state *dce_call, arcfour_crypt_blob(pwbuf->data, 516, &co_session_key); - if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass), - STR_UNICODE)) { + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { 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(sam_ctx, mem_ctx, account_dn, domain_dn, - msg, new_pass, + msg, &new_password, NULL, NULL, false, /* This is a password set, not change */ NULL, NULL); |