diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/providers/ldap/ldap_init.c | 2 | ||||
-rw-r--r-- | src/providers/ldap/sdap_access.c | 557 | ||||
-rw-r--r-- | src/providers/ldap/sdap_access.h | 9 |
3 files changed, 568 insertions, 0 deletions
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c index 9960fd3ab..44333a9a3 100644 --- a/src/providers/ldap/ldap_init.c +++ b/src/providers/ldap/ldap_init.c @@ -421,6 +421,8 @@ int sssm_ldap_access_init(struct be_ctx *bectx, access_ctx->access_rule[c] = LDAP_ACCESS_SERVICE; } else if (strcasecmp(order_list[c], LDAP_ACCESS_HOST_NAME) == 0) { access_ctx->access_rule[c] = LDAP_ACCESS_HOST; + } else if (strcasecmp(order_list[c], LDAP_ACCESS_LOCK_NAME) == 0) { + access_ctx->access_rule[c] = LDAP_ACCESS_LOCKOUT; } else { DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected access rule name [%s].\n", order_list[c]); diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c index fa05a452d..3b2d3970c 100644 --- a/src/providers/ldap/sdap_access.c +++ b/src/providers/ldap/sdap_access.c @@ -40,6 +40,7 @@ #include "providers/data_provider.h" #include "providers/dp_backend.h" +#define PERMANENTLY_LOCKED_ACCOUNT "000001010000Z" #define MALFORMED_FILTER "Malformed access control filter [%s]\n" static errno_t sdap_save_user_cache_bool(struct sss_domain_info *domain, @@ -51,6 +52,16 @@ static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry, const char *username, const char **_basedn); +static struct tevent_req * +sdap_access_lock_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry); + static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_ctx *be_ctx, @@ -62,6 +73,8 @@ static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx, static errno_t sdap_access_filter_recv(struct tevent_req *req); +static errno_t sdap_access_lock_recv(struct tevent_req *req); + static errno_t sdap_account_expired(struct sdap_access_ctx *access_ctx, struct pam_data *pd, struct ldb_message *user_entry); @@ -73,6 +86,7 @@ static errno_t sdap_access_host(struct ldb_message *user_entry); enum sdap_access_control_type { SDAP_ACCESS_CONTROL_FILTER, + SDAP_ACCESS_CONTROL_PPOLICY_LOCK, }; struct sdap_access_req_ctx { @@ -184,6 +198,23 @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state, /* we are done with no errors */ return EOK; + case LDAP_ACCESS_LOCKOUT: + subreq = sdap_access_lock_send(state, state->ev, state->be_ctx, + state->domain, + state->access_ctx, + state->conn, + state->pd->user, + state->user_entry); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_lock_send failed.\n"); + return ENOMEM; + } + + state->ac_type = SDAP_ACCESS_CONTROL_PPOLICY_LOCK; + + tevent_req_set_callback(subreq, sdap_access_done, req); + return EAGAIN; + case LDAP_ACCESS_FILTER: subreq = sdap_access_filter_send(state, state->ev, state->be_ctx, state->domain, @@ -240,6 +271,9 @@ static void sdap_access_done(struct tevent_req *subreq) case SDAP_ACCESS_CONTROL_FILTER: ret = sdap_access_filter_recv(subreq); break; + case SDAP_ACCESS_CONTROL_PPOLICY_LOCK: + ret = sdap_access_lock_recv(subreq); + break; default: ret = EINVAL; DEBUG(SSSDBG_MINOR_FAILURE, "Unknown access control type: %d.", @@ -685,6 +719,8 @@ struct sdap_access_filter_req_ctx { static errno_t sdap_access_decide_offline(bool cached_ac); static int sdap_access_filter_retry(struct tevent_req *req); +static void sdap_access_lock_connect_done(struct tevent_req *subreq); +static errno_t sdap_access_lock_get_lockout_step(struct tevent_req *req); static void sdap_access_filter_connect_done(struct tevent_req *subreq); static void sdap_access_filter_done(struct tevent_req *req); static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx, @@ -1159,6 +1195,527 @@ static errno_t sdap_access_host(struct ldb_message *user_entry) return ret; } +static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq); +static int sdap_access_lock_retry(struct tevent_req *req); +static errno_t sdap_access_lock_step(struct tevent_req *req); +static void sdap_access_lock_step_done(struct tevent_req *subreq); + +struct sdap_access_lock_req_ctx { + const char *username; + const char *filter; + struct tevent_context *ev; + struct sdap_access_ctx *access_ctx; + struct sdap_options *opts; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *sdap_op; + struct sysdb_handle *handle; + struct sss_domain_info *domain; + /* cached results of access control checks */ + bool cached_access; + const char *basedn; + /* default DNs to ppolicy */ + const char **ppolicy_dns; + unsigned int ppolicy_dns_index; +}; + +static struct tevent_req * +sdap_access_lock_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + struct sdap_access_ctx *access_ctx, + struct sdap_id_conn_ctx *conn, + const char *username, + struct ldb_message *user_entry) +{ + struct sdap_access_lock_req_ctx *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, struct sdap_access_lock_req_ctx); + if (req == NULL) { + return NULL; + } + + state->filter = NULL; + state->username = username; + state->opts = access_ctx->id_ctx->opts; + state->conn = conn; + state->ev = ev; + state->access_ctx = access_ctx; + state->domain = domain; + state->ppolicy_dns_index = 0; + + DEBUG(SSSDBG_TRACE_FUNC, + "Performing access lock check for user [%s]\n", username); + + state->cached_access = ldb_msg_find_attr_as_bool( + user_entry, SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, false); + + /* Ok, we have one result, check if we are online or offline */ + if (be_is_offline(be_ctx)) { + /* Ok, we're offline. Return from the cache */ + ret = sdap_access_decide_offline(state->cached_access); + goto done; + } + + ret = sdap_get_basedn_user_entry(user_entry, state->username, + &state->basedn); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking lock against LDAP\n"); + + state->sdap_op = sdap_id_op_create(state, + state->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_access_lock_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static int sdap_access_lock_retry(struct tevent_req *req) +{ + struct sdap_access_lock_req_ctx *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d (%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_access_lock_connect_done, req); + return EOK; +} + +static const char** +get_default_ppolicy_dns(TALLOC_CTX *mem_ctx, struct sdap_domain *sdom) +{ + const char **ppolicy_dns; + int count = 0; + int i; + + while(sdom->search_bases[count] != NULL) { + count++; + } + + /* +1 to have space for final NULL */ + ppolicy_dns = talloc_array(mem_ctx, const char*, count + 1); + + for(i = 0; i < count; i++) { + ppolicy_dns[i] = talloc_asprintf(mem_ctx, "cn=ppolicy,ou=policies,%s", + sdom->search_bases[i]->basedn); + } + + ppolicy_dns[count] = NULL; + return ppolicy_dns; +} + +static void sdap_access_lock_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_access_lock_req_ctx *state; + int ret, dp_error; + const char *ppolicy_dn; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + if (ret == EOK) { + tevent_req_done(req); + return; + } + } + + tevent_req_error(req, ret); + return; + } + + ppolicy_dn = dp_opt_get_string(state->opts->basic, + SDAP_PWDLOCKOUT_DN); + + /* option was configured */ + if (ppolicy_dn != NULL) { + state->ppolicy_dns = talloc_array(state, const char*, 2); + if (state->ppolicy_dns == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate ppolicy_dns.\n"); + tevent_req_error(req, ERR_ACCESS_DENIED); + return; + } + + state->ppolicy_dns[0] = ppolicy_dn; + state->ppolicy_dns[1] = NULL; + + } else { + /* try to determine default value */ + DEBUG(SSSDBG_CONF_SETTINGS, + "ldap_pwdlockout_dn was not defined in configuration file.\n"); + + state->ppolicy_dns = get_default_ppolicy_dns(state, state->opts->sdom); + if (state->ppolicy_dns == NULL) { + tevent_req_error(req, ERR_ACCESS_DENIED); + return; + } + } + + /* Connection to LDAP succeeded + * Send 'pwdLockout' request + */ + ret = sdap_access_lock_get_lockout_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_lock_get_lockout_step failed: [%d][%s]\n", + ret, strerror(ret)); + tevent_req_error(req, ERR_ACCESS_DENIED); + return; + } +} + +static errno_t +sdap_access_lock_get_lockout_step(struct tevent_req *req) +{ + const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKOUT, NULL }; + struct sdap_access_lock_req_ctx *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + + /* no more DNs to try */ + if (state->ppolicy_dns[state->ppolicy_dns_index] == NULL) { + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Trying to find out if ppolicy is enabled using the DN: %s\n", + state->ppolicy_dns[state->ppolicy_dns_index]); + + subreq = sdap_get_generic_send(state, + state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + state->ppolicy_dns[state->ppolicy_dns_index], + LDAP_SCOPE_BASE, + NULL, attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not start LDAP communication\n"); + tevent_req_error(req, EIO); + ret = EIO; + goto done; + } + + /* try next basedn */ + state->ppolicy_dns_index++; + tevent_req_set_callback(subreq, sdap_access_lock_get_lockout_done, req); + + ret = EAGAIN; + +done: + return ret; +} + +static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq) +{ + int ret, tret, dp_error; + size_t num_results; + bool pwdLockout = false; + struct sysdb_attrs **results; + struct tevent_req *req; + struct sdap_access_lock_req_ctx *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + + /* Check the number of responses we got + * If it's exactly 1, we passed the check + * If it's < 1, we failed the check + * Anything else is an error + */ + /* Didn't find ppolicy attribute */ + if (num_results < 1) { + /* Try using next $search_base */ + ret = sdap_access_lock_get_lockout_step(req); + if (ret == EOK) { + /* No more search bases to try */ + DEBUG(SSSDBG_CONF_SETTINGS, + "[%s] was not found. Granting access.\n", + SYSDB_LDAP_ACCESS_LOCKOUT); + } else { + if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_lock_get_lockout_step failed: [%d][%s]\n", + ret, strerror(ret)); + } + goto done; + } + } else if (results == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } else if (num_results > 1) { + /* It should not be possible to get more than one reply + * here, since we're doing a base-scoped search + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } else { /* Ok, we got a single reply */ + ret = sysdb_attrs_get_bool(results[0], SYSDB_LDAP_ACCESS_LOCKOUT, + &pwdLockout); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading %s: [%s]\n", SYSDB_LDAP_ACCESS_LOCKOUT, + strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + } + + if (pwdLockout) { + DEBUG(SSSDBG_TRACE_FUNC, + "Password policy is enabled on LDAP server.\n"); + + /* ppolicy is enabled => find out if account is locked */ + ret = sdap_access_lock_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_access_lock_step failed: [%d][%s].\n", + ret, strerror(ret)); + } + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Password policy is disabled on LDAP server " + "- storing 'access granted' in sysdb.\n"); + tret = sdap_save_user_cache_bool(state->domain, state->username, + SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, + true); + if (tret != EOK) { + /* Failing to save to the cache is non-fatal. + * Just return the result. + */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set user locked attribute\n"); + goto done; + } + + ret = EOK; + goto done; + } + +done: + if (ret != EAGAIN) { + /* release connection */ + tret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_send() returned error [%d][%s]\n", + ret, sss_strerror(ret)); + } + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } +} + +errno_t sdap_access_lock_step(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq; + struct sdap_access_lock_req_ctx *state; + const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME, NULL }; + + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + + subreq = sdap_get_generic_send(state, + state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + state->basedn, + LDAP_SCOPE_BASE, + NULL, attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_lock_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_access_lock_step_done, req); + ret = EAGAIN; + +done: + return ret; +} + +static void sdap_access_lock_step_done(struct tevent_req *subreq) +{ + int ret, tret, dp_error; + size_t num_results; + bool locked = false; + const char *pwdAccountLockedTime; + struct sysdb_attrs **results; + struct tevent_req *req; + struct sdap_access_lock_req_ctx *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_access_lock_req_ctx); + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (ret != EOK) { + if (dp_error == DP_ERR_OK) { + /* retry */ + tret = sdap_access_lock_retry(req); + if (tret == EOK) { + return; + } + } else if (dp_error == DP_ERR_OFFLINE) { + ret = sdap_access_decide_offline(state->cached_access); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_send() returned error [%d][%s]\n", + ret, sss_strerror(ret)); + } + + goto done; + } + + /* Check the number of responses we got + * If it's exactly 1, we passed the check + * If it's < 1, we failed the check + * Anything else is an error + */ + if (num_results < 1) { + DEBUG(SSSDBG_CONF_SETTINGS, + "User [%s] was not found with the specified filter. " + "Denying access.\n", state->username); + } else if (results == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n"); + ret = ERR_INTERNAL; + goto done; + } else if (num_results > 1) { + /* It should not be possible to get more than one reply + * here, since we're doing a base-scoped search + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Received multiple replies\n"); + ret = ERR_INTERNAL; + goto done; + } else { /* Ok, we got a single reply */ + ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACCESS_LOCKED_TIME, + &pwdAccountLockedTime); + if (ret == EOK) { + /* We do *not* care about exact value of account locked time, we + * only *do* care if the value is equal to + * PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked + * permanently. + */ + if (strcasecmp(pwdAccountLockedTime, + PERMANENTLY_LOCKED_ACCOUNT) == 0) { + locked = true; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Account of: %s is beeing blocked by password policy, " + "but value: [%s] value is ignored by SSSD.\n", + state->username, pwdAccountLockedTime); + } + } else { + /* Attribute SYSDB_LDAP_ACCESS_LOCKED_TIME in not be present unless + * user's account is blocked by password policy. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Attribute %s failed to be obtained - [%d][%s].\n", + SYSDB_LDAP_ACCESS_LOCKED_TIME, ret, strerror(ret)); + } + } + + if (locked) { + DEBUG(SSSDBG_TRACE_FUNC, + "Access denied by online lookup - account is locked.\n"); + ret = ERR_ACCESS_DENIED; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Access granted by online lookup - account is not locked.\n"); + ret = EOK; + } + + /* Save '!locked' to the cache for future offline access checks. + * Locked == true => access denied, + * Locked == false => access granted + */ + tret = sdap_save_user_cache_bool(state->domain, state->username, + SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, + !locked); + + if (tret != EOK) { + /* Failing to save to the cache is non-fatal. + * Just return the result. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user locked attribute\n"); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_access_lock_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry, const char *username, const char **_basedn) diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h index 30097e21f..f085e6199 100644 --- a/src/providers/ldap/sdap_access.h +++ b/src/providers/ldap/sdap_access.h @@ -28,12 +28,20 @@ #include "providers/dp_backend.h" #include "providers/ldap/ldap_common.h" +/* Attributes in sysdb, used for caching last values of lockout or filter + * access control checks. + */ #define SYSDB_LDAP_ACCESS_FILTER "ldap_access_filter_allow" +#define SYSDB_LDAP_ACCESS_CACHED_LOCKOUT "ldap_access_lockout_allow" +/* names of ppolicy attributes */ +#define SYSDB_LDAP_ACCESS_LOCKED_TIME "pwdAccountLockedTime" +#define SYSDB_LDAP_ACCESS_LOCKOUT "pwdLockout" #define LDAP_ACCESS_FILTER_NAME "filter" #define LDAP_ACCESS_EXPIRE_NAME "expire" #define LDAP_ACCESS_SERVICE_NAME "authorized_service" #define LDAP_ACCESS_HOST_NAME "host" +#define LDAP_ACCESS_LOCK_NAME "lockout" #define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow" #define LDAP_ACCOUNT_EXPIRE_AD "ad" @@ -48,6 +56,7 @@ enum ldap_access_rule { LDAP_ACCESS_EXPIRE, LDAP_ACCESS_SERVICE, LDAP_ACCESS_HOST, + LDAP_ACCESS_LOCKOUT, LDAP_ACCESS_LAST }; |