From: Douglas Bagnall Date: Wed, 24 Sep 2025 23:45:30 +0000 (+1200) Subject: ldb: add "policy hints" controls to be used by password_hash module X-Git-Tag: tdb-1.4.15~99 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b003beb85a648eae5bfe7e38362abd8d798e8f86;p=thirdparty%2Fsamba.git ldb: add "policy hints" controls to be used by password_hash module These won't have any effect yet, but soon they will allow a privileged account to perform a password reset that respects constraints on password history, age, and length, as if the reset was an ordinary password change (that is, where the user provides the old password). A normal user can't reset their own password using this, if the organisation is using a remote service (e.g. Entra ID or Keycloak) to manage passwords, that service can use a policy hints control to ensure it follows AD password policy. Entra ID Self Service Password Reset (SSPR) uses the deprecated OID. BUG: https://bugzilla.samba.org/show_bug.cgi?id=12020 Signed-off-by: Douglas Bagnall Reviewed-by: Gary Lockyer --- diff --git a/lib/ldb/common/ldb_controls.c b/lib/ldb/common/ldb_controls.c index 3bf89d844f9..a80c89964c3 100644 --- a/lib/ldb/common/ldb_controls.c +++ b/lib/ldb/common/ldb_controls.c @@ -1296,6 +1296,52 @@ struct ldb_control *ldb_parse_control_from_string(struct ldb_context *ldb, TALLO ctrl->data = control; return ctrl; } + + if (LDB_CONTROL_CMP(control_strings, LDB_CONTROL_POLICY_HINTS_NAME) == 0) { + const char *p = NULL; + int crit, val, ret; + + p = &(control_strings[sizeof(LDB_CONTROL_POLICY_HINTS_NAME)]); + ret = sscanf(p, "%d:%d", &crit, &val); + if ((ret != 2) || (crit < 0) || (crit > 1)) { + 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 = LDB_CONTROL_POLICY_HINTS_OID; + ctrl->critical = crit; + ctrl->data = talloc(ctrl, int); + *((int*)ctrl->data) = val; + return ctrl; + } + + if (LDB_CONTROL_CMP(control_strings, LDB_CONTROL_POLICY_HINTS_DEPRECATED_NAME) == 0) { + const char *p = NULL; + int crit, val, ret; + + p = &(control_strings[sizeof(LDB_CONTROL_POLICY_HINTS_DEPRECATED_NAME)]); + ret = sscanf(p, "%d:%d", &crit, &val); + if ((ret != 2) || (crit < 0) || (crit > 1)) { + 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 = LDB_CONTROL_POLICY_HINTS_DEPRECATED_OID; + ctrl->critical = crit; + ctrl->data = talloc(ctrl, int); + *((int*)ctrl->data) = val; + return ctrl; + } + + /* * When no matching control has been found. */ diff --git a/lib/ldb/include/ldb.h b/lib/ldb/include/ldb.h index 0d1d455d0cf..4cd6ada194e 100644 --- a/lib/ldb/include/ldb.h +++ b/lib/ldb/include/ldb.h @@ -808,6 +808,36 @@ typedef int (*ldb_qsort_cmp_fn_t) (void *v1, void *v2, void *opaque); */ #define LDB_EXTENDED_WHOAMI_OID "1.3.6.1.4.1.4203.1.11.3" +/** + OID used to enforce password history length constraints in password reset + + If this is control as the value 0x1 on a password set operation, + the password history constraints in MS-SAMR 3.1.1.7.1 "General + Password Policy" apply as if this was a password change. This may + be used by Entra ID or Keycloak. + + LDB_CONTROL_POLICY_HINTS_DEPRECATED_OID (below) does exactly the + same thing and seems to be more widely used in practice. +*/ +#define LDB_CONTROL_POLICY_HINTS_OID "1.2.840.113556.1.4.2239" +#define LDB_CONTROL_POLICY_HINTS_NAME "policy_hints" + +/** + Another OID used to enforce password history length constraints in password reset + + This works just like LDB_CONTROL_POLICY_HINTS_OID (above): if the + control value is 0x1 on a password set operation, the password + history constraints in MS-SAMR 3.1.1.7.1 "General Password Policy" + apply as if this was a password change. + + This is used by Entra ID in a password change. + + It is also the OID for the ms-DS-Required-Domain-Behavior-Version + attribute), which is unlikely to cause confusion given the contexts. +*/ +#define LDB_CONTROL_POLICY_HINTS_DEPRECATED_OID "1.2.840.113556.1.4.2066" +#define LDB_CONTROL_POLICY_HINTS_DEPRECATED_NAME "policy_hints_deprecated" + struct ldb_sd_flags_control { /* * request the owner 0x00000001 diff --git a/source4/libcli/ldap/ldap_controls.c b/source4/libcli/ldap/ldap_controls.c index 8d3a40f602e..a38c0846ab7 100644 --- a/source4/libcli/ldap/ldap_controls.c +++ b/source4/libcli/ldap/ldap_controls.c @@ -684,6 +684,42 @@ static bool decode_vlv_response(void *mem_ctx, DATA_BLOB in, void *_out) return true; } +static bool decode_policy_hints_request(void *mem_ctx, DATA_BLOB in, void *_out) +{ + void **out = (void **)_out; + int *valp = NULL; + int val; + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + if (!data) { + return false; + } + + if (!asn1_load(data, in)) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &val)) { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + valp = talloc(mem_ctx, int); + if (valp == NULL) { + return false; + } + *valp = val; + *out = valp; + + return true; +} + static bool encode_server_sort_response(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_sort_resp_control *lsrc = talloc_get_type(in, struct ldb_sort_resp_control); @@ -1208,6 +1244,40 @@ static bool decode_openldap_dereference(void *mem_ctx, DATA_BLOB in, void *_out) return true; } + + +static bool encode_policy_hints_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + int *val = talloc_get_type(in, int); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + if (!data) { + return false; + } + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, *val)) { + 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_flag_request(void *mem_ctx, void *in, DATA_BLOB *out) { if (in) { @@ -1251,6 +1321,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 }, + { LDB_CONTROL_POLICY_HINTS_OID, decode_policy_hints_request, encode_policy_hints_request }, + { LDB_CONTROL_POLICY_HINTS_DEPRECATED_OID, decode_policy_hints_request, encode_policy_hints_request }, /* the following are internal only, with a network representation */