struct ldb_request *dom_req;
struct ldb_reply *dom_res;
+ struct ldb_reply *pso_res;
+
struct ldb_reply *search_res;
struct ldb_message *update_msg;
NTTIME pwdLastSet;
const char *sAMAccountName;
const char *user_principal_name;
- bool is_computer;
bool is_krbtgt;
uint32_t restrictions;
struct dom_sid *account_sid;
{
struct ldb_context *ldb;
krb5_error_code krb5_ret;
- krb5_principal salt_principal;
+ char *salt_principal = NULL;
+ char *salt_data = NULL;
krb5_data salt;
krb5_keyblock key;
krb5_data cleartext_data;
+ uint32_t uac_flags = 0;
ldb = ldb_module_get_ctx(io->ac->module);
cleartext_data.data = (char *)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
- * samba-technical@lists.samba.org */
-
- /*
- * Determine a salting principal
- */
- if (io->u.is_computer) {
- char *name;
- char *saltbody;
-
- name = strlower_talloc(io->ac, io->u.sAMAccountName);
- if (!name) {
- return ldb_oom(ldb);
- }
-
- if (name[strlen(name)-1] == '$') {
- name[strlen(name)-1] = '\0';
- }
-
- saltbody = talloc_asprintf(io->ac, "%s.%s", name,
- io->ac->status->domain_data.dns_domain);
- if (!saltbody) {
- return ldb_oom(ldb);
- }
-
- krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
- &salt_principal,
- io->ac->status->domain_data.realm,
- "host", saltbody, NULL);
- } else if (io->u.user_principal_name) {
- char *user_principal_name;
- char *p;
-
- user_principal_name = talloc_strdup(io->ac, io->u.user_principal_name);
- if (!user_principal_name) {
- return ldb_oom(ldb);
- }
-
- p = strchr(user_principal_name, '@');
- if (p) {
- p[0] = '\0';
- }
-
- krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
- &salt_principal,
- io->ac->status->domain_data.realm,
- user_principal_name, NULL);
- } else {
- krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
- &salt_principal,
- io->ac->status->domain_data.realm,
- io->u.sAMAccountName, NULL);
- }
+ uac_flags = io->u.userAccountControl & UF_ACCOUNT_TYPE_MASK;
+ krb5_ret = smb_krb5_salt_principal(io->ac->status->domain_data.realm,
+ io->u.sAMAccountName,
+ io->u.user_principal_name,
+ uac_flags,
+ io->ac,
+ &salt_principal);
if (krb5_ret) {
ldb_asprintf_errstring(ldb,
"setup_kerberos_keys: "
/*
* create salt from salt_principal
*/
- krb5_ret = smb_krb5_get_pw_salt(io->smb_krb5_context->krb5_context,
- salt_principal, &salt);
- krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal);
+ krb5_ret = smb_krb5_salt_principal2data(io->smb_krb5_context->krb5_context,
+ salt_principal, io->ac, &salt_data);
if (krb5_ret) {
ldb_asprintf_errstring(ldb,
"setup_kerberos_keys: "
krb5_ret, io->ac));
return LDB_ERR_OPERATIONS_ERROR;
}
- /* create a talloc copy */
- io->g.salt = talloc_strndup(io->ac,
- (char *)salt.data,
- salt.length);
- smb_krb5_free_data_contents(io->smb_krb5_context->krb5_context, &salt);
- if (!io->g.salt) {
- return ldb_oom(ldb);
- }
+ io->g.salt = salt_data;
+
/* now use the talloced copy of the salt */
salt.data = discard_const(io->g.salt);
salt.length = strlen(io->g.salt);
gret = gpgme_get_key(ctx, key_id, &keys[ki], 0 /* public key */);
if (gret != GPG_ERR_NO_ERROR) {
keys[ki] = NULL;
- ldb_debug(ldb, LDB_DEBUG_ERROR,
- "%s:%s: ki[%zu] key_id[%s] gret[%u] %s\n",
- __location__, __func__,
- ki, key_id,
- gret, gpgme_strerror(gret));
+ if (gpg_err_source(gret) == GPG_ERR_SOURCE_GPGME
+ && gpg_err_code(gret) == GPG_ERR_EOF) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Invalid "
+ "'password hash gpg key ids': "
+ "Public Key ID [%s] "
+ "not found in keyring\n",
+ key_id);
+
+ } else {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: ki[%zu] key_id[%s] "
+ "gret[%u] %s\n",
+ __location__, __func__,
+ ki, key_id,
+ gret, gpgme_strerror(gret));
+ }
for (kr = 0; keys[kr] != NULL; kr++) {
gpgme_key_release(keys[kr]);
}
if (!io->ac->update_password) {
break;
}
- /* fall through */
+ FALL_THROUGH;
case UINT64_MAX:
if (!io->ac->update_password &&
io->u.pwdLastSet != 0 &&
{
struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
struct ldb_message *mod_msg = NULL;
+ struct ldb_message *pso_msg = NULL;
NTSTATUS status;
int ret;
+ /* PSO search result is optional (NULL if no PSO applies) */
+ if (io->ac->pso_res != NULL) {
+ pso_msg = io->ac->pso_res->message;
+ }
+
status = dsdb_update_bad_pwd_count(io->ac, ldb,
io->ac->search_res->message,
io->ac->dom_res->message,
+ pso_msg,
&mod_msg);
if (!NT_STATUS_IS_OK(status)) {
goto done;
}
log_authentication_event(msg_ctx,
lp_ctx,
+ NULL,
&ui,
status,
domain_name,
info_msg = client_msg;
}
- if (smb_krb5_init_context(ac,
+ ret = smb_krb5_init_context(ac,
(struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"),
- &io->smb_krb5_context) != 0) {
- return ldb_operr(ldb);
+ &io->smb_krb5_context);
+
+ if (ret != 0) {
+ /*
+ * In the special case of mit krb5.conf vs heimdal, the includedir
+ * statement causes ret == 22 (KRB5_CONFIG_BADFORMAT) to be returned.
+ * We look for this case so that we can give a more instructional
+ * message to the administrator.
+ */
+ if (ret == KRB5_CONFIG_BADFORMAT || ret == EINVAL) {
+ ldb_asprintf_errstring(ldb, "Failed to setup krb5_context: %s - "
+ "This could be due to an invalid krb5 configuration. "
+ "Please check your system's krb5 configuration is correct.",
+ error_message(ret));
+ } else {
+ ldb_asprintf_errstring(ldb, "Failed to setup krb5_context: %s",
+ error_message(ret));
+ }
+ return LDB_ERR_OPERATIONS_ERROR;
}
io->ac = ac;
"sAMAccountName", NULL);
io->u.user_principal_name = ldb_msg_find_attr_as_string(info_msg,
"userPrincipalName", NULL);
- io->u.is_computer = ldb_msg_check_string_attribute(info_msg, "objectClass", "computer");
/* Ensure it has an objectSID too */
io->u.account_sid = samdb_result_dom_sid(ac, info_msg, "objectSid");
/* Only non-trust accounts have restrictions (possibly this test is the
* wrong way around, but we like to be restrictive if possible */
- io->u.restrictions = !(io->u.userAccountControl
- & (UF_INTERDOMAIN_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT
- | UF_SERVER_TRUST_ACCOUNT));
+ io->u.restrictions = !(io->u.userAccountControl & UF_TRUST_ACCOUNT_MASK);
if (io->u.is_krbtgt) {
io->u.restrictions = 0;
/* On "add" we have only "password reset" */
ac->pwd_reset = true;
} else if (ac->req->operation == LDB_MODIFY) {
- if (io->og.cleartext_utf8 || io->og.cleartext_utf16
+ struct ldb_control *pav_ctrl = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ pav_ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (pav_ctrl != NULL) {
+ pav = talloc_get_type_abort(pav_ctrl->data,
+ struct dsdb_control_password_acl_validation);
+ }
+
+ if (pav == NULL && ac->update_password) {
+ bool ok;
+
+ /*
+ * If the DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID
+ * control is missing, we require system access!
+ */
+ ok = dsdb_module_am_system(ac->module);
+ if (!ok) {
+ return ldb_module_operr(ac->module);
+ }
+ }
+
+ if (pav != NULL) {
+ /*
+ * We assume what the acl module has validated.
+ */
+ ac->pwd_reset = pav->pwd_reset;
+ } else if (io->og.cleartext_utf8 || io->og.cleartext_utf16
|| io->og.nt_hash || io->og.lm_hash) {
/* If we have an old password specified then for sure it
* is a user "password change" */
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);
+/*
+ * LDB callback handler for searching for a user's PSO. Once we have all the
+ * Password Settings that apply to the user, we can continue with the modify
+ * operation
+ */
+static int get_pso_data_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb = NULL;
+ struct ph_context *ac = NULL;
+ bool domain_complexity = true;
+ bool pso_complexity = true;
+ struct dsdb_user_pwd_settings *settings = NULL;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ /* check status was initialized by the domain query */
+ if (ac->status == NULL) {
+ talloc_free(ares);
+ ldb_set_errstring(ldb, "Uninitialized status");
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /*
+ * use the PSO's values instead of the domain defaults (the PSO
+ * attributes should always exist, but use the domain default
+ * values as a fallback).
+ */
+ settings = &ac->status->domain_data;
+ settings->store_cleartext =
+ ldb_msg_find_attr_as_bool(ares->message,
+ "msDS-PasswordReversibleEncryptionEnabled",
+ settings->store_cleartext);
+
+ settings->pwdHistoryLength =
+ ldb_msg_find_attr_as_uint(ares->message,
+ "msDS-PasswordHistoryLength",
+ settings->pwdHistoryLength);
+ settings->maxPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message,
+ "msDS-MaximumPasswordAge",
+ settings->maxPwdAge);
+ settings->minPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message,
+ "msDS-MinimumPasswordAge",
+ settings->minPwdAge);
+ settings->minPwdLength =
+ ldb_msg_find_attr_as_uint(ares->message,
+ "msDS-MinimumPasswordLength",
+ settings->minPwdLength);
+ domain_complexity =
+ (settings->pwdProperties & DOMAIN_PASSWORD_COMPLEX);
+ pso_complexity =
+ ldb_msg_find_attr_as_bool(ares->message,
+ "msDS-PasswordComplexityEnabled",
+ domain_complexity);
+
+ /* set or clear the complexity bit if required */
+ if (pso_complexity && !domain_complexity) {
+ settings->pwdProperties |= DOMAIN_PASSWORD_COMPLEX;
+ } else if (domain_complexity && !pso_complexity) {
+ settings->pwdProperties &= ~DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ if (ac->pso_res != NULL) {
+ DBG_ERR("Too many PSO results for %s",
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ talloc_free(ac->pso_res);
+ }
+
+ /* store the PSO result (we may need its lockout settings) */
+ ac->pso_res = talloc_steal(ac, ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_free(ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+
+ /*
+ * perform the next step of the modify operation (this code
+ * shouldn't get called in the 'user add' case)
+ */
+ if (ac->req->operation == LDB_MODIFY) {
+ ret = password_hash_mod_do_mod(ac);
+ } else {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ break;
+ }
+
+done:
+ if (ret != LDB_SUCCESS) {
+ struct ldb_reply *new_ares;
+
+ new_ares = talloc_zero(ac->req, struct ldb_reply);
+ if (new_ares == NULL) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ new_ares->error = ret;
+ if ((ret != LDB_ERR_OPERATIONS_ERROR) && (ac->change_status)) {
+ /* On success and trivial errors a status control is being
+ * added (used for example by the "samdb_set_password" call) */
+ ldb_reply_add_control(new_ares,
+ DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID,
+ false,
+ ac->status);
+ }
+
+ return ldb_module_done(ac->req, new_ares->controls,
+ new_ares->response, new_ares->error);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Builds and returns a search request to lookup up the PSO that applies to
+ * the user in question. Returns NULL if no PSO applies, or could not be found
+ */
+static struct ldb_request * build_pso_data_request(struct ph_context *ac)
+{
+ /* attrs[] is returned from this function in
+ pso_req->op.search.attrs, so it must be static, as
+ otherwise the compiler can put it on the stack */
+ static const char * const attrs[] = { "msDS-PasswordComplexityEnabled",
+ "msDS-PasswordReversibleEncryptionEnabled",
+ "msDS-PasswordHistoryLength",
+ "msDS-MaximumPasswordAge",
+ "msDS-MinimumPasswordAge",
+ "msDS-MinimumPasswordLength",
+ "msDS-LockoutThreshold",
+ "msDS-LockoutObservationWindow",
+ NULL };
+ struct ldb_context *ldb = NULL;
+ struct ldb_request *pso_req = NULL;
+ struct ldb_dn *pso_dn = NULL;
+ TALLOC_CTX *mem_ctx = ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* if a PSO applies to the user, we need to lookup the PSO as well */
+ pso_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, ac->search_res->message,
+ "msDS-ResultantPSO");
+ if (pso_dn == NULL) {
+ return NULL;
+ }
+
+ ret = ldb_build_search_req(&pso_req, ldb, mem_ctx, pso_dn,
+ LDB_SCOPE_BASE, NULL, attrs, NULL,
+ ac, get_pso_data_callback,
+ ac->dom_req);
+
+ /* log errors, but continue with the default domain settings */
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d constructing PSO query for user %s", ret,
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ }
+ LDB_REQ_SET_LOCATION(pso_req);
+ return pso_req;
+}
+
+
static int get_domain_data_callback(struct ldb_request *req,
struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct ph_context *ac;
struct loadparm_context *lp_ctx;
+ struct ldb_request *pso_req = NULL;
int ret = LDB_SUCCESS;
ac = talloc_get_type(req->context, struct ph_context);
break;
case LDB_MODIFY:
- ret = password_hash_mod_do_mod(ac);
+
+ /*
+ * The user may have an optional PSO applied. If so,
+ * query the PSO to get the Fine-Grained Password Policy
+ * for the user, before we perform the modify
+ */
+ pso_req = build_pso_data_request(ac);
+ if (pso_req != NULL) {
+ ret = ldb_next_request(ac->module, pso_req);
+ } else {
+
+ /* no PSO, so we can perform the modify now */
+ ret = password_hash_mod_do_mod(ac);
+ }
break;
default:
const struct ldb_message *msg = NULL;
struct ph_context *ac = NULL;
const char *passwordAttrs[] = {
- "userPassword",
- "clearTextPassword",
- "unicodePwd",
- "dBCSPwd",
+ DSDB_PASSWORD_ATTRIBUTES,
NULL
};
const char **a = NULL;
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ph_context *ac = NULL;
- const char *passwordAttrs[] = { "userPassword", "clearTextPassword",
- "unicodePwd", "dBCSPwd", NULL }, **l;
+ const char *passwordAttrs[] = {DSDB_PASSWORD_ATTRIBUTES, NULL}, **l;
unsigned int del_attr_cnt, add_attr_cnt, rep_attr_cnt;
struct ldb_message_element *passwordAttr;
struct ldb_message *msg;
}
while ((passwordAttr = ldb_msg_find_element(msg, *l)) != NULL) {
- if (LDB_FLAG_MOD_TYPE(passwordAttr->flags) == LDB_FLAG_MOD_DELETE) {
+ unsigned int mtype = LDB_FLAG_MOD_TYPE(passwordAttr->flags);
+ unsigned int nvalues = passwordAttr->num_values;
+
+ if (mtype == LDB_FLAG_MOD_DELETE) {
++del_attr_cnt;
}
- if (LDB_FLAG_MOD_TYPE(passwordAttr->flags) == LDB_FLAG_MOD_ADD) {
+ if (mtype == LDB_FLAG_MOD_ADD) {
++add_attr_cnt;
}
- if (LDB_FLAG_MOD_TYPE(passwordAttr->flags) == LDB_FLAG_MOD_REPLACE) {
+ if (mtype == LDB_FLAG_MOD_REPLACE) {
++rep_attr_cnt;
}
- if ((passwordAttr->num_values != 1) &&
- (LDB_FLAG_MOD_TYPE(passwordAttr->flags) == LDB_FLAG_MOD_ADD)) {
+ if ((nvalues != 1) && (mtype == LDB_FLAG_MOD_ADD)) {
talloc_free(ac);
ldb_asprintf_errstring(ldb,
"'%s' attribute must have exactly one value on add operations!",
*l);
return LDB_ERR_CONSTRAINT_VIOLATION;
}
- if ((passwordAttr->num_values > 1) &&
- (LDB_FLAG_MOD_TYPE(passwordAttr->flags) == LDB_FLAG_MOD_DELETE)) {
+ if ((nvalues > 1) && (mtype == LDB_FLAG_MOD_DELETE)) {
talloc_free(ac);
ldb_asprintf_errstring(ldb,
"'%s' attribute must have zero or one value(s) on delete operations!",
struct ldb_context *ldb;
static const char * const attrs[] = { "objectClass",
"userAccountControl",
+ "msDS-ResultantPSO",
"msDS-User-Account-Control-Computed",
"pwdLastSet",
"sAMAccountName",