summaryrefslogtreecommitdiffstats
path: root/src/providers/ldap/sdap_access.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ldap/sdap_access.c')
-rw-r--r--src/providers/ldap/sdap_access.c2402
1 files changed, 2402 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
new file mode 100644
index 0000000..c7e48d9
--- /dev/null
+++ b/src/providers/ldap/sdap_access.c
@@ -0,0 +1,2402 @@
+/*
+ SSSD
+
+ sdap_access.c
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2010 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <time.h>
+#include <security/pam_modules.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/strtonum.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap.h"
+#include "providers/ldap/sdap_access.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/data_provider.h"
+#include "providers/backend.h"
+#include "providers/ldap/ldap_auth.h"
+#include "providers/ipa/ipa_common.h"
+
+#define PERMANENTLY_LOCKED_ACCOUNT "000001010000Z"
+#define MALFORMED_FILTER "Malformed access control filter [%s]\n"
+
+enum sdap_pwpolicy_mode {
+ PWP_LOCKOUT_ONLY,
+ PWP_LOCKOUT_EXPIRE,
+ PWP_SENTINEL,
+};
+
+static errno_t perform_pwexpire_policy(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct pam_data *pd,
+ enum sdap_access_type access_type,
+ struct sdap_options *opts);
+
+static errno_t sdap_save_user_cache_bool(struct sss_domain_info *domain,
+ const char *username,
+ const char *attr_name,
+ bool value);
+
+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_ppolicy_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,
+ enum sdap_pwpolicy_mode pwpol_mod);
+
+static struct tevent_req *sdap_access_filter_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 errno_t sdap_access_filter_recv(struct tevent_req *req);
+
+static errno_t sdap_access_ppolicy_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);
+
+static errno_t sdap_access_service(struct pam_data *pd,
+ struct ldb_message *user_entry);
+
+static errno_t sdap_access_host(struct ldb_message *user_entry);
+
+errno_t sdap_access_rhost(struct ldb_message *user_entry, char *rhost);
+
+enum sdap_access_control_type {
+ SDAP_ACCESS_CONTROL_FILTER,
+ SDAP_ACCESS_CONTROL_PPOLICY_LOCK,
+};
+
+struct sdap_access_req_ctx {
+ struct pam_data *pd;
+ struct tevent_context *ev;
+ struct sdap_access_ctx *access_ctx;
+ struct sdap_id_conn_ctx *conn;
+ struct be_ctx *be_ctx;
+ struct sss_domain_info *domain;
+ struct ldb_message *user_entry;
+ size_t current_rule;
+ enum sdap_access_control_type ac_type;
+};
+
+static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
+ struct tevent_req *req);
+static void sdap_access_done(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_access_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,
+ struct pam_data *pd)
+{
+ errno_t ret;
+ struct sdap_access_req_ctx *state;
+ struct tevent_req *req;
+ struct ldb_result *res;
+ const char *attrs[] = { "*", NULL };
+
+ req = tevent_req_create(mem_ctx, &state, struct sdap_access_req_ctx);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
+ return NULL;
+ }
+
+ state->be_ctx = be_ctx;
+ state->domain = domain;
+ state->pd = pd;
+ state->ev = ev;
+ state->access_ctx = access_ctx;
+ state->conn = conn;
+ state->current_rule = 0;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing access check for user [%s]\n", pd->user);
+
+ if (access_ctx->access_rule[0] == LDAP_ACCESS_EMPTY) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "No access rules defined, access denied.\n");
+ ret = ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* Get original user DN, domain already points to the right (sub)domain */
+ ret = sysdb_get_user_attr(state, domain, pd->user, attrs, &res);
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* If we can't find the user, return access denied */
+ ret = ERR_ACCESS_DENIED;
+ goto done;
+ }
+ goto done;
+ }
+ else {
+ if (res->count == 0) {
+ /* If we can't find the user, return access denied */
+ ret = ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ if (res->count != 1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid response from sysdb_get_user_attr\n");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ state->user_entry = res->msgs[0];
+
+ ret = sdap_access_check_next_rule(state, req);
+ if (ret == EAGAIN) {
+ return req;
+ }
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
+ struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ int ret = EOK;
+
+ while (ret == EOK) {
+ switch (state->access_ctx->access_rule[state->current_rule]) {
+ case LDAP_ACCESS_EMPTY:
+ /* we are done with no errors */
+ return EOK;
+
+ /* This option is deprecated by LDAP_ACCESS_PPOLICY */
+ case LDAP_ACCESS_LOCKOUT:
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "WARNING: %s option is deprecated and might be removed in "
+ "a future release. Please migrate to %s option instead.\n",
+ LDAP_ACCESS_LOCK_NAME, LDAP_ACCESS_PPOLICY_NAME);
+
+ subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx,
+ state->domain,
+ state->access_ctx,
+ state->conn,
+ state->pd->user,
+ state->user_entry,
+ PWP_LOCKOUT_ONLY);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_access_ppolicy_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_PPOLICY:
+ subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx,
+ state->domain,
+ state->access_ctx,
+ state->conn,
+ state->pd->user,
+ state->user_entry,
+ PWP_LOCKOUT_EXPIRE);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_access_ppolicy_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,
+ state->access_ctx,
+ state->conn,
+ state->pd->user,
+ state->user_entry);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_filter_send failed.\n");
+ return ENOMEM;
+ }
+
+ state->ac_type = SDAP_ACCESS_CONTROL_FILTER;
+
+ tevent_req_set_callback(subreq, sdap_access_done, req);
+ return EAGAIN;
+
+ case LDAP_ACCESS_EXPIRE:
+ ret = sdap_account_expired(state->access_ctx,
+ state->pd, state->user_entry);
+ break;
+
+ case LDAP_ACCESS_EXPIRE_POLICY_REJECT:
+ ret = perform_pwexpire_policy(state, state->domain, state->pd,
+ state->access_ctx->type,
+ state->access_ctx->id_ctx->opts);
+ if (ret == ERR_PASSWORD_EXPIRED) {
+ ret = ERR_PASSWORD_EXPIRED_REJECT;
+ }
+ break;
+
+ case LDAP_ACCESS_EXPIRE_POLICY_WARN:
+ ret = perform_pwexpire_policy(state, state->domain, state->pd,
+ state->access_ctx->type,
+ state->access_ctx->id_ctx->opts);
+ if (ret == ERR_PASSWORD_EXPIRED) {
+ ret = ERR_PASSWORD_EXPIRED_WARN;
+ }
+ break;
+
+ case LDAP_ACCESS_EXPIRE_POLICY_RENEW:
+ ret = perform_pwexpire_policy(state, state->domain, state->pd,
+ state->access_ctx->type,
+ state->access_ctx->id_ctx->opts);
+ if (ret == ERR_PASSWORD_EXPIRED) {
+ ret = ERR_PASSWORD_EXPIRED_RENEW;
+ }
+ break;
+
+ case LDAP_ACCESS_SERVICE:
+ ret = sdap_access_service( state->pd, state->user_entry);
+ break;
+
+ case LDAP_ACCESS_HOST:
+ ret = sdap_access_host(state->user_entry);
+ break;
+
+ case LDAP_ACCESS_RHOST:
+ ret = sdap_access_rhost(state->user_entry, state->pd->rhost);
+ break;
+
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected access rule type %d. Access denied.\n",
+ state->access_ctx->access_rule[state->current_rule]);
+ ret = ERR_ACCESS_DENIED;
+ }
+
+ state->current_rule++;
+ }
+
+ return ret;
+}
+
+static void sdap_access_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct sdap_access_req_ctx *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_access_req_ctx);
+
+ /* process subrequest */
+ switch(state->ac_type) {
+ case SDAP_ACCESS_CONTROL_FILTER:
+ ret = sdap_access_filter_recv(subreq);
+ break;
+ case SDAP_ACCESS_CONTROL_PPOLICY_LOCK:
+ ret = sdap_access_ppolicy_recv(subreq);
+ break;
+ default:
+ ret = EINVAL;
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unknown access control type: %d.\n",
+ state->ac_type);
+ break;
+ }
+
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (ret == ERR_ACCESS_DENIED) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Access was denied.\n");
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Error retrieving access check result.\n");
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_rule++;
+
+ ret = sdap_access_check_next_rule(state, req);
+ switch (ret) {
+ case EAGAIN:
+ return;
+ case EOK:
+ tevent_req_done(req);
+ return;
+ default:
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+errno_t sdap_access_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+#define SHADOW_EXPIRE_MSG "Account expired according to shadow attributes"
+
+static errno_t sdap_account_expired_shadow(struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ int ret;
+ const char *val;
+ long sp_expire;
+ long today;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing access shadow check for user [%s]\n", pd->user);
+
+ val = ldb_msg_find_attr_as_string(user_entry, SYSDB_SHADOWPW_EXPIRE, NULL);
+ if (val == NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Shadow expire attribute not found. "
+ "Access will be granted.\n");
+ return EOK;
+ }
+ ret = string_to_shadowpw_days(val, &sp_expire);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve shadow expire date.\n");
+ return ret;
+ }
+
+ today = (long) (time(NULL) / (60 * 60 * 24));
+ if (sp_expire > 0 && today >= sp_expire) {
+
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(SHADOW_EXPIRE_MSG),
+ (const uint8_t *) SHADOW_EXPIRE_MSG);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCOUNT_EXPIRED;
+ }
+
+ return EOK;
+}
+
+#define UAC_ACCOUNTDISABLE 0x00000002
+#define AD_NEVER_EXP 0x7fffffffffffffffLL
+#define AD_TO_UNIX_TIME_CONST 11644473600LL
+#define AD_DISABLE_MESSAGE "The user account is disabled on the AD server"
+#define AD_EXPIRED_MESSAGE "The user account is expired on the AD server"
+
+static bool ad_account_expired(uint64_t expiration_time)
+{
+ time_t now;
+ int err;
+ uint64_t nt_now;
+
+ if (expiration_time == 0 || expiration_time == AD_NEVER_EXP) {
+ return false;
+ }
+
+ now = time(NULL);
+ if (now == ((time_t) -1)) {
+ err = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "time failed [%d][%s].\n", err, strerror(err));
+ return true;
+ }
+
+ /* NT timestamps start at 1601-01-01 and use a 100ns base */
+ nt_now = (now + AD_TO_UNIX_TIME_CONST) * 1000 * 1000 * 10;
+
+ if (nt_now > expiration_time) {
+ return true;
+ }
+
+ return false;
+}
+
+static errno_t sdap_account_expired_ad(struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ uint32_t uac;
+ uint64_t expiration_time;
+ int ret;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing AD access check for user [%s]\n", pd->user);
+
+ uac = ldb_msg_find_attr_as_uint(user_entry, SYSDB_AD_USER_ACCOUNT_CONTROL,
+ 0);
+ DEBUG(SSSDBG_TRACE_ALL, "User account control for user [%s] is [%X].\n",
+ pd->user, uac);
+
+ expiration_time = ldb_msg_find_attr_as_uint64(user_entry,
+ SYSDB_AD_ACCOUNT_EXPIRES, 0);
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Expiration time for user [%s] is [%"PRIu64"].\n",
+ pd->user, expiration_time);
+
+ if (uac & UAC_ACCOUNTDISABLE) {
+
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(AD_DISABLE_MESSAGE),
+ (const uint8_t *) AD_DISABLE_MESSAGE);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+
+ } else if (ad_account_expired(expiration_time)) {
+
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(AD_EXPIRED_MESSAGE),
+ (const uint8_t *) AD_EXPIRED_MESSAGE);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCOUNT_EXPIRED;
+ }
+
+ return EOK;
+}
+
+#define RHDS_LOCK_MSG "The user account is locked on the server"
+
+static errno_t sdap_account_expired_rhds(struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ bool locked;
+ int ret;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing RHDS access check for user [%s]\n", pd->user);
+
+ locked = ldb_msg_find_attr_as_bool(user_entry, SYSDB_NS_ACCOUNT_LOCK, false);
+ DEBUG(SSSDBG_TRACE_ALL, "Account for user [%s] is%s locked.\n", pd->user,
+ locked ? "" : " not" );
+
+ if (locked) {
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(RHDS_LOCK_MSG),
+ (const uint8_t *) RHDS_LOCK_MSG);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+ }
+
+ return EOK;
+}
+
+#define NDS_DISABLE_MSG "The user account is disabled on the server"
+#define NDS_EXPIRED_MSG "The user account is expired"
+#define NDS_TIME_MAP_MSG "The user account is not allowed at this time"
+
+bool nds_check_expired(const char *exp_time_str)
+{
+ time_t expire_time;
+ time_t now;
+ errno_t ret;
+
+ if (exp_time_str == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "ndsLoginExpirationTime is not set, access granted.\n");
+ return false;
+ }
+
+ ret = sss_utc_to_time_t(exp_time_str, "%Y%m%d%H%M%SZ",
+ &expire_time);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "sss_utc_to_time_t failed with %d:%s.\n",
+ ret, sss_strerror(ret));
+ return true;
+ }
+
+ now = time(NULL);
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Time info: tzname[0] [%s] tzname[1] [%s] timezone [%ld] "
+ "daylight [%d] now [%"SPRItime"] expire_time [%"SPRItime"].\n",
+ tzname[0], tzname[1], timezone, daylight, now, expire_time);
+
+ if (difftime(now, expire_time) > 0.0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "NDS account expired.\n");
+ return true;
+ }
+
+ return false;
+}
+
+/* There is no real documentation of the byte string value of
+ * loginAllowedTimeMap, but some good example code in
+ * http://http://developer.novell.com/documentation/samplecode/extjndi_sample/CheckBind.java.html
+ */
+static bool nds_check_time_map(const struct ldb_val *time_map)
+{
+ time_t now;
+ struct tm *tm_now;
+ size_t map_index;
+ div_t q;
+ uint8_t mask = 0;
+
+ if (time_map == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "loginAllowedTimeMap is missing, access granted.\n");
+ return false;
+ }
+
+ if (time_map->length != 42) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Allowed time map has the wrong size, "
+ "got [%zu], expected 42.\n", time_map->length);
+ return true;
+ }
+
+ now = time(NULL);
+ tm_now = gmtime(&now);
+
+ map_index = tm_now->tm_wday * 48 + tm_now->tm_hour * 2 +
+ (tm_now->tm_min < 30 ? 0 : 1);
+
+ if (map_index > 335) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected index value [%zu] for time map.\n", map_index);
+ return true;
+ }
+
+ q = div(map_index, 8);
+
+ if (q.quot > 41 || q.quot < 0 || q.rem > 7 || q.rem < 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected result of div(), [%zu][%d][%d].\n",
+ map_index, q.quot, q.rem);
+ return true;
+ }
+
+ if (q.rem > 0) {
+ mask = 1 << q.rem;
+ }
+
+ if (time_map->data[q.quot] & mask) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access allowed by time map.\n");
+ return false;
+ }
+
+ return true;
+}
+
+static errno_t sdap_account_expired_nds(struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ bool locked = true;
+ int ret;
+ const char *exp_time_str;
+ const struct ldb_val *time_map;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing NDS access check for user [%s]\n", pd->user);
+
+ locked = ldb_msg_find_attr_as_bool(user_entry, SYSDB_NDS_LOGIN_DISABLED,
+ false);
+ DEBUG(SSSDBG_TRACE_ALL, "Account for user [%s] is%s disabled.\n", pd->user,
+ locked ? "" : " not");
+
+ if (locked) {
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(NDS_DISABLE_MSG),
+ (const uint8_t *) NDS_DISABLE_MSG);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+
+ } else {
+ exp_time_str = ldb_msg_find_attr_as_string(user_entry,
+ SYSDB_NDS_LOGIN_EXPIRATION_TIME,
+ NULL);
+ locked = nds_check_expired(exp_time_str);
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Account for user [%s] is%s expired.\n", pd->user,
+ locked ? "" : " not");
+
+ if (locked) {
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(NDS_EXPIRED_MSG),
+ (const uint8_t *) NDS_EXPIRED_MSG);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+
+ } else {
+ time_map = ldb_msg_find_ldb_val(user_entry,
+ SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP);
+
+ locked = nds_check_time_map(time_map);
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Account for user [%s] is%s locked at this time.\n",
+ pd->user, locked ? "" : " not");
+
+ if (locked) {
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(NDS_TIME_MAP_MSG),
+ (const uint8_t *) NDS_TIME_MAP_MSG);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+ }
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t sdap_account_expired(struct sdap_access_ctx *access_ctx,
+ struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ const char *expire;
+ int ret;
+
+ expire = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+ SDAP_ACCOUNT_EXPIRE_POLICY);
+ if (expire == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing account expire policy. Access denied\n");
+ return ERR_ACCESS_DENIED;
+ } else {
+ if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_SHADOW) == 0) {
+ ret = sdap_account_expired_shadow(pd, user_entry);
+ if (ret == ERR_ACCOUNT_EXPIRED) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "sdap_account_expired_shadow: %s.\n", sss_strerror(ret));
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_account_expired_shadow failed.\n");
+ }
+ } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_AD) == 0) {
+ ret = sdap_account_expired_ad(pd, user_entry);
+ if (ret == ERR_ACCOUNT_EXPIRED || ret == ERR_ACCESS_DENIED) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "sdap_account_expired_ad: %s.\n", sss_strerror(ret));
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sdap_account_expired_ad failed.\n");
+ }
+ } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_RHDS) == 0 ||
+ strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_IPA) == 0 ||
+ strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_389DS) == 0) {
+ ret = sdap_account_expired_rhds(pd, user_entry);
+ if (ret == ERR_ACCESS_DENIED) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "sdap_account_expired_rhds: %s.\n", sss_strerror(ret));
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_account_expired_rhds failed.\n");
+ }
+
+ if (ret == EOK &&
+ strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_IPA) == 0) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "IPA access control succeeded, checking AD "
+ "access control\n");
+ ret = sdap_account_expired_ad(pd, user_entry);
+ if (ret == ERR_ACCOUNT_EXPIRED || ret == ERR_ACCESS_DENIED) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "sdap_account_expired_ad: %s.\n", sss_strerror(ret));
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_account_expired_ad failed.\n");
+ }
+ }
+ } else if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_NDS) == 0) {
+ ret = sdap_account_expired_nds(pd, user_entry);
+ if (ret == ERR_ACCESS_DENIED) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "sdap_account_expired_nds: %s.\n", sss_strerror(ret));
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_account_expired_nds failed.\n");
+ }
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unsupported LDAP account expire policy [%s]. "
+ "Access denied.\n", expire);
+ ret = ERR_ACCESS_DENIED;
+ }
+ }
+
+ return ret;
+}
+
+static errno_t perform_pwexpire_policy(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct pam_data *pd,
+ enum sdap_access_type access_type,
+ struct sdap_options *opts)
+{
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+ errno_t ret;
+ char *dn;
+
+ ret = get_user_dn(mem_ctx, domain, access_type, opts, pd->user, &dn,
+ &pw_expire_type, &pw_expire_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "get_user_dn returned %d:[%s].\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = check_pwexpire_policy(pw_expire_type, pw_expire_data, pd,
+ domain->pwd_expiration_warning);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "check_pwexpire_policy returned %d:[%s].\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
+struct sdap_access_filter_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 result of access control checks */
+ bool cached_access;
+ const char *basedn;
+};
+
+static errno_t sdap_access_decide_offline(bool cached_ac);
+static int sdap_access_filter_retry(struct tevent_req *req);
+static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq);
+static errno_t sdap_access_ppolicy_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,
+ 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_filter_req_ctx *state;
+ struct tevent_req *req;
+ char *clean_username;
+ errno_t ret = ERR_INTERNAL;
+ char *name;
+
+ req = tevent_req_create(mem_ctx, &state, struct sdap_access_filter_req_ctx);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (access_ctx->filter == NULL || *access_ctx->filter == '\0') {
+ /* If no filter is set, default to restrictive */
+ DEBUG(SSSDBG_TRACE_FUNC, "No filter set. Access is denied.\n");
+ ret = ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ 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;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing access filter check for user [%s]\n", username);
+
+ state->cached_access = ldb_msg_find_attr_as_bool(user_entry,
+ SYSDB_LDAP_ACCESS_FILTER,
+ 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;
+ }
+
+ /* Construct the filter */
+ ret = sss_parse_internal_fqname(state, username, &name, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not parse [%s] into name and "
+ "domain components, access might fail\n", username);
+ name = discard_const(username);
+ }
+
+ ret = sss_filter_sanitize(state, name, &clean_username);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ state->filter = talloc_asprintf(
+ state,
+ "(&(%s=%s)(objectclass=%s)%s)",
+ state->opts->user_map[SDAP_AT_USER_NAME].name,
+ clean_username,
+ state->opts->user_map[SDAP_OC_USER].name,
+ state->access_ctx->filter);
+ if (state->filter == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Could not construct access filter\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ talloc_zfree(clean_username);
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Checking filter 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_filter_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;
+}
+
+/* Helper function,
+ * cached_ac => access granted
+ * !cached_ac => access denied
+ */
+static errno_t sdap_access_decide_offline(bool cached_ac)
+{
+ if (cached_ac) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Access granted by cached credentials\n");
+ return EOK;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC, "Access denied by cached credentials\n");
+ return ERR_ACCESS_DENIED;
+ }
+}
+
+static int sdap_access_filter_retry(struct tevent_req *req)
+{
+ struct sdap_access_filter_req_ctx *state =
+ tevent_req_data(req, struct sdap_access_filter_req_ctx);
+ struct tevent_req *subreq;
+ int ret;
+
+ 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_filter_connect_done, req);
+ return EOK;
+}
+
+static void sdap_access_filter_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_access_filter_req_ctx *state =
+ tevent_req_data(req, struct sdap_access_filter_req_ctx);
+ int ret, dp_error;
+
+ 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;
+ }
+
+ /* Connection to LDAP succeeded
+ * Send filter request
+ */
+ subreq = sdap_get_generic_send(state,
+ state->ev,
+ state->opts,
+ sdap_id_op_handle(state->sdap_op),
+ state->basedn,
+ LDAP_SCOPE_BASE,
+ state->filter, NULL,
+ 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);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, sdap_access_filter_done, req);
+}
+
+static void sdap_access_filter_done(struct tevent_req *subreq)
+{
+ int ret, tret, dp_error;
+ size_t num_results;
+ bool found = false;
+ struct sysdb_attrs **results;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct sdap_access_filter_req_ctx *state =
+ tevent_req_data(req, struct sdap_access_filter_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_filter_retry(req);
+ if (tret == EOK) {
+ return;
+ }
+ } else if (dp_error == DP_ERR_OFFLINE) {
+ ret = sdap_access_decide_offline(state->cached_access);
+ } else if (ret == ERR_INVALID_FILTER) {
+ sss_log(SSS_LOG_ERR, MALFORMED_FILTER, state->filter);
+ DEBUG(SSSDBG_CRIT_FAILURE, MALFORMED_FILTER, state->filter);
+ ret = ERR_ACCESS_DENIED;
+ } 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);
+ found = false;
+ }
+ 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 */
+ found = true;
+ }
+
+ if (found) {
+ /* Save "allow" to the cache for future offline access checks. */
+ DEBUG(SSSDBG_TRACE_FUNC, "Access granted by online lookup\n");
+ ret = EOK;
+ }
+ else {
+ /* Save "disallow" to the cache for future offline
+ * access checks.
+ */
+ DEBUG(SSSDBG_TRACE_FUNC, "Access denied by online lookup\n");
+ ret = ERR_ACCESS_DENIED;
+ }
+
+ tret = sdap_save_user_cache_bool(state->domain, state->username,
+ SYSDB_LDAP_ACCESS_FILTER, found);
+ if (tret != EOK) {
+ /* Failing to save to the cache is non-fatal.
+ * Just return the result.
+ */
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user access attribute\n");
+ goto done;
+ }
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ }
+ else {
+ tevent_req_error(req, ret);
+ }
+}
+
+static errno_t sdap_access_filter_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+#define AUTHR_SRV_MISSING_MSG "Authorized service attribute missing, " \
+ "access denied"
+#define AUTHR_SRV_DENY_MSG "Access denied by authorized service attribute"
+#define AUTHR_SRV_NO_MATCH_MSG "Authorized service attribute has " \
+ "no matching rule, access denied"
+
+static errno_t sdap_access_service(struct pam_data *pd,
+ struct ldb_message *user_entry)
+{
+ errno_t ret, tret;
+ struct ldb_message_element *el;
+ unsigned int i;
+ char *service;
+
+ el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_SERVICE);
+ if (!el || el->num_values == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Missing authorized services. Access denied\n");
+
+ tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(AUTHR_SRV_MISSING_MSG),
+ (const uint8_t *) AUTHR_SRV_MISSING_MSG);
+ if (tret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ return ERR_ACCESS_DENIED;
+ }
+
+ ret = ENOENT;
+
+ for (i = 0; i < el->num_values; i++) {
+ service = (char *)el->values[i].data;
+ if (service[0] == '!' &&
+ strcasecmp(pd->service, service+1) == 0) {
+ /* This service is explicitly denied */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access denied by [%s]\n", service);
+
+ tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(AUTHR_SRV_DENY_MSG),
+ (const uint8_t *) AUTHR_SRV_DENY_MSG);
+ if (tret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ /* A denial trumps all. Break here */
+ return ERR_ACCESS_DENIED;
+
+ } else if (strcasecmp(pd->service, service) == 0) {
+ /* This service is explicitly allowed */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access granted for [%s]\n", service);
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ } else if (strcmp("*", service) == 0) {
+ /* This user has access to all services */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access granted to all services\n");
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ }
+ }
+
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "No matching service rule found\n");
+
+ tret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(AUTHR_SRV_NO_MATCH_MSG),
+ (const uint8_t *) AUTHR_SRV_NO_MATCH_MSG);
+ if (tret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ }
+
+ ret = ERR_ACCESS_DENIED;
+ }
+
+ return ret;
+}
+
+static errno_t sdap_save_user_cache_bool(struct sss_domain_info *domain,
+ const char *username,
+ const char *attr_name,
+ bool value)
+{
+ errno_t ret;
+ struct sysdb_attrs *attrs;
+
+ attrs = sysdb_new_attrs(NULL);
+ if (attrs == NULL) {
+ ret = ENOMEM;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not create attrs\n");
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_bool(attrs, attr_name, value);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up attr value\n");
+ goto done;
+ }
+
+ ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user access attribute\n");
+ goto done;
+ }
+
+done:
+ talloc_free(attrs);
+ return ret;
+}
+
+static errno_t sdap_access_host_comp(struct ldb_message_element *el, char *hostname)
+{
+ errno_t ret = ENOENT;
+ unsigned int i;
+ char *host;
+
+ for (i = 0; i < el->num_values; i++) {
+ host = (char *)el->values[i].data;
+ if (host[0] == '!' &&
+ strcasecmp(hostname, host+1) == 0) {
+ /* This host is explicitly denied */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access denied by [%s]\n", host);
+ /* A denial trumps all. Break here */
+ return ERR_ACCESS_DENIED;
+
+ } else if (strcasecmp(hostname, host) == 0) {
+ /* This host is explicitly allowed */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access granted for [%s]\n", host);
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ } else if (strcmp("*", host) == 0) {
+ /* This user has access to all hosts */
+ DEBUG(SSSDBG_CONF_SETTINGS, "Access granted to all hosts\n");
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ }
+ }
+ return ret;
+}
+
+static errno_t sdap_access_host(struct ldb_message *user_entry)
+{
+ errno_t ret;
+ struct ldb_message_element *el;
+ char hostname[HOST_NAME_MAX + 1];
+ struct addrinfo *res = NULL;
+ struct addrinfo hints;
+
+ el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_HOST);
+ if (!el || el->num_values == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing hosts. Access denied\n");
+ return ERR_ACCESS_DENIED;
+ }
+
+ if (gethostname(hostname, sizeof(hostname)) == -1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to get system hostname. Access denied\n");
+ return ERR_ACCESS_DENIED;
+ }
+ hostname[HOST_NAME_MAX] = '\0';
+
+ /* Canonicalize the hostname */
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_CANONNAME;
+ ret = getaddrinfo(hostname, NULL, &hints, &res);
+ if (ret != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Failed to canonicalize hostname\n");
+ freeaddrinfo(res);
+ res = NULL;
+ }
+
+ ret = sdap_access_host_comp(el, hostname);
+ if (ret == ENOENT && res != NULL && res->ai_canonname != NULL) {
+ ret = sdap_access_host_comp(el, res->ai_canonname);
+ }
+ freeaddrinfo(res);
+
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "No matching host rule found\n");
+ ret = ERR_ACCESS_DENIED;
+ }
+
+ return ret;
+}
+
+errno_t sdap_access_rhost(struct ldb_message *user_entry, char *pam_rhost)
+{
+ errno_t ret;
+ struct ldb_message_element *el;
+ char *be_rhost_rule;
+ unsigned int i;
+
+ /* If user_entry is NULL do not perform any checks */
+ if (user_entry == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "user_entry is NULL, that is not possible, "
+ "so we just reject access\n");
+ return ERR_ACCESS_DENIED;
+ }
+
+ /* If pam_rhost is NULL do not perform any checks */
+ if (pam_rhost == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pam_rhost is NULL, no rhost check is possible\n");
+ return EOK;
+ }
+
+ /* When the access is local we get empty string as pam_rhost
+ in which case we should not evaluate rhost access rules */
+ /* FIXME: I think ideally should have LDAP to define what to do in
+ * this case */
+ if (pam_rhost[0] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pam_rhost is empty, possible local access, "
+ "no rhost check possible\n");
+ return EOK;
+ }
+
+ /* If rhost validation is enabled and entry has no relevant attribute -
+ * deny access */
+ el = ldb_msg_find_element(user_entry, SYSDB_AUTHORIZED_RHOST);
+ if (!el || el->num_values == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Missing rhost entries. Access denied\n");
+ return ERR_ACCESS_DENIED;
+ }
+
+ ret = ENOENT;
+
+ for (i = 0; i < el->num_values; i++) {
+ be_rhost_rule = (char *)el->values[i].data;
+ if (be_rhost_rule[0] == '!'
+ && strcasecmp(pam_rhost, be_rhost_rule+1) == 0) {
+ /* This rhost is explicitly denied */
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Access from [%s] denied by [%s]\n",
+ pam_rhost, be_rhost_rule);
+ /* A denial trumps all. Break here */
+ return ERR_ACCESS_DENIED;
+ } else if (strcasecmp(pam_rhost, be_rhost_rule) == 0) {
+ /* This rhost is explicitly allowed */
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Access from [%s] granted by [%s]\n",
+ pam_rhost, be_rhost_rule);
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ } else if (strcmp("*", be_rhost_rule) == 0) {
+ /* This user has access from anywhere */
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Access from [%s] granted by [*]\n", pam_rhost);
+ /* We still need to loop through to make sure
+ * that it's not also explicitly denied
+ */
+ ret = EOK;
+ }
+ }
+
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "No matching rhost rules found\n");
+ ret = ERR_ACCESS_DENIED;
+ }
+
+ return ret;
+}
+
+static void sdap_access_ppolicy_get_lockout_done(struct tevent_req *subreq);
+static int sdap_access_ppolicy_retry(struct tevent_req *req);
+static errno_t sdap_access_ppolicy_step(struct tevent_req *req);
+static void sdap_access_ppolicy_step_done(struct tevent_req *subreq);
+
+struct sdap_access_ppolicy_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;
+ enum sdap_pwpolicy_mode pwpol_mode;
+};
+
+static struct tevent_req *
+sdap_access_ppolicy_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,
+ enum sdap_pwpolicy_mode pwpol_mode)
+{
+ struct sdap_access_ppolicy_req_ctx *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx,
+ &state, struct sdap_access_ppolicy_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;
+ state->pwpol_mode = pwpol_mode;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Performing access ppolicy 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 ppolicy 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_ppolicy_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_ppolicy_retry(struct tevent_req *req)
+{
+ struct sdap_access_ppolicy_req_ctx *state;
+ struct tevent_req *subreq;
+ int ret;
+
+ state = tevent_req_data(req, struct sdap_access_ppolicy_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, sss_strerror(ret));
+ return ret;
+ }
+
+ tevent_req_set_callback(subreq, sdap_access_ppolicy_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_ppolicy_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_access_ppolicy_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_ppolicy_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_INTERNAL);
+ 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_INTERNAL);
+ return;
+ }
+ }
+
+ /* Connection to LDAP succeeded
+ * Send 'pwdLockout' request
+ */
+ ret = sdap_access_ppolicy_get_lockout_step(req);
+ if (ret != EOK && ret != EAGAIN) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ERR_INTERNAL);
+ return;
+ }
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ }
+}
+
+static errno_t
+sdap_access_ppolicy_get_lockout_step(struct tevent_req *req)
+{
+ const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKOUT, NULL };
+ struct sdap_access_ppolicy_req_ctx *state;
+ struct tevent_req *subreq;
+ errno_t ret;
+
+ state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+ /* no more DNs to try */
+ if (state->ppolicy_dns[state->ppolicy_dns_index] == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC, "No more DNs to try.\n");
+ 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");
+ ret = EIO;
+ goto done;
+ }
+
+ /* try next basedn */
+ state->ppolicy_dns_index++;
+ tevent_req_set_callback(subreq, sdap_access_ppolicy_get_lockout_done, req);
+
+ ret = EAGAIN;
+
+done:
+ return ret;
+}
+
+static void sdap_access_ppolicy_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_ppolicy_req_ctx *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+ ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve ppolicy\n");
+ ret = ERR_NETWORK_IO;
+ 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
+ */
+ /* Didn't find ppolicy attribute */
+ if (num_results < 1) {
+ /* Try using next $search_base */
+ ret = sdap_access_ppolicy_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_ppolicy_get_lockout_step failed: "
+ "[%d][%s]\n",
+ ret, sss_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,
+ sss_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_ppolicy_step(req);
+ if (ret != EOK && ret != EAGAIN) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sdap_access_ppolicy_step failed: [%d][%s].\n",
+ ret, sss_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_ppolicy_step(struct tevent_req *req)
+{
+ errno_t ret;
+ struct tevent_req *subreq;
+ struct sdap_access_ppolicy_req_ctx *state;
+ const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME,
+ SYSDB_LDAP_ACESS_LOCKOUT_DURATION,
+ NULL };
+
+ state = tevent_req_data(req, struct sdap_access_ppolicy_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_get_generic_send failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sdap_access_ppolicy_step_done, req);
+ ret = EAGAIN;
+
+done:
+ return ret;
+}
+
+static errno_t
+is_account_locked(const char *pwdAccountLockedTime,
+ const char *pwdAccountLockedDurationTime,
+ enum sdap_pwpolicy_mode pwpol_mode,
+ const char *username,
+ bool *_locked)
+{
+ errno_t ret;
+ time_t lock_time;
+ time_t duration;
+ time_t now;
+ bool locked;
+ char *endptr;
+
+ /* Default action is to consider account to be locked. */
+ locked = true;
+
+ /* account is permanently locked */
+ if (strcasecmp(pwdAccountLockedTime,
+ PERMANENTLY_LOCKED_ACCOUNT) == 0) {
+ ret = EOK;
+ goto done;
+ }
+
+ switch(pwpol_mode) {
+ case PWP_LOCKOUT_ONLY:
+ /* 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.
+ */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Account of: %s is being blocked by password policy, "
+ "but value: [%s] value is ignored by SSSD.\n",
+ username, pwdAccountLockedTime);
+ locked = false;
+ break;
+ case PWP_LOCKOUT_EXPIRE:
+ /* Account may be locked out from natural reasons (too many attempts,
+ * expired password). In this case, pwdAccountLockedTime is also set,
+ * to the time of lock out.
+ */
+ ret = sss_utc_to_time_t(pwdAccountLockedTime, "%Y%m%d%H%M%SZ",
+ &lock_time);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_TRACE_FUNC, "sss_utc_to_time_t failed with %d:%s.\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ now = time(NULL);
+
+ /* Account was NOT locked in past. */
+ if (difftime(lock_time, now) > 0.0) {
+ locked = false;
+ } else if (pwdAccountLockedDurationTime != NULL) {
+ duration = strtouint32(pwdAccountLockedDurationTime, &endptr, 0);
+ if (errno || *endptr) {
+ ret = errno ? errno : EINVAL;
+ goto done;
+ }
+ /* Lockout has expired */
+ if (duration != 0 && difftime(now, lock_time) > duration) {
+ locked = false;
+ }
+ }
+ break;
+ case PWP_SENTINEL:
+ default:
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unexpected value of password policy mode: %d.\n", pwpol_mode);
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_locked = locked;
+ }
+
+ return ret;
+}
+
+static void sdap_access_ppolicy_step_done(struct tevent_req *subreq)
+{
+ int ret, tret, dp_error;
+ size_t num_results;
+ bool locked = false;
+ const char *pwdAccountLockedTime;
+ const char *pwdAccountLockedDurationTime;
+ struct sysdb_attrs **results;
+ struct tevent_req *req;
+ struct sdap_access_ppolicy_req_ctx *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_access_ppolicy_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_ppolicy_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_id_op_done() 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_ACESS_LOCKOUT_DURATION,
+ &pwdAccountLockedDurationTime);
+ if (ret != EOK) {
+ /* This attribute might not be set even if account is locked */
+ pwdAccountLockedDurationTime = NULL;
+ }
+
+ ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACCESS_LOCKED_TIME,
+ &pwdAccountLockedTime);
+ if (ret == EOK) {
+
+ ret = is_account_locked(pwdAccountLockedTime,
+ pwdAccountLockedDurationTime,
+ state->pwpol_mode,
+ state->username,
+ &locked);
+ if (ret != EOK) {
+ if (ret == ERR_TIMESPEC_NOT_SUPPORTED) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "timezone specifier in ppolicy is not supported\n");
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "is_account_locked failed: %d:[%s].\n",
+ ret, sss_strerror(ret));
+ }
+
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Account will be considered to be locked.\n");
+ locked = true;
+ }
+ } 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_ppolicy_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)
+{
+ const char *basedn;
+ errno_t ret;
+
+ basedn = ldb_msg_find_attr_as_string(user_entry, SYSDB_ORIG_DN, NULL);
+ if (basedn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,"Could not find originalDN for user [%s]\n",
+ username);
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_basedn = basedn;
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+static errno_t get_access_filter(TALLOC_CTX *mem_ctx,
+ struct dp_option *opts,
+ const char **_filter)
+{
+ const char *filter;
+
+ filter = dp_opt_get_cstring(opts, SDAP_ACCESS_FILTER);
+ if (filter == NULL) {
+ /* It's okay if this is NULL. In that case we will simply act
+ * like the 'deny' provider.
+ */
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Warning: LDAP access rule 'filter' is set, "
+ "but no ldap_access_filter configured. "
+ "All domain users will be denied access.\n");
+ return EOK;
+ }
+
+ filter = sdap_get_access_filter(mem_ctx, filter);
+ if (filter == NULL) {
+ return ENOMEM;
+ }
+
+ *_filter = filter;
+
+ return EOK;
+}
+
+static errno_t check_expire_policy(struct dp_option *opts)
+{
+ const char *expire_policy;
+ bool matched_policy = false;
+ const char *policies[] = {LDAP_ACCOUNT_EXPIRE_SHADOW,
+ LDAP_ACCOUNT_EXPIRE_AD,
+ LDAP_ACCOUNT_EXPIRE_NDS,
+ LDAP_ACCOUNT_EXPIRE_RHDS,
+ LDAP_ACCOUNT_EXPIRE_IPA,
+ LDAP_ACCOUNT_EXPIRE_389DS,
+ NULL};
+
+ expire_policy = dp_opt_get_cstring(opts, SDAP_ACCOUNT_EXPIRE_POLICY);
+ if (expire_policy == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Warning: LDAP access rule 'expire' is set, "
+ "but no ldap_account_expire_policy configured. "
+ "All domain users will be denied access.\n");
+ return EOK;
+ }
+
+ for (unsigned i = 0; policies[i] != NULL; i++) {
+ if (strcasecmp(expire_policy, policies[i]) == 0) {
+ matched_policy = true;
+ break;
+ }
+ }
+
+ if (matched_policy == false) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unsupported LDAP account expire policy [%s].\n",
+ expire_policy);
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+/* Please use this only for short lists */
+static errno_t check_order_list_for_duplicates(char **list,
+ bool case_sensitive)
+{
+ size_t c;
+ size_t d;
+ int cmp;
+
+ for (c = 0; list[c] != NULL; c++) {
+ for (d = c + 1; list[d] != NULL; d++) {
+ if (case_sensitive) {
+ cmp = strcmp(list[c], list[d]);
+ } else {
+ cmp = strcasecmp(list[c], list[d]);
+ }
+ if (cmp == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Duplicate string [%s] found.\n", list[c]);
+ return EINVAL;
+ }
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t get_access_order_list(TALLOC_CTX *mem_ctx,
+ const char *order,
+ char ***_order_list)
+{
+ errno_t ret;
+ char **order_list;
+ int order_list_len;
+
+ ret = split_on_separator(mem_ctx, order, ',', true, true,
+ &order_list, &order_list_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "split_on_separator failed.\n");
+ goto done;
+ }
+
+ ret = check_order_list_for_duplicates(order_list, false);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "check_order_list_for_duplicates failed.\n");
+ goto done;
+ }
+
+ if (order_list_len > LDAP_ACCESS_LAST) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Currently only [%d] different access rules are supported.\n",
+ LDAP_ACCESS_LAST);
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_order_list = order_list;
+
+done:
+ if (ret != EOK) {
+ talloc_free(order_list);
+ }
+
+ return ret;
+}
+
+
+static errno_t get_order_list(TALLOC_CTX *mem_ctx,
+ enum sdap_access_type type,
+ struct dp_option *opts,
+ char ***_order_list)
+{
+ errno_t ret = EOK;
+ const char *order;
+
+ switch (type) {
+ case SDAP_TYPE_LDAP:
+ order = dp_opt_get_cstring(opts, SDAP_ACCESS_ORDER);
+ if (order == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ldap_access_order not given, using 'filter'.\n");
+ order = "filter";
+ }
+ break;
+ case SDAP_TYPE_IPA:
+ order = dp_opt_get_cstring(opts, IPA_ACCESS_ORDER);
+ if (order == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "ipa_access_order not given, using 'expire'.\n");
+ order = "expire";
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown sdap_access_type [%i].\n", type);
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* We can pass _order_list because it is the last operation.
+ * Should this ever change, this would require an intermediate variable and
+ * to assign the value to _order_list on success at the very last moment. */
+ ret = get_access_order_list(mem_ctx, order, _order_list);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "get_access_order_list failed: [%d][%s].\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
+
+static errno_t set_sdap_access_rules(TALLOC_CTX *mem_ctx,
+ struct dp_option *opts,
+ char **order_list,
+ struct sdap_access_ctx *access_ctx)
+{
+ int ret = EOK;
+ int c;
+ const char *filter = NULL;
+
+
+ for (c = 0; order_list[c] != NULL; c++) {
+ if (strcasecmp(order_list[c], LDAP_ACCESS_FILTER_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_FILTER;
+ if (get_access_filter(mem_ctx, opts, &filter) != EOK) {
+ goto done;
+ }
+
+ } else if (strcasecmp(order_list[c], LDAP_ACCESS_EXPIRE_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE;
+ if (check_expire_policy(opts) != EOK) {
+ goto done;
+ }
+
+ } else if (strcasecmp(order_list[c], LDAP_ACCESS_SERVICE_NAME) == 0) {
+ 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_RHOST_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_RHOST;
+ } else if (strcasecmp(order_list[c], LDAP_ACCESS_LOCK_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_LOCKOUT;
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_REJECT;
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_WARN;
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_RENEW;
+ } else if (strcasecmp(order_list[c], LDAP_ACCESS_PPOLICY_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_PPOLICY;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected access rule name [%s].\n", order_list[c]);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+ access_ctx->access_rule[c] = LDAP_ACCESS_EMPTY;
+ if (c == 0) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Warning: access_provider=ldap set, "
+ "but ldap_access_order is empty. "
+ "All domain users will be denied access.\n");
+ }
+
+done:
+ if (ret == EOK) {
+ access_ctx->filter = filter;
+ } else {
+ talloc_zfree(filter);
+ }
+
+ return ret;
+}
+
+
+static errno_t set_ipa_access_rules(TALLOC_CTX *mem_ctx,
+ struct dp_option *opts,
+ char **order_list,
+ struct sdap_access_ctx *access_ctx)
+{
+ int ret = EOK;
+ int c;
+ const char *filter = NULL;
+
+
+ for (c = 0; order_list[c] != NULL; c++) {
+ if (strcasecmp(order_list[c], LDAP_ACCESS_EXPIRE_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE;
+ if (check_expire_policy(opts) != EOK) {
+ goto done;
+ }
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_REJECT;
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_WARN;
+ } else if (strcasecmp(order_list[c],
+ LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_RENEW;
+ } else if (strcasecmp(order_list[c], LDAP_ACCESS_PPOLICY_NAME) == 0) {
+ access_ctx->access_rule[c] = LDAP_ACCESS_PPOLICY;
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected access rule name [%s].\n", order_list[c]);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+ access_ctx->access_rule[c] = LDAP_ACCESS_EMPTY;
+ if (c == 0) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Warning: access_provider=ipa set, "
+ "but ipa_access_order is empty. "
+ "All domain users will be denied access.\n");
+ }
+
+done:
+ if (ret == EOK) {
+ access_ctx->filter = filter;
+ } else {
+ talloc_zfree(filter);
+ }
+
+ return ret;
+}
+
+
+errno_t sdap_set_access_rules(TALLOC_CTX *mem_ctx,
+ struct sdap_access_ctx *access_ctx,
+ struct dp_option *opts,
+ struct dp_option *more_opts)
+{
+ errno_t ret;
+ char **order_list = NULL;
+
+ ret = get_order_list(mem_ctx, access_ctx->type, opts, &order_list);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ switch (access_ctx->type) {
+ case SDAP_TYPE_LDAP:
+ ret = set_sdap_access_rules(mem_ctx, opts, order_list, access_ctx);
+ break;
+ case SDAP_TYPE_IPA:
+ ret = set_ipa_access_rules(mem_ctx, more_opts, order_list, access_ctx);
+ break;
+ default:
+ ret = EINVAL;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid sdap_access_type [%i].\n", access_ctx->type);
+ break;
+ }
+
+done:
+ talloc_free(order_list);
+ return ret;
+}