X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fdsdb%2Fsamdb%2Fldb_modules%2Fpassword_hash.c;h=7779a1752be8e093c06240c4e3835301cccb5e0f;hb=40a06c0101bf6426e0752cd695044049a8058f54;hp=22d04a5519a4a49a242b66d22f2eaaac24c155f6;hpb=ffeee68e4b72dd94fee57366bd8d38b8c284c3d4;p=samba.git diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 22d04a5519a..7779a1752be 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -1,7 +1,7 @@ /* ldb database module - Copyright (C) Simo Sorce 2004-2006 + Copyright (C) Simo Sorce 2004-2008 Copyright (C) Andrew Bartlett 2005-2006 Copyright (C) Andrew Tridgell 2004 Copyright (C) Stefan Metzmacher 2007 @@ -25,16 +25,15 @@ * * Component: ldb password_hash module * - * Description: correctly update hash values based on changes to sambaPassword and friends + * Description: correctly update hash values based on changes to userPassword and friends * * Author: Andrew Bartlett * Author: Stefan Metzmacher */ #include "includes.h" -#include "libcli/ldap/ldap.h" -#include "ldb/include/ldb_errors.h" -#include "ldb/include/ldb_private.h" +#include "libcli/ldap/ldap_ndr.h" +#include "ldb_module.h" #include "librpc/gen_ndr/misc.h" #include "librpc/gen_ndr/samr.h" #include "libcli/auth/libcli_auth.h" @@ -43,17 +42,17 @@ #include "auth/kerberos/kerberos.h" #include "system/time.h" #include "dsdb/samdb/samdb.h" -#include "dsdb/common/flags.h" +#include "../libds/common/flags.h" #include "dsdb/samdb/ldb_modules/password_modules.h" #include "librpc/ndr/libndr.h" #include "librpc/gen_ndr/ndr_drsblobs.h" -#include "lib/crypto/crypto.h" +#include "../lib/crypto/crypto.h" #include "param/param.h" /* If we have decided there is reason to work on this request, then * setup all the password hash types correctly. * - * If the administrator doesn't want the sambaPassword stored (set in the + * If the administrator doesn't want the userPassword stored (set in the * domain and per-account policies) then we must strip that out before * we do the first operation. * @@ -72,32 +71,24 @@ struct ph_context { - enum ph_type {PH_ADD, PH_MOD} type; - enum ph_step {PH_ADD_SEARCH_DOM, PH_ADD_DO_ADD, PH_MOD_DO_REQ, PH_MOD_SEARCH_SELF, PH_MOD_SEARCH_DOM, PH_MOD_DO_MOD} step; - struct ldb_module *module; - struct ldb_request *orig_req; + struct ldb_request *req; struct ldb_request *dom_req; struct ldb_reply *dom_res; - struct ldb_request *down_req; - - struct ldb_request *search_req; struct ldb_reply *search_res; - struct ldb_request *mod_req; - - struct dom_sid *domain_sid; + struct domain_data *domain; }; struct domain_data { - BOOL store_cleartext; + bool store_cleartext; uint_t pwdProperties; uint_t pwdHistoryLength; - char *netbios_domain; - char *dns_domain; - char *realm; + const char *netbios_domain; + const char *dns_domain; + const char *realm; }; struct setup_password_fields_io { @@ -115,7 +106,9 @@ struct setup_password_fields_io { /* new credentials */ struct { - const char *cleartext; + const struct ldb_val *cleartext_utf8; + const struct ldb_val *cleartext_utf16; + struct ldb_val quoted_utf16; struct samr_Password *nt_hash; struct samr_Password *lm_hash; } n; @@ -139,17 +132,27 @@ struct setup_password_fields_io { struct samr_Password *nt_history; uint32_t lm_history_len; struct samr_Password *lm_history; + const char *salt; + DATA_BLOB aes_256; + DATA_BLOB aes_128; + DATA_BLOB des_md5; + DATA_BLOB des_crc; struct ldb_val supplemental; NTTIME last_set; uint32_t kvno; } 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) { + struct ldb_context *ldb; uint32_t i; io->g.nt_hash = io->n.nt_hash; + ldb = ldb_module_get_ctx(io->ac->module); if (io->domain->pwdHistoryLength == 0) { return LDB_SUCCESS; @@ -160,7 +163,7 @@ static int setup_nt_fields(struct setup_password_fields_io *io) struct samr_Password, io->domain->pwdHistoryLength); if (!io->g.nt_history) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -182,11 +185,16 @@ 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) { + struct ldb_context *ldb; uint32_t i; io->g.lm_hash = io->n.lm_hash; + ldb = ldb_module_get_ctx(io->ac->module); if (io->domain->pwdHistoryLength == 0) { return LDB_SUCCESS; @@ -197,7 +205,7 @@ static int setup_lm_fields(struct setup_password_fields_io *io) struct samr_Password, io->domain->pwdHistoryLength); if (!io->g.lm_history) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -215,21 +223,18 @@ static int setup_lm_fields(struct setup_password_fields_io *io) return LDB_SUCCESS; } -static int setup_primary_kerberos(struct setup_password_fields_io *io, - const struct supplementalCredentialsBlob *old_scb, - struct package_PrimaryKerberosBlob *pkb) +static int setup_kerberos_keys(struct setup_password_fields_io *io) { + struct ldb_context *ldb; krb5_error_code krb5_ret; Principal *salt_principal; krb5_salt salt; krb5_keyblock key; - uint32_t k=0; - struct package_PrimaryKerberosCtr3 *pkb3 = &pkb->ctr.ctr3; - struct supplementalCredentialsPackage *old_scp = NULL; - struct package_PrimaryKerberosBlob _old_pkb; - struct package_PrimaryKerberosCtr3 *old_pkb3 = NULL; - uint32_t i; - NTSTATUS status; + krb5_data cleartext_data; + + ldb = ldb_module_get_ctx(io->ac->module); + 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 @@ -242,9 +247,9 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, char *name; char *saltbody; - name = talloc_strdup(io->ac, io->u.sAMAccountName); + name = strlower_talloc(io->ac, io->u.sAMAccountName); if (!name) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -254,7 +259,7 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, saltbody = talloc_asprintf(io->ac, "%s.%s", name, io->domain->dns_domain); if (!saltbody) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -268,7 +273,7 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, user_principal_name = talloc_strdup(io->ac, io->u.user_principal_name); if (!user_principal_name) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -288,8 +293,8 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, NULL); } if (krb5_ret) { - ldb_asprintf_errstring(io->ac->module->ldb, - "setup_primary_kerberos: " + ldb_asprintf_errstring(ldb, + "setup_kerberos_keys: " "generation of a salting principal failed: %s", smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; @@ -302,142 +307,166 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, salt_principal, &salt); krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal); if (krb5_ret) { - ldb_asprintf_errstring(io->ac->module->ldb, - "setup_primary_kerberos: " + ldb_asprintf_errstring(ldb, + "setup_kerberos_keys: " "generation of krb5_salt failed: %s", smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; } /* create a talloc copy */ - pkb3->salt.string = talloc_strndup(io->ac, - salt.saltvalue.data, - salt.saltvalue.length); + io->g.salt = talloc_strndup(io->ac, + salt.saltvalue.data, + salt.saltvalue.length); krb5_free_salt(io->smb_krb5_context->krb5_context, salt); - if (!pkb3->salt.string) { - ldb_oom(io->ac->module->ldb); + if (!io->g.salt) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - salt.saltvalue.data = discard_const(pkb3->salt.string); - salt.saltvalue.length = strlen(pkb3->salt.string); + salt.saltvalue.data = discard_const(io->g.salt); + salt.saltvalue.length = strlen(io->g.salt); /* - * prepare generation of keys - * - * ENCTYPE_AES256_CTS_HMAC_SHA1_96 (disabled by default) - * ENCTYPE_DES_CBC_MD5 - * ENCTYPE_DES_CBC_CRC - * - * NOTE: update num_keys when you add another enctype! + * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of + * the salt and the cleartext password */ - pkb3->num_keys = 3; - pkb3->keys = talloc_array(io->ac, struct package_PrimaryKerberosKey, pkb3->num_keys); - if (!pkb3->keys) { - ldb_oom(io->ac->module->ldb); + 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(ldb, + "setup_kerberos_keys: " + "generation of a aes256-cts-hmac-sha1-96 key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; } - pkb3->unknown3 = talloc_zero_array(io->ac, uint64_t, pkb3->num_keys); - if (!pkb3->unknown3) { - ldb_oom(io->ac->module->ldb); + io->g.aes_256 = data_blob_talloc(io->ac, + key.keyvalue.data, + key.keyvalue.length); + krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); + if (!io->g.aes_256.data) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - if (lp_parm_bool(-1, "password_hash", "create_aes_key", false)) { - /* - * TODO: - * - * w2k and w2k3 doesn't support AES, so we'll not include - * the AES key here yet. - * - * Also we don't have an example supplementalCredentials blob - * from Windows Longhorn Server with AES support - * - */ /* - * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of + * 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_AES256_CTS_HMAC_SHA1_96, - io->n.cleartext, - salt, - &key); - pkb3->keys[k].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96; - pkb3->keys[k].value = talloc(pkb3->keys, DATA_BLOB); - if (!pkb3->keys[k].value) { - krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - ldb_oom(io->ac->module->ldb); + 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(ldb, + "setup_kerberos_keys: " + "generation of a aes128-cts-hmac-sha1-96 key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; } - *pkb3->keys[k].value = data_blob_talloc(pkb3->keys[k].value, - key.keyvalue.data, - key.keyvalue.length); + io->g.aes_128 = data_blob_talloc(io->ac, + key.keyvalue.data, + key.keyvalue.length); krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - if (!pkb3->keys[k].value->data) { - ldb_oom(io->ac->module->ldb); + if (!io->g.aes_128.data) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - k++; -} /* * 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); - pkb3->keys[k].keytype = ENCTYPE_DES_CBC_MD5; - pkb3->keys[k].value = talloc(pkb3->keys, DATA_BLOB); - if (!pkb3->keys[k].value) { - krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - ldb_oom(io->ac->module->ldb); + 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(ldb, + "setup_kerberos_keys: " + "generation of a des-cbc-md5 key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; } - *pkb3->keys[k].value = data_blob_talloc(pkb3->keys[k].value, - key.keyvalue.data, - key.keyvalue.length); + io->g.des_md5 = data_blob_talloc(io->ac, + key.keyvalue.data, + key.keyvalue.length); krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - if (!pkb3->keys[k].value->data) { - ldb_oom(io->ac->module->ldb); + if (!io->g.des_md5.data) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - k++; /* * 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); - pkb3->keys[k].keytype = ENCTYPE_DES_CBC_CRC; - pkb3->keys[k].value = talloc(pkb3->keys, DATA_BLOB); - if (!pkb3->keys[k].value) { - krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - ldb_oom(io->ac->module->ldb); + 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(ldb, + "setup_kerberos_keys: " + "generation of a des-cbc-crc key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac)); return LDB_ERR_OPERATIONS_ERROR; } - *pkb3->keys[k].value = data_blob_talloc(pkb3->keys[k].value, - key.keyvalue.data, - key.keyvalue.length); + io->g.des_crc = data_blob_talloc(io->ac, + key.keyvalue.data, + key.keyvalue.length); krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); - if (!pkb3->keys[k].value->data) { - ldb_oom(io->ac->module->ldb); + if (!io->g.des_crc.data) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + return LDB_SUCCESS; +} + +static int setup_primary_kerberos(struct setup_password_fields_io *io, + const struct supplementalCredentialsBlob *old_scb, + struct package_PrimaryKerberosBlob *pkb) +{ + struct ldb_context *ldb; + struct package_PrimaryKerberosCtr3 *pkb3 = &pkb->ctr.ctr3; + struct supplementalCredentialsPackage *old_scp = NULL; + struct package_PrimaryKerberosBlob _old_pkb; + struct package_PrimaryKerberosCtr3 *old_pkb3 = NULL; + uint32_t i; + enum ndr_err_code ndr_err; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* + * prepare generation of keys + * + * ENCTYPE_DES_CBC_MD5 + * ENCTYPE_DES_CBC_CRC + */ + pkb->version = 3; + pkb3->salt.string = io->g.salt; + pkb3->num_keys = 2; + pkb3->keys = talloc_array(io->ac, + struct package_PrimaryKerberosKey3, + pkb3->num_keys); + if (!pkb3->keys) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - k++; - /* fix up key number */ - pkb3->num_keys = k; + pkb3->keys[0].keytype = ENCTYPE_DES_CBC_MD5; + pkb3->keys[0].value = &io->g.des_md5; + pkb3->keys[1].keytype = ENCTYPE_DES_CBC_CRC; + pkb3->keys[1].value = &io->g.des_crc; /* initialize the old keys to zero */ pkb3->num_old_keys = 0; pkb3->old_keys = NULL; - pkb3->unknown3_old = NULL; /* if there're no old keys, then we're done */ if (!old_scb) { @@ -445,10 +474,6 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, } for (i=0; i < old_scb->sub.num_packages; i++) { - if (old_scb->sub.packages[i].unknown1 != 0x00000001) { - continue; - } - if (strcmp("Primary:Kerberos", old_scb->sub.packages[i].name) != 0) { continue; } @@ -464,18 +489,18 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, if (old_scp) { DATA_BLOB blob; - blob = strhex_to_data_blob(old_scp->data); + blob = strhex_to_data_blob(io->ac, old_scp->data); if (!blob.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - talloc_steal(io->ac, blob.data); /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */ - status = ndr_pull_struct_blob(&blob, io->ac, &_old_pkb, - (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_pull_struct_blob(&blob, io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &_old_pkb, + (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_primary_kerberos: " "failed to pull old package_PrimaryKerberosBlob: %s", nt_errstr(status)); @@ -483,7 +508,7 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, } if (_old_pkb.version != 3) { - ldb_asprintf_errstring(io->ac->module->ldb, + ldb_asprintf_errstring(ldb, "setup_primary_kerberos: " "package_PrimaryKerberosBlob version[%u] expected[3]", _old_pkb.version); @@ -501,7 +526,126 @@ static int setup_primary_kerberos(struct setup_password_fields_io *io, /* fill in the old keys */ pkb3->num_old_keys = old_pkb3->num_keys; pkb3->old_keys = old_pkb3->keys; - pkb3->unknown3_old = old_pkb3->unknown3; + + return LDB_SUCCESS; +} + +static int setup_primary_kerberos_newer(struct setup_password_fields_io *io, + const struct supplementalCredentialsBlob *old_scb, + struct package_PrimaryKerberosBlob *pkb) +{ + struct ldb_context *ldb; + struct package_PrimaryKerberosCtr4 *pkb4 = &pkb->ctr.ctr4; + struct supplementalCredentialsPackage *old_scp = NULL; + struct package_PrimaryKerberosBlob _old_pkb; + struct package_PrimaryKerberosCtr4 *old_pkb4 = NULL; + uint32_t i; + enum ndr_err_code ndr_err; + + ldb = ldb_module_get_ctx(io->ac->module); + + /* + * prepare generation of keys + * + * ENCTYPE_AES256_CTS_HMAC_SHA1_96 + * ENCTYPE_AES128_CTS_HMAC_SHA1_96 + * ENCTYPE_DES_CBC_MD5 + * ENCTYPE_DES_CBC_CRC + */ + pkb->version = 4; + pkb4->salt.string = io->g.salt; + pkb4->default_iteration_count = 4096; + pkb4->num_keys = 4; + + pkb4->keys = talloc_array(io->ac, + struct package_PrimaryKerberosKey4, + pkb4->num_keys); + if (!pkb4->keys) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + pkb4->keys[0].iteration_count = 4096; + pkb4->keys[0].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96; + pkb4->keys[0].value = &io->g.aes_256; + pkb4->keys[1].iteration_count = 4096; + pkb4->keys[1].keytype = ENCTYPE_AES128_CTS_HMAC_SHA1_96; + pkb4->keys[1].value = &io->g.aes_128; + pkb4->keys[2].iteration_count = 4096; + pkb4->keys[2].keytype = ENCTYPE_DES_CBC_MD5; + pkb4->keys[2].value = &io->g.des_md5; + pkb4->keys[3].iteration_count = 4096; + pkb4->keys[3].keytype = ENCTYPE_DES_CBC_CRC; + pkb4->keys[3].value = &io->g.des_crc; + + /* initialize the old keys to zero */ + pkb4->num_old_keys = 0; + pkb4->old_keys = NULL; + pkb4->num_older_keys = 0; + pkb4->older_keys = NULL; + + /* if there're no old keys, then we're done */ + if (!old_scb) { + return LDB_SUCCESS; + } + + for (i=0; i < old_scb->sub.num_packages; i++) { + if (strcmp("Primary:Kerberos-Newer-Keys", old_scb->sub.packages[i].name) != 0) { + continue; + } + + if (!old_scb->sub.packages[i].data || !old_scb->sub.packages[i].data[0]) { + continue; + } + + old_scp = &old_scb->sub.packages[i]; + break; + } + /* Primary:Kerberos-Newer-Keys element of supplementalCredentials */ + if (old_scp) { + DATA_BLOB blob; + + blob = strhex_to_data_blob(io->ac, old_scp->data); + if (!blob.data) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */ + ndr_err = ndr_pull_struct_blob(&blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &_old_pkb, + (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, + "setup_primary_kerberos_newer: " + "failed to pull old package_PrimaryKerberosBlob: %s", + nt_errstr(status)); + return LDB_ERR_OPERATIONS_ERROR; + } + + if (_old_pkb.version != 4) { + ldb_asprintf_errstring(ldb, + "setup_primary_kerberos_newer: " + "package_PrimaryKerberosBlob version[%u] expected[4]", + _old_pkb.version); + return LDB_ERR_OPERATIONS_ERROR; + } + + old_pkb4 = &_old_pkb.ctr.ctr4; + } + + /* if we didn't found the old keys we're done */ + if (!old_pkb4) { + return LDB_SUCCESS; + } + + /* fill in the old keys */ + pkb4->num_old_keys = old_pkb4->num_keys; + pkb4->old_keys = old_pkb4->keys; + pkb4->num_older_keys = old_pkb4->num_old_keys; + pkb4->older_keys = old_pkb4->old_keys; return LDB_SUCCESS; } @@ -510,6 +654,7 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, const struct supplementalCredentialsBlob *old_scb, struct package_PrimaryWDigestBlob *pdb) { + struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); DATA_BLOB sAMAccountName; DATA_BLOB sAMAccountName_l; DATA_BLOB sAMAccountName_u; @@ -523,7 +668,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; @@ -757,12 +901,12 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, sAMAccountName = data_blob_string_const(io->u.sAMAccountName); sAMAccountName_l = data_blob_string_const(strlower_talloc(io->ac, io->u.sAMAccountName)); if (!sAMAccountName_l.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } sAMAccountName_u = data_blob_string_const(strupper_talloc(io->ac, io->u.sAMAccountName)); if (!sAMAccountName_u.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -772,31 +916,31 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, io->u.sAMAccountName, io->domain->dns_domain); if (!user_principal_name) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } } userPrincipalName = data_blob_string_const(user_principal_name); userPrincipalName_l = data_blob_string_const(strlower_talloc(io->ac, user_principal_name)); if (!userPrincipalName_l.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } userPrincipalName_u = data_blob_string_const(strupper_talloc(io->ac, user_principal_name)); if (!userPrincipalName_u.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } netbios_domain = data_blob_string_const(io->domain->netbios_domain); netbios_domain_l = data_blob_string_const(strlower_talloc(io->ac, io->domain->netbios_domain)); if (!netbios_domain_l.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } netbios_domain_u = data_blob_string_const(strupper_talloc(io->ac, io->domain->netbios_domain)); if (!netbios_domain_u.data) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -804,8 +948,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(":"); @@ -814,7 +956,7 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, pdb->num_hashes = ARRAY_SIZE(wdigest); pdb->hashes = talloc_array(io->ac, struct package_PrimaryWDigestHash, pdb->num_hashes); if (!pdb->hashes) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } @@ -831,7 +973,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); } @@ -840,35 +982,57 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io, static int setup_supplemental_field(struct setup_password_fields_io *io) { + struct ldb_context *ldb; struct supplementalCredentialsBlob scb; struct supplementalCredentialsBlob _old_scb; struct supplementalCredentialsBlob *old_scb = NULL; - /* Packages + (Kerberos, WDigest and maybe CLEARTEXT) */ - uint32_t num_packages = 1 + 2; - struct supplementalCredentialsPackage packages[1+3]; - struct supplementalCredentialsPackage *pp = &packages[0]; - struct supplementalCredentialsPackage *pk = &packages[1]; - struct supplementalCredentialsPackage *pd = &packages[2]; - struct supplementalCredentialsPackage *pc = NULL; + /* Packages + (Kerberos-Newer-Keys, Kerberos, WDigest and CLEARTEXT) */ + uint32_t num_names = 0; + const char *names[1+4]; + uint32_t num_packages = 0; + struct supplementalCredentialsPackage packages[1+4]; + /* Packages */ + struct supplementalCredentialsPackage *pp = NULL; struct package_PackagesBlob pb; DATA_BLOB pb_blob; char *pb_hexstr; + /* Primary:Kerberos-Newer-Keys */ + const char **nkn = NULL; + struct supplementalCredentialsPackage *pkn = NULL; + struct package_PrimaryKerberosBlob pknb; + DATA_BLOB pknb_blob; + char *pknb_hexstr; + /* Primary:Kerberos */ + const char **nk = NULL; + struct supplementalCredentialsPackage *pk = NULL; struct package_PrimaryKerberosBlob pkb; DATA_BLOB pkb_blob; char *pkb_hexstr; + /* Primary:WDigest */ + const char **nd = NULL; + struct supplementalCredentialsPackage *pd = NULL; struct package_PrimaryWDigestBlob pdb; DATA_BLOB pdb_blob; char *pdb_hexstr; + /* Primary:CLEARTEXT */ + const char **nc = NULL; + struct supplementalCredentialsPackage *pc = NULL; struct package_PrimaryCLEARTEXTBlob pcb; DATA_BLOB pcb_blob; char *pcb_hexstr; int ret; - NTSTATUS status; + enum ndr_err_code ndr_err; uint8_t zero16[16]; + bool do_newer_keys = false; + bool do_cleartext = false; + int *domainFunctionality; ZERO_STRUCT(zero16); + ZERO_STRUCT(names); - if (!io->n.cleartext) { + ldb = ldb_module_get_ctx(io->ac->module); + + if (!io->n.cleartext_utf8) { /* * when we don't have a cleartext password * we can't setup a supplementalCredential value @@ -878,80 +1042,159 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) /* if there's an old supplementaCredentials blob then parse it */ if (io->o.supplemental) { - status = ndr_pull_struct_blob_all(io->o.supplemental, io->ac, &_old_scb, - (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_pull_struct_blob_all(io->o.supplemental, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &_old_scb, + (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to pull old supplementalCredentialsBlob: %s", nt_errstr(status)); return LDB_ERR_OPERATIONS_ERROR; } - old_scb = &_old_scb; + if (_old_scb.sub.signature == SUPPLEMENTAL_CREDENTIALS_SIGNATURE) { + old_scb = &_old_scb; + } else { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "setup_supplemental_field: " + "supplementalCredentialsBlob signature[0x%04X] expected[0x%04X]", + _old_scb.sub.signature, SUPPLEMENTAL_CREDENTIALS_SIGNATURE); + } } + /* Per MS-SAMR 3.1.1.8.11.6 we create AES keys if our domain functionality level is 2008 or higher */ + domainFunctionality = talloc_get_type(ldb_get_opaque(ldb, "domainFunctionality"), int); + + do_newer_keys = *domainFunctionality && + (*domainFunctionality >= DS_DOMAIN_FUNCTION_2008); if (io->domain->store_cleartext && (io->u.user_account_control & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) { - pc = &packages[3]; - num_packages++; + do_cleartext = true; } - /* Kerberos, WDigest, CLEARTEXT and termination(counted by the Packages element) */ - pb.names = talloc_zero_array(io->ac, const char *, num_packages); + /* + * The ordering is this + * + * Primary:Kerberos-Newer-Keys (optional) + * Primary:Kerberos + * Primary:WDigest + * Primary:CLEARTEXT (optional) + * + * And the 'Packages' package is insert before the last + * other package. + */ + if (do_newer_keys) { + /* Primary:Kerberos-Newer-Keys */ + nkn = &names[num_names++]; + pkn = &packages[num_packages++]; + } + + /* Primary:Kerberos */ + nk = &names[num_names++]; + pk = &packages[num_packages++]; + + if (!do_cleartext) { + /* Packages */ + pp = &packages[num_packages++]; + } + + /* Primary:WDigest */ + nd = &names[num_names++]; + pd = &packages[num_packages++]; + + if (do_cleartext) { + /* Packages */ + pp = &packages[num_packages++]; + + /* Primary:CLEARTEXT */ + nc = &names[num_names++]; + pc = &packages[num_packages++]; + } + + if (pkn) { + /* + * setup 'Primary:Kerberos-Newer-Keys' element + */ + *nkn = "Kerberos-Newer-Keys"; + + ret = setup_primary_kerberos_newer(io, old_scb, &pknb); + if (ret != LDB_SUCCESS) { + return ret; + } + + ndr_err = ndr_push_struct_blob(&pknb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &pknb, + (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, + "setup_supplemental_field: " + "failed to push package_PrimaryKerberosNeverBlob: %s", + nt_errstr(status)); + return LDB_ERR_OPERATIONS_ERROR; + } + pknb_hexstr = data_blob_hex_string(io->ac, &pknb_blob); + if (!pknb_hexstr) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + pkn->name = "Primary:Kerberos-Newer-Keys"; + pkn->reserved = 1; + pkn->data = pknb_hexstr; + } /* * setup 'Primary:Kerberos' element */ - pb.names[0] = "Kerberos"; + *nk = "Kerberos"; ret = setup_primary_kerberos(io, old_scb, &pkb); if (ret != LDB_SUCCESS) { return ret; } - status = ndr_push_struct_blob(&pkb_blob, io->ac, &pkb, - (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_push_struct_blob(&pkb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &pkb, + (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to push package_PrimaryKerberosBlob: %s", nt_errstr(status)); return LDB_ERR_OPERATIONS_ERROR; } - /* - * TODO: - * - * This is ugly, but we want to generate the same blob as - * w2k and w2k3...we should handle this in the idl - */ - if (!data_blob_append(io->ac, &pkb_blob, zero16, sizeof(zero16))) { - ldb_oom(io->ac->module->ldb); - return LDB_ERR_OPERATIONS_ERROR; - } pkb_hexstr = data_blob_hex_string(io->ac, &pkb_blob); if (!pkb_hexstr) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } pk->name = "Primary:Kerberos"; - pk->unknown1 = 1; + pk->reserved = 1; pk->data = pkb_hexstr; /* * setup 'Primary:WDigest' element */ - pb.names[1] = "WDigest"; + *nd = "WDigest"; ret = setup_primary_wdigest(io, old_scb, &pdb); if (ret != LDB_SUCCESS) { return ret; } - status = ndr_push_struct_blob(&pdb_blob, io->ac, &pdb, - (ndr_push_flags_fn_t)ndr_push_package_PrimaryWDigestBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_push_struct_blob(&pdb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &pdb, + (ndr_push_flags_fn_t)ndr_push_package_PrimaryWDigestBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to push package_PrimaryWDigestBlob: %s", nt_errstr(status)); @@ -959,25 +1202,28 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) } pdb_hexstr = data_blob_hex_string(io->ac, &pdb_blob); if (!pdb_hexstr) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } pd->name = "Primary:WDigest"; - pd->unknown1 = 1; + pd->reserved = 1; pd->data = pdb_hexstr; /* * setup 'Primary:CLEARTEXT' element */ if (pc) { - pb.names[2] = "CLEARTEXT"; + *nc = "CLEARTEXT"; - pcb.cleartext = io->n.cleartext; + pcb.cleartext = *io->n.cleartext_utf16; - status = ndr_push_struct_blob(&pcb_blob, io->ac, &pcb, - (ndr_push_flags_fn_t)ndr_push_package_PrimaryCLEARTEXTBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_push_struct_blob(&pcb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &pcb, + (ndr_push_flags_fn_t)ndr_push_package_PrimaryCLEARTEXTBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to push package_PrimaryCLEARTEXTBlob: %s", nt_errstr(status)); @@ -985,21 +1231,25 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) } pcb_hexstr = data_blob_hex_string(io->ac, &pcb_blob); if (!pcb_hexstr) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } pc->name = "Primary:CLEARTEXT"; - pc->unknown1 = 1; + pc->reserved = 1; pc->data = pcb_hexstr; } /* * setup 'Packages' element */ - status = ndr_push_struct_blob(&pb_blob, io->ac, &pb, - (ndr_push_flags_fn_t)ndr_push_package_PackagesBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + pb.names = names; + ndr_err = ndr_push_struct_blob(&pb_blob, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &pb, + (ndr_push_flags_fn_t)ndr_push_package_PackagesBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to push package_PackagesBlob: %s", nt_errstr(status)); @@ -1007,23 +1257,27 @@ static int setup_supplemental_field(struct setup_password_fields_io *io) } pb_hexstr = data_blob_hex_string(io->ac, &pb_blob); if (!pb_hexstr) { - ldb_oom(io->ac->module->ldb); + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } pp->name = "Packages"; - pp->unknown1 = 2; + pp->reserved = 2; pp->data = pb_hexstr; /* * setup 'supplementalCredentials' value */ + ZERO_STRUCT(scb); scb.sub.num_packages = num_packages; scb.sub.packages = packages; - status = ndr_push_struct_blob(&io->g.supplemental, io->ac, &scb, - (ndr_push_flags_fn_t)ndr_push_supplementalCredentialsBlob); - if (!NT_STATUS_IS_OK(status)) { - ldb_asprintf_errstring(io->ac->module->ldb, + ndr_err = ndr_push_struct_blob(&io->g.supplemental, io->ac, + lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + &scb, + (ndr_push_flags_fn_t)ndr_push_supplementalCredentialsBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + ldb_asprintf_errstring(ldb, "setup_supplemental_field: " "failed to push supplementalCredentialsBlob: %s", nt_errstr(status)); @@ -1051,267 +1305,374 @@ static int setup_kvno_field(struct setup_password_fields_io *io) static int setup_password_fields(struct setup_password_fields_io *io) { + struct ldb_context *ldb; bool ok; int ret; + size_t converted_pw_len; + + ldb = ldb_module_get_ctx(io->ac->module); /* * 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)) { - ldb_asprintf_errstring(io->ac->module->ldb, + if ((io->n.cleartext_utf8 || io->n.cleartext_utf16) && (io->n.nt_hash || io->n.lm_hash)) { + ldb_asprintf_errstring(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 && !io->n.nt_hash) { - struct samr_Password *hash; - - hash = talloc(io->ac, struct samr_Password); - if (!hash) { - ldb_oom(io->ac->module->ldb); + + if (io->n.cleartext_utf8 && io->n.cleartext_utf16) { + ldb_asprintf_errstring(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(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 { - ldb_asprintf_errstring(io->ac->module->ldb, + if (!convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF8, CH_UTF16, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_utf16_str, &converted_pw_len, false)) { + ldb_asprintf_errstring(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(ldb); return LDB_ERR_OPERATIONS_ERROR; } + if (!convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF16MUNGED, CH_UTF8, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length, + (void **)&cleartext_utf8_str, &converted_pw_len, false)) { + /* 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_utf16) { + struct samr_Password *nt_hash; + nt_hash = talloc(io->ac, struct samr_Password); + if (!nt_hash) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + io->n.nt_hash = nt_hash; - if (io->n.cleartext && !io->n.lm_hash) { - struct samr_Password *hash; + /* compute the new nt hash */ + mdfour(nt_hash->hash, io->n.cleartext_utf16->data, io->n.cleartext_utf16->length); + } - hash = talloc(io->ac, struct samr_Password); - if (!hash) { - ldb_oom(io->ac->module->ldb); - return LDB_ERR_OPERATIONS_ERROR; + if (io->n.cleartext_utf8) { + struct samr_Password *lm_hash; + char *cleartext_unix; + if (lp_lanman_auth(ldb_get_opaque(ldb, "loadparm")) && + convert_string_talloc_convenience(io->ac, lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), + CH_UTF8, CH_UNIX, io->n.cleartext_utf8->data, io->n.cleartext_utf8->length, + (void **)&cleartext_unix, &converted_pw_len, false)) { + lm_hash = talloc(io->ac, struct samr_Password); + if (!lm_hash) { + ldb_oom(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); + } } - /* 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); + ret = setup_kerberos_keys(io); + if (ret != LDB_SUCCESS) { + return ret; } } ret = setup_nt_fields(io); - if (ret != 0) { + if (ret != LDB_SUCCESS) { return ret; } ret = setup_lm_fields(io); - if (ret != 0) { + if (ret != LDB_SUCCESS) { return ret; } ret = setup_supplemental_field(io); - if (ret != 0) { + if (ret != LDB_SUCCESS) { return ret; } ret = setup_last_set_field(io); - if (ret != 0) { + if (ret != LDB_SUCCESS) { return ret; } ret = setup_kvno_field(io); - if (ret != 0) { + if (ret != LDB_SUCCESS) { return ret; } return LDB_SUCCESS; } -static struct ldb_handle *ph_init_handle(struct ldb_request *req, struct ldb_module *module, enum ph_type type) -{ - struct ph_context *ac; - struct ldb_handle *h; +static int setup_io(struct ph_context *ac, + const struct ldb_message *new_msg, + const struct ldb_message *searched_msg, + struct setup_password_fields_io *io) +{ + const struct ldb_val *quoted_utf16; + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - h = talloc_zero(req, struct ldb_handle); - if (h == NULL) { - ldb_set_errstring(module->ldb, "Out of Memory"); - return NULL; - } - - h->module = module; + ZERO_STRUCTP(io); - ac = talloc_zero(h, struct ph_context); - if (ac == NULL) { - ldb_set_errstring(module->ldb, "Out of Memory"); - talloc_free(h); - return NULL; + /* Some operations below require kerberos contexts */ + if (smb_krb5_init_context(ac, + ldb_get_event_context(ldb), + (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"), + &io->smb_krb5_context) != 0) { + return LDB_ERR_OPERATIONS_ERROR; } - h->private_data = (void *)ac; - - h->state = LDB_ASYNC_INIT; - h->status = LDB_SUCCESS; + io->ac = ac; + io->domain = ac->domain; + + io->u.user_account_control = samdb_result_uint(searched_msg, "userAccountControl", 0); + io->u.sAMAccountName = samdb_result_string(searched_msg, "samAccountName", NULL); + 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_utf8 = ldb_msg_find_ldb_val(new_msg, "userPassword"); + io->n.cleartext_utf16 = ldb_msg_find_ldb_val(new_msg, "clearTextPassword"); + + /* this rather strange looking piece of code is there to + handle a ldap client setting a password remotely using the + unicodePwd ldap field. The syntax is that the password is + in UTF-16LE, with a " at either end. Unfortunately the + unicodePwd field is also used to store the nt hashes + internally in Samba, and is used in the nt hash format on + the wire in DRS replication, so we have a single name for + two distinct values. The code below leaves us with a small + chance (less than 1 in 2^32) of a mixup, if someone manages + to create a MD4 hash which starts and ends in 0x22 0x00, as + that would then be treated as a UTF16 password rather than + a nthash */ + quoted_utf16 = ldb_msg_find_ldb_val(new_msg, "unicodePwd"); + if (quoted_utf16 && + quoted_utf16->length >= 4 && + quoted_utf16->data[0] == '"' && + quoted_utf16->data[1] == 0 && + quoted_utf16->data[quoted_utf16->length-2] == '"' && + quoted_utf16->data[quoted_utf16->length-1] == 0) { + io->n.quoted_utf16.data = talloc_memdup(io->ac, quoted_utf16->data+2, quoted_utf16->length-4); + io->n.quoted_utf16.length = quoted_utf16->length-4; + io->n.cleartext_utf16 = &io->n.quoted_utf16; + io->n.nt_hash = NULL; + } else { + io->n.nt_hash = samdb_result_hash(io->ac, new_msg, "unicodePwd"); + } - ac->type = type; - ac->module = module; - ac->orig_req = req; + io->n.lm_hash = samdb_result_hash(io->ac, new_msg, "dBCSPwd"); - return h; + return LDB_SUCCESS; } -static int get_domain_data_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares) +static struct ph_context *ph_init_context(struct ldb_module *module, + struct ldb_request *req) { + struct ldb_context *ldb; struct ph_context *ac; - ac = talloc_get_type(context, struct ph_context); + ldb = ldb_module_get_ctx(module); - /* we are interested only in the single reply (base search) we receive here */ - if (ares->type == LDB_REPLY_ENTRY) { - if (ac->dom_res != NULL) { - ldb_set_errstring(ldb, "Too many results"); - talloc_free(ares); - return LDB_ERR_OPERATIONS_ERROR; - } - ac->dom_res = talloc_steal(ac, ares); - } else { - talloc_free(ares); + ac = talloc_zero(req, struct ph_context); + if (ac == NULL) { + ldb_set_errstring(ldb, "Out of Memory"); + return NULL; } - return LDB_SUCCESS; + ac->module = module; + ac->req = req; + + return ac; } -static int build_domain_data_request(struct ph_context *ac) +static int ph_op_callback(struct ldb_request *req, struct ldb_reply *ares) { - /* attrs[] is returned from this function in - ac->dom_req->op.search.attrs, so it must be static, as - otherwise the compiler can put it on the stack */ - static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", NULL }; - char *filter; + struct ph_context *ac; - ac->dom_req = talloc_zero(ac, struct ldb_request); - if (ac->dom_req == NULL) { - ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n"); - return LDB_ERR_OPERATIONS_ERROR; - } - ac->dom_req->operation = LDB_SEARCH; - ac->dom_req->op.search.base = ldb_get_default_basedn(ac->module->ldb); - ac->dom_req->op.search.scope = LDB_SCOPE_SUBTREE; + ac = talloc_get_type(req->context, struct ph_context); - filter = talloc_asprintf(ac->dom_req, "(&(objectSid=%s)(|(objectClass=domain)(objectClass=builtinDomain)))", - ldap_encode_ndr_dom_sid(ac->dom_req, ac->domain_sid)); - if (filter == NULL) { - ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n"); - talloc_free(ac->dom_req); - return LDB_ERR_OPERATIONS_ERROR; + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); } - ac->dom_req->op.search.tree = ldb_parse_tree(ac->dom_req, filter); - if (ac->dom_req->op.search.tree == NULL) { - ldb_set_errstring(ac->module->ldb, "Invalid search filter"); - talloc_free(ac->dom_req); - return LDB_ERR_OPERATIONS_ERROR; + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); } - ac->dom_req->op.search.attrs = attrs; - ac->dom_req->controls = NULL; - ac->dom_req->context = ac; - ac->dom_req->callback = get_domain_data_callback; - ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->dom_req); - return LDB_SUCCESS; + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); } -static struct domain_data *get_domain_data(struct ldb_module *module, void *ctx, struct ldb_reply *res) +static int password_hash_add_do_add(struct ph_context *ac); +static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares); +static int password_hash_mod_search_self(struct ph_context *ac); +static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares); +static int password_hash_mod_do_mod(struct ph_context *ac); + +static int get_domain_data_callback(struct ldb_request *req, + struct ldb_reply *ares) { + struct ldb_context *ldb; struct domain_data *data; - const char *tmp; struct ph_context *ac; - char *p; + struct loadparm_context *lp_ctx; + int ret; - ac = talloc_get_type(ctx, struct ph_context); + ac = talloc_get_type(req->context, struct ph_context); + ldb = ldb_module_get_ctx(ac->module); - data = talloc_zero(ac, struct domain_data); - if (data == NULL) { - return NULL; + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); } - - if (res == NULL) { - ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Could not find this user's domain: %s!\n", dom_sid_string(data, ac->domain_sid)); - talloc_free(data); - return NULL; + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); } - data->pwdProperties= samdb_result_uint(res->message, "pwdProperties", 0); - data->store_cleartext = data->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT; - data->pwdHistoryLength = samdb_result_uint(res->message, "pwdHistoryLength", 0); + switch (ares->type) { + case LDB_REPLY_ENTRY: + if (ac->domain != NULL) { + ldb_set_errstring(ldb, "Too many results"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } - /* For a domain DN, this puts things in dotted notation */ - /* For builtin domains, this will give details for the host, - * but that doesn't really matter, as it's just used for salt - * and kerberos principals, which don't exist here */ + data = talloc_zero(ac, struct domain_data); + if (data == NULL) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } - tmp = ldb_dn_canonical_string(ctx, res->message->dn); - if (!tmp) { - return NULL; - } - - /* But it puts a trailing (or just before 'builtin') / on things, so kill that */ - p = strchr(tmp, '/'); - if (p) { - p[0] = '\0'; - } + data->pwdProperties = samdb_result_uint(ares->message, "pwdProperties", 0); + data->store_cleartext = data->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT; + data->pwdHistoryLength = samdb_result_uint(ares->message, "pwdHistoryLength", 0); - if (tmp != NULL) { - data->dns_domain = strlower_talloc(data, tmp); - if (data->dns_domain == NULL) { - ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n"); - return NULL; - } - data->realm = strupper_talloc(data, tmp); - if (data->realm == NULL) { - ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n"); - return NULL; - } - p = strchr(tmp, '.'); - if (p) { - p[0] = '\0'; + /* For a domain DN, this puts things in dotted notation */ + /* For builtin domains, this will give details for the host, + * but that doesn't really matter, as it's just used for salt + * and kerberos principals, which don't exist here */ + + lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + + data->dns_domain = lp_dnsdomain(lp_ctx); + data->realm = lp_realm(lp_ctx); + data->netbios_domain = lp_sam_name(lp_ctx); + + ac->domain = data; + break; + + case LDB_REPLY_DONE: + + /* call the next step */ + switch (ac->req->operation) { + case LDB_ADD: + ret = password_hash_add_do_add(ac); + break; + + case LDB_MODIFY: + ret = password_hash_mod_do_mod(ac); + break; + + default: + ret = LDB_ERR_OPERATIONS_ERROR; + break; } - data->netbios_domain = strupper_talloc(data, tmp); - if (data->netbios_domain == NULL) { - ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n"); - return NULL; + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; } - return data; + talloc_free(ares); + return LDB_SUCCESS; +} + +static int build_domain_data_request(struct ph_context *ac) +{ + /* attrs[] is returned from this function in + ac->dom_req->op.search.attrs, so it must be static, as + otherwise the compiler can put it on the stack */ + struct ldb_context *ldb; + static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", NULL }; + + ldb = ldb_module_get_ctx(ac->module); + + return ldb_build_search_req(&ac->dom_req, ldb, ac, + ldb_get_default_basedn(ldb), + LDB_SCOPE_BASE, + NULL, attrs, + NULL, + ac, get_domain_data_callback, + ac->req); } static int password_hash_add(struct ldb_module *module, struct ldb_request *req) { - struct ldb_handle *h; + struct ldb_context *ldb; 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; - ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add\n"); + ldb = ldb_module_get_ctx(module); + + ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_add\n"); if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */ return ldb_next_request(module, req); } /* If the caller is manipulating the local passwords directly, let them pass */ - if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE), + if (ldb_dn_compare_base(ldb_dn_new(req, ldb, LOCAL_BASE), req->op.add.message->dn) == 0) { return ldb_next_request(module, req); } - /* nobody must touch this fields */ + /* nobody must touch these fields */ if (ldb_msg_find_element(req->op.add.message, "ntPwdHistory")) { return LDB_ERR_UNWILLING_TO_PERFORM; } @@ -1322,149 +1683,122 @@ static int password_hash_add(struct ldb_module *module, struct ldb_request *req) return LDB_ERR_UNWILLING_TO_PERFORM; } - /* If no part of this ADD touches the sambaPassword, or the NT + /* If no part of this ADD touches the userPassword, or the NT * or LM hashes, then we don't need to make any changes. */ - sambaAttr = ldb_msg_find_element(req->op.mod.message, "sambaPassword"); + 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"); - if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) { + if ((!sambaAttr) && (!clearTextPasswordAttr) && (!ntAttr) && (!lmAttr)) { return ldb_next_request(module, req); } /* if it is not an entry of type person its an error */ - /* TODO: remove this when sambaPassword will be in schema */ + /* TODO: remove this when userPassword will be in schema */ if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) { - ldb_set_errstring(module->ldb, "Cannot set a password on entry that does not have objectClass 'person'"); + ldb_set_errstring(ldb, "Cannot set a password on entry that does not have objectClass 'person'"); return LDB_ERR_OBJECT_CLASS_VIOLATION; } - /* check sambaPassword is single valued here */ - /* TODO: remove this when sambaPassword will be single valued in schema */ + /* check userPassword is single valued here */ + /* TODO: remove this when userPassword will be single valued in schema */ if (sambaAttr && sambaAttr->num_values > 1) { - ldb_set_errstring(module->ldb, "mupltiple values for sambaPassword not allowed!\n"); + ldb_set_errstring(ldb, "mupltiple values for userPassword not allowed!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values > 1) { + ldb_set_errstring(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"); + ldb_set_errstring(ldb, "mupltiple values for unicodePwd not allowed!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; } if (lmAttr && (lmAttr->num_values > 1)) { - ldb_set_errstring(module->ldb, "mupltiple values for dBCSPwd not allowed!\n"); + ldb_set_errstring(ldb, "mupltiple values for dBCSPwd not allowed!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; } if (sambaAttr && sambaAttr->num_values == 0) { - ldb_set_errstring(module->ldb, "sambaPassword must have a value!\n"); + ldb_set_errstring(ldb, "userPassword must have a value!\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (clearTextPasswordAttr && clearTextPasswordAttr->num_values == 0) { + ldb_set_errstring(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"); + ldb_set_errstring(ldb, "unicodePwd must have a value!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; } if (lmAttr && (lmAttr->num_values == 0)) { - ldb_set_errstring(module->ldb, "dBCSPwd must have a value!\n"); + ldb_set_errstring(ldb, "dBCSPwd must have a value!\n"); return LDB_ERR_CONSTRAINT_VIOLATION; } - h = ph_init_handle(req, module, PH_ADD); - if (!h) { + ac = ph_init_context(module, req); + if (ac == NULL) { return LDB_ERR_OPERATIONS_ERROR; } - ac = talloc_get_type(h->private_data, struct ph_context); /* get user domain data */ - ac->domain_sid = samdb_result_sid_prefix(ac, req->op.add.message, "objectSid"); - if (ac->domain_sid == NULL) { - ldb_debug(module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n"); - return LDB_ERR_OPERATIONS_ERROR; - } - ret = build_domain_data_request(ac); if (ret != LDB_SUCCESS) { return ret; } - ac->step = PH_ADD_SEARCH_DOM; - - req->handle = h; - return ldb_next_request(module, ac->dom_req); } -static int password_hash_add_do_add(struct ldb_handle *h) { - - struct ph_context *ac; - struct domain_data *domain; - struct smb_krb5_context *smb_krb5_context; +static int password_hash_add_do_add(struct ph_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *down_req; struct ldb_message *msg; struct setup_password_fields_io io; int ret; - ac = talloc_get_type(h->private_data, struct ph_context); - - domain = get_domain_data(ac->module, ac, ac->dom_res); - if (domain == NULL) { - return LDB_ERR_OPERATIONS_ERROR; - } - - ac->down_req = talloc(ac, struct ldb_request); - if (ac->down_req == NULL) { - return LDB_ERR_OPERATIONS_ERROR; - } - - *(ac->down_req) = *(ac->orig_req); - ac->down_req->op.add.message = msg = ldb_msg_copy_shallow(ac->down_req, ac->orig_req->op.add.message); - if (ac->down_req->op.add.message == NULL) { - return LDB_ERR_OPERATIONS_ERROR; + /* Prepare the internal data structure containing the passwords */ + ret = setup_io(ac, ac->req->op.add.message, ac->req->op.add.message, &io); + if (ret != LDB_SUCCESS) { + return ret; } - /* Some operations below require kerberos contexts */ - if (smb_krb5_init_context(ac->down_req, - ldb_get_opaque(h->module->ldb, "EventContext"), - &smb_krb5_context) != 0) { + msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message); + if (msg == NULL) { return LDB_ERR_OPERATIONS_ERROR; } - ZERO_STRUCT(io); - io.ac = ac; - io.domain = domain; - io.smb_krb5_context = smb_krb5_context; - - io.u.user_account_control = samdb_result_uint(msg, "userAccountControl", 0); - io.u.sAMAccountName = samdb_result_string(msg, "samAccountName", NULL); - 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, "sambaPassword", NULL); - 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, "sambaPassword"); - if (io.n.nt_hash) ldb_msg_remove_attr(msg, "unicodePwd"); - if (io.n.lm_hash) ldb_msg_remove_attr(msg, "dBCSPwd"); + /* remove attributes that we just read into 'io' */ + ldb_msg_remove_attr(msg, "userPassword"); + ldb_msg_remove_attr(msg, "clearTextPassword"); + ldb_msg_remove_attr(msg, "unicodePwd"); + ldb_msg_remove_attr(msg, "dBCSPwd"); ldb_msg_remove_attr(msg, "pwdLastSet"); io.o.kvno = samdb_result_uint(msg, "msDs-KeyVersionNumber", 1) - 1; ldb_msg_remove_attr(msg, "msDs-KeyVersionNumber"); + ldb = ldb_module_get_ctx(ac->module); + ret = setup_password_fields(&io); if (ret != LDB_SUCCESS) { return ret; } if (io.g.nt_hash) { - ret = samdb_msg_add_hash(ac->module->ldb, ac, msg, + ret = samdb_msg_add_hash(ldb, ac, msg, "unicodePwd", io.g.nt_hash); if (ret != LDB_SUCCESS) { return ret; } } if (io.g.lm_hash) { - ret = samdb_msg_add_hash(ac->module->ldb, ac, msg, + ret = samdb_msg_add_hash(ldb, ac, msg, "dBCSPwd", io.g.lm_hash); if (ret != LDB_SUCCESS) { return ret; @@ -1495,49 +1829,53 @@ static int password_hash_add_do_add(struct ldb_handle *h) { return ret; } } - ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg, + ret = samdb_msg_add_uint64(ldb, ac, msg, "pwdLastSet", io.g.last_set); if (ret != LDB_SUCCESS) { return ret; } - ret = samdb_msg_add_uint(ac->module->ldb, ac, msg, + ret = samdb_msg_add_uint(ldb, ac, msg, "msDs-KeyVersionNumber", io.g.kvno); if (ret != LDB_SUCCESS) { return ret; } - h->state = LDB_ASYNC_INIT; - h->status = LDB_SUCCESS; - - ac->step = PH_ADD_DO_ADD; - - ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->down_req); + ret = ldb_build_add_req(&down_req, ldb, ac, + msg, + ac->req->controls, + ac, ph_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } - /* perform the operation */ - return ldb_next_request(ac->module, ac->down_req); + return ldb_next_request(ac->module, down_req); } -static int password_hash_mod_search_self(struct ldb_handle *h); - static int password_hash_modify(struct ldb_module *module, struct ldb_request *req) { - struct ldb_handle *h; + struct ldb_context *ldb; 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; + struct ldb_request *down_req; + int ret; - ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify\n"); + ldb = ldb_module_get_ctx(module); + + ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_modify\n"); if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */ return ldb_next_request(module, req); } /* If the caller is manipulating the local passwords directly, let them pass */ - if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE), + if (ldb_dn_compare_base(ldb_dn_new(req, ldb, LOCAL_BASE), req->op.mod.message->dn) == 0) { return ldb_next_request(module, req); } @@ -1553,85 +1891,134 @@ static int password_hash_modify(struct ldb_module *module, struct ldb_request *r return LDB_ERR_UNWILLING_TO_PERFORM; } - sambaAttr = ldb_msg_find_element(req->op.mod.message, "sambaPassword"); + 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 sambaPassword 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); } /* check passwords are single valued here */ /* TODO: remove this when passwords will be single valued in schema */ if (sambaAttr && (sambaAttr->num_values > 1)) { + DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb))); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + if (clearTextAttr && (clearTextAttr->num_values > 1)) { + DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb))); return LDB_ERR_CONSTRAINT_VIOLATION; } if (ntAttr && (ntAttr->num_values > 1)) { + DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb))); return LDB_ERR_CONSTRAINT_VIOLATION; } if (lmAttr && (lmAttr->num_values > 1)) { + DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb))); return LDB_ERR_CONSTRAINT_VIOLATION; } - h = ph_init_handle(req, module, PH_MOD); - if (!h) { + ac = ph_init_context(module, req); + if (!ac) { + DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb))); return LDB_ERR_OPERATIONS_ERROR; } - ac = talloc_get_type(h->private_data, struct ph_context); - - /* return or own handle to deal with this call */ - req->handle = h; - /* prepare the first operation */ - ac->down_req = talloc_zero(ac, struct ldb_request); - if (ac->down_req == NULL) { - ldb_set_errstring(module->ldb, "Out of memory!"); + /* use a new message structure so that we can modify it */ + msg = ldb_msg_copy_shallow(ac, req->op.mod.message); + if (msg == NULL) { + ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } - *(ac->down_req) = *req; /* copy the request */ - - /* use a new message structure so that we can modify it */ - ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message); - - /* - remove any imodification to the password from the first commit + /* - remove any modification to the password from the first commit * we will make the real modification later */ - if (sambaAttr) ldb_msg_remove_attr(msg, "sambaPassword"); + 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"); - /* if there was nothing else to be modify skip to next step */ + /* if there was nothing else to be modified skip to next step */ if (msg->num_elements == 0) { - talloc_free(ac->down_req); - ac->down_req = NULL; - return password_hash_mod_search_self(h); + return password_hash_mod_search_self(ac); + } + + ret = ldb_build_mod_req(&down_req, ldb, ac, + msg, + req->controls, + ac, ph_modify_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(module, down_req); +} + +static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct ph_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct ph_context); + + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); } - - ac->down_req->context = NULL; - ac->down_req->callback = NULL; - ac->step = PH_MOD_DO_REQ; + if (ares->type != LDB_REPLY_DONE) { + talloc_free(ares); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } - ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req); + ret = password_hash_mod_search_self(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); + } - return ldb_next_request(module, ac->down_req); + talloc_free(ares); + return LDB_SUCCESS; } -static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares) +static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares) { + struct ldb_context *ldb; struct ph_context *ac; + int ret; + + ac = talloc_get_type(req->context, struct ph_context); + ldb = ldb_module_get_ctx(ac->module); - ac = talloc_get_type(context, struct ph_context); + if (!ares) { + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + /* we are interested only in the single reply (base search) */ + switch (ares->type) { + case LDB_REPLY_ENTRY: - /* we are interested only in the single reply (base search) we receive here */ - if (ares->type == LDB_REPLY_ENTRY) { if (ac->search_res != NULL) { ldb_set_errstring(ldb, "Too many results"); talloc_free(ares); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); } /* if it is not an entry of type person this is an error */ @@ -1639,20 +2026,35 @@ static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_ if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) { ldb_set_errstring(ldb, "Object class violation"); talloc_free(ares); - return LDB_ERR_OBJECT_CLASS_VIOLATION; + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OBJECT_CLASS_VIOLATION); } ac->search_res = talloc_steal(ac, ares); - } else { - talloc_free(ares); + return LDB_SUCCESS; + + case LDB_REPLY_DONE: + + /* get user domain data */ + ret = build_domain_data_request(ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL,ret); + } + + return ldb_next_request(ac->module, ac->dom_req); + + case LDB_REPLY_REFERRAL: + /*ignore anything else for now */ + break; } + talloc_free(ares); return LDB_SUCCESS; } -static int password_hash_mod_search_self(struct ldb_handle *h) { - - struct ph_context *ac; +static int password_hash_mod_search_self(struct ph_context *ac) +{ + struct ldb_context *ldb; static const char * const attrs[] = { "userAccountControl", "lmPwdHistory", "ntPwdHistory", "objectSid", "msDS-KeyVersionNumber", @@ -1661,118 +2063,59 @@ static int password_hash_mod_search_self(struct ldb_handle *h) { "dBCSPwd", "unicodePwd", "supplementalCredentials", NULL }; - - ac = talloc_get_type(h->private_data, struct ph_context); - - /* prepare the search operation */ - ac->search_req = talloc_zero(ac, struct ldb_request); - if (ac->search_req == NULL) { - ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n"); - return LDB_ERR_OPERATIONS_ERROR; - } - - ac->search_req->operation = LDB_SEARCH; - ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn; - ac->search_req->op.search.scope = LDB_SCOPE_BASE; - ac->search_req->op.search.tree = ldb_parse_tree(ac->search_req, NULL); - if (ac->search_req->op.search.tree == NULL) { - ldb_set_errstring(ac->module->ldb, "Invalid search filter"); - return LDB_ERR_OPERATIONS_ERROR; - } - ac->search_req->op.search.attrs = attrs; - ac->search_req->controls = NULL; - ac->search_req->context = ac; - ac->search_req->callback = get_self_callback; - ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req); - - ac->step = PH_MOD_SEARCH_SELF; - - return ldb_next_request(ac->module, ac->search_req); -} - -static int password_hash_mod_search_dom(struct ldb_handle *h) { - - struct ph_context *ac; + struct ldb_request *search_req; int ret; - ac = talloc_get_type(h->private_data, struct ph_context); + ldb = ldb_module_get_ctx(ac->module); - /* get object domain sid */ - ac->domain_sid = samdb_result_sid_prefix(ac, ac->search_res->message, "objectSid"); - if (ac->domain_sid == NULL) { - ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n"); - return LDB_ERR_OPERATIONS_ERROR; - } + ret = ldb_build_search_req(&search_req, ldb, ac, + ac->req->op.mod.message->dn, + LDB_SCOPE_BASE, + "(objectclass=*)", + attrs, + NULL, + ac, ph_mod_search_callback, + ac->req); - /* get user domain data */ - ret = build_domain_data_request(ac); if (ret != LDB_SUCCESS) { return ret; } - ac->step = PH_MOD_SEARCH_DOM; - - return ldb_next_request(ac->module, ac->dom_req); + return ldb_next_request(ac->module, search_req); } -static int password_hash_mod_do_mod(struct ldb_handle *h) { - - struct ph_context *ac; - struct domain_data *domain; - struct smb_krb5_context *smb_krb5_context; +static int password_hash_mod_do_mod(struct ph_context *ac) +{ + struct ldb_context *ldb; + struct ldb_request *mod_req; struct ldb_message *msg; - struct ldb_message *orig_msg; - struct ldb_message *searched_msg; + const struct ldb_message *searched_msg; struct setup_password_fields_io io; int ret; - ac = talloc_get_type(h->private_data, struct ph_context); - - domain = get_domain_data(ac->module, ac, ac->dom_res); - if (domain == NULL) { - return LDB_ERR_OPERATIONS_ERROR; - } + ldb = ldb_module_get_ctx(ac->module); - ac->mod_req = talloc(ac, struct ldb_request); - if (ac->mod_req == NULL) { - return LDB_ERR_OPERATIONS_ERROR; - } - - *(ac->mod_req) = *(ac->orig_req); - /* use a new message structure so that we can modify it */ - ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req); + msg = ldb_msg_new(ac); if (msg == NULL) { return LDB_ERR_OPERATIONS_ERROR; } /* modify dn */ - msg->dn = ac->orig_req->op.mod.message->dn; + msg->dn = ac->req->op.mod.message->dn; - /* Some operations below require kerberos contexts */ - if (smb_krb5_init_context(ac->mod_req, - ldb_get_opaque(h->module->ldb, "EventContext"), - &smb_krb5_context) != 0) { - return LDB_ERR_OPERATIONS_ERROR; + /* Prepare the internal data structure containing the passwords */ + ret = setup_io(ac, + ac->req->op.mod.message, + ac->search_res->message, + &io); + if (ret != LDB_SUCCESS) { + return ret; } + + searched_msg = ac->search_res->message; - orig_msg = discard_const(ac->orig_req->op.mod.message); - searched_msg = ac->search_res->message; - - ZERO_STRUCT(io); - io.ac = ac; - io.domain = domain; - io.smb_krb5_context = smb_krb5_context; - - io.u.user_account_control = samdb_result_uint(searched_msg, "userAccountControl", 0); - io.u.sAMAccountName = samdb_result_string(searched_msg, "samAccountName", NULL); - 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, "sambaPassword", NULL); - io.n.nt_hash = samdb_result_hash(io.ac, orig_msg, "unicodePwd"); - io.n.lm_hash = samdb_result_hash(io.ac, orig_msg, "dBCSPwd"); - + /* Fill in some final details (only relevent once the password has been set) */ io.o.kvno = samdb_result_uint(searched_msg, "msDs-KeyVersionNumber", 0); io.o.nt_history_len = samdb_result_hashes(io.ac, searched_msg, "ntPwdHistory", &io.o.nt_history); io.o.lm_history_len = samdb_result_hashes(io.ac, searched_msg, "lmPwdHistory", &io.o.lm_history); @@ -1793,14 +2136,14 @@ static int password_hash_mod_do_mod(struct ldb_handle *h) { ret = ldb_msg_add_empty(msg, "msDs-KeyVersionNumber", LDB_FLAG_MOD_REPLACE, NULL); if (io.g.nt_hash) { - ret = samdb_msg_add_hash(ac->module->ldb, ac, msg, + ret = samdb_msg_add_hash(ldb, ac, msg, "unicodePwd", io.g.nt_hash); if (ret != LDB_SUCCESS) { return ret; } } if (io.g.lm_hash) { - ret = samdb_msg_add_hash(ac->module->ldb, ac, msg, + ret = samdb_msg_add_hash(ldb, ac, msg, "dBCSPwd", io.g.lm_hash); if (ret != LDB_SUCCESS) { return ret; @@ -1831,208 +2174,33 @@ static int password_hash_mod_do_mod(struct ldb_handle *h) { return ret; } } - ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg, + ret = samdb_msg_add_uint64(ldb, ac, msg, "pwdLastSet", io.g.last_set); if (ret != LDB_SUCCESS) { return ret; } - ret = samdb_msg_add_uint(ac->module->ldb, ac, msg, + ret = samdb_msg_add_uint(ldb, ac, msg, "msDs-KeyVersionNumber", io.g.kvno); if (ret != LDB_SUCCESS) { return ret; } - h->state = LDB_ASYNC_INIT; - h->status = LDB_SUCCESS; - - ac->step = PH_MOD_DO_MOD; - - ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->mod_req); - - /* perform the search */ - return ldb_next_request(ac->module, ac->mod_req); -} - -static int ph_wait(struct ldb_handle *handle) { - struct ph_context *ac; - int ret; - - if (!handle || !handle->private_data) { - return LDB_ERR_OPERATIONS_ERROR; - } - - if (handle->state == LDB_ASYNC_DONE) { - return handle->status; - } - - handle->state = LDB_ASYNC_PENDING; - handle->status = LDB_SUCCESS; - - ac = talloc_get_type(handle->private_data, struct ph_context); - - switch (ac->step) { - case PH_ADD_SEARCH_DOM: - ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->dom_req->handle->status != LDB_SUCCESS) { - handle->status = ac->dom_req->handle->status; - goto done; - } - - if (ac->dom_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - /* domain search done, go on */ - return password_hash_add_do_add(handle); - - case PH_ADD_DO_ADD: - ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->down_req->handle->status != LDB_SUCCESS) { - handle->status = ac->down_req->handle->status; - goto done; - } - - if (ac->down_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - break; - - case PH_MOD_DO_REQ: - ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->down_req->handle->status != LDB_SUCCESS) { - handle->status = ac->down_req->handle->status; - goto done; - } - - if (ac->down_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - /* non-password mods done, go on */ - return password_hash_mod_search_self(handle); - - case PH_MOD_SEARCH_SELF: - ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->search_req->handle->status != LDB_SUCCESS) { - handle->status = ac->search_req->handle->status; - goto done; - } - - if (ac->search_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - if (ac->search_res == NULL) { - return LDB_ERR_NO_SUCH_OBJECT; - } - - /* self search done, go on */ - return password_hash_mod_search_dom(handle); - - case PH_MOD_SEARCH_DOM: - ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->dom_req->handle->status != LDB_SUCCESS) { - handle->status = ac->dom_req->handle->status; - goto done; - } - - if (ac->dom_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - /* domain search done, go on */ - return password_hash_mod_do_mod(handle); - - case PH_MOD_DO_MOD: - ret = ldb_wait(ac->mod_req->handle, LDB_WAIT_NONE); - - if (ret != LDB_SUCCESS) { - handle->status = ret; - goto done; - } - if (ac->mod_req->handle->status != LDB_SUCCESS) { - handle->status = ac->mod_req->handle->status; - goto done; - } - - if (ac->mod_req->handle->state != LDB_ASYNC_DONE) { - return LDB_SUCCESS; - } - - break; - - default: - ret = LDB_ERR_OPERATIONS_ERROR; - goto done; - } - - ret = LDB_SUCCESS; - -done: - handle->state = LDB_ASYNC_DONE; - return ret; -} - -static int ph_wait_all(struct ldb_handle *handle) { - - int ret; - - while (handle->state != LDB_ASYNC_DONE) { - ret = ph_wait(handle); - if (ret != LDB_SUCCESS) { - return ret; - } + ret = ldb_build_mod_req(&mod_req, ldb, ac, + msg, + ac->req->controls, + ac, ph_op_callback, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; } - return handle->status; + return ldb_next_request(ac->module, mod_req); } -static int password_hash_wait(struct ldb_handle *handle, enum ldb_wait_type type) -{ - if (type == LDB_WAIT_ALL) { - return ph_wait_all(handle); - } else { - return ph_wait(handle); - } -} - -static const struct ldb_module_ops password_hash_ops = { +_PUBLIC_ const struct ldb_module_ops ldb_password_hash_module_ops = { .name = "password_hash", .add = password_hash_add, .modify = password_hash_modify, - .wait = password_hash_wait }; - - -int password_hash_module_init(void) -{ - return ldb_register_module(&password_hash_ops); -}