From f5e0e480e1d080fc62f22b07cab41aa78e9b54a8 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Jul 2016 14:47:21 +1200 Subject: [PATCH] HACK: DSDB_LDAP_SERVER_POLICY_HINTS_OID Signed-off-by: Andrew Bartlett --- lib/ldb/common/ldb_controls.c | 26 +++++++++++ lib/ldb/include/ldb.h | 7 +++ source4/dsdb/pydsdb.c | 2 + source4/dsdb/samdb/ldb_modules/password_hash.c | 48 ++++++++++++++++++- source4/dsdb/tests/python/passwords.py | 42 +++++++++++++++++ source4/libcli/ldap/ldap_controls.c | 64 ++++++++++++++++++++++++++ 6 files changed, 187 insertions(+), 2 deletions(-) diff --git a/lib/ldb/common/ldb_controls.c b/lib/ldb/common/ldb_controls.c index a83768a..4db2808 100644 --- a/lib/ldb/common/ldb_controls.c +++ b/lib/ldb/common/ldb_controls.c @@ -755,6 +755,32 @@ struct ldb_control *ldb_parse_control_from_string(struct ldb_context *ldb, TALLO return ctrl; } + if (LDB_CONTROL_CMP(control_strings, DSDB_LDAP_SERVER_POLICY_HINTS_NAME) == 0) { + struct dsdb_pwd_policy_hints_control *control; + const char *p; + int crit, ret; + unsigned flags; + + p = &(control_strings[sizeof(DSDB_LDAP_SERVER_POLICY_HINTS_NAME)]); + ret = sscanf(p, "%d:%u", &crit, &flags); + if ((ret != 2) || (crit < 0) || (crit > 1) || (flags > 0xF)) { + ldb_set_errstring(ldb, + "invalid pwd_policy_hints control syntax\n" + " syntax: crit(b):flags(n)\n" + " note: b = boolean, n = number"); + talloc_free(ctrl); + return NULL; + } + + ctrl->oid = DSDB_LDAP_SERVER_POLICY_HINTS_OID; + ctrl->critical = crit; + control = talloc(ctrl, struct dsdb_pwd_policy_hints_control); + control->flags = flags; + ctrl->data = control; + + return ctrl; + } + if (LDB_CONTROL_CMP(control_strings, LDB_CONTROL_BYPASS_OPERATIONAL_NAME) == 0) { const char *p; int crit, ret; diff --git a/lib/ldb/include/ldb.h b/lib/ldb/include/ldb.h index 7422d46..6f06a13 100644 --- a/lib/ldb/include/ldb.h +++ b/lib/ldb/include/ldb.h @@ -866,6 +866,13 @@ struct ldb_verify_name_control { char *gc; }; +#define DSDB_LDAP_SERVER_POLICY_HINTS_NAME "pwd_policy_hints" +#define DSDB_LDAP_SERVER_POLICY_HINTS_OID "1.2.840.113556.1.4.2239" +#define DSDB_LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID "1.2.840.113556.1.4.2066" +struct dsdb_pwd_policy_hints_control { + int flags; +}; + struct ldb_control { const char *oid; int critical; diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index efaf66b..0aca15e 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -1326,6 +1326,8 @@ void initdsdb(void) ADD_DSDB_STRING(DSDB_CONTROL_REPLMD_VANISH_LINKS); ADD_DSDB_STRING(DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID); ADD_DSDB_STRING(DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID); + ADD_DSDB_STRING(DSDB_LDAP_SERVER_POLICY_HINTS_OID); + ADD_DSDB_STRING(DSDB_LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID); ADD_DSDB_STRING(DS_GUID_COMPUTERS_CONTAINER); ADD_DSDB_STRING(DS_GUID_DELETED_OBJECTS_CONTAINER); diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index d52ad2d..1f2cbb8 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -102,6 +102,7 @@ struct ph_context { bool update_lastset; bool pwd_last_set_bypass; bool pwd_last_set_default; + bool pwd_policy_hints_control; }; @@ -2220,7 +2221,7 @@ static int check_password_restrictions(struct setup_password_fields_io *io) } } - if (io->ac->pwd_reset) { + if (io->ac->pwd_reset && !io->ac->pwd_policy_hints_control) { return LDB_SUCCESS; } @@ -2262,6 +2263,10 @@ static int check_password_restrictions(struct setup_password_fields_io *io) } } + if (io->ac->pwd_reset) { + return LDB_SUCCESS; + } + /* are all password changes disallowed? */ if (io->ac->status->domain_data.pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { ret = LDB_ERR_CONSTRAINT_VIOLATION; @@ -3024,6 +3029,20 @@ static void ph_apply_controls(struct ph_context *ac) /* Mark the "bypass pwdLastSet" control as uncritical (done) */ ctrl->critical = false; } + + ac->pwd_policy_hints_control = false; + ctrl = ldb_request_get_control(ac->req, + DSDB_LDAP_SERVER_POLICY_HINTS_OID); + if (ctrl == NULL) { + ctrl = ldb_request_get_control(ac->req, + DSDB_LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID); + } + if (ctrl != NULL) { + ac->pwd_policy_hints_control = true; + + /* Mark the "apply password policy hints" control as uncritical (done) */ + ctrl->critical = false; + } } static int ph_op_callback(struct ldb_request *req, struct ldb_reply *ares) @@ -3783,10 +3802,35 @@ static int password_hash_mod_do_mod(struct ph_context *ac) return ldb_next_request(ac->module, mod_req); } +static int password_hash_init(struct ldb_module *module) +{ + struct ldb_context *ldb; + int ret; + + ldb = ldb_module_get_ctx(module); + + ret = ldb_mod_register_control(module, DSDB_LDAP_SERVER_POLICY_HINTS_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "password_hash: Unable to register control with rootdse!\n"); + return ldb_operr(ldb); + } + + ret = ldb_mod_register_control(module, DSDB_LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "password_hash: Unable to register control with rootdse!\n"); + return ldb_operr(ldb); + } + + return ldb_next_init(module); +} + static const struct ldb_module_ops ldb_password_hash_module_ops = { .name = "password_hash", .add = password_hash_add, - .modify = password_hash_modify + .modify = password_hash_modify, + .init_context = password_hash_init }; int ldb_password_hash_module_init(const char *version) diff --git a/source4/dsdb/tests/python/passwords.py b/source4/dsdb/tests/python/passwords.py index db013ea..1e2767c 100755 --- a/source4/dsdb/tests/python/passwords.py +++ b/source4/dsdb/tests/python/passwords.py @@ -34,6 +34,7 @@ from samba import gensec from samba.samdb import SamDB import samba.tests from samba.tests import delete_force +from samba import dsdb parser = optparse.OptionParser("passwords.py [options] ") sambaopts = options.SambaOptions(parser) @@ -243,6 +244,47 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) + def test_unicodePwd_clear_change_with_hints(self): + """Performs a password cleartext reset operation on 'unicodePwd', + but expect failure due to history""" + + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ +""") + + # Wrong old password + try: + self.ldb2.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')) + """ +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg) + + # A change to the same password again will not work (password history) + try: + self.ldb.modify_ldif(""" +dn: cn=testuser,cn=users,""" + self.base_dn + """ +changetype: modify +replace: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ +""", controls=["pwd_policy_hints:1:1"]) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('0000052D' in msg) + def test_dBCSPwd_hash_set(self): """Performs a password hash set operation on 'dBCSPwd' which should be prevented""" # Notice: Direct hash password sets should never work diff --git a/source4/libcli/ldap/ldap_controls.c b/source4/libcli/ldap/ldap_controls.c index 9df95c3..e368c34 100644 --- a/source4/libcli/ldap/ldap_controls.c +++ b/source4/libcli/ldap/ldap_controls.c @@ -263,6 +263,40 @@ static bool decode_search_options_request(void *mem_ctx, DATA_BLOB in, void *_ou return true; } +static bool decode_pwd_policy_hints_request(void *mem_ctx, DATA_BLOB in, void *_out) +{ + void **out = (void **)_out; + struct asn1_data *data = asn1_init(mem_ctx); + struct dsdb_pwd_policy_hints_control *lsoc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lsoc = talloc(mem_ctx, struct dsdb_pwd_policy_hints_control); + if (!lsoc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, (int *) &(lsoc->flags))) { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lsoc; + + return true; +} + static bool decode_paged_results_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; @@ -863,6 +897,34 @@ static bool encode_search_options_request(void *mem_ctx, void *in, DATA_BLOB *ou return true; } +static bool encode_pwd_policy_hints_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct dsdb_pwd_policy_hints_control *lsoc = talloc_get_type(in, struct dsdb_pwd_policy_hints_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lsoc->flags)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + if (!asn1_extract_blob(data, mem_ctx, out)) { + return false; + } + + talloc_free(data); + + return true; +} + static bool encode_paged_results_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_paged_control *lprc = talloc_get_type(in, struct ldb_paged_control); @@ -1246,6 +1308,8 @@ static const struct ldap_control_handler ldap_known_controls[] = { { LDB_CONTROL_RELAX_OID, decode_flag_request, encode_flag_request }, { DSDB_OPENLDAP_DEREFERENCE_CONTROL, decode_openldap_dereference, encode_openldap_dereference }, { LDB_CONTROL_VERIFY_NAME_OID, decode_verify_name_request, encode_verify_name_request }, + { DSDB_LDAP_SERVER_POLICY_HINTS_OID, decode_pwd_policy_hints_request, encode_pwd_policy_hints_request }, + { DSDB_LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID, decode_pwd_policy_hints_request, encode_pwd_policy_hints_request }, /* the following are internal only, with a network representation */ -- 2.8.1