diff options
Diffstat (limited to 'src/providers/ldap')
76 files changed, 48980 insertions, 0 deletions
diff --git a/src/providers/ldap/ldap_access.c b/src/providers/ldap/ldap_access.c new file mode 100644 index 0000000..4ec4702 --- /dev/null +++ b/src/providers/ldap/ldap_access.c @@ -0,0 +1,128 @@ +/* + SSSD + + ldap_access.c + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2013 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 <security/pam_modules.h> +#include "src/util/util.h" +#include "src/providers/data_provider.h" +#include "src/providers/backend.h" +#include "src/providers/ldap/sdap_access.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_pam_access_handler_state { + struct pam_data *pd; +}; + +static void sdap_pam_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + + subreq = sdap_access_send(state, params->ev, params->be_ctx, + params->domain, access_ctx, + access_ctx->id_ctx->conn, pd); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_access_handler_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_pam_access_handler_done(struct tevent_req *subreq) +{ + struct sdap_pam_access_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_access_handler_state); + + ret = sdap_access_recv(subreq); + talloc_free(subreq); + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED_WARN: + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + case ERR_ACCESS_DENIED: + case ERR_PASSWORD_EXPIRED: + case ERR_PASSWORD_EXPIRED_REJECT: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_PASSWORD_EXPIRED_RENEW: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Error retrieving access check result.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c new file mode 100644 index 0000000..8ec4d3a --- /dev/null +++ b/src/providers/ldap/ldap_auth.c @@ -0,0 +1,1641 @@ +/* + SSSD + + LDAP Backend Module + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2008 Red Hat + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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/>. +*/ + +#ifdef WITH_MOZLDAP +#define LDAP_OPT_SUCCESS LDAP_SUCCESS +#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ((ber_tag_t) 0x81U) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U) +#endif + +#include "config.h" + +#include <time.h> +#include <errno.h> +#include <sys/time.h> +#include <strings.h> + +#include <shadow.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "util/user_info_msg.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_auth.h" + + +#define LDAP_PWEXPIRE_WARNING_TIME 0 + +static errno_t add_expired_warning(struct pam_data *pd, long exp_time) +{ + int ret; + uint32_t *data; + + if (exp_time < 0 || exp_time > UINT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Time to expire out of range.\n"); + return EINVAL; + } + + data = talloc_array(pd, uint32_t, 2); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + data[0] = SSS_PAM_USER_INFO_EXPIRE_WARN; + data[1] = (uint32_t) exp_time; + + ret = pam_add_response(pd, SSS_PAM_USER_INFO, 2 * sizeof(uint32_t), + (uint8_t *) data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + + return EOK; +} + +static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now, + struct pam_data *pd, + int pwd_exp_warning) +{ + time_t expire_time; + int expiration_warning; + int ret = ERR_INTERNAL; + + ret = sss_utc_to_time_t(expire_date, "%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 ret; + } + + 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 (expire_time == 0) { + /* Used by the MIT LDAP KDB plugin to indicate "never" */ + ret = EOK; + } else if (difftime(now, expire_time) > 0.0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Kerberos password expired.\n"); + if (pd != NULL) { + ret = add_expired_warning(pd, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + ret = ERR_PASSWORD_EXPIRED; + } else { + if (pwd_exp_warning >= 0) { + expiration_warning = pwd_exp_warning; + } else { + expiration_warning = KERBEROS_PWEXPIRE_WARNING_TIME; + } + if (pd != NULL && + (difftime(now + expiration_warning, expire_time) > 0.0 || + expiration_warning == 0)) { + ret = add_expired_warning(pd, (long) difftime(expire_time, now)); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + ret = EOK; + } + + return ret; +} + +static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now, + struct pam_data *pd) +{ + long today; + long password_age; + long exp; + int ret; + + if (spwd->sp_lstchg <= 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Last change day is not set, new password needed.\n"); + return ERR_PASSWORD_EXPIRED; + } + + today = (long) (now / (60 * 60 *24)); + password_age = today - spwd->sp_lstchg; + if (password_age < 0) { + DEBUG(SSSDBG_OP_FAILURE, + "The last password change time is in the future!.\n"); + return EOK; + } + + if ((spwd->sp_expire != -1 && today >= spwd->sp_expire) || + (spwd->sp_max != -1 && spwd->sp_inact != -1 && + password_age > spwd->sp_max + spwd->sp_inact)) + { + DEBUG(SSSDBG_CONF_SETTINGS, "Account expired.\n"); + return ERR_ACCOUNT_EXPIRED; + } + + if (spwd->sp_max != -1 && password_age > spwd->sp_max) { + DEBUG(SSSDBG_CONF_SETTINGS, "Password expired.\n"); + if (pd != NULL) { + ret = add_expired_warning(pd, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + return ERR_PASSWORD_EXPIRED; + } + + if (pd != NULL && spwd->sp_max != -1 && spwd->sp_warn != -1 && + password_age > spwd->sp_max - spwd->sp_warn ) { + + /* add_expired_warning() expects time in seconds */ + exp = (spwd->sp_max - password_age) * (60 * 60 * 24); + if (exp == 0) { + /* Seconds until next midnight */ + exp = ((today + 1) * (60 * 60 * 24)) - now; + } + + ret = add_expired_warning(pd, exp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n"); + } + } + + return EOK; +} + +static errno_t check_pwexpire_ldap(struct pam_data *pd, + struct sdap_ppolicy_data *ppolicy, + int pwd_exp_warning) +{ + int ret = EOK; + + if (ppolicy->grace >= 0 || ppolicy->expire > 0) { + uint32_t *data; + uint32_t *ptr; + + if (pwd_exp_warning < 0) { + pwd_exp_warning = 0; + } + + data = talloc_size(pd, 2* sizeof(uint32_t)); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + ptr = data; + if (ppolicy->grace >= 0) { + *ptr = SSS_PAM_USER_INFO_GRACE_LOGIN; + ptr++; + *ptr = ppolicy->grace; + } else if (ppolicy->expire > 0) { + if (pwd_exp_warning != 0 && ppolicy->expire > pwd_exp_warning) { + /* do not warn */ + goto done; + } + + /* send warning */ + *ptr = SSS_PAM_USER_INFO_EXPIRE_WARN; + ptr++; + *ptr = ppolicy->expire; + } + + ret = pam_add_response(pd, SSS_PAM_USER_INFO, 2* sizeof(uint32_t), + (uint8_t*)data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + +done: + return ret; +} + +errno_t check_pwexpire_policy(enum pwexpire pw_expire_type, + void *pw_expire_data, + struct pam_data *pd, + int pwd_expiration_warning) +{ + errno_t ret; + + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), pd); + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), pd, + pwd_expiration_warning); + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + ret = check_pwexpire_ldap(pd, pw_expire_data, + pwd_expiration_warning); + break; + case PWEXPIRE_NONE: + ret = EOK; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown password expiration type %d.\n", pw_expire_type); + ret = EINVAL; + } + + return ret; +} + +static errno_t +find_password_expiration_attributes(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + enum sdap_access_type access_type, + struct dp_option *opts, + enum pwexpire *pwd_exp_type, + void **data) +{ + const char *mark; + const char *val; + struct spwd *spwd; + const char *pwd_policy; + int ret; + + *pwd_exp_type = PWEXPIRE_NONE; + *data = NULL; + + switch (access_type) { + case SDAP_TYPE_IPA: + /* MIT-Kerberos is the only option for IPA */ + pwd_policy = PWD_POL_OPT_MIT; + break; + case SDAP_TYPE_LDAP: + pwd_policy = dp_opt_get_string(opts, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing password policy.\n"); + return EINVAL; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE,"Unknown access_type [%i].\n", access_type); + return EINVAL; + } + + if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No password policy requested.\n"); + return EOK; + } else if (strcasecmp(pwd_policy, PWD_POL_OPT_MIT) == 0) { + mark = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Found Kerberos password expiration attributes.\n"); + val = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_EXPIRATION, + NULL); + if (val != NULL) { + *data = talloc_strdup(mem_ctx, val); + if (*data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + *pwd_exp_type = PWEXPIRE_KERBEROS; + + return EOK; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "No Kerberos password expiration attributes found, " + "but MIT Kerberos password policy was requested. " + "Access will be denied.\n"); + return EACCES; + } + } else if (strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) == 0) { + mark = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Found shadow password expiration attributes.\n"); + spwd = talloc_zero(mem_ctx, struct spwd); + if (spwd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_lstchg); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MIN, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_min); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MAX, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_max); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_WARNING, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_warn); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_INACTIVE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_inact); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_EXPIRE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_expire); + if (ret != EOK) goto shadow_fail; + + *data = spwd; + *pwd_exp_type = PWEXPIRE_SHADOW; + + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No shadow password attributes found, " + "but shadow password policy was requested. " + "Access will be denied.\n"); + return EACCES; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "No password expiration attributes found.\n"); + return EOK; + +shadow_fail: + talloc_free(spwd); + return ret; +} + +/* ==Get-User-DN========================================================== */ +struct get_user_dn_state { + char *username; + + char *orig_dn; +}; + +static void get_user_dn_done(struct tevent_req *subreq); + +static struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *username) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct get_user_dn_state *state; + char *clean_name; + char *filter; + const char **attrs; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct get_user_dn_state); + if (!req) return NULL; + + ret = sss_parse_internal_fqname(state, username, + &state->username, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", username); + goto done; + } + + ret = sss_filter_sanitize(state, state->username, &clean_name); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + opts->user_map[SDAP_AT_USER_NAME].name, + clean_name, + opts->user_map[SDAP_OC_USER].name); + talloc_zfree(clean_name); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto done; + } + + /* We're mostly interested in the DN anyway */ + attrs = talloc_array(state, const char *, 3); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + attrs[0] = "objectclass"; + attrs[1] = opts->user_map[SDAP_AT_USER_NAME].name; + attrs[2] = NULL; + + subreq = sdap_search_user_send(state, ev, domain, opts, + opts->sdom->user_search_bases, + sh, attrs, filter, + dp_opt_get_int(opts->basic, + SDAP_SEARCH_TIMEOUT), + SDAP_LOOKUP_SINGLE); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, get_user_dn_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static void get_user_dn_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + struct ldb_message_element *el; + struct sysdb_attrs **users; + size_t count; + + ret = sdap_search_user_recv(state, subreq, NULL, &users, &count); + talloc_zfree(subreq); + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve users\n"); + tevent_req_error(req, ret); + return; + } + + if (count == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No such user\n"); + tevent_req_error(req, ENOMEM); + return; + } else if (count > 1) { + DEBUG(SSSDBG_OP_FAILURE, "Multiple users matched\n"); + tevent_req_error(req, EIO); + return; + } + + /* exactly one user. Get the originalDN */ + ret = sysdb_attrs_get_el_ext(users[0], SYSDB_ORIG_DN, false, &el); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "originalDN is not available for [%s].\n", state->username); + tevent_req_error(req, ret); + return; + } + + state->orig_dn = talloc_strdup(state, (const char *) el->values[0].data); + if (state->orig_dn == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found originalDN [%s] for [%s]\n", + state->orig_dn, state->username); + tevent_req_done(req); +} + +static int get_user_dn_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, + char **orig_dn) +{ + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + + if (orig_dn) { + *orig_dn = talloc_move(mem_ctx, &state->orig_dn); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +int get_user_dn(TALLOC_CTX *memctx, + struct sss_domain_info *domain, + enum sdap_access_type access_type, + struct sdap_options *opts, + const char *username, + char **user_dn, + enum pwexpire *user_pw_expire_type, + void **user_pw_expire_data) +{ + TALLOC_CTX *tmpctx; + enum pwexpire pw_expire_type = PWEXPIRE_NONE; + void *pw_expire_data; + struct ldb_result *res; + const char **attrs; + const char *dn = NULL; + int ret; + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + attrs = talloc_array(tmpctx, const char *, 11); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + attrs[0] = SYSDB_ORIG_DN; + attrs[1] = SYSDB_SHADOWPW_LASTCHANGE; + attrs[2] = SYSDB_SHADOWPW_MIN; + attrs[3] = SYSDB_SHADOWPW_MAX; + attrs[4] = SYSDB_SHADOWPW_WARNING; + attrs[5] = SYSDB_SHADOWPW_INACTIVE; + attrs[6] = SYSDB_SHADOWPW_EXPIRE; + attrs[7] = SYSDB_KRBPW_LASTCHANGE; + attrs[8] = SYSDB_KRBPW_EXPIRATION; + attrs[9] = SYSDB_PWD_ATTRIBUTE; + attrs[10] = NULL; + + ret = sysdb_get_user_attr(tmpctx, domain, username, attrs, &res); + if (ret) { + goto done; + } + + switch (res->count) { + case 0: + /* No such user entry? Look it up */ + ret = EAGAIN; + break; + + case 1: + dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + /* The user entry has no original DN. This is the case when the ID + * provider is not LDAP-based (proxy perhaps) */ + ret = EAGAIN; + break; + } + + dn = talloc_strdup(tmpctx, dn); + if (!dn) { + ret = ENOMEM; + break; + } + + ret = find_password_expiration_attributes(tmpctx, + res->msgs[0], + access_type, + opts->basic, + &pw_expire_type, + &pw_expire_data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "find_password_expiration_attributes failed.\n"); + } + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "User search by name (%s) returned > 1 results!\n", + username); + ret = EFAULT; + break; + } + +done: + if (ret == EOK) { + *user_dn = talloc_strdup(memctx, dn); + if (!*user_dn) { + ret = ENOMEM; + } + /* pw_expire_data may be NULL */ + *user_pw_expire_data = talloc_steal(memctx, pw_expire_data); + *user_pw_expire_type = pw_expire_type; + } + + talloc_zfree(tmpctx); + return ret; +} + +/* ==Authenticate-User==================================================== */ + +struct auth_state { + struct tevent_context *ev; + struct sdap_auth_ctx *ctx; + const char *username; + struct sss_auth_token *authtok; + struct sdap_service *sdap_service; + + struct sdap_handle *sh; + + char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; +}; + +static struct tevent_req *auth_connect_send(struct tevent_req *req); +static void auth_get_dn_done(struct tevent_req *subreq); +static void auth_do_bind(struct tevent_req *req); +static void auth_connect_done(struct tevent_req *subreq); +static void auth_bind_user_done(struct tevent_req *subreq); + +static struct tevent_req *auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_auth_ctx *ctx, + const char *username, + struct sss_auth_token *authtok, + bool try_chpass_service) +{ + struct tevent_req *req; + struct auth_state *state; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct auth_state); + if (!req) return NULL; + + /* The token must be a password token */ + if (sss_authtok_get_type(authtok) != SSS_AUTHTOK_TYPE_PASSWORD) { + if (sss_authtok_get_type(authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + /* Tell frontend that we do not support Smartcard authentication */ + ret = ERR_SC_AUTH_NOT_SUPPORTED; + } else { + ret = ERR_AUTH_FAILED; + } + goto fail; + } + + state->ev = ev; + state->ctx = ctx; + state->username = username; + state->authtok = authtok; + if (try_chpass_service && ctx->chpass_service != NULL && + ctx->chpass_service->name != NULL) { + state->sdap_service = ctx->chpass_service; + } else { + state->sdap_service = ctx->service; + } + + ret = get_user_dn(state, state->ctx->be->domain, SDAP_TYPE_LDAP, + state->ctx->opts, state->username, &state->dn, + &state->pw_expire_type, &state->pw_expire_data); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, + "Need to look up the DN of %s later\n", state->username); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get user DN [%d]: %s\n", ret, sss_strerror(ret)); + goto fail; + } + + if (auth_connect_send(req) == NULL) { + ret = ENOMEM; + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static struct tevent_req *auth_connect_send(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct auth_state *state = tevent_req_data(req, + struct auth_state); + bool use_tls; + bool skip_conn_auth = false; + const char *sasl_mech; + + /* Check for undocumented debugging feature to disable TLS + * for authentication. This should never be used in production + * for obvious reasons. + */ + use_tls = !dp_opt_get_bool(state->ctx->opts->basic, SDAP_DISABLE_AUTH_TLS); + if (!use_tls) { + sss_log(SSS_LOG_ALERT, "LDAP authentication being performed over " + "insecure connection. This should be done " + "for debugging purposes only."); + } + + if (state->dn != NULL) { + /* In case the user's DN is known, the connection will only be used + * to bind as the user to perform the authentication. In that case, + * we don't need to authenticate the connection, because we're not + * looking up any information using the connection. This might be + * needed e.g. in case both ID and AUTH providers are set to LDAP + * and the server is AD, because otherwise the connection would both + * do a startTLS and later bind using GSSAPI or GSS-SPNEGO which + * doesn't work well with AD. + */ + skip_conn_auth = true; + } + + if (skip_conn_auth == false) { + sasl_mech = dp_opt_get_string(state->ctx->opts->basic, + SDAP_SASL_MECH); + if (sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + /* Don't force TLS on if we're told to use GSSAPI or GSS-SPNEGO */ + use_tls = false; + } + } + + if (ldap_is_ldapi_url(state->sdap_service->uri)) { + /* Don't force TLS on if we're a unix domain socket */ + use_tls = false; + } + + subreq = sdap_cli_connect_send(state, state->ev, state->ctx->opts, + state->ctx->be, + state->sdap_service, false, + use_tls ? CON_TLS_ON : CON_TLS_OFF, + skip_conn_auth); + + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return NULL; + } + + tevent_req_set_callback(subreq, auth_connect_done, req); + + return subreq; +} + +static bool check_encryption_used(LDAP *ldap) +{ + ber_len_t sasl_ssf = 0; + int tls_inplace = 0; + int ret; + + ret = ldap_get_option(ldap, LDAP_OPT_X_SASL_SSF, &sasl_ssf); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, "ldap_get_option failed to get sasl ssf, " + "assuming SASL is not used.\n"); + sasl_ssf = 0; + } + + tls_inplace = ldap_tls_inplace(ldap); + + DEBUG(SSSDBG_TRACE_ALL, + "Encryption used: SASL SSF [%lu] tls_inplace [%s].\n", sasl_ssf, + tls_inplace == 1 ? "TLS inplace" : "TLS NOT inplace"); + + if (sasl_ssf <= 1 && tls_inplace != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No encryption detected on LDAP connection.\n"); + sss_log(SSS_LOG_CRIT, "No encryption detected on LDAP connection.\n"); + return false; + } + + return true; +} + +static void auth_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, + struct auth_state); + int ret; + + ret = sdap_cli_connect_recv(subreq, state, NULL, &state->sh, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + /* As sdap_cli_connect_recv() returns EIO in case all the servers are + * down and we have to go offline, let's treat it accordingly here and + * allow the PAM responder to switch to offline authentication. + * + * Unfortunately, there's not much pattern within our code and the way + * to indicate we're going down in this part of the code is returning + * an ETIMEDOUT. + */ + if (ret == EIO) { + tevent_req_error(req, ETIMEDOUT); + } else { + if (auth_connect_send(req) == NULL) { + tevent_req_error(req, ENOMEM); + } + } + return; + } + + if (!ldap_is_ldapi_url(state->sdap_service->uri) && + !check_encryption_used(state->sh->ldap) && + !dp_opt_get_bool(state->ctx->opts->basic, SDAP_DISABLE_AUTH_TLS)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Aborting the authentication request.\n"); + sss_log(SSS_LOG_CRIT, "Aborting the authentication request.\n"); + tevent_req_error(req, ERR_AUTH_FAILED); + return; + } + + if (state->dn == NULL) { + /* The cached user entry was missing the bind DN. Need to look + * it up based on user name in order to perform the bind */ + subreq = get_user_dn_send(req, state->ev, state->ctx->be->domain, + state->sh, state->ctx->opts, state->username); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, auth_get_dn_done, req); + return; + } + + /* All required user data was pre-cached during an identity lookup. + * We can proceed with the bind */ + auth_do_bind(req); + return; +} + +static void auth_get_dn_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, struct auth_state); + errno_t ret; + + ret = get_user_dn_recv(state, subreq, &state->dn); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ERR_ACCOUNT_UNKNOWN); + return; + } + + /* The DN was found with an LDAP lookup + * We can proceed with the bind */ + return auth_do_bind(req); +} + +static void auth_do_bind(struct tevent_req *req) +{ + struct auth_state *state = tevent_req_data(req, struct auth_state); + struct tevent_req *subreq; + + subreq = sdap_auth_send(state, state->ev, state->sh, + NULL, NULL, state->dn, + state->authtok, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_OPT_TIMEOUT)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, auth_bind_user_done, req); +} + +static void auth_bind_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct auth_state *state = tevent_req_data(req, + struct auth_state); + int ret; + struct sdap_ppolicy_data *ppolicy = NULL; + + ret = sdap_auth_recv(subreq, state, &ppolicy); + talloc_zfree(subreq); + if (ppolicy != NULL) { + DEBUG(SSSDBG_TRACE_ALL,"Found ppolicy data, " + "assuming LDAP password policies are active.\n"); + state->pw_expire_type = PWEXPIRE_LDAP_PASSWORD_POLICY; + state->pw_expire_data = ppolicy; + } + switch (ret) { + case EOK: + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + if (auth_connect_send(req) == NULL) { + tevent_req_error(req, ENOMEM); + } + return; + default: + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t auth_recv(struct tevent_req *req, TALLOC_CTX *memctx, + struct sdap_handle **sh, char **dn, + enum pwexpire *pw_expire_type, void **pw_expire_data) +{ + struct auth_state *state = tevent_req_data(req, struct auth_state); + + if (sh != NULL) { + *sh = talloc_steal(memctx, state->sh); + if (*sh == NULL) return ENOMEM; + } + + if (dn != NULL) { + *dn = talloc_steal(memctx, state->dn); + if (*dn == NULL) return ENOMEM; + } + + if (pw_expire_data != NULL) { + *pw_expire_data = talloc_steal(memctx, state->pw_expire_data); + } + + *pw_expire_type = state->pw_expire_type; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_pam_auth_handler_state { + struct pam_data *pd; + struct be_ctx *be_ctx; +}; + +static void sdap_pam_auth_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_auth_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_auth_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->be_ctx = params->be_ctx; + pd->pam_status = PAM_SYSTEM_ERR; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, false); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_auth_handler_done, req); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, true); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_auth_handler_done, req); + break; + case SSS_PAM_CHAUTHTOK: + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + goto immediately; + default: + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_pam_auth_handler_done(struct tevent_req *subreq) +{ + struct sdap_pam_auth_handler_state *state; + struct tevent_req *req; + enum pwexpire pw_expire_type; + void *pw_expire_data; + const char *password; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_auth_handler_state); + + ret = auth_recv(subreq, state, NULL, NULL, + &pw_expire_type, &pw_expire_data); + talloc_free(subreq); + + if (ret == EOK) { + ret = check_pwexpire_policy(pw_expire_type, pw_expire_data, state->pd, + state->be_ctx->domain->pwd_expiration_warning); + if (ret == EINVAL) { + /* Unknown password expiration type. */ + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (ret) { + case EOK: + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_AUTH_DENIED: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_AUTH_FAILED: + state->pd->pam_status = PAM_AUTH_ERR; + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + be_mark_offline(state->be_ctx); + break; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + case ERR_PASSWORD_EXPIRED: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_ACCOUNT_LOCKED: + state->pd->account_locked = true; + state->pd->pam_status = PAM_PERM_DENIED; + break; + case ERR_SC_AUTH_NOT_SUPPORTED: + state->pd->pam_status = PAM_BAD_ITEM; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + if (ret == EOK && state->be_ctx->domain->cache_credentials) { + ret = sss_authtok_get_password(state->pd->authtok, &password, NULL); + if (ret == EOK) { + ret = sysdb_cache_password(state->be_ctx->domain, state->pd->user, + password); + } + + /* password caching failures are not fatal errors */ + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password for %s\n", + state->pd->user); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Password successfully cached for %s\n", + state->pd->user); + } + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_auth_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_auth_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct sdap_pam_change_password_state { + enum pwmodify_mode mode; + char *user_error_message; +}; + +static void sdap_pam_change_password_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_pam_change_password_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct pam_data *pd, + char *user_dn) +{ + struct sdap_pam_change_password_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *password; + const char *new_password; + char *pwd_attr; + int timeout; + errno_t ret; + + pwd_attr = opts->user_map[SDAP_AT_USER_PWD].name; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_change_password_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->mode = opts->pwmodify_mode; + + ret = sss_authtok_get_password(pd->authtok, &password, NULL); + if (ret != EOK) { + goto done; + } + + ret = sss_authtok_get_password(pd->newauthtok, &new_password, NULL); + if (ret != EOK) { + goto done; + } + + timeout = dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT); + + switch (opts->pwmodify_mode) { + case SDAP_PWMODIFY_EXOP: + subreq = sdap_exop_modify_passwd_send(state, ev, sh, user_dn, + password, new_password, + timeout); + break; + case SDAP_PWMODIFY_LDAP: + subreq = sdap_modify_passwd_send(state, ev, sh, timeout, pwd_attr, + user_dn, new_password); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %d\n", + opts->pwmodify_mode); + ret = EINVAL; + goto done; + } + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_pam_change_password_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_pam_change_password_done(struct tevent_req *subreq) +{ + struct sdap_pam_change_password_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_change_password_state); + + switch (state->mode) { + case SDAP_PWMODIFY_EXOP: + ret = sdap_exop_modify_passwd_recv(subreq, state, + &state->user_error_message); + break; + case SDAP_PWMODIFY_LDAP: + ret = sdap_modify_passwd_recv(subreq, state, + &state->user_error_message); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %d\n", + state->mode); + ret = EINVAL; + } + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t +sdap_pam_change_password_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_user_error_message) +{ + struct sdap_pam_change_password_state *state; + state = tevent_req_data(req, struct sdap_pam_change_password_state); + + /* We want to return the error message even on failure */ + *_user_error_message = talloc_steal(mem_ctx, state->user_error_message); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +struct sdap_pam_chpass_handler_state { + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct sdap_auth_ctx *auth_ctx; + struct pam_data *pd; + struct sdap_handle *sh; + char *dn; + enum pwexpire pw_expire_type; +}; + +static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq); +static int +sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state, + struct tevent_req *req, + enum pwexpire pw_expire_type); +static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq); +static void sdap_pam_chpass_handler_last_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_pam_chpass_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_pam_chpass_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->be_ctx = params->be_ctx; + state->auth_ctx = auth_ctx; + state->ev = params->ev; + + if (be_is_offline(state->be_ctx)) { + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto immediately; + } + + if ((pd->priv == 1) && (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) && + (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Password reset by root is not supported.\n"); + pd->pam_status = PAM_PERM_DENIED; + goto immediately; + } + + DEBUG(SSSDBG_OP_FAILURE, + "starting password change request for user [%s].\n", pd->user); + + pd->pam_status = PAM_SYSTEM_ERR; + + if (pd->cmd != SSS_PAM_CHAUTHTOK && pd->cmd != SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(SSSDBG_OP_FAILURE, + "chpass target was called by wrong pam command.\n"); + goto immediately; + } + + subreq = auth_send(state, params->ev, auth_ctx, + pd->user, pd->authtok, true); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_pam_chpass_handler_auth_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static bool confdb_is_set_explicit(struct confdb_ctx *cdb, const char *section, + const char *attribute) +{ + int ret; + char **vals = NULL; + bool update_option_set_explictly = false; + + ret = confdb_get_param(cdb, NULL, section, attribute, &vals); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check if [%s] is set " + "explicitly in sssd.conf, assuming it is not set.\n", + attribute); + } else if (vals != NULL && vals[0] != NULL) { + update_option_set_explictly = true; + } + talloc_free(vals); + + return update_option_set_explictly; +} + +static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + void *pw_expire_data; + size_t msg_len; + uint8_t *msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = auth_recv(subreq, state, &state->sh, &state->dn, + &state->pw_expire_type, &pw_expire_data); + talloc_free(subreq); + + if ((ret == EOK || ret == ERR_PASSWORD_EXPIRED) && + state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(SSSDBG_TRACE_ALL, "Initial authentication for change " + "password operation successful.\n"); + state->pd->pam_status = PAM_SUCCESS; + goto done; + } + + if (ret == EOK) { + switch (state->pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), NULL); + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), NULL, + state->be_ctx->domain->pwd_expiration_warning); + + if (ret == ERR_PASSWORD_EXPIRED) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP provider cannot change " + "kerberos passwords.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + case PWEXPIRE_NONE: + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown password expiration type %d.\n", + state->pw_expire_type); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED: + DEBUG(SSSDBG_TRACE_LIBS, + "user [%s] successfully authenticated.\n", state->dn); + ret = sdap_pam_chpass_handler_change_step(state, req, + state->pw_expire_type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_pam_chpass_handler_change_step() failed.\n"); + goto done; + } + return; + break; + case ERR_AUTH_DENIED: + case ERR_AUTH_FAILED: + state->pd->pam_status = PAM_AUTH_ERR; + ret = pack_user_info_chpass_error(state->pd, "Old password not " + "accepted.", &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_chpass_error failed.\n"); + } else { + ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, + msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case ETIMEDOUT: + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + be_mark_offline(state->be_ctx); + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static int +sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state, + struct tevent_req *req, + enum pwexpire pw_expire_type) +{ + int ret; + struct tevent_req *subreq; + bool update_option_set_explictly = false; + + if (pw_expire_type == PWEXPIRE_SHADOW) { + + update_option_set_explictly = confdb_is_set_explicit( + state->be_ctx->cdb, state->be_ctx->conf_path, + state->auth_ctx->opts->basic[SDAP_CHPASS_UPDATE_LAST_CHANGE].opt_name); + + if (!dp_opt_get_bool(state->auth_ctx->opts->basic, + SDAP_CHPASS_UPDATE_LAST_CHANGE) + && !update_option_set_explictly) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Shadow password policy is selected but " + "ldap_chpass_update_last_change is not set, please " + "make sure your LDAP server can update the [%s] " + "attribute automatically. Otherwise SSSD might " + "consider your password as expired.\n", + state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name); + } + ret = sysdb_invalidate_cache_entry(state->be_ctx->domain, + state->pd->user, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to invalidate cache entry for user [%s] with error code " + "[%d][%s]. The last changed attribute [%s] might not get " + "refreshed after the password and the password might still be " + "considered as expired. Call sss_cache for this user " + "to expire the entry manually in this case.\n", + state->pd->user, ret, sss_strerror(ret), + state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name); + } + } + subreq = sdap_pam_change_password_send(state, state->ev, + state->sh, + state->auth_ctx->opts, + state->pd, + state->dn); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to change password for " + "%s\n", state->pd->user); + state->pd->pam_status = PAM_SYSTEM_ERR; + return ENOMEM; + } + + tevent_req_set_callback(subreq, + sdap_pam_chpass_handler_chpass_done, + req); + return EOK; +} + +static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + char *user_error_message = NULL; + char *lastchanged_name; + size_t msg_len; + uint8_t *msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = sdap_pam_change_password_recv(state, subreq, &user_error_message); + talloc_free(subreq); + + switch (ret) { + case EOK: + if (state->pw_expire_type == PWEXPIRE_SHADOW) { + ret = sysdb_update_user_shadow_last_change(state->be_ctx->domain, + state->pd->user, SYSDB_SHADOWPW_LASTCHANGE); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_CHPASS_DENIED: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_NETWORK_IO: + state->pd->pam_status = PAM_AUTHTOK_ERR; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + if (state->pd->pam_status != PAM_SUCCESS && user_error_message != NULL) { + ret = pack_user_info_chpass_error(state->pd, user_error_message, + &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_user_info_chpass_error failed.\n"); + } else { + ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + } + + if (state->pd->pam_status == PAM_SUCCESS && + dp_opt_get_bool(state->auth_ctx->opts->basic, + SDAP_CHPASS_UPDATE_LAST_CHANGE)) { + lastchanged_name = state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name; + + subreq = sdap_modify_shadow_lastchange_send(state, state->ev, + state->sh, state->dn, + lastchanged_name); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + tevent_req_set_callback(subreq, sdap_pam_chpass_handler_last_done, req); + return; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void sdap_pam_chpass_handler_last_done(struct tevent_req *subreq) +{ + struct sdap_pam_chpass_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + ret = sdap_modify_shadow_lastchange_recv(subreq); + talloc_free(subreq); + + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + state->pd->pam_status = PAM_SUCCESS; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +sdap_pam_chpass_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct sdap_pam_chpass_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_pam_chpass_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ldap/ldap_auth.h b/src/providers/ldap/ldap_auth.h new file mode 100644 index 0000000..0a277ed --- /dev/null +++ b/src/providers/ldap/ldap_auth.h @@ -0,0 +1,49 @@ +/* + SSSD + + Copyright (C) Pavel Reichl <preichl@redhat.com> 2015 + + 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/>. +*/ + +#ifndef _LDAP_AUTH_H_ +#define _LDAP_AUTH_H_ + +#include "config.h" + +#include "providers/ldap/sdap_access.h" + +enum pwexpire { + PWEXPIRE_NONE = 0, + PWEXPIRE_LDAP_PASSWORD_POLICY, + PWEXPIRE_KERBEROS, + PWEXPIRE_SHADOW +}; + +int get_user_dn(TALLOC_CTX *memctx, + struct sss_domain_info *domain, + enum sdap_access_type access_type, + struct sdap_options *opts, + const char *username, + char **user_dn, + enum pwexpire *user_pw_expire_type, + void **user_pw_expire_data); + +errno_t check_pwexpire_policy(enum pwexpire pw_expire_type, + void *pw_expire_data, + struct pam_data *pd, + errno_t checkb); + + +#endif /* _LDAP_AUTH_H_ */ diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c new file mode 100644 index 0000000..6c167d2 --- /dev/null +++ b/src/providers/ldap/ldap_child.c @@ -0,0 +1,788 @@ +/* + SSSD + + LDAP Backend Module -- prime ccache with TGT in a child process + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2009 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 <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <signal.h> +#include <popt.h> +#include <sys/prctl.h> + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "util/child_common.h" +#include "providers/backend.h" +#include "providers/krb5/krb5_common.h" + +char *global_ccname_file_dummy = NULL; + +static void sig_term_handler(int sig) +{ + if (global_ccname_file_dummy != NULL) { + /* Cast to void to avoid a complaint by Coverity */ + (void) unlink(global_ccname_file_dummy); + } + + _exit(CHILD_TIMEOUT_EXIT_CODE); +} + +static krb5_context krb5_error_ctx; +#define LDAP_CHILD_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error) + +struct input_buffer { + const char *realm_str; + const char *princ_str; + char *keytab_name; + krb5_deltat lifetime; + krb5_context context; + uid_t uid; + gid_t gid; +}; + +static errno_t unpack_buffer(uint8_t *buf, size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + + DEBUG(SSSDBG_TRACE_LIBS, "total buffer size: %zu\n", size); + + /* realm_str size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "realm_str size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->realm_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got realm_str: %s\n", ibuf->realm_str); + if (ibuf->realm_str == NULL) return ENOMEM; + p += len; + } + + /* princ_str size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "princ_str size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->princ_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got princ_str: %s\n", ibuf->princ_str); + if (ibuf->princ_str == NULL) return ENOMEM; + p += len; + } + + /* keytab_name size and length */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + DEBUG(SSSDBG_TRACE_LIBS, "keytab_name size: %d\n", len); + if (len) { + if (len > size - p) return EINVAL; + ibuf->keytab_name = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(SSSDBG_TRACE_LIBS, "got keytab_name: %s\n", ibuf->keytab_name); + if (ibuf->keytab_name == NULL) return ENOMEM; + p += len; + } + + /* ticket lifetime */ + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->lifetime, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_LIBS, "lifetime: %u\n", ibuf->lifetime); + + /* UID and GID to run as */ + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->uid, buf + p, size, &p); + SAFEALIGN_COPY_UINT32_CHECK(&ibuf->gid, buf + p, size, &p); + DEBUG(SSSDBG_FUNC_DATA, + "Will run as [%"SPRIuid"][%"SPRIgid"].\n", ibuf->uid, ibuf->gid); + + return EOK; +} + +static int pack_buffer(struct response *r, int result, krb5_error_code krberr, + const char *msg, time_t expire_time) +{ + int len; + size_t p = 0; + + len = strlen(msg); + r->size = 2 * sizeof(uint32_t) + sizeof(krb5_error_code) + + len + sizeof(time_t); + + DEBUG(SSSDBG_TRACE_INTERNAL, "response size: %zu\n",r->size); + + r->buf = talloc_array(r, uint8_t, r->size); + if(!r->buf) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "result [%d] krberr [%d] msgsize [%d] msg [%s]\n", + result, krberr, len, msg); + + /* result */ + SAFEALIGN_SET_UINT32(&r->buf[p], result, &p); + + /* krb5 error code */ + safealign_memcpy(&r->buf[p], &krberr, sizeof(krberr), &p); + + /* message size */ + SAFEALIGN_SET_UINT32(&r->buf[p], len, &p); + + /* message itself */ + safealign_memcpy(&r->buf[p], msg, len, &p); + + /* ticket expiration time */ + safealign_memcpy(&r->buf[p], &expire_time, sizeof(expire_time), &p); + + return EOK; +} + +static errno_t +set_child_debugging(krb5_context ctx) +{ + krb5_error_code kerr; + + /* Set the global error context */ + krb5_error_ctx = ctx; + + if (debug_level & SSSDBG_TRACE_ALL) { + kerr = sss_child_set_krb5_tracing(ctx); + if (kerr) { + LDAP_CHILD_DEBUG(SSSDBG_MINOR_FAILURE, kerr); + return EIO; + } + } + + return EOK; +} + +static int lc_verify_keytab_ex(const char *principal, + const char *keytab_name, + krb5_context context, + krb5_keytab keytab) +{ + bool found; + char *kt_principal; + krb5_error_code krberr; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + + krberr = krb5_kt_start_seq_get(context, keytab, &cursor); + if (krberr) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot read keytab [%s]: [%d][%s].\n", + sss_printable_keytab_name(context, keytab_name), + krberr, __err_msg); + + sss_log(SSS_LOG_ERR, "Error reading keytab file [%s]: [%d][%s]. " + "Unable to create GSSAPI-encrypted LDAP " + "connection.", + sss_printable_keytab_name(context, keytab_name), + krberr, __err_msg); + + sss_krb5_free_error_message(context, __err_msg); + return EIO; + } + + found = false; + while ((krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) { + krberr = krb5_unparse_name(context, entry.principal, &kt_principal); + if (krberr) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not parse keytab entry\n"); + sss_log(SSS_LOG_ERR, "Could not parse keytab entry\n"); + krb5_kt_end_seq_get(context, keytab, &cursor); + return EIO; + } + + if (strcmp(principal, kt_principal) == 0) { + found = true; + } + free(kt_principal); + krberr = sss_krb5_free_keytab_entry_contents(context, &entry); + if (krberr) { + /* This should never happen. The API docs for this function + * specify only success for this function + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Could not free keytab entry contents\n"); + /* This is non-fatal, so we'll continue here */ + } + + if (found) { + break; + } + } + + krberr = krb5_kt_end_seq_get(context, keytab, &cursor); + if (krberr) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not close keytab.\n"); + sss_log(SSS_LOG_ERR, "Could not close keytab file [%s].", + sss_printable_keytab_name(context, keytab_name)); + return EIO; + } + + if (!found) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Principal [%s] not found in keytab [%s]\n", + principal, + sss_printable_keytab_name(context, keytab_name)); + sss_log(SSS_LOG_ERR, "Error processing keytab file [%s]: " + "Principal [%s] was not found. " + "Unable to create GSSAPI-encrypted LDAP connection.", + sss_printable_keytab_name(context, keytab_name), + principal); + + return EFAULT; + } + + return EOK; +} + +static krb5_error_code ldap_child_get_tgt_sync(TALLOC_CTX *memctx, + krb5_context context, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + const krb5_deltat lifetime, + const char **ccname_out, + time_t *expire_time_out, + char **_krb5_msg) +{ + char *ccname; + char *ccname_dummy; + char *realm_name = NULL; + char *full_princ = NULL; + char *default_realm = NULL; + char *tmp_str = NULL; + krb5_keytab keytab = NULL; + krb5_ccache ccache = NULL; + krb5_principal kprinc; + krb5_creds my_creds; + krb5_get_init_creds_opt *options = NULL; + krb5_error_code krberr; + krb5_timestamp kdc_time_offset; + int canonicalize = 0; + int kdc_time_offset_usec; + int ret; + errno_t error_code; + TALLOC_CTX *tmp_ctx; + char *ccname_file_dummy = NULL; + char *ccname_file; + + *_krb5_msg = NULL; + + tmp_ctx = talloc_new(memctx); + if (tmp_ctx == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + + error_code = set_child_debugging(context); + if (error_code != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set krb5_child debugging\n"); + } + + if (!realm_str) { + krberr = krb5_get_default_realm(context, &default_realm); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_default_realm() failed: %d\n", krberr); + goto done; + } + + realm_name = talloc_strdup(tmp_ctx, default_realm); + krb5_free_default_realm(context, default_realm); + if (!realm_name) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + } else { + realm_name = talloc_strdup(tmp_ctx, realm_str); + if (!realm_name) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "got realm_name: [%s]\n", realm_name); + + if (princ_str) { + if (!strchr(princ_str, '@')) { + full_princ = talloc_asprintf(tmp_ctx, "%s@%s", + princ_str, realm_name); + } else { + full_princ = talloc_strdup(tmp_ctx, princ_str); + } + } else { + char hostname[HOST_NAME_MAX + 1]; + + ret = gethostname(hostname, sizeof(hostname)); + if (ret == -1) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, "hostname() failed: [%d][%s]", + errno, strerror(errno)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + + DEBUG(SSSDBG_TRACE_LIBS, "got hostname: [%s]\n", hostname); + + ret = select_principal_from_keytab(tmp_ctx, hostname, realm_name, + keytab_name, &full_princ, NULL, NULL); + if (ret) { + krberr = KRB5_KT_IOERR; + *_krb5_msg = talloc_strdup(memctx, + "select_principal_from_keytab() failed"); + goto done; + } + } + if (!full_princ) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Principal name is: [%s]\n", full_princ); + + if (keytab_name) { + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + } else { + krberr = krb5_kt_default(context, &keytab); + } + DEBUG(SSSDBG_CONF_SETTINGS, "Using keytab [%s]\n", + sss_printable_keytab_name(context, keytab_name)); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read keytab file: %d\n", krberr); + goto done; + } + + /* Verify the keytab */ + ret = lc_verify_keytab_ex(full_princ, keytab_name, context, keytab); + if (ret) { + krberr = KRB5_KT_IOERR; + *_krb5_msg = talloc_strdup(memctx, "Unable to verify principal is present in the keytab"); + goto done; + } + + memset(&my_creds, 0, sizeof(my_creds)); + + krberr = krb5_get_init_creds_opt_alloc(context, &options); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_get_init_creds_opt_alloc failed.\n"); + goto done; + } + + krb5_get_init_creds_opt_set_address_list(options, NULL); + krb5_get_init_creds_opt_set_forwardable(options, 0); + krb5_get_init_creds_opt_set_proxiable(options, 0); + krb5_get_init_creds_opt_set_tkt_life(options, lifetime); + krberr = krb5_get_init_creds_opt_set_pa(context, options, + "X509_user_identity", ""); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_init_creds_opt_set_pa failed [%d], ignored.\n", + krberr); + } + + + tmp_str = getenv("KRB5_CANONICALIZE"); + if (tmp_str != NULL && strcasecmp(tmp_str, "true") == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Will canonicalize principals\n"); + canonicalize = 1; + } + sss_krb5_get_init_creds_opt_set_canonicalize(options, canonicalize); + + ccname_file = talloc_asprintf(tmp_ctx, "%s/ccache_%s", + DB_PATH, realm_name); + if (ccname_file == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + + ccname_file_dummy = talloc_asprintf(tmp_ctx, "%s/ccache_%s_XXXXXX", + DB_PATH, realm_name); + if (ccname_file_dummy == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + global_ccname_file_dummy = ccname_file_dummy; + + ret = sss_unique_filename(tmp_ctx, ccname_file_dummy); + if (ret != EOK) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, + "sss_unique_filename() failed: [%d][%s]", + ret, strerror(ret)); + goto done; + } + + krberr = krb5_parse_name(context, full_princ, &kprinc); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_parse_name() failed: %d\n", krberr); + goto done; + } + krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, + keytab, 0, NULL, options); + krb5_free_principal(context, kprinc); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_get_init_creds_keytab() failed: %d\n", krberr); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "credentials initialized\n"); + krb5_kt_close(context, keytab); + keytab = NULL; + + ccname_dummy = talloc_asprintf(tmp_ctx, "FILE:%s", ccname_file_dummy); + ccname = talloc_asprintf(tmp_ctx, "FILE:%s", ccname_file); + if (ccname_dummy == NULL || ccname == NULL) { + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_strdup(memctx, strerror(ENOMEM)); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "keytab ccname: [%s]\n", ccname_dummy); + + krberr = krb5_cc_resolve(context, ccname_dummy, &ccache); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_resolve() failed: %d\n", krberr); + goto done; + } + + /* Use updated principal if changed due to canonicalization. */ + krberr = krb5_cc_initialize(context, ccache, my_creds.client); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_initialize() failed: %d\n", krberr); + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_store_cred() failed: %d\n", krberr); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "credentials stored\n"); + +#ifdef HAVE_KRB5_GET_TIME_OFFSETS + krberr = krb5_get_time_offsets(context, &kdc_time_offset, + &kdc_time_offset_usec); + if (krberr != 0) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + DEBUG(SSSDBG_OP_FAILURE, "Failed to get KDC time offset: %s\n", + __err_msg); + sss_krb5_free_error_message(context, __err_msg); + kdc_time_offset = 0; + } else { + if (kdc_time_offset_usec > 0) { + kdc_time_offset++; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Got KDC time offset\n"); +#else + /* If we don't have this function, just assume no offset */ + kdc_time_offset = 0; +#endif + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Renaming [%s] to [%s]\n", ccname_file_dummy, ccname_file); + ret = rename(ccname_file_dummy, ccname_file); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + krberr = KRB5KRB_ERR_GENERIC; + *_krb5_msg = talloc_asprintf(memctx, + "rename() failed: [%d][%s]", + ret, strerror(ret)); + + goto done; + } + global_ccname_file_dummy = NULL; + + krberr = 0; + *ccname_out = talloc_steal(memctx, ccname); + *expire_time_out = my_creds.times.endtime - kdc_time_offset; + +done: + krb5_get_init_creds_opt_free(context, options); + if (krberr != 0) { + if (*_krb5_msg == NULL) { + /* no custom error message provided hence get one from libkrb5 */ + const char *__krberr_msg = sss_krb5_get_error_message(context, krberr); + *_krb5_msg = talloc_strdup(memctx, __krberr_msg); + sss_krb5_free_error_message(context, __krberr_msg); + } + + sss_log(SSS_LOG_ERR, + "Failed to initialize credentials using keytab [%s]: %s. " + "Unable to create GSSAPI-encrypted LDAP connection.", + sss_printable_keytab_name(context, keytab_name), *_krb5_msg); + + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to initialize credentials using keytab [%s]: %s. " + "Unable to create GSSAPI-encrypted LDAP connection.\n", + sss_printable_keytab_name(context, keytab_name), *_krb5_msg); + } + if (keytab) krb5_kt_close(context, keytab); + if (context) krb5_free_context(context); + talloc_free(tmp_ctx); + return krberr; +} + +static int prepare_response(TALLOC_CTX *mem_ctx, + const char *ccname, + time_t expire_time, + krb5_error_code kerr, + char *krb5_msg, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (!r) return ENOMEM; + + r->buf = NULL; + r->size = 0; + + DEBUG(SSSDBG_TRACE_FUNC, "Building response for result [%d]\n", kerr); + + if (kerr == 0) { + ret = pack_buffer(r, EOK, kerr, ccname, expire_time); + } else { + if (krb5_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Empty krb5 error message for non-zero kerr: %"PRIi32"\n", + kerr); + return ENOMEM; + } + ret = pack_buffer(r, EFAULT, kerr, krb5_msg, 0); + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n"); + return ret; + } + + *rsp = r; + return EOK; +} + +static krb5_error_code privileged_krb5_setup(struct input_buffer *ibuf) +{ + krb5_error_code kerr; + char *keytab_name; + + kerr = sss_krb5_init_context(&ibuf->context); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init kerberos context\n"); + return kerr; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Kerberos context initialized\n"); + + kerr = copy_keytab_into_memory(ibuf, ibuf->context, ibuf->keytab_name, + &keytab_name, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "copy_keytab_into_memory failed.\n"); + return kerr; + } + talloc_free(ibuf->keytab_name); + ibuf->keytab_name = keytab_name; + + return 0; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int kerr; + int opt; + int dumpable = 1; + int debug_fd = -1; + const char *opt_logger = NULL; + poptContext pc; + TALLOC_CTX *main_ctx = NULL; + uint8_t *buf = NULL; + ssize_t len = 0; + const char *ccname = NULL; + char *krb5_msg = NULL; + time_t expire_time = 0; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + ssize_t written; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "ldap_child[%d]", getpid()); + if (!debug_prg_name) { + debug_prg_name = "ldap_child"; + ERROR("talloc_asprintf failed.\n"); + goto fail; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + + BlockSignals(false, SIGTERM); + CatchSignal(SIGTERM, sig_term_handler); + + DEBUG(SSSDBG_TRACE_FUNC, "ldap_child started.\n"); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + goto fail; + } + talloc_steal(main_ctx, debug_prg_name); + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "context initialized\n"); + + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + kerr = privileged_krb5_setup(ibuf); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Privileged Krb5 setup failed.\n"); + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Kerberos context initialized\n"); + + kerr = become_user(ibuf->uid, ibuf->gid); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "become_user failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid()); + + DEBUG(SSSDBG_TRACE_INTERNAL, "getting TGT sync\n"); + kerr = ldap_child_get_tgt_sync(main_ctx, ibuf->context, + ibuf->realm_str, ibuf->princ_str, + ibuf->keytab_name, ibuf->lifetime, + &ccname, &expire_time, &krb5_msg); + if (kerr != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_child_get_tgt_sync failed.\n"); + /* Do not return, must report failure */ + } + + ret = prepare_response(main_ctx, ccname, expire_time, kerr, krb5_msg, + &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "prepare_response failed. [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + errno = 0; + written = sss_atomic_write_s(STDOUT_FILENO, resp->buf, resp->size); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + goto fail; + } + + if (written != resp->size) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + resp->size, written); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "ldap_child completed successfully\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(0); + +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_child failed!\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(-1); +} diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c new file mode 100644 index 0000000..90ca22d --- /dev/null +++ b/src/providers/ldap/ldap_common.c @@ -0,0 +1,891 @@ +/* + SSSD + + LDAP Provider Common Functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2008-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 "providers/ldap/ldap_common.h" +#include "providers/fail_over.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/krb5/krb5_common.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" + +#include "util/sss_krb5.h" +#include "util/crypto/sss_crypto.h" + +#include "providers/ldap/sdap_idmap.h" + +errno_t ldap_id_setup_tasks(struct sdap_id_ctx *ctx) +{ + return sdap_id_setup_tasks(ctx->be, ctx, ctx->opts->sdom, + ldap_id_enumeration_send, + ldap_id_enumeration_recv, + ctx); +} + +errno_t sdap_id_setup_tasks(struct be_ctx *be_ctx, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt) +{ + int ret; + + /* set up enumeration task */ + if (sdom->dom->enumerate) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting up enumeration for %s\n", + sdom->dom->name); + ret = ldap_id_setup_enumeration(be_ctx, ctx, sdom, + send_fn, recv_fn, pvt); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not running we need to schedule it */ + DEBUG(SSSDBG_TRACE_FUNC, "Setting up cleanup task for %s\n", + sdom->dom->name); + ret = ldap_id_setup_cleanup(ctx, sdom); + } + + return ret; +} + +static void sdap_uri_callback(void *private_data, struct fo_server *server) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sdap_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr *sockaddr; + const char *tmp; + const char *srv_name; + char *new_uri; + socklen_t sockaddr_len; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return; + } + + service = talloc_get_type(private_data, struct sdap_service); + if (!service) { + talloc_free(tmp_ctx); + return; + } + + tmp = (const char *)fo_get_server_user_data(server); + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "FATAL: No hostent available for server (%s)\n", + fo_get_server_str_name(server)); + talloc_free(tmp_ctx); + return; + } + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, + fo_get_server_port(server), + &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); + talloc_free(tmp_ctx); + return; + } + + if (fo_is_srv_lookup(server)) { + if (!tmp) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown service, using ldap\n"); + tmp = SSS_LDAP_SRV_NAME; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get server host name\n"); + talloc_free(tmp_ctx); + return; + } + + new_uri = talloc_asprintf(service, "%s://%s:%d", + tmp, srv_name, + fo_get_server_port(server)); + } else { + new_uri = talloc_strdup(service, tmp); + } + + if (!new_uri) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy URI ...\n"); + talloc_free(tmp_ctx); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Constructed uri '%s'\n", new_uri); + + /* free old one and replace with new one */ + talloc_zfree(service->uri); + service->uri = new_uri; + talloc_zfree(service->sockaddr); + service->sockaddr = talloc_steal(service, sockaddr); + service->sockaddr_len = sockaddr_len; + talloc_free(tmp_ctx); +} + +errno_t +sdap_set_sasl_options(struct sdap_options *id_opts, + char *default_primary, + char *default_realm, + const char *keytab_path) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + char *sasl_primary; + char *desired_primary; + char *primary_realm; + char *sasl_realm; + char *desired_realm; + bool primary_requested = true; + bool realm_requested = true; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Configuration of SASL auth ID and realm */ + desired_primary = dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID); + if (!desired_primary) { + primary_requested = false; + desired_primary = default_primary; + } + + if ((primary_realm = strchr(desired_primary, '@'))) { + *primary_realm = '\0'; + desired_realm = primary_realm+1; + DEBUG(SSSDBG_TRACE_INTERNAL, + "authid contains realm [%s]\n", desired_realm); + } else { + desired_realm = dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM); + if (!desired_realm) { + realm_requested = false; + desired_realm = default_realm; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Will look for %s@%s in %s\n", + desired_primary, desired_realm, + keytab_path ? keytab_path : "default keytab"); + + ret = select_principal_from_keytab(tmp_ctx, + desired_primary, desired_realm, + keytab_path, + NULL, &sasl_primary, &sasl_realm); + if (ret != EOK) { + goto done; + } + + if (primary_requested && strcmp(desired_primary, sasl_primary) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Configured SASL auth ID not found in keytab. " + "Requested %s, found %s\n", desired_primary, sasl_primary); + } + + if (realm_requested && strcmp(desired_realm, sasl_realm) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Configured SASL realm not found in keytab. " + "Requested %s, found %s\n", desired_realm, sasl_realm); + } + + ret = dp_opt_set_string(id_opts->basic, + SDAP_SASL_AUTHID, sasl_primary); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + id_opts->basic[SDAP_SASL_AUTHID].opt_name, + dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID)); + + ret = dp_opt_set_string(id_opts->basic, + SDAP_SASL_REALM, sasl_realm); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + id_opts->basic[SDAP_SASL_REALM].opt_name, + dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM)); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +sdap_gssapi_get_default_realm(TALLOC_CTX *mem_ctx) +{ + char *krb5_realm = NULL; + const char *realm = NULL; + krb5_error_code krberr; + krb5_context context = NULL; + + krberr = sss_krb5_init_context(&context); + if (krberr) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to init kerberos context\n"); + goto done; + } + + krberr = krb5_get_default_realm(context, &krb5_realm); + if (krberr) { + const char *__err_msg = sss_krb5_get_error_message(context, krberr); + DEBUG(SSSDBG_OP_FAILURE, "Failed to get default realm name: %s\n", + __err_msg); + sss_krb5_free_error_message(context, __err_msg); + goto done; + } + + realm = talloc_strdup(mem_ctx, krb5_realm); + krb5_free_default_realm(context, krb5_realm); + if (!realm) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Will use default realm %s\n", realm); +done: + if (context) krb5_free_context(context); + return realm; +} + +const char *sdap_gssapi_realm(struct dp_option *opts) +{ + const char *realm; + + realm = dp_opt_get_cstring(opts, SDAP_SASL_REALM); + if (!realm) { + realm = dp_opt_get_cstring(opts, SDAP_KRB5_REALM); + } + + return realm; +} + +int sdap_gssapi_init(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + struct be_ctx *bectx, + struct sdap_service *sdap_service, + struct krb5_service **krb5_service) +{ + int ret; + const char *krb5_servers; + const char *krb5_backup_servers; + const char *krb5_realm; + const char *krb5_opt_realm; + struct krb5_service *service = NULL; + TALLOC_CTX *tmp_ctx; + size_t n_lookahead_primary; + size_t n_lookahead_backup; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + krb5_servers = dp_opt_get_string(opts, SDAP_KRB5_KDC); + krb5_backup_servers = dp_opt_get_string(opts, SDAP_KRB5_BACKUP_KDC); + + krb5_opt_realm = sdap_gssapi_realm(opts); + if (krb5_opt_realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing krb5_realm option, will use libkrb default\n"); + krb5_realm = sdap_gssapi_get_default_realm(tmp_ctx); + if (krb5_realm == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot determine the Kerberos realm, aborting\n"); + ret = EIO; + goto done; + } + } else { + krb5_realm = talloc_strdup(tmp_ctx, krb5_opt_realm); + if (krb5_realm == NULL) { + ret = ENOMEM; + goto done; + } + } + + sss_krb5_parse_lookahead( + dp_opt_get_string(opts, SDAP_KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + + ret = krb5_service_init(mem_ctx, bectx, + SSS_KRB5KDC_FO_SRV, krb5_servers, + krb5_backup_servers, krb5_realm, + dp_opt_get_bool(opts, + SDAP_KRB5_USE_KDCINFO), + n_lookahead_primary, + n_lookahead_backup, + &service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init KRB5 failover service!\n"); + goto done; + } + + sdap_service->kinit_service_name = talloc_strdup(sdap_service, + service->name); + if (sdap_service->kinit_service_name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *krb5_service = service; +done: + talloc_free(tmp_ctx); + if (ret != EOK) talloc_free(service); + return ret; +} + +static errno_t _sdap_urls_init(struct be_ctx *ctx, + struct sdap_service *service, + const char *service_name, + const char *dns_service_name, + const char *urls, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char *srv_user_data; + char **list = NULL; + LDAPURLDesc *lud; + errno_t ret = 0; + int i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, urls, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + /* now for each URI add a new server to the failover service */ + for (i = 0; list[i]; i++) { + if (be_fo_is_srv_identifier(list[i])) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + if (!dns_service_name) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Missing DNS service name for service [%s].\n", + service_name); + ret = EINVAL; + goto done; + } + srv_user_data = talloc_strdup(service, dns_service_name); + if (!srv_user_data) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_srv_server(ctx, service_name, + dns_service_name, NULL, + BE_FO_PROTO_TCP, false, srv_user_data); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup\n"); + continue; + } + + ret = ldap_url_parse(list[i], &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse ldap URI (%s)!\n", list[i]); + ret = EINVAL; + goto done; + } + + if (lud->lud_host == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "The LDAP URI (%s) did not contain a host name\n", + list[i]); + ldap_free_urldesc(lud); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added URI %s\n", list[i]); + + talloc_steal(service, list[i]); + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(lud->lud_host); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, service->name, lud->lud_host, + lud->lud_port, list[i], primary); + ldap_free_urldesc(lud); + if (ret) { + goto done; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + + +static inline errno_t +sdap_primary_urls_init(struct be_ctx *ctx, struct sdap_service *service, + const char *service_name, const char *dns_service_name, + const char *urls) +{ + return _sdap_urls_init(ctx, service, service_name, + dns_service_name, urls, true); +} + +static inline errno_t +sdap_backup_urls_init(struct be_ctx *ctx, struct sdap_service *service, + const char *service_name, const char *dns_service_name, + const char *urls) +{ + return _sdap_urls_init(ctx, service, service_name, + dns_service_name, urls, false); +} + +static int ldap_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +void sdap_service_reset_fo(struct be_ctx *ctx, + struct sdap_service *service) +{ + if (service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "NULL service\n"); + return; + } + + be_fo_reset_svc(ctx, service->name); +} + +int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *dns_service_name, + const char *urls, const char *backup_urls, + struct sdap_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct sdap_service *service; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + service = talloc_zero(tmp_ctx, struct sdap_service); + if (!service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, service_name, ldap_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + service->name = talloc_strdup(service, service_name); + if (!service->name) { + ret = ENOMEM; + goto done; + } + + if (!urls) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + urls = BE_SRV_IDENTIFIER; + } + + ret = sdap_primary_urls_init(ctx, service, service_name, dns_service_name, + urls); + if (ret != EOK) { + goto done; + } + + if (backup_urls) { + ret = sdap_backup_urls_init(ctx, service, service_name, + dns_service_name, backup_urls); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, service->name, + sdap_uri_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + +errno_t string_to_shadowpw_days(const char *s, long *d) +{ + long l; + char *endptr; + int ret; + + if (s == NULL || *s == '\0') { + *d = -1; + return EOK; + } + + errno = 0; + l = strtol(s, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "strtol failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Input string [%s] is invalid.\n", s); + return EINVAL; + } + + if (l < -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input string contains not allowed negative value [%ld].\n", + l); + return EINVAL; + } + + *d = l; + + return EOK; +} + +errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + const char *ldap_name, + char **sysdb_name) +{ + size_t i; + + for (i = 0; i < map_size; i++) { + /* Skip map entries with no name (may depend on + * schema selected) + */ + if (!map[i].name) continue; + + /* Check if it is a mapped attribute */ + if(strcasecmp(ldap_name, map[i].name) == 0) break; + } + + if (i < map_size) { + /* We found a mapped name, return that */ + *sysdb_name = talloc_strdup(mem_ctx, map[i].sys_name); + } else { + /* Not mapped, use the same name */ + *sysdb_name = talloc_strdup(mem_ctx, ldap_name); + } + + if (!*sysdb_name) { + return ENOMEM; + } + + return EOK; +} + +errno_t list_missing_attrs(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + struct sysdb_attrs *recvd_attrs, + char ***missing_attrs) +{ + errno_t ret; + size_t attr_count = 0; + size_t i, j, k; + char **missing = NULL; + const char **expected_attrs; + char *sysdb_name; + TALLOC_CTX *tmp_ctx; + + if (!recvd_attrs || !missing_attrs) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = build_attrs_from_map(tmp_ctx, map, map_size, NULL, + &expected_attrs, &attr_count); + if (ret != EOK) { + goto done; + } + + /* Allocate the maximum possible values for missing_attrs, to + * be on the safe side + */ + missing = talloc_array(tmp_ctx, char *, attr_count + 2); + if (!missing) { + ret = ENOMEM; + goto done; + } + + k = 0; + /* Check for each expected attribute */ + for (i = 0; i < attr_count; i++) { + ret = get_sysdb_attr_name(tmp_ctx, map, map_size, + expected_attrs[i], + &sysdb_name); + if (ret != EOK) { + goto done; + } + + /* objectClass is a special-case and we need to + * check for it explicitly. + */ + if (strcasecmp(sysdb_name, "objectClass") == 0) { + talloc_free(sysdb_name); + continue; + } + + /* GECOS is another special case. Its value can come + * either from the 'gecos' attribute or the 'cn' + * attribute. It's best if we just never remove it. + */ + if (strcasecmp(sysdb_name, SYSDB_GECOS) == 0) { + talloc_free(sysdb_name); + continue; + } + + for (j = 0; j < recvd_attrs->num; j++) { + /* Check whether this expected attribute appeared in the + * received attributes and had a non-zero number of + * values. + */ + if ((strcasecmp(recvd_attrs->a[j].name, sysdb_name) == 0) && + (recvd_attrs->a[j].num_values > 0)) { + break; + } + } + + if (j < recvd_attrs->num) { + /* Attribute was found, therefore not missing */ + talloc_free(sysdb_name); + } else { + /* Attribute could not be found. Add to the missing list */ + missing[k] = talloc_steal(missing, sysdb_name); + k++; + + /* Remove originalMemberOf as well if MemberOf is missing */ + if (strcmp(sysdb_name, SYSDB_MEMBEROF) == 0) { + missing[k] = talloc_strdup(missing, SYSDB_ORIG_MEMBEROF); + k++; + } + } + } + + if (k == 0) { + *missing_attrs = NULL; + } else { + /* Terminate the list */ + missing[k] = NULL; + *missing_attrs = talloc_steal(mem_ctx, missing); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +bool sdap_is_secure_uri(const char *uri) +{ + /* LDAPS URI's are secure channels */ + if (strncasecmp(uri, LDAP_SSL_URI, strlen(LDAP_SSL_URI)) == 0) { + return true; + } + return false; +} + +char *sdap_get_access_filter(TALLOC_CTX *mem_ctx, + const char *base_filter) +{ + char *filter = NULL; + + if (base_filter == NULL) return NULL; + + if (base_filter[0] == '(') { + /* This filter is wrapped in parentheses. + * Pass it as-is to the openldap libraries. + */ + filter = talloc_strdup(mem_ctx, base_filter); + } else { + filter = talloc_asprintf(mem_ctx, "(%s)", base_filter); + } + + return filter; +} + +errno_t +sdap_attrs_get_sid_str(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + struct sysdb_attrs *sysdb_attrs, + const char *sid_attr, + char **_sid_str) +{ + errno_t ret; + enum idmap_error_code err; + struct ldb_message_element *el; + char *sid_str; + + ret = sysdb_attrs_get_el(sysdb_attrs, sid_attr, &el); + if (ret != EOK || el->num_values != 1) { + DEBUG(SSSDBG_TRACE_LIBS, + "No [%s] attribute. [%d][%s]\n", + sid_attr, el->num_values, strerror(ret)); + return ENOENT; + } + + if (el->values[0].length > 2 && + el->values[0].data[0] == 'S' && + el->values[0].data[1] == '-') { + sid_str = talloc_strndup(mem_ctx, (char *) el->values[0].data, + el->values[0].length); + if (sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + } else { + err = sss_idmap_bin_sid_to_sid(idmap_ctx->map, + el->values[0].data, + el->values[0].length, + &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID: [%s]\n", + idmap_error_string(err)); + return EIO; + } + } + + *_sid_str = talloc_steal(mem_ctx, sid_str); + + return EOK; +} + +struct sdap_id_conn_ctx * +sdap_id_ctx_conn_add(struct sdap_id_ctx *id_ctx, + struct sdap_service *sdap_service) +{ + struct sdap_id_conn_ctx *conn; + errno_t ret; + + conn = talloc_zero(id_ctx, struct sdap_id_conn_ctx); + if (conn == NULL) { + return NULL; + } + conn->service = talloc_steal(conn, sdap_service); + conn->id_ctx = id_ctx; + + /* Create a connection cache */ + ret = sdap_id_conn_cache_create(conn, conn, &conn->conn_cache); + if (ret != EOK) { + talloc_free(conn); + return NULL; + } + DLIST_ADD_END(id_ctx->conn, conn, struct sdap_id_conn_ctx *); + + return conn; +} + +static int sdap_id_ctx_destructor(struct sdap_id_ctx *id_ctx) +{ + be_ptask_destroy(&id_ctx->task); + return 0; +} + +struct sdap_id_ctx * +sdap_id_ctx_new(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + struct sdap_service *sdap_service) +{ + struct sdap_id_ctx *sdap_ctx; + + sdap_ctx = talloc_zero(mem_ctx, struct sdap_id_ctx); + if (sdap_ctx == NULL) { + return NULL; + } + talloc_set_destructor(sdap_ctx, sdap_id_ctx_destructor); + + sdap_ctx->be = bectx; + + /* There should be at least one connection context */ + sdap_ctx->conn = sdap_id_ctx_conn_add(sdap_ctx, sdap_service); + if (sdap_ctx->conn == NULL) { + talloc_free(sdap_ctx); + return NULL; + } + + return sdap_ctx; +} + +errno_t +sdap_resolver_ctx_new(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_resolver_ctx **out_ctx) +{ + struct sdap_resolver_ctx *sdap_ctx; + + sdap_ctx = talloc_zero(mem_ctx, struct sdap_resolver_ctx); + if (sdap_ctx == NULL) { + return ENOMEM; + } + sdap_ctx->id_ctx = id_ctx; + + *out_ctx = sdap_ctx; + + return EOK; +} diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h new file mode 100644 index 0000000..7159d63 --- /dev/null +++ b/src/providers/ldap/ldap_common.h @@ -0,0 +1,486 @@ +/* + SSSD + + LDAP Common utility code + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + + 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/>. +*/ + +#ifndef _LDAP_COMMON_H_ +#define _LDAP_COMMON_H_ + +#include <ldb.h> + +#include "providers/backend.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/fail_over.h" +#include "providers/krb5/krb5_common.h" +#include "lib/idmap/sss_idmap.h" + +#define PWD_POL_OPT_NONE "none" +#define PWD_POL_OPT_SHADOW "shadow" +#define PWD_POL_OPT_MIT "mit_kerberos" + +#define SSS_LDAP_SRV_NAME "ldap" + +#define LDAP_STANDARD_URI "ldap://" +#define LDAP_SSL_URI "ldaps://" +#define LDAP_LDAPI_URI "ldapi://" + +/* Only the asterisk is allowed in wildcard requests */ +#define LDAP_ALLOWED_WILDCARDS "*" + +#define LDAP_ENUM_PURGE_TIMEOUT 10800 + +struct sdap_id_ctx; + +struct sdap_id_conn_ctx { + struct sdap_id_ctx *id_ctx; + + struct sdap_service *service; + /* LDAP connection cache */ + struct sdap_id_conn_cache *conn_cache; + /* dlinklist pointers */ + struct sdap_id_conn_ctx *prev, *next; + /* do not go offline, try another connection */ + bool ignore_mark_offline; + /* do not fall back to user lookups for mpg domains on this connection */ + bool no_mpg_user_fallback; +}; + +struct sdap_id_ctx { + struct be_ctx *be; + struct sdap_options *opts; + + /* If using GSSAPI or GSS-SPNEGO */ + struct krb5_service *krb5_service; + /* connection to a server */ + struct sdap_id_conn_ctx *conn; + + struct sdap_server_opts *srv_opts; + + /* Enumeration/cleanup periodic task. Only the enumeration or the cleanup + * task is started depending on the value of the domain's enumeration + * setting, this is why there is only one task pointer for both tasks. */ + struct be_ptask *task; + + /* enumeration loop timer */ + struct timeval last_enum; + /* cleanup loop timer */ + struct timeval last_purge; +}; + +struct sdap_auth_ctx { + struct be_ctx *be; + struct sdap_options *opts; + struct sdap_service *service; + struct sdap_service *chpass_service; +}; + +struct sdap_resolver_ctx { + struct sdap_id_ctx *id_ctx; + + /* Enumeration/cleanup periodic task */ + struct be_ptask *task; + + /* enumeration loop timer */ + struct timeval last_enum; + /* cleanup loop timer */ + struct timeval last_purge; +}; + +struct tevent_req * +sdap_online_check_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + void *data, + struct dp_req_params *params); + +errno_t sdap_online_check_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req* sdap_reinit_cleanup_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx); + +errno_t sdap_reinit_cleanup_recv(struct tevent_req *req); + +/* id */ +struct tevent_req * +sdap_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t sdap_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* Set up enumeration and/or cleanup */ +errno_t ldap_id_setup_tasks(struct sdap_id_ctx *ctx); +errno_t sdap_id_setup_tasks(struct be_ctx *be_ctx, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt); + +/* Allow shortcutting an enumeration request */ +bool sdap_is_enum_request(struct dp_id_data *ar); + +struct tevent_req * +sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete); +errno_t +sdap_handle_acct_req_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret); + +struct tevent_req * +sdap_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +sdap_pam_chpass_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_chpass_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* autofs */ +struct tevent_req * +sdap_autofs_enumerate_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_enumerate_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +struct tevent_req * +sdap_autofs_get_map_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_get_map_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +struct tevent_req * +sdap_autofs_get_entry_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params); + +errno_t +sdap_autofs_get_entry_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output); + +int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *dns_service_name, + const char *urls, const char *backup_urls, + struct sdap_service **_service); + +void sdap_service_reset_fo(struct be_ctx *ctx, + struct sdap_service *service); + +const char *sdap_gssapi_realm(struct dp_option *opts); + +int sdap_gssapi_init(TALLOC_CTX *mem_ctx, + struct dp_option *opts, + struct be_ctx *bectx, + struct sdap_service *sdap_service, + struct krb5_service **krb5_service); + +errno_t sdap_install_offline_callback(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char *realm, + const char *service_name); + +void sdap_remove_kdcinfo_files_callback(void *pvt); + +/* options parser */ +int ldap_get_options(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts); + +int ldap_get_sudo_options(struct confdb_ctx *cdb, + struct ldb_context *ldb, + const char *conf_path, + struct sdap_options *opts, + struct sdap_attr_map *native_map, + bool *use_host_filter, + bool *include_regexp, + bool *include_netgroups); + +int ldap_get_autofs_options(TALLOC_CTX *memctx, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options *opts); + +/* Calling ldap_id_setup_enumeration will set up a periodic task + * that would periodically call send_fn/recv_fn request. The + * send_fn's pvt parameter will be a pointer to ldap_enum_ctx + * structure that contains the request data + */ +struct ldap_enum_ctx { + struct sdap_domain *sdom; + void *pvt; +}; + +errno_t ldap_id_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt); +struct tevent_req * +ldap_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); +errno_t ldap_id_enumeration_recv(struct tevent_req *req); + +errno_t ldap_id_setup_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom); + +errno_t ldap_id_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + int filter_type, + bool noexist_delete, + bool no_members, + bool set_non_posix); +int groups_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix); + +int groups_by_user_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req *ldap_netgroup_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + bool noexist_delete); +int ldap_netgroup_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req * +services_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + const char *protocol, + int filter_type, + bool noexist_delete); + +errno_t +services_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret); + +struct tevent_req * +sdap_iphost_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +sdap_iphost_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req * +sdap_ipnetwork_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +sdap_ipnetwork_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + + +errno_t string_to_shadowpw_days(const char *s, long *d); + +errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + const char *ldap_name, + char **sysdb_name); + +errno_t list_missing_attrs(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + size_t map_size, + struct sysdb_attrs *recvd_attrs, + char ***missing_attrs); + +bool sdap_is_secure_uri(const char *uri); + +char *sdap_or_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter); + +char *sdap_combine_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter); + +char *get_enterprise_principal_string_filter(TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *princ, + struct dp_option *sdap_basic_opts); + +char *sdap_get_access_filter(TALLOC_CTX *mem_ctx, + const char *base_filter); + +errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, + struct ldb_message **msgs, + struct sysdb_attrs ***attrs); + +errno_t sdap_domain_add(struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_domain **_sdom); +errno_t +sdap_domain_subdom_add(struct sdap_id_ctx *sdap_id_ctx, + struct sdap_domain *sdom_list, + struct sss_domain_info *parent); + +void +sdap_domain_remove(struct sdap_options *opts, + struct sss_domain_info *dom); + +struct sdap_domain *sdap_domain_get(struct sdap_options *opts, + struct sss_domain_info *dom); + +struct sdap_domain *sdap_domain_get_by_name(struct sdap_options *opts, + const char *dom_name); + +struct sdap_domain *sdap_domain_get_by_dn(struct sdap_options *opts, + const char *dn); + +errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases); +errno_t common_parse_search_base(TALLOC_CTX *mem_ctx, + const char *unparsed_base, + struct ldb_context *ldb, + const char *class_name, + const char *old_filter, + struct sdap_search_base ***_search_bases); + +errno_t +sdap_attrs_get_sid_str(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + struct sysdb_attrs *sysdb_attrs, + const char *sid_attr, + char **_sid_str); + +errno_t +sdap_set_sasl_options(struct sdap_options *id_opts, + char *default_primary, + char *default_realm, + const char *keytab_path); + +struct sdap_id_conn_ctx * +sdap_id_ctx_conn_add(struct sdap_id_ctx *id_ctx, + struct sdap_service *sdap_service); + +struct sdap_id_ctx * +sdap_id_ctx_new(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + struct sdap_service *sdap_service); + +errno_t +sdap_resolver_ctx_new(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_resolver_ctx **out_ctx); + +errno_t sdap_refresh_init(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx); + +errno_t sdap_init_certmap(TALLOC_CTX *mem_ctx, struct sdap_id_ctx *id_ctx); + +errno_t sdap_setup_certmap(struct sdap_certmap_ctx *sdap_certmap_ctx, + struct certmap_info **certmap_list); +struct sss_certmap_ctx *sdap_get_sss_certmap(struct sdap_certmap_ctx *ctx); + +errno_t users_get_handle_no_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value, + bool name_is_upn); + +errno_t groups_get_handle_no_group(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value); + +#ifdef BUILD_SUBID +struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char* filter_value, + const char *extra_value); + +int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out, + int *sdap_ret); +#endif + +#endif /* _LDAP_COMMON_H_ */ diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c new file mode 100644 index 0000000..da54816 --- /dev/null +++ b/src/providers/ldap/ldap_id.c @@ -0,0 +1,1955 @@ +/* + SSSD + + LDAP Identity Backend Module + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2008 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 <errno.h> +#include <time.h> +#include <sys/time.h> + +#include "util/util.h" +#include "util/probes.h" +#include "util/strtonum.h" +#include "util/cert.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +errno_t users_get_handle_no_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value, + bool name_is_upn) +{ + int ret; + const char *del_name; + struct ldb_message *msg = NULL; + uid_t uid; + char *endptr; + + switch (filter_type) { + case BE_FILTER_ENUM: + ret = EOK; + break; + case BE_FILTER_NAME: + if (name_is_upn == true) { + ret = sysdb_search_user_by_upn(mem_ctx, domain, false, + filter_value, + NULL, &msg); + if (ret == ENOENT) { + return EOK; + } else if (ret != EOK && ret != ENOENT) { + return ret; + } + del_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + } else { + del_name = filter_value; + } + + if (del_name == NULL) { + ret = ENOMEM; + break; + } + + ret = sysdb_delete_user(domain, del_name, 0); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_user failed [%d].\n", ret); + } else { + ret = EOK; + } + break; + + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(filter_value, &endptr, 10); + if (errno || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + break; + } + + ret = sysdb_delete_user(domain, NULL, uid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_user failed [%d].\n", ret); + } else { + ret = EOK; + } + break; + + case BE_FILTER_SECID: + case BE_FILTER_UUID: + /* Since it is not clear if the SID/UUID belongs to a user or a + * group we have nothing to do here. */ + ret = EOK; + break; + + case BE_FILTER_WILDCARD: + /* We can't know if all users are up-to-date, especially in a large + * environment. Do not delete any records, let the responder fetch + * the entries they are requested in + */ + ret = EOK; + break; + + case BE_FILTER_CERT: + ret = sysdb_remove_cert(domain, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove user certificate" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + break; + + default: + ret = EINVAL; + } + + talloc_free(msg); + return ret; +} + +/* =Users-Related-Functions-(by-name,by-uid)============================== */ + +struct users_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + char *shortname; + + const char *filter_value; + int filter_type; + bool name_is_upn; + + char *filter; + const char **attrs; + bool use_id_mapping; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; + struct sysdb_attrs *extra_attrs; +}; + +static int users_get_retry(struct tevent_req *req); +static void users_get_connect_done(struct tevent_req *subreq); +static void users_get_search(struct tevent_req *req); +static void users_get_done(struct tevent_req *subreq); + +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix) +{ + struct tevent_req *req; + struct users_get_state *state; + const char *attr_name = NULL; + char *clean_value = NULL; + char *endptr; + int ret; + uid_t uid; + enum idmap_error_code err; + char *sid; + char *user_filter = NULL; + char *ep_filter; + + req = tevent_req_create(memctx, &state, struct users_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + state->extra_attrs = NULL; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + switch (filter_type) { + case BE_FILTER_WILDCARD: + attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; + ret = sss_filter_sanitize_ex(state, filter_value, &clean_value, + LDAP_ALLOWED_WILDCARDS); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_NAME: + if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) { + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + + ep_filter = get_enterprise_principal_string_filter(state, + ctx->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_value, ctx->opts->basic); + /* TODO: Do we have to check the attribute names more carefully? */ + user_filter = talloc_asprintf(state, "(|(%s=%s)(%s=%s)%s)", + ctx->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_value, + ctx->opts->user_map[SDAP_AT_USER_EMAIL].name, + clean_value, + ep_filter == NULL ? "" : ep_filter); + talloc_zfree(clean_value); + if (user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &state->shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, state->shortname, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_IDNUM: + if (state->use_id_mapping) { + /* If we're ID-mapping, we need to use the objectSID + * in the search filter. + */ + uid = strtouint32(filter_value, &endptr, 10); + if ((errno != EOK) || *endptr || (filter_value == endptr)) { + ret = EINVAL; + goto done; + } + + /* Convert the UID to its objectSID */ + err = sss_idmap_unix_to_sid(ctx->opts->idmap_ctx->map, + uid, &sid); + if (err == IDMAP_NO_DOMAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not match any configured ID mapping domain\n", + filter_value); + + ret = sysdb_delete_user(state->domain, NULL, uid); + if (ret == ENOENT) { + /* Ignore errors to remove users that were not cached previously */ + ret = EOK; + } + + goto done; + } else if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Mapping ID [%s] to SID failed: [%s]\n", + filter_value, idmap_error_string(err)); + ret = EIO; + goto done; + } + + attr_name = ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + ret = sss_filter_sanitize(state, sid, &clean_value); + sss_idmap_free_sid(ctx->opts->idmap_ctx->map, sid); + if (ret != EOK) { + goto done; + } + + } else { + attr_name = ctx->opts->user_map[SDAP_AT_USER_UID].name; + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_SECID: + attr_name = ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_UUID: + attr_name = ctx->opts->user_map[SDAP_AT_USER_UUID].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UUID search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_CERT: + attr_name = ctx->opts->user_map[SDAP_AT_USER_CERT].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_cert_derb64_to_ldap_filter(state, filter_value, attr_name, + sdap_get_sss_certmap(ctx->opts->sdap_certmap_ctx), + state->domain, &user_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + + /* Typically sss_cert_derb64_to_ldap_filter() will fail if there + * is no mapping rule matching the current certificate. But this + * just means that no matching user can be found so we can finish + * the request with this result. Even if + * sss_cert_derb64_to_ldap_filter() would fail for other reason + * there is no need to return an error which might cause the + * domain go offline. */ + + if (noexist_delete) { + ret = sysdb_remove_cert(state->domain, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Ignoring error while removing user certificate " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + ret = EOK; + state->sdap_ret = ENOENT; + state->dp_error = DP_ERR_OK; + goto done; + } + + state->extra_attrs = sysdb_new_attrs(state); + if (state->extra_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(state->extra_attrs, + SYSDB_USER_MAPPED_CERT, filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + break; + default: + ret = EINVAL; + goto done; + } + + if (attr_name == NULL && user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name or filter.\n"); + ret = EINVAL; + goto done; + } + + if (user_filter == NULL) { + user_filter = talloc_asprintf(state, "(%s=%s)", attr_name, clean_value); + talloc_free(clean_value); + if (user_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (state->non_posix) { + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name); + } else if (state->use_id_mapping || filter_type == BE_FILTER_SECID) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to users with a UID value. But there must be a SID to map + * from. + */ + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*)(%s=*))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* When not ID-mapping or looking up POSIX users, + * make sure there is a non-NULL UID */ + state->filter = talloc_asprintf(state, + "(&%s(objectclass=%s)(%s=*)(&(%s=*)(!(%s=0))))", + user_filter, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_AT_USER_UID].name, + ctx->opts->user_map[SDAP_AT_USER_UID].name); + } + + talloc_zfree(user_filter); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto done; + } + + ret = build_attrs_from_map(state, ctx->opts->user_map, + ctx->opts->user_map_cnt, + NULL, &state->attrs, NULL); + if (ret != EOK) goto done; + + ret = users_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int users_get_retry(struct tevent_req *req) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, users_get_connect_done, req); + return EOK; +} + +static void users_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + users_get_search(req); +} + +static void users_get_search(struct tevent_req *req) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + struct tevent_req *subreq; + enum sdap_entry_lookup_type lookup_type; + + if (state->filter_type == BE_FILTER_WILDCARD) { + lookup_type = SDAP_LOOKUP_WILDCARD; + } else { + lookup_type = SDAP_LOOKUP_SINGLE; + } + + subreq = sdap_get_users_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, + state->sdom->user_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + lookup_type, state->extra_attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_done, req); +} + +static void users_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + char *endptr; + uid_t uid = 0; + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_users_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = users_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + + if ((ret == ENOENT) && + (state->ctx->opts->schema_type == SDAP_SCHEMA_RFC2307) && + (dp_opt_get_bool(state->ctx->opts->basic, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS) == true)) { + struct sysdb_attrs **usr_attrs; + bool fallback; + + switch (state->filter_type) { + case BE_FILTER_NAME: + uid = -1; + fallback = true; + break; + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(state->filter_value, &endptr, 10); + if (errno || *endptr || (state->filter_value == endptr)) { + tevent_req_error(req, errno ? errno : EINVAL); + return; + } + fallback = true; + break; + default: + fallback = false; + break; + } + + if (fallback) { + ret = sdap_fallback_local_user(state, state->shortname, uid, &usr_attrs); + if (ret == EOK) { + ret = sdap_save_user(state, state->ctx->opts, state->domain, + usr_attrs[0], NULL, NULL, 0, + state->non_posix); + } + } + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + ret = users_get_handle_no_user(state, state->domain, state->filter_type, + state->filter_value, state->name_is_upn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + /* FIXME - return sdap error so that we know the user was not found */ + tevent_req_done(req); +} + +int users_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct users_get_state *state = tevent_req_data(req, + struct users_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Groups-Related-Functions-(by-name,by-uid)============================= */ + +struct groups_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_value; + int filter_type; + + char *filter; + const char **attrs; + bool use_id_mapping; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; + bool no_members; +}; + +static int groups_get_retry(struct tevent_req *req); +static void groups_get_connect_done(struct tevent_req *subreq); +static void groups_get_mpg_done(struct tevent_req *subreq); +static void groups_get_search(struct tevent_req *req); +static void groups_get_done(struct tevent_req *subreq); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + bool noexist_delete, + bool no_members, + bool set_non_posix) +{ + struct tevent_req *req; + struct groups_get_state *state; + const char *attr_name = NULL; + char *shortname = NULL; + char *clean_value; + char *endptr; + int ret; + gid_t gid; + enum idmap_error_code err; + char *sid; + const char *member_filter[2]; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct groups_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + state->no_members = no_members; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + switch(filter_type) { + case BE_FILTER_WILDCARD: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + ret = sss_filter_sanitize_ex(state, filter_value, &clean_value, + LDAP_ALLOWED_WILDCARDS); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_NAME: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, shortname, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_IDNUM: + if (state->use_id_mapping) { + /* If we're ID-mapping, we need to use the objectSID + * in the search filter. + */ + gid = strtouint32(filter_value, &endptr, 10); + if ((errno != EOK) || *endptr || (filter_value == endptr)) { + ret = EINVAL; + goto done; + } + + /* Convert the GID to its objectSID */ + err = sss_idmap_unix_to_sid(ctx->opts->idmap_ctx->map, + gid, &sid); + if (err == IDMAP_NO_DOMAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not match any configured ID mapping domain\n", + filter_value); + + ret = sysdb_delete_group(state->domain, NULL, gid); + if (ret == ENOENT) { + /* Ignore errors to remove users that were not cached previously */ + ret = EOK; + } + + goto done; + } else if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Mapping ID [%s] to SID failed: [%s]\n", + filter_value, idmap_error_string(err)); + ret = EIO; + goto done; + } + + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name; + ret = sss_filter_sanitize(state, sid, &clean_value); + sss_idmap_free_sid(ctx->opts->idmap_ctx->map, sid); + if (ret != EOK) { + goto done; + } + + } else { + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_GID].name; + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + } + break; + case BE_FILTER_SECID: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name; + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + case BE_FILTER_UUID: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_UUID].name; + if (attr_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UUID search not configured for this backend.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_value); + if (ret != EOK) { + goto done; + } + break; + default: + ret = EINVAL; + goto done; + } + + if (attr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name.\n"); + ret = EINVAL; + goto done; + } + + oc_list = sdap_make_oc_list(state, ctx->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + if (state->non_posix + || state->use_id_mapping + || filter_type == BE_FILTER_SECID) { + /* When mapping IDs or looking for SIDs, or when in a non-POSIX domain, + * we don't want to limit ourselves to groups with a GID value + */ + + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*))", + attr_name, clean_value, oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name); + } else { + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)(&(%s=*)(!(%s=0))))", + attr_name, clean_value, oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name); + } + + talloc_zfree(clean_value); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + member_filter[0] = (const char *)ctx->opts->group_map[SDAP_AT_GROUP_MEMBER].name; + member_filter[1] = NULL; + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + (state->domain->ignore_group_members + || state->no_members) ? + (const char **)member_filter : NULL, + &state->attrs, NULL); + + if (ret != EOK) goto done; + + ret = groups_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int groups_get_retry(struct tevent_req *req) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, groups_get_connect_done, req); + return EOK; +} + +static void groups_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + groups_get_search(req); +} + +static void groups_get_search(struct tevent_req *req) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + struct tevent_req *subreq; + enum sdap_entry_lookup_type lookup_type; + + if (state->filter_type == BE_FILTER_WILDCARD) { + lookup_type = SDAP_LOOKUP_WILDCARD; + } else { + lookup_type = SDAP_LOOKUP_SINGLE; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->sdom, + state->ctx->opts, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + lookup_type, + state->no_members); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_done, req); +} + +static void groups_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_groups_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = groups_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT + && sss_domain_is_mpg(state->domain) == true + && !state->conn->no_mpg_user_fallback) { + /* The requested filter did not find a group. Before giving up, we must + * also check if the GID can be resolved through a primary group of a + * user + */ + subreq = users_get_send(state, + state->ev, + state->ctx, + state->sdom, + state->conn, + state->filter_value, + state->filter_type, + NULL, + state->noexist_delete, + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_mpg_done, req); + return; + } else if (ret == ENOENT && state->noexist_delete == true) { + ret = groups_get_handle_no_group(state, state->domain, + state->filter_type, + state->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete group [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static void groups_get_mpg_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->sdap_ret == ENOENT && state->noexist_delete == true) { + ret = groups_get_handle_no_group(state, state->domain, + state->filter_type, + state->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete group [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + /* GID resolved to a user private group, done */ + tevent_req_done(req); + return; +} + +errno_t groups_get_handle_no_group(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int filter_type, const char *filter_value) +{ + errno_t ret; + char *endptr; + gid_t gid; + + switch (filter_type) { + case BE_FILTER_ENUM: + ret = ENOENT; + break; + case BE_FILTER_NAME: + ret = sysdb_delete_group(domain, filter_value, 0); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete group %s [%d]: %s\n", + filter_value, ret, sss_strerror(ret)); + return ret; + } + ret = EOK; + break; + case BE_FILTER_IDNUM: + gid = (gid_t) strtouint32(filter_value, &endptr, 10); + if (errno || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + break; + } + + ret = sysdb_delete_group(domain, NULL, gid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete group %"SPRIgid" [%d]: %s\n", + gid, ret, sss_strerror(ret)); + return ret; + } + ret = EOK; + break; + case BE_FILTER_SECID: + case BE_FILTER_UUID: + /* Since it is not clear if the SID/UUID belongs to a user or a + * group we have nothing to do here. */ + ret = EOK; + break; + case BE_FILTER_WILDCARD: + /* We can't know if all groups are up-to-date, especially in + * a large environment. Do not delete any records, let the + * responder fetch the entries they are requested in. + */ + ret = EOK; + break; + default: + ret = EINVAL; + break; + } + + return ret; +} + +int groups_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct groups_get_state *state = tevent_req_data(req, + struct groups_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* =Get-Groups-for-User================================================== */ + +struct groups_by_user_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_value; + int filter_type; + const char *extra_value; + const char **attrs; + bool non_posix; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static int groups_by_user_retry(struct tevent_req *req); +static void groups_by_user_connect_done(struct tevent_req *subreq); +static void groups_by_user_done(struct tevent_req *subreq); + +struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + bool noexist_delete, + bool set_non_posix) +{ + struct tevent_req *req; + struct groups_by_user_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct groups_by_user_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->dp_error = DP_ERR_FATAL; + state->conn = conn; + state->sdom = sdom; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->filter_value = filter_value; + state->filter_type = filter_type; + state->extra_value = extra_value; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + + if (state->domain->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + ret = groups_by_user_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int groups_by_user_retry(struct tevent_req *req) +{ + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, groups_by_user_connect_done, req); + return EOK; +} + +static void groups_by_user_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_initgr_send(state, + state->ev, + state->sdom, + sdap_id_op_handle(state->op), + state->ctx, + state->conn, + state->filter_value, + state->filter_type, + state->extra_value, + state->attrs, + state->non_posix); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_by_user_done, req); +} + +static void groups_by_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_initgr_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = groups_by_user_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + switch (state->sdap_ret) { + case ENOENT: + if (state->noexist_delete == true) { + const char *cname; + + /* state->filter_value is still the name used for the original + * req. The cached object might have a different name, e.g. a + * fully-qualified name. */ + ret = sysdb_get_real_name(state, + state->domain, + state->filter_value, + &cname); + if (ret != EOK) { + cname = state->filter_value; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Failed to canonicalize name, using [%s] [%d]: %s.\n", + cname, ret, sss_strerror(ret)); + } + + ret = sysdb_delete_user(state->domain, cname, 0); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + break; + case EOK: + break; + default: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int groups_by_user_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct groups_by_user_state *state = tevent_req_data(req, + struct groups_by_user_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Get-Account-Info-Call================================================= */ + +/* FIXME: embed this function in sssd_be and only call out + * specific functions from modules? */ + +static struct tevent_req *get_user_and_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + bool noexist_delete); + +errno_t sdap_get_user_and_group_recv(struct tevent_req *req, + int *dp_error_out, int *sdap_ret); + +bool sdap_is_enum_request(struct dp_id_data *ar) +{ + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_SERVICES: + if (ar->filter_type == BE_FILTER_ENUM) { + return true; + } + } + + return false; +} + +/* A generic LDAP account info handler */ +struct sdap_handle_acct_req_state { + struct dp_id_data *ar; + const char *err; + int dp_error; + int sdap_ret; +}; + +static void sdap_handle_acct_req_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + bool noexist_delete) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_handle_acct_req_state *state; + errno_t ret; + + + req = tevent_req_create(mem_ctx, &state, + struct sdap_handle_acct_req_state); + if (!req) { + return NULL; + } + state->ar = ar; + + if (ar == NULL) { + ret = EINVAL; + goto done; + } + + PROBE(SDAP_ACCT_REQ_SEND, + state->ar->entry_type & BE_REQ_TYPE_MASK, + state->ar->filter_type, state->ar->filter_value, + PROBE_SAFE_STR(state->ar->extra_value)); + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + subreq = users_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, + false); + break; + + case BE_REQ_GROUP: /* group */ + subreq = groups_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete, false, false); + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (ar->filter_type != BE_FILTER_NAME + && ar->filter_type != BE_FILTER_SECID + && ar->filter_type != BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = groups_by_user_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, false); + break; + + case BE_REQ_SUBID_RANGES: +#ifdef BUILD_SUBID + if (!ar->extra_value) { + ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED; + state->err = "This id_provider doesn't support subid ranges"; + goto done; + } + subreq = subid_ranges_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->extra_value); +#else + ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED; + state->err = "Subid ranges are not supported"; + goto done; +#endif + break; + + case BE_REQ_NETGROUP: + if (ar->filter_type != BE_FILTER_NAME) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = ldap_netgroup_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + noexist_delete); + break; + + case BE_REQ_SERVICES: + if (ar->filter_type == BE_FILTER_SECID + || ar->filter_type == BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = services_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->extra_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_SECID: + if (ar->filter_type != BE_FILTER_SECID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_UUID: + if (ar->filter_type != BE_FILTER_UUID) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_USER_AND_GROUP: + if (!(ar->filter_type == BE_FILTER_NAME || + ar->filter_type == BE_FILTER_IDNUM)) { + ret = EINVAL; + state->err = "Invalid filter type"; + goto done; + } + + subreq = get_user_and_group_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + noexist_delete); + break; + + case BE_REQ_BY_CERT: + subreq = users_get_send(state, be_ctx->ev, id_ctx, + sdom, conn, + ar->filter_value, + ar->filter_type, + ar->extra_value, + noexist_delete, + false); + break; + + default: /*fail*/ + ret = EINVAL; + state->err = "Invalid request type"; + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected request type: 0x%X [%s:%s] in %s\n", + ar->entry_type, ar->filter_value, + ar->extra_value?ar->extra_value:"-", + ar->domain); + goto done; + } + + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_handle_acct_req_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void +sdap_handle_acct_req_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_handle_acct_req_state *state; + errno_t ret; + const char *err = "Invalid request type"; + + state = tevent_req_data(req, struct sdap_handle_acct_req_state); + + switch (state->ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + err = "User lookup failed"; + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_GROUP: /* group */ + err = "Group lookup failed"; + ret = groups_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_INITGROUPS: /* init groups for user */ + err = "Init group lookup failed"; + ret = groups_by_user_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_SUBID_RANGES: + err = "Subid ranges lookup failed"; +#ifdef BUILD_SUBID + ret = subid_ranges_get_recv(subreq, &state->dp_error, &state->sdap_ret); +#else + ret = EINVAL; +#endif + break; + case BE_REQ_NETGROUP: + err = "Netgroup lookup failed"; + ret = ldap_netgroup_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_SERVICES: + err = "Service lookup failed"; + ret = services_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + case BE_REQ_BY_SECID: + /* Fall through */ + case BE_REQ_BY_UUID: + /* Fall through */ + case BE_REQ_USER_AND_GROUP: + err = "Lookup by SID failed"; + ret = sdap_get_user_and_group_recv(subreq, &state->dp_error, + &state->sdap_ret); + break; + case BE_REQ_BY_CERT: + err = "User lookup by certificate failed"; + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + break; + default: /* fail */ + ret = EINVAL; + break; + } + talloc_zfree(subreq); + + if (ret != EOK) { + state->err = err; + tevent_req_error(req, ret); + return; + } + + state->err = "Success"; + tevent_req_done(req); +} + +errno_t +sdap_handle_acct_req_recv(struct tevent_req *req, + int *_dp_error, const char **_err, + int *sdap_ret) +{ + struct sdap_handle_acct_req_state *state; + + state = tevent_req_data(req, struct sdap_handle_acct_req_state); + + PROBE(SDAP_ACCT_REQ_RECV, + state->ar->entry_type & BE_REQ_TYPE_MASK, + state->ar->filter_type, state->ar->filter_value, + PROBE_SAFE_STR(state->ar->extra_value)); + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + if (_err) { + *_err = state->err; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct get_user_and_group_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *filter_val; + int filter_type; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static void get_user_and_group_users_done(struct tevent_req *subreq); +static void get_user_and_group_groups_done(struct tevent_req *subreq); + +static struct tevent_req *get_user_and_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_val, + int filter_type, + bool noexist_delete) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct get_user_and_group_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct get_user_and_group_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_val = filter_val; + state->filter_type = filter_type; + + subreq = groups_get_send(req, state->ev, state->id_ctx, + state->sdom, state->conn, + state->filter_val, state->filter_type, + state->noexist_delete, false, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "groups_get_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, get_user_and_group_groups_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void get_user_and_group_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + int ret; + struct sdap_id_conn_ctx *user_conn; + + ret = groups_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { /* Fatal error while looking up group */ + tevent_req_error(req, ret); + return; + } + + if (state->sdap_ret == EOK) { /* Matching group found */ + tevent_req_done(req); + return; + } else if (state->sdap_ret != ENOENT) { + tevent_req_error(req, EIO); + return; + } + + /* Now the search finished fine but did not find an entry. + * Retry with users. */ + + /* Prefer LDAP over GC for users */ + user_conn = get_ldap_conn_from_sdom_pvt(state->id_ctx->opts, state->sdom); + if (user_conn == NULL) { + user_conn = state->conn; + } + + subreq = users_get_send(req, state->ev, state->id_ctx, + state->sdom, user_conn, + state->filter_val, state->filter_type, NULL, + state->noexist_delete, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "users_get_send failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, get_user_and_group_users_done, req); +} + +static void get_user_and_group_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + int ret; + + ret = users_get_recv(subreq, &state->dp_error, &state->sdap_ret); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + if (state->sdap_ret == ENOENT) { + if (state->noexist_delete == true) { + /* The search ran to completion, but nothing was found. + * Delete the existing entry, if any. */ + ret = sysdb_delete_by_sid(state->sysdb, state->domain, + state->filter_val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete entry by SID!\n"); + tevent_req_error(req, ret); + return; + } + } + } else if (state->sdap_ret != EOK) { + tevent_req_error(req, EIO); + return; + } + + /* Both ret and sdap->ret are EOK. Matching user found */ + tevent_req_done(req); + return; +} + +errno_t sdap_get_user_and_group_recv(struct tevent_req *req, + int *dp_error_out, int *sdap_ret) +{ + struct get_user_and_group_state *state = tevent_req_data(req, + struct get_user_and_group_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_account_info_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct sdap_account_info_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_handle_acct_req_send(state, params->be_ctx, data, id_ctx, + id_ctx->opts->sdom, id_ctx->conn, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_account_info_handler_done(struct tevent_req *subreq) +{ + struct sdap_account_info_handler_state *state; + struct tevent_req *req; + const char *error_msg; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_account_info_handler_state); + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &error_msg, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, error_msg); + tevent_req_done(req); +} + +errno_t sdap_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_cleanup.c b/src/providers/ldap/ldap_id_cleanup.c new file mode 100644 index 0000000..ac06753 --- /dev/null +++ b/src/providers/ldap/ldap_id_cleanup.c @@ -0,0 +1,520 @@ +/* + SSSD + + LDAP Identity Cleanup Functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 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 <errno.h> +#include <time.h> +#include <sys/time.h> + +#include "util/util.h" +#include "util/find_uid.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +/* ==Cleanup-Task========================================================= */ +struct ldap_id_cleanup_ctx { + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; +}; + +static errno_t ldap_cleanup_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_id_cleanup_ctx *cleanup_ctx = NULL; + + cleanup_ctx = talloc_get_type(pvt, struct ldap_id_cleanup_ctx); + return ldap_id_cleanup(cleanup_ctx->ctx, cleanup_ctx->sdom); +} + +errno_t ldap_id_setup_cleanup(struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + struct ldap_id_cleanup_ctx *cleanup_ctx = NULL; + char *name = NULL; + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (period == 0) { + /* Cleanup has been explicitly disabled, so we won't + * create any cleanup tasks. */ + ret = EOK; + goto done; + } + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_OFFSET); + + /* Run the first one in a couple of seconds so that we have time to + * finish initializations first. */ + first_delay = 10; + + cleanup_ctx = talloc_zero(sdom, struct ldap_id_cleanup_ctx); + if (cleanup_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cleanup_ctx->ctx = id_ctx; + cleanup_ctx->sdom = sdom; + + name = talloc_asprintf(cleanup_ctx, "Cleanup [id] of %s", sdom->dom->name); + if (name == NULL) { + return ENOMEM; + } + + ret = be_ptask_create_sync(id_ctx, id_ctx->be, period, first_delay, + 5 /* enabled delay */, offset /* random offset */, + period /* timeout */, 0, + ldap_cleanup_task, cleanup_ctx, name, + BE_PTASK_OFFLINE_SKIP, + &id_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize cleanup periodic " + "task for %s\n", sdom->dom->name); + goto done; + } + + ret = EOK; + +done: + talloc_free(name); + if (ret != EOK) { + talloc_free(cleanup_ctx); + } + + return ret; +} + +static int cleanup_users(struct sdap_options *opts, + struct sss_domain_info *dom); +static int cleanup_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain); + +errno_t ldap_id_cleanup(struct sdap_id_ctx *ctx, + struct sdap_domain *sdom) +{ + int ret, tret; + bool in_transaction = false; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = cleanup_users(ctx->opts, sdom->dom); + if (ret && ret != ENOENT) { + goto done; + } + + ret = cleanup_groups(tmp_ctx, sdom->dom->sysdb, sdom->dom); + if (ret) { + goto done; + } + + ret = sysdb_transaction_commit(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ctx->last_purge = tevent_timeval_current(); + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sdom->dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + + +/* ==User-Cleanup-Process================================================= */ + +static int cleanup_users_logged_in(hash_table_t *table, + const struct ldb_message *msg); + +static errno_t expire_memberof_target_groups(struct sss_domain_info *dom, + struct ldb_message *user); + +static int cleanup_users(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, SYSDB_MEMBEROF, NULL }; + time_t now = time(NULL); + char *subfilter = NULL; + char *ts_subfilter = NULL; + int account_cache_expiration; + hash_table_t *uid_table; + struct ldb_message **msgs; + size_t count; + const char *name; + int ret; + int i; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + account_cache_expiration = dp_opt_get_int(opts->basic, SDAP_ACCOUNT_CACHE_EXPIRATION); + DEBUG(SSSDBG_TRACE_ALL, "Cache expiration is set to %d days\n", + account_cache_expiration); + + if (account_cache_expiration > 0) { + subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(|(!(%s=*))(%s<=%"SPRItime")))", + SYSDB_CACHE_EXPIRE, + SYSDB_LAST_LOGIN, + SYSDB_LAST_LOGIN, + (now - (account_cache_expiration * 86400))); + + ts_subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(%s<=%"SPRItime")(|(!(%s=*))(%s<=%"SPRItime")))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, + now, + SYSDB_LAST_LOGIN, + SYSDB_LAST_LOGIN, + (now - (account_cache_expiration * 86400))); + } else { + subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(!(%s=*)))", + SYSDB_CACHE_EXPIRE, + SYSDB_LAST_LOGIN); + + ts_subfilter = talloc_asprintf(tmpctx, + "(&(!(%s=0))(%s<=%"SPRItime")(!(%s=*)))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, + now, + SYSDB_LAST_LOGIN); + } + if (subfilter == NULL || ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_users_by_timestamp(tmpctx, dom, subfilter, ts_subfilter, + attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_users failed: %d\n", ret); + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired user entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + ret = get_uid_table(tmpctx, &uid_table); + /* get_uid_table returns ENOSYS on non-Linux platforms. We proceed with + * the cleanup in that case + */ + if (ret != EOK && ret != ENOSYS) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_uid_table failed: %d\n", ret); + goto done; + } + + for (i = 0; i < count; i++) { + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + ret = EFAULT; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Processing user %s\n", name); + + if (uid_table) { + ret = cleanup_users_logged_in(uid_table, msgs[i]); + if (ret == EOK) { + /* If the user is logged in, proceed to the next one */ + DEBUG(SSSDBG_FUNC_DATA, + "User %s is still logged in or a dummy entry, " + "keeping data\n", name); + continue; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot check if user is logged in: %d\n", ret); + goto done; + } + } + + /* If not logged in or cannot check the table, delete him */ + DEBUG(SSSDBG_TRACE_ALL, "About to delete user %s\n", name); + ret = sysdb_delete_user(dom, name, 0); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_user failed: %d\n", ret); + goto done; + } + + /* Mark all groups of which user was a member as expired in cache, + * so that its ghost/member attributes are refreshed on next + * request. */ + ret = expire_memberof_target_groups(dom, msgs[i]); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "expire_memberof_target_groups failed: [%d]:%s\n", + ret, sss_strerror(ret)); + goto done; + } + } + +done: + talloc_zfree(tmpctx); + return ret; +} + +static errno_t expire_memberof_target_groups(struct sss_domain_info *dom, + struct ldb_message *user) +{ + struct ldb_message_element *memberof_el = NULL; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + memberof_el = ldb_msg_find_element(user, SYSDB_MEMBEROF); + if (memberof_el == NULL) { + /* User has no cached groups. Nothing to be marked as expired. */ + ret = EOK; + goto done; + } + + for (unsigned int i = 0; i < memberof_el->num_values; i++) { + ret = sysdb_mark_entry_as_expired_ldb_val(dom, + &memberof_el->values[i]); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_mark_entry_as_expired_ldb_val failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int cleanup_users_logged_in(hash_table_t *table, + const struct ldb_message *msg) +{ + uid_t uid; + hash_key_t key; + hash_value_t value; + int ret; + + uid = ldb_msg_find_attr_as_uint64(msg, + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no UID Attribute!\n", + ldb_dn_get_linearized(msg->dn)); + return ENOENT; + } + + key.type = HASH_KEY_ULONG; + key.ul = (unsigned long) uid; + + ret = hash_lookup(table, &key, &value); + if (ret == HASH_SUCCESS) { + return EOK; + } else if (ret == HASH_ERROR_KEY_NOT_FOUND) { + return ENOENT; + } + + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed: %d\n", ret); + return EIO; +} + +/* ==Group-Cleanup-Process================================================ */ + +static int cleanup_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + const char *dn; + gid_t gid; + struct ldb_message **msgs; + size_t count; + struct ldb_message **u_msgs; + size_t u_count; + int ret; + int i; + const char *posix; + struct ldb_dn *base_dn; + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmpctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmpctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_groups_by_timestamp(tmpctx, domain, subfilter, + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_groups failed: %d\n", ret); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired group entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + char *sanitized_dn; + + dn = ldb_dn_get_linearized(msgs[i]->dn); + if (!dn) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot linearize DN!\n"); + ret = EFAULT; + goto done; + } + + /* sanitize dn */ + ret = sss_filter_sanitize_dn(tmpctx, dn, &sanitized_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_filter_sanitize failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + posix = ldb_msg_find_attr_as_string(msgs[i], SYSDB_POSIX, NULL); + if (!posix || strcmp(posix, "TRUE") == 0) { + /* Search for users that are members of this group, or + * that have this group as their primary GID. + * Include subdomain users as well. + */ + gid = (gid_t) ldb_msg_find_attr_as_uint(msgs[i], SYSDB_GIDNUM, 0); + subfilter = talloc_asprintf(tmpctx, "(&(%s=%s)(|(%s=%s)(%s=%lu)))", + SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS, + SYSDB_MEMBEROF, sanitized_dn, + SYSDB_GIDNUM, (long unsigned) gid); + } else { + subfilter = talloc_asprintf(tmpctx, "(%s=%s)", SYSDB_MEMBEROF, + sanitized_dn); + } + talloc_zfree(sanitized_dn); + + if (!subfilter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_base_dn(sysdb, tmpctx); + if (base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Searching with: %s\n", subfilter); + + ret = sysdb_search_entry(tmpctx, sysdb, base_dn, + LDB_SCOPE_SUBTREE, subfilter, NULL, + &u_count, &u_msgs); + if (ret == ENOENT) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + ret = EFAULT; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete group %s\n", name); + ret = sysdb_delete_group(domain, name, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Group delete returned %d (%s)\n", + ret, strerror(ret)); + goto done; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to search sysdb using %s: [%d] %s\n", + subfilter, ret, sss_strerror(ret)); + goto done; + } + talloc_zfree(u_msgs); + } + +done: + talloc_zfree(tmpctx); + return ret; +} diff --git a/src/providers/ldap/ldap_id_enum.c b/src/providers/ldap/ldap_id_enum.c new file mode 100644 index 0000000..684dc70 --- /dev/null +++ b/src/providers/ldap/ldap_id_enum.c @@ -0,0 +1,212 @@ +/* + SSSD + + LDAP Identity Enumeration + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 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 "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_enum.h" + +errno_t ldap_id_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn, + void *pvt) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + time_t cleanup; + bool has_enumerated; + struct ldap_enum_ctx *ectx = NULL; + char *name = NULL; + + ret = sysdb_has_enumerated(sdom->dom, SYSDB_HAS_ENUMERATED_ID, + &has_enumerated); + if (ret == ENOENT) { + /* default value */ + has_enumerated = false; + } else if (ret != EOK) { + return ret; + } + + if (has_enumerated) { + /* At least one enumeration has previously run, + * so clients will get cached data. We will delay + * starting to enumerate by 10s so we don't slow + * down the startup process if this is happening + * during system boot. + */ + first_delay = 10; + } else { + /* This is our first startup. Schedule the + * enumeration to start immediately once we + * enter the mainloop. + */ + first_delay = 0; + } + + cleanup = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (cleanup == 0) { + /* We need to cleanup the cache once in a while when enumerating, otherwise + * enumeration would only download deltas since the previous lastUSN and would + * not detect removed entries + */ + ret = dp_opt_set_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT, + LDAP_ENUM_PURGE_TIMEOUT); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set cleanup timeout, enumeration wouldn't " + "detect removed entries!\n"); + return ret; + } + } + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_OFFSET); + + ectx = talloc(sdom, struct ldap_enum_ctx); + if (ectx == NULL) { + return ENOMEM; + } + ectx->sdom = sdom; + ectx->pvt = pvt; + + name = talloc_asprintf(NULL, "Enumeration [id] of %s", + sdom->dom->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = be_ptask_create(id_ctx, be_ctx, + period, /* period */ + first_delay, /* first_delay */ + 5, /* enabled delay */ + offset, /* random offset */ + period, /* timeout */ + 0, /* max_backoff */ + send_fn, recv_fn, + ectx, name, + BE_PTASK_OFFLINE_SKIP | BE_PTASK_SCHEDULE_FROM_LAST, + &id_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize enumeration periodic task\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(name); + if (ret != EOK) { + talloc_free(ectx); + } + return ret; +} + +struct ldap_enumeration_state { + struct ldap_enum_ctx *ectx; + struct sdap_id_ctx *id_ctx; + struct sss_domain_info *dom; +}; + +static void ldap_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ldap_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_enumeration_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct ldap_enum_ctx *ectx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ldap_enumeration_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ectx = talloc_get_type(pvt, struct ldap_enum_ctx); + if (ectx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ldap_enum_ctx!\n"); + ret = EFAULT; + goto fail; + } + state->ectx = ectx; + state->dom = ectx->sdom->dom; + state->id_ctx = talloc_get_type_abort(ectx->pvt, struct sdap_id_ctx); + + subreq = sdap_dom_enum_send(state, ev, state->id_ctx, ectx->sdom, + state->id_ctx->conn); + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + ret = EIO; + goto fail; + } + + tevent_req_set_callback(subreq, ldap_enumeration_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ldap_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_dom_enum_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ldap_id_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_netgroup.c b/src/providers/ldap/ldap_id_netgroup.c new file mode 100644 index 0000000..1fb01cf --- /dev/null +++ b/src/providers/ldap/ldap_id_netgroup.c @@ -0,0 +1,247 @@ +/* + SSSD + + LDAP Identity Backend Module - Netgroup support + + Authors: + Sumit Bose <sbose@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 <errno.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + + +struct ldap_netgroup_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sdap_id_conn_ctx *conn; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int timeout; + + char *filter; + const char **attrs; + + size_t count; + struct sysdb_attrs **netgroups; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static int ldap_netgroup_get_retry(struct tevent_req *req); +static void ldap_netgroup_get_connect_done(struct tevent_req *subreq); +static void ldap_netgroup_get_done(struct tevent_req *subreq); + +struct tevent_req *ldap_netgroup_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + bool noexist_delete) +{ + struct tevent_req *req; + struct ldap_netgroup_get_state *state; + char *clean_name; + int ret; + + req = tevent_req_create(memctx, &state, struct ldap_netgroup_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->name = name; + state->timeout = dp_opt_get_int(ctx->opts->basic, SDAP_SEARCH_TIMEOUT); + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + ctx->opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name, + clean_name, + ctx->opts->netgroup_map[SDAP_OC_NETGROUP].name); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_zfree(clean_name); + + ret = build_attrs_from_map(state, ctx->opts->netgroup_map, SDAP_OPTS_NETGROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + ret = ldap_netgroup_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int ldap_netgroup_get_retry(struct tevent_req *req) +{ + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, ldap_netgroup_get_connect_done, req); + return EOK; +} + +static void ldap_netgroup_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_netgroups_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, + state->sdom->netgroup_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + state->timeout); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ldap_netgroup_get_done, req); + + return; +} + +static void ldap_netgroup_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_get_netgroups_recv(subreq, state, NULL, &state->count, + &state->netgroups); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ldap_netgroup_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == EOK && state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one netgroup with the name [%s].\n", + state->name); + tevent_req_error(req, EINVAL); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + ret = sysdb_delete_netgroup(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +int ldap_netgroup_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct ldap_netgroup_get_state *state = tevent_req_data(req, + struct ldap_netgroup_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_services.c b/src/providers/ldap/ldap_id_services.c new file mode 100644 index 0000000..52a1563 --- /dev/null +++ b/src/providers/ldap/ldap_id_services.c @@ -0,0 +1,308 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 <errno.h> + +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +struct sdap_services_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + const char *name; + const char *protocol; + + char *filter; + const char **attrs; + + int filter_type; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +services_get_retry(struct tevent_req *req); +static void +services_get_connect_done(struct tevent_req *subreq); +static void +services_get_done(struct tevent_req *subreq); + +struct tevent_req * +services_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *name, + const char *protocol, + int filter_type, + bool noexist_delete) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_services_get_state *state; + const char *attr_name; + char *clean_name; + char *clean_protocol = NULL; + + req = tevent_req_create(mem_ctx, &state, struct sdap_services_get_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->name = name; + state->protocol = protocol; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_MINOR_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto error; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name; + break; + case BE_FILTER_IDNUM: + attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name; + break; + default: + ret = EINVAL; + goto error; + } + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) goto error; + + if (protocol != NULL) { + ret = sss_filter_sanitize(state, protocol, &clean_protocol); + if (ret != EOK) goto error; + } + + if (clean_protocol) { + state->filter = talloc_asprintf( + state, "(&(%s=%s)(%s=%s)(objectclass=%s))", + attr_name, clean_name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name, + clean_protocol, + id_ctx->opts->service_map[SDAP_OC_SERVICE].name); + } else { + state->filter = + talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + attr_name, clean_name, + id_ctx->opts->service_map[SDAP_OC_SERVICE].name); + } + talloc_zfree(clean_name); + talloc_zfree(clean_protocol); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build the base filter\n"); + ret = ENOMEM; + goto error; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Preparing to search for services with filter [%s]\n", + state->filter); + + ret = build_attrs_from_map(state, id_ctx->opts->service_map, + SDAP_OPTS_SERVICES, NULL, + &state->attrs, NULL); + if (ret != EOK) goto error; + + ret = services_get_retry(req); + if (ret != EOK) goto error; + + return req; + +error: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +services_get_retry(struct tevent_req *req) +{ + errno_t ret; + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + struct tevent_req *subreq; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, services_get_connect_done, req); + return EOK; +} + +static void +services_get_connect_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + int dp_error = DP_ERR_FATAL; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_services_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->service_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, services_get_done, req); +} + +static void +services_get_done(struct tevent_req *subreq) +{ + errno_t ret; + uint16_t port; + char *endptr; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + int dp_error = DP_ERR_FATAL; + + ret = sdap_get_services_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. + */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = services_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch(state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_svc_delete(state->domain, state->name, + 0, state->protocol); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_IDNUM: + port = strtouint16(state->name, &endptr, 10); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, (errno ? errno : EINVAL)); + return; + } + + ret = sysdb_svc_delete(state->domain, NULL, port, + state->protocol); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +errno_t +services_get_recv(struct tevent_req *req, int *dp_error_out, int *sdap_ret) +{ + struct sdap_services_get_state *state = + tevent_req_data(req, struct sdap_services_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_id_subid.c b/src/providers/ldap/ldap_id_subid.c new file mode 100644 index 0000000..d25c6aa --- /dev/null +++ b/src/providers/ldap/ldap_id_subid.c @@ -0,0 +1,255 @@ +/* + SSSD + + LDAP Identity Backend Module - subid ranges support + + Copyright (C) 2021 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 <errno.h> + +#include "db/sysdb_subid.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_ops.h" + +static int subid_ranges_get_retry(struct tevent_req *req); +static void subid_ranges_get_connect_done(struct tevent_req *subreq); +static void subid_ranges_get_search(struct tevent_req *req); +static void subid_ranges_get_done(struct tevent_req *subreq); + + +struct subid_ranges_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *op; + struct sss_domain_info *domain; + + char *filter; + char *name; + const char **attrs; + + int dp_error; + int sdap_ret; +}; + +struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + const char *extra_value) +{ + struct tevent_req *req; + struct subid_ranges_get_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct subid_ranges_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->name = talloc_strdup(state, filter_value); + if (!state->name) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed\n"); + ret = ENOMEM; + goto done; + } + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->domain = sdom->dom; + + state->filter = talloc_asprintf(state, + "(&(%s=%s)(%s=%s))", + SYSDB_OBJECTCLASS, + ctx->opts->subid_map[SDAP_OC_SUBID_RANGE].name, + ctx->opts->subid_map[SDAP_AT_SUBID_RANGE_OWNER].name, + extra_value); + + ret = subid_ranges_get_retry(req); + if (ret != EOK) { + goto done; + } + + return req; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return tevent_req_post(req, ev); +} + +static int subid_ranges_get_retry(struct tevent_req *req) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, subid_ranges_get_connect_done, req); + return EOK; +} + +static void subid_ranges_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subid_ranges_get_search(req); +} + +static void subid_ranges_get_search(struct tevent_req *req) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + struct tevent_req *subreq = NULL; + const char **attrs; + int ret; + + ret = build_attrs_from_map(state, state->ctx->opts->subid_map, + SDAP_OPTS_SUBID_RANGE, NULL, &attrs, NULL); + if (ret != EOK) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = sdap_search_bases_send(state, state->ev, state->ctx->opts, + sdap_id_op_handle(state->op), + state->sdom->subid_ranges_search_bases, + state->ctx->opts->subid_map, + false, /* allow_paging */ + dp_opt_get_int(state->ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + state->filter, + attrs, + NULL); + talloc_free(attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, subid_ranges_get_done, req); +} + +static void subid_ranges_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct sysdb_attrs **results; + size_t num_results; + + ret = sdap_search_bases_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = subid_ranges_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } + state->sdap_ret = ret; + + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve subid ranges.\n"); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (num_results == 0 || !results) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No such user '%s' or user doesn't have subid range\n", + state->name); + sysdb_delete_subid_range(state->domain, state->name); + } else { + if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Multiple subid ranges, only first will be processed\n"); + } + + /* store range */ + sysdb_store_subid_range(state->domain, state->name, + state->domain->user_timeout, + results[0]); + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out, + int *sdap_ret) +{ + struct subid_ranges_get_state *state = tevent_req_data(req, + struct subid_ranges_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c new file mode 100644 index 0000000..8d96ea1 --- /dev/null +++ b/src/providers/ldap/ldap_init.c @@ -0,0 +1,529 @@ +/* + SSSD + + LDAP Provider Initialization functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 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 "util/child_common.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_hostid.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/fail_over_srv.h" +#include "providers/be_refresh.h" + +struct ldap_init_ctx { + struct sdap_options *options; + struct sdap_id_ctx *id_ctx; + struct sdap_auth_ctx *auth_ctx; + struct sdap_resolver_ctx *resolver_ctx; +}; + +static errno_t ldap_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_options *options, + struct sdap_auth_ctx **_auth_ctx) +{ + struct sdap_auth_ctx *auth_ctx; + + auth_ctx = talloc(mem_ctx, struct sdap_auth_ctx); + if (auth_ctx == NULL) { + return ENOMEM; + } + + auth_ctx->be = be_ctx; + auth_ctx->opts = options; + auth_ctx->service = id_ctx->conn->service; + auth_ctx->chpass_service = NULL; + + *_auth_ctx = auth_ctx; + + return EOK; +} + +static errno_t init_chpass_service(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_options *opts, + struct sdap_service **_chpass_service) +{ + errno_t ret; + const char *urls; + const char *backup_urls; + const char *dns_service_name; + struct sdap_service *chpass_service; + + dns_service_name = dp_opt_get_string(opts->basic, + SDAP_CHPASS_DNS_SERVICE_NAME); + if (dns_service_name != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Service name for chpass discovery set to %s\n", + dns_service_name); + } + + urls = dp_opt_get_string(opts->basic, SDAP_CHPASS_URI); + backup_urls = dp_opt_get_string(opts->basic, SDAP_CHPASS_BACKUP_URI); + + if (urls != NULL || backup_urls != NULL || dns_service_name != NULL) { + ret = sdap_service_init(mem_ctx, + be_ctx, + "LDAP_CHPASS", + dns_service_name, + urls, + backup_urls, + &chpass_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize failover service!\n"); + return ret; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, + "ldap_chpass_uri and ldap_chpass_dns_service_name not set, " + "using ldap_uri.\n"); + chpass_service = NULL; + } + + *_chpass_service = chpass_service; + return EOK; +} + +static errno_t get_sdap_service(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_options *opts, + struct sdap_service **_sdap_service) +{ + errno_t ret; + const char *urls; + const char *backup_urls; + const char *dns_service_name; + struct sdap_service *sdap_service; + + urls = dp_opt_get_string(opts->basic, SDAP_URI); + backup_urls = dp_opt_get_string(opts->basic, SDAP_BACKUP_URI); + dns_service_name = dp_opt_get_string(opts->basic, SDAP_DNS_SERVICE_NAME); + if (dns_service_name != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Service name for discovery set to %s\n", dns_service_name); + } + + ret = sdap_service_init(mem_ctx, be_ctx, "LDAP", + dns_service_name, + urls, + backup_urls, + &sdap_service); + if (ret != EOK) { + return ret; + } + + *_sdap_service = sdap_service; + return EOK; +} + +static bool should_call_gssapi_init(struct sdap_options *opts) +{ + const char *sasl_mech; + + sasl_mech = dp_opt_get_string(opts->basic, SDAP_SASL_MECH); + if (sasl_mech == NULL) { + return false; + } + + if (!sdap_sasl_mech_needs_kinit(sasl_mech)) { + return false; + } + + if (dp_opt_get_bool(opts->basic, SDAP_KRB5_KINIT) == false) { + return false; + } + + return true; +} + +static errno_t ldap_init_misc(struct be_ctx *be_ctx, + struct sdap_options *options, + struct sdap_id_ctx *id_ctx) +{ + errno_t ret; + + if (should_call_gssapi_init(options)) { + ret = sdap_gssapi_init(id_ctx, options->basic, be_ctx, + id_ctx->conn->service, &id_ctx->krb5_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_gssapi_init failed [%d][%s].\n", + ret, sss_strerror(ret)); + return ret; + } + } + + setup_ldap_debug(options->basic); + + ret = setup_tls_config(options->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get TLS options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Setup the ID mapping object */ + ret = sdap_idmap_init(id_ctx, id_ctx, &options->idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize ID mapping. In case ID mapping properties " + "changed on the server, please remove the SSSD database\n"); + return ret; + } + + ret = ldap_id_setup_tasks(id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup background tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + /* Setup SRV lookup plugin */ + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + /* Setup periodical refresh of expired records */ + ret = sdap_refresh_init(be_ctx, id_ctx); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh will not work " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + + ret = sdap_init_certmap(id_ctx, id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialized certificate mapping.\n"); + return ret; + } + + return EOK; +} + +errno_t sssm_ldap_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct sdap_service *sdap_service; + struct ldap_init_ctx *init_ctx; + errno_t ret; + + init_ctx = talloc_zero(mem_ctx, struct ldap_init_ctx); + if (init_ctx == NULL) { + return ENOMEM; + } + + /* Always initialize options since it is needed everywhere. */ + ret = ldap_get_options(init_ctx, be_ctx->domain, be_ctx->cdb, + be_ctx->conf_path, be_ctx->provider, + &init_ctx->options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize LDAP options " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Always initialize id_ctx since it is needed everywhere. */ + ret = get_sdap_service(init_ctx, be_ctx, init_ctx->options, &sdap_service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to initialize failover service " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + init_ctx->id_ctx = sdap_id_ctx_new(init_ctx, be_ctx, sdap_service); + if (init_ctx->id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize LDAP ID context\n"); + ret = ENOMEM; + goto done; + } + + init_ctx->id_ctx->opts = init_ctx->options; + + /* Setup miscellaneous things. */ + ret = ldap_init_misc(be_ctx, init_ctx->options, init_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init LDAP module " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Initialize auth_ctx only if one of the target is enabled. */ + if (dp_target_enabled(provider, module_name, DPT_AUTH, DPT_CHPASS)) { + ret = ldap_init_auth_ctx(init_ctx, be_ctx, init_ctx->id_ctx, + init_ctx->options, &init_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create auth context " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + *_module_data = init_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(init_ctx); + } + + return ret; +} + +errno_t sssm_ldap_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_id_ctx *id_ctx; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + id_ctx = init_ctx->id_ctx; + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + sdap_account_info_handler_send, sdap_account_info_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_CHECK_ONLINE, + sdap_online_check_handler_send, sdap_online_check_handler_recv, id_ctx, + struct sdap_id_ctx, void, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +errno_t sssm_ldap_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_auth_ctx *auth_ctx; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + sdap_pam_auth_handler_send, sdap_pam_auth_handler_recv, auth_ctx, + struct sdap_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ldap_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_auth_ctx *auth_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + ret = init_chpass_service(auth_ctx, be_ctx, init_ctx->options, + &auth_ctx->chpass_service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize chpass service " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + sdap_pam_chpass_handler_send, sdap_pam_chpass_handler_recv, auth_ctx, + struct sdap_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ldap_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + struct sdap_access_ctx *access_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + access_ctx = talloc_zero(mem_ctx, struct sdap_access_ctx); + if(access_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + access_ctx->type = SDAP_TYPE_LDAP; + access_ctx->id_ctx = init_ctx->id_ctx; + + ret = sdap_set_access_rules(access_ctx, access_ctx, + access_ctx->id_ctx->opts->basic, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "set_access_rules failed: [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + sdap_pam_access_handler_send, sdap_pam_access_handler_recv, access_ctx, + struct sdap_access_ctx, struct pam_data, struct pam_data *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(access_ctx); + } + + return ret; +} + +errno_t sssm_ldap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SSH + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP host handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_hostid_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); + +#else + DEBUG(SSSDBG_MINOR_FAILURE, "HostID init handler called but SSSD is " + "built without SSH support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_AUTOFS + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP autofs handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_autofs_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Autofs init handler called but SSSD is " + "built without autofs support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SUDO + struct ldap_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP sudo handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + return sdap_sudo_init(mem_ctx, + be_ctx, + init_ctx->id_ctx, + native_sudorule_map, + dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Sudo init handler called but SSSD is " + "built without sudo support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ldap_resolver_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ldap_init_ctx *init_ctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing LDAP resolver handler\n"); + init_ctx = talloc_get_type(module_data, struct ldap_init_ctx); + + ret = sdap_resolver_ctx_new(init_ctx, init_ctx->id_ctx, + &init_ctx->resolver_ctx); + if (ret != EOK) { + return ret; + } + + ret = ldap_resolver_setup_tasks(be_ctx, init_ctx->resolver_ctx, + ldap_resolver_enumeration_send, + ldap_resolver_enumeration_recv); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to setup resolver background tasks [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + dp_set_method(dp_methods, DPM_RESOLVER_HOSTS_HANDLER, + sdap_iphost_handler_send, sdap_iphost_handler_recv, + init_ctx->resolver_ctx, struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_RESOLVER_IP_NETWORK_HANDLER, + sdap_ipnetwork_handler_send, sdap_ipnetwork_handler_recv, + init_ctx->resolver_ctx, struct sdap_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + return EOK; +} diff --git a/src/providers/ldap/ldap_options.c b/src/providers/ldap/ldap_options.c new file mode 100644 index 0000000..277bcb5 --- /dev/null +++ b/src/providers/ldap/ldap_options.c @@ -0,0 +1,827 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2008-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 "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ldap/sdap_async_private.h" +#include "util/crypto/sss_crypto.h" + +int ldap_get_options(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts) +{ + struct sdap_attr_map *default_attr_map; + struct sdap_attr_map *default_user_map; + struct sdap_attr_map *default_group_map; + struct sdap_attr_map *default_netgroup_map; + struct sdap_attr_map *default_host_map; + struct sdap_attr_map *default_service_map; + struct sdap_attr_map *default_iphost_map; + struct sdap_attr_map *default_ipnetwork_map; + struct sdap_options *opts; + struct ldb_context *ldb; + char *schema; + char *pwmodify; + const char *search_base; + const char *pwd_policy; + int ret; + int account_cache_expiration; + int offline_credentials_expiration; + const char *ldap_deref; + int ldap_deref_val; + int o; + const char *authtok_type; + struct dp_opt_blob authtok_blob; + char *cleartext; + const int search_base_options[] = { SDAP_USER_SEARCH_BASE, + SDAP_GROUP_SEARCH_BASE, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_HOST_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + SDAP_IPHOST_SEARCH_BASE, + SDAP_IPNETWORK_SEARCH_BASE, + -1 }; + + opts = talloc_zero(memctx, struct sdap_options); + if (!opts) return ENOMEM; + opts->dp = dp; + + ret = sdap_domain_add(opts, dom, NULL); + if (ret != EOK) { + goto done; + } + + ret = dp_get_options(opts, cdb, conf_path, + default_basic_opts, + SDAP_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + /* Handle search bases */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set user/group/netgroup search bases if they are not */ + for (o = 0; search_base_options[o] != -1; o++) { + if (NULL == dp_opt_get_string(opts->basic, search_base_options[o])) { + ret = dp_opt_set_string(opts->basic, search_base_options[o], + search_base); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + opts->basic[search_base_options[o]].opt_name, + dp_opt_get_string(opts->basic, + search_base_options[o])); + } + } + } else { + DEBUG(SSSDBG_FUNC_DATA, + "Search base not set, trying to discover it later when " + "connecting to the LDAP server.\n"); + } + + ldb = sysdb_ctx_get_ldb(dom->sysdb); + + /* Default search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_SEARCH_BASE, + &opts->sdom->search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* User search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_USER_SEARCH_BASE, + &opts->sdom->user_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Group search base */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_GROUP_SEARCH_BASE, + &opts->sdom->group_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_NETGROUP_SEARCH_BASE, + &opts->sdom->netgroup_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_HOST_SEARCH_BASE, + &opts->sdom->host_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Service search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_SERVICE_SEARCH_BASE, + &opts->sdom->service_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* IP host search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_IPHOST_SEARCH_BASE, + &opts->sdom->iphost_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* IP network search */ + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_IPNETWORK_SEARCH_BASE, + &opts->sdom->ipnetwork_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing password policy, this may not happen.\n"); + ret = EINVAL; + goto done; + } + if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) != 0 && + strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) != 0 && + strcasecmp(pwd_policy, PWD_POL_OPT_MIT) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported password policy [%s].\n", pwd_policy); + ret = EINVAL; + goto done; + } + + /* account_cache_expiration must be >= than offline_credentials_expiration */ + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CRED_TIMEOUT, 0, + &offline_credentials_expiration); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get value of %s from confdb \n", + CONFDB_PAM_CRED_TIMEOUT); + goto done; + } + + account_cache_expiration = dp_opt_get_int(opts->basic, + SDAP_ACCOUNT_CACHE_EXPIRATION); + + /* account cache_expiration must not be smaller than + * offline_credentials_expiration to prevent deleting entries that + * still contain credentials valid for offline login. + * + * offline_credentials_expiration == 0 is a special case that says + * that the cached credentials are valid forever. Therefore, the cached + * entries must not be purged from cache. + */ + if (!offline_credentials_expiration && account_cache_expiration) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Conflicting values for options %s (unlimited) " + "and %s (%d)\n", + opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, + CONFDB_PAM_CRED_TIMEOUT, + offline_credentials_expiration); + ret = EINVAL; + goto done; + } + if (offline_credentials_expiration && account_cache_expiration && + offline_credentials_expiration > account_cache_expiration) { + DEBUG(SSSDBG_CRIT_FAILURE, "Value of %s (now %d) must be larger " + "than value of %s (now %d)\n", + opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, + account_cache_expiration, + CONFDB_PAM_CRED_TIMEOUT, + offline_credentials_expiration); + ret = EINVAL; + goto done; + } + + ldap_deref = dp_opt_get_string(opts->basic, SDAP_DEREF); + if (ldap_deref != NULL) { + ret = deref_string_to_val(ldap_deref, &ldap_deref_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify ldap_deref option.\n"); + goto done; + } + } + +#ifndef HAVE_LDAP_CONNCB + bool ldap_referrals; + + ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS); + if (ldap_referrals) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDAP referrals are not supported, because the LDAP library " + "is too old, see sssd-ldap(5) for details.\n"); + ret = dp_opt_set_bool(opts->basic, SDAP_REFERRALS, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + } +#endif + + /* schema type */ + schema = dp_opt_get_string(opts->basic, SDAP_SCHEMA); + if (strcasecmp(schema, "rfc2307") == 0) { + opts->schema_type = SDAP_SCHEMA_RFC2307; + default_attr_map = generic_attr_map; + default_user_map = rfc2307_user_map; + default_group_map = rfc2307_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "rfc2307bis") == 0) { + opts->schema_type = SDAP_SCHEMA_RFC2307BIS; + default_attr_map = generic_attr_map; + default_user_map = rfc2307bis_user_map; + default_group_map = rfc2307bis_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "IPA") == 0) { + opts->schema_type = SDAP_SCHEMA_IPA_V1; + default_attr_map = gen_ipa_attr_map; + default_user_map = rfc2307bis_user_map; + default_group_map = rfc2307bis_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else + if (strcasecmp(schema, "AD") == 0) { + opts->schema_type = SDAP_SCHEMA_AD; + default_attr_map = gen_ad_attr_map; + default_user_map = gen_ad2008r2_user_map; + default_group_map = gen_ad2008r2_group_map; + default_netgroup_map = netgroup_map; + default_host_map = host_map; + default_service_map = service_map; + default_iphost_map = iphost_map; + default_ipnetwork_map = ipnetwork_map; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized schema type: %s\n", schema); + ret = EINVAL; + goto done; + } + + /* pwmodify mode */ + pwmodify = dp_opt_get_string(opts->basic, SDAP_PWMODIFY_MODE); + if (strcasecmp(pwmodify, "exop") == 0) { + opts->pwmodify_mode = SDAP_PWMODIFY_EXOP; + } else if (strcasecmp(pwmodify, "ldap_modify") == 0) { + opts->pwmodify_mode = SDAP_PWMODIFY_LDAP; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized pwmodify mode: %s\n", pwmodify); + ret = EINVAL; + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_attr_map, + SDAP_AT_GENERAL, + &opts->gen_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_user_map, + SDAP_OPTS_USER, + &opts->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_extend_map_with_list(opts, opts, SDAP_USER_EXTRA_ATTRS, + opts->user_map, SDAP_OPTS_USER, + &opts->user_map, &opts->user_map_cnt); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_group_map, + SDAP_OPTS_GROUP, + &opts->group_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_netgroup_map, + SDAP_OPTS_NETGROUP, + &opts->netgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_host_map, + SDAP_OPTS_HOST, + &opts->host_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_service_map, + SDAP_OPTS_SERVICES, + &opts->service_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_iphost_map, + SDAP_OPTS_IPHOST, + &opts->iphost_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_ipnetwork_map, + SDAP_OPTS_IPNETWORK, + &opts->ipnetwork_map); + if (ret != EOK) { + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, opts->basic, SDAP_KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + authtok_type = dp_opt_get_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE); + if (authtok_type != NULL && + strcasecmp(authtok_type,"obfuscated_password") == 0) { + DEBUG(SSSDBG_TRACE_ALL, "Found obfuscated password, " + "trying to convert to cleartext.\n"); + + authtok_blob = dp_opt_get_blob(opts->basic, SDAP_DEFAULT_AUTHTOK); + if (authtok_blob.data == NULL || authtok_blob.length == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing obfuscated password string.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_password_decrypt(memctx, (char *) authtok_blob.data, + &cleartext); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot convert the obfuscated " + "password back to cleartext\n"); + goto done; + } + + authtok_blob.data = (uint8_t *) cleartext; + authtok_blob.length = strlen(cleartext); + ret = dp_opt_set_blob(opts->basic, SDAP_DEFAULT_AUTHTOK, authtok_blob); + /* `cleartext` is erased only to reduce possible attack surface. + * Its copy will be kept in opts->basic[SDAP_DEFAULT_AUTHTOK] + * during program execution anyway. + * This option is never replaced so it doesn't make a sense to set + * a destructor. + */ + sss_erase_talloc_mem_securely(cleartext); + talloc_free(cleartext); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_blob(authtok) failed.\n"); + goto done; + } + + ret = dp_opt_set_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE, + "password"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "dp_opt_set_string(authtok_type) failed.\n"); + goto done; + } + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +int ldap_get_sudo_options(struct confdb_ctx *cdb, + struct ldb_context *ldb, + const char *conf_path, + struct sdap_options *opts, + struct sdap_attr_map *native_map, + bool *use_host_filter, + bool *include_regexp, + bool *include_netgroups) +{ + const char *search_base; + int ret; + + /* search base */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set sudo search bases if they are not */ + if (dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE) == NULL) { + ret = dp_opt_set_string(opts->basic, SDAP_SUDO_SEARCH_BASE, + search_base); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set SUDO search base" + "to default value\n"); + return ret; + } + + DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", + opts->basic[SDAP_SUDO_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE)); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " + "connecting to the LDAP server.\n"); + } + + ret = sdap_parse_search_base(opts, ldb, + opts->basic, SDAP_SUDO_SEARCH_BASE, + &opts->sdom->sudo_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse SUDO search base\n"); + return ret; + } + + /* attrs map */ + ret = sdap_get_map(opts, cdb, conf_path, + native_map, + SDAP_OPTS_SUDO, + &opts->sudorule_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get SUDO attribute map\n"); + return ret; + } + + /* host filter */ + *use_host_filter = dp_opt_get_bool(opts->basic, SDAP_SUDO_USE_HOST_FILTER); + *include_netgroups = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_NETGROUPS); + *include_regexp = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_REGEXP); + + return EOK; +} + +int ldap_get_autofs_options(TALLOC_CTX *memctx, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options *opts) +{ + const char *search_base; + struct sdap_attr_map *default_entry_map; + struct sdap_attr_map *default_mobject_map; + int ret; + + /* search base */ + search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); + if (search_base != NULL) { + /* set autofs search bases if they are not */ + if (dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE) == NULL) { + ret = dp_opt_set_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE, + search_base); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set autofs search base" + "to default value\n"); + return ret; + } + + DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", + opts->basic[SDAP_AUTOFS_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE)); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " + "connecting to the LDAP server.\n"); + } + + ret = sdap_parse_search_base(opts, ldb, opts->basic, + SDAP_AUTOFS_SEARCH_BASE, + &opts->sdom->autofs_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse autofs search base\n"); + return ret; + } + + /* attribute maps */ + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + default_mobject_map = rfc2307_autofs_mobject_map; + default_entry_map = rfc2307_autofs_entry_map; + break; + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + default_mobject_map = rfc2307bis_autofs_mobject_map; + default_entry_map = rfc2307bis_autofs_entry_map; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown LDAP schema %d!\n", opts->schema_type); + return EINVAL; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_mobject_map, + SDAP_OPTS_AUTOFS_MAP, + &opts->autofs_mobject_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs map object attribute map\n"); + return ret; + } + + ret = sdap_get_map(opts, cdb, conf_path, + default_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + &opts->autofs_entry_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs entry object attribute map\n"); + return ret; + } + + return EOK; +} + +errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases) +{ + const char *class_name; + char *unparsed_base; + const char *old_filter = NULL; + + switch (class) { + case SDAP_SEARCH_BASE: + class_name = "DEFAULT"; + break; + case SDAP_USER_SEARCH_BASE: + class_name = "USER"; + old_filter = dp_opt_get_string(opts, SDAP_USER_SEARCH_FILTER); + break; + case SDAP_GROUP_SEARCH_BASE: + class_name = "GROUP"; + old_filter = dp_opt_get_string(opts, SDAP_GROUP_SEARCH_FILTER); + break; + case SDAP_NETGROUP_SEARCH_BASE: + class_name = "NETGROUP"; + break; + case SDAP_HOST_SEARCH_BASE: + class_name = "HOST"; + break; + case SDAP_SUDO_SEARCH_BASE: + class_name = "SUDO"; + break; + case SDAP_SERVICE_SEARCH_BASE: + class_name = "SERVICE"; + break; + case SDAP_AUTOFS_SEARCH_BASE: + class_name = "AUTOFS"; + break; + case SDAP_IPHOST_SEARCH_BASE: + class_name = "IPHOST"; + break; + case SDAP_IPNETWORK_SEARCH_BASE: + class_name = "IPNETWORK"; + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "Unknown search base type: [%d]\n", class); + class_name = "UNKNOWN"; + /* Non-fatal */ + break; + } + + unparsed_base = dp_opt_get_string(opts, class); + if (!unparsed_base || unparsed_base[0] == '\0') return ENOENT; + + return common_parse_search_base(mem_ctx, unparsed_base, ldb, + class_name, old_filter, + _search_bases); +} + +errno_t common_parse_search_base(TALLOC_CTX *mem_ctx, + const char *unparsed_base, + struct ldb_context *ldb, + const char *class_name, + const char *old_filter, + struct sdap_search_base ***_search_bases) +{ + errno_t ret; + struct sdap_search_base **search_bases; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *ldn; + struct ldb_parse_tree *tree; + char **split_bases; + char *filter; + int count; + int i, c; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, unparsed_base, '?', false, false, + &split_bases, &count); + if (ret != EOK) goto done; + + /* The split must be either exactly one value or a multiple of + * three in order to be valid. + * One value: just a base, backwards-compatible with pre-1.7.0 versions + * Multiple: search_base?scope?filter[?search_base?scope?filter]* + */ + if (count > 1 && (count % 3)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unparseable search base: [%s][%d]\n", unparsed_base, count); + ret = EINVAL; + goto done; + } + + if (count == 1) { + search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, 2); + if (!search_bases) { + ret = ENOMEM; + goto done; + } + + if (old_filter != NULL) { + /* Using a deprecated ldap_{user,group}_search_filter */ + DEBUG(SSSDBG_IMPORTANT_INFO, "WARNING: Using a deprecated filter " + "option for %s. Please see the documentation on LDAP search " + "bases to see how the obsolete option can be migrated\n", + class_name); + sss_log(SSS_LOG_NOTICE, "WARNING: Using a deprecated filter option" + "for %s. Please see the documentation on LDAP search bases " + "to see how the obsolete option can be migrated\n", + class_name); + } + + ret = sdap_create_search_base(search_bases, ldb, unparsed_base, + LDAP_SCOPE_SUBTREE, old_filter, + &search_bases[0]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create new sdap search base\n"); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Search base added: [%s][%s][%s][%s]\n", + class_name, + search_bases[0]->basedn, + "SUBTREE", + search_bases[0]->filter ? search_bases[0]->filter : ""); + + search_bases[1] = NULL; + } else { + search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, + (count / 3) + 1); + if (!search_bases) { + ret = ENOMEM; + goto done; + } + + i = 0; + for (c = 0; c < count; c += 3) { + search_bases[i] = talloc_zero(search_bases, + struct sdap_search_base); + if (!search_bases[i]) { + ret = ENOMEM; + goto done; + } + + if (split_bases[c][0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Zero-length search base: [%s]\n", unparsed_base); + ret = EINVAL; + goto done; + } + + /* Validate the basedn */ + ldn = ldb_dn_new(tmp_ctx, ldb, split_bases[c]); + if (!ldn) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(ldn)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid base DN [%s]\n", + split_bases[c]); + ret = EINVAL; + goto done; + } + + /* Set the search base DN */ + search_bases[i]->ldb_basedn = talloc_steal(search_bases[i], ldn); + search_bases[i]->basedn = talloc_strdup(search_bases[i], + split_bases[c]); + if (!search_bases[i]->basedn) { + ret = ENOMEM; + goto done; + } + + /* Set the search scope for this base DN */ + if ((split_bases[c+1][0] == '\0') + || strcasecmp(split_bases[c+1], "sub") == 0 + || strcasecmp(split_bases[c+1], "subtree") == 0) { + /* If unspecified, default to subtree */ + search_bases[i]->scope = LDAP_SCOPE_SUBTREE; + } else if (strcasecmp(split_bases[c+1], "one") == 0 + || strcasecmp(split_bases[c+1], "onelevel") == 0) { + search_bases[i]->scope = LDAP_SCOPE_ONELEVEL; + } else if (strcasecmp(split_bases[c+1], "base") == 0) { + search_bases[i]->scope = LDAP_SCOPE_BASE; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown search scope: [%s]\n", split_bases[c+1]); + ret = EINVAL; + goto done; + } + + /* Get a specialized filter if provided */ + if (split_bases[c+2][0] == '\0') { + search_bases[i]->filter = NULL; + } else { + if (split_bases[c+2][0] != '(') { + /* Filters need to be enclosed in parentheses + * to be validated properly by ldb_parse_tree() + */ + filter = talloc_asprintf(tmp_ctx, "(%s)", + split_bases[c+2]); + } else { + filter = talloc_strdup(tmp_ctx, split_bases[c+2]); + } + if (!filter) { + ret = ENOMEM; + goto done; + } + + tree = ldb_parse_tree(tmp_ctx, filter); + if(!tree) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid search filter: [%s]\n", filter); + ret = EINVAL; + goto done; + } + talloc_zfree(tree); + + search_bases[i]->filter = talloc_steal(search_bases[i], + filter); + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Search base added: [%s][%s][%s][%s]\n", + class_name, + search_bases[i]->basedn, + split_bases[c+1][0] ? split_bases[c+1] : "SUBTREE", + search_bases[i]->filter ? search_bases[i]->filter : ""); + + i++; + } + search_bases[i] = NULL; + } + + *_search_bases = talloc_steal(mem_ctx, search_bases); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/ldap_opts.c b/src/providers/ldap/ldap_opts.c new file mode 100644 index 0000000..bcf0299 --- /dev/null +++ b/src/providers/ldap/ldap_opts.c @@ -0,0 +1,425 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 "src/providers/data_provider.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_services.h" +#include "db/sysdb_iphosts.h" +#include "db/sysdb_ipnetworks.h" +#include "providers/ldap/ldap_common.h" + +struct dp_option default_basic_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, { "password" }, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 8 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, /* 360 mins */ + { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */ + { "ldap_sudo_random_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, /* disabled */ + { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_autofs_map_master_name", DP_OPT_STRING, { "auto.master" }, NULL_STRING }, + { "ldap_iphost_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_ipnetwork_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "rfc2307" }, NULL_STRING }, + { "ldap_pwmodify_mode", DP_OPT_STRING, { "exop" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_enumeration_refresh_offset", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_purge_cache_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_id_mapping", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_mech", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_sasl_maxssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" }, NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }, + { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER }, + { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER }, + { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_account_expire_policy", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_access_order", DP_OPT_STRING, { "filter" }, NULL_STRING }, + { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + /* Do not include ldap_auth_disable_tls_never_use_in_production in the + * manpages or SSSDConfig API + */ + { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_ignore_unreadable_references", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_connection_expire_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_connection_idle_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, + { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_helper_table_size", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_use_tokengroups", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE}, + { "ldap_rfc2307_fallback_to_local_users", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_disable_range_retrieval", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, + { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map generic_attr_map[] = { + { "ldap_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", NULL, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ipa_attr_map[] = { + { "ldap_entry_usn", SDAP_IPA_USN, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", SDAP_IPA_LAST_USN, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad_attr_map[] = { + { "ldap_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", SDAP_AD_LAST_USN, SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", NULL, SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_user_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", "rhost", SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_group_map[] = { + { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "memberuid", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_group_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_user_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", "rhost", SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_group_map[] = { + { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", NULL, SYSDB_UUID, NULL }, + { "ldap_group_objectsid", NULL, SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad2008r2_user_map[] = { + { "ldap_user_object_class", "user", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "unixUserPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "unixHomeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "userPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "name", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_user_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_user_primary_group", "primaryGroupID", SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", NULL, SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", NULL, SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", NULL, SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", NULL, SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", NULL, SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", NULL, SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", NULL, SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", NULL, SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", NULL, SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", NULL, SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", NULL, SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", NULL, SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", NULL, SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", NULL, SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", NULL, SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", NULL, SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", NULL, SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", NULL, SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", NULL, SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "passkey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map gen_ad2008r2_group_map[] = { + { "ldap_group_object_class", "group", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", NULL, SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "sAMAccountName", SYSDB_NAME, NULL }, + { "ldap_group_pwd", NULL, SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "objectGUID", SYSDB_UUID, NULL }, + { "ldap_group_objectsid", "objectSID", SYSDB_SID, NULL }, + { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL }, + { "ldap_group_type", "groupType", SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map netgroup_map[] = { + { "ldap_netgroup_object_class", "nisNetgroup", SYSDB_NETGROUP_CLASS, NULL }, + { "ldap_netgroup_name", "cn", SYSDB_NAME, NULL }, + { "ldap_netgroup_member", "memberNisNetgroup", SYSDB_ORIG_NETGROUP_MEMBER, NULL }, + { "ldap_netgroup_triple", "nisNetgroupTriple", SYSDB_NETGROUP_TRIPLE, NULL }, + { "ldap_netgroup_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map host_map[] = { + { "ldap_host_object_class", "ipHost", SYSDB_HOST_CLASS, NULL }, + { "ldap_host_name", "cn", SYSDB_NAME, NULL }, + { "ldap_host_fqdn", "fqdn", SYSDB_FQDN, NULL }, + { "ldap_host_serverhostname", "serverHostname", SYSDB_SERVERHOSTNAME, NULL }, + { "ldap_host_member_of", NULL, SYSDB_ORIG_MEMBEROF, NULL }, + { "ldap_host_ssh_public_key", "sshPublicKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_host_uuid", NULL, SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map native_sudorule_map[] = { + { "ldap_sudorule_object_class", "sudoRole", SYSDB_SUDO_CACHE_OC, NULL }, + { "ldap_sudorule_object_class_attr", "objectClass", SYSDB_OBJECTCATEGORY, NULL }, + { "ldap_sudorule_name", "cn", SYSDB_SUDO_CACHE_AT_CN, NULL }, + { "ldap_sudorule_command", "sudoCommand", SYSDB_SUDO_CACHE_AT_COMMAND, NULL }, + { "ldap_sudorule_host", "sudoHost", SYSDB_SUDO_CACHE_AT_HOST, NULL }, + { "ldap_sudorule_user", "sudoUser", SYSDB_SUDO_CACHE_AT_USER, NULL }, + { "ldap_sudorule_option", "sudoOption", SYSDB_SUDO_CACHE_AT_OPTION, NULL }, + { "ldap_sudorule_runas", "sudoRunAs", SYSDB_SUDO_CACHE_AT_RUNAS, NULL }, + { "ldap_sudorule_runasuser", "sudoRunAsUser", SYSDB_SUDO_CACHE_AT_RUNASUSER, NULL }, + { "ldap_sudorule_runasgroup", "sudoRunAsGroup", SYSDB_SUDO_CACHE_AT_RUNASGROUP, NULL }, + { "ldap_sudorule_notbefore", "sudoNotBefore", SYSDB_SUDO_CACHE_AT_NOTBEFORE, NULL }, + { "ldap_sudorule_notafter", "sudoNotAfter", SYSDB_SUDO_CACHE_AT_NOTAFTER, NULL }, + { "ldap_sudorule_order", "sudoOrder", SYSDB_SUDO_CACHE_AT_ORDER, NULL }, + { "ldap_sudorule_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map service_map[] = { + { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL }, + { "ldap_service_name", "cn", SYSDB_NAME, NULL }, + { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL }, + { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL }, + { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map iphost_map[] = { + { "ldap_iphost_object_class", "ipHost", SYSDB_IP_HOST_CLASS, NULL }, + { "ldap_iphost_name", "cn", SYSDB_NAME, NULL }, + { "ldap_iphost_number", "ipHostNumber", SYSDB_IP_HOST_ATTR_ADDRESS, NULL }, + { "ldap_iphost_entry_usn", NULL, SYSDB_USN, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipnetwork_map[] = { + { "ldap_ipnetwork_object_class", "ipNetwork", SYSDB_IP_NETWORK_CLASS, NULL }, + { "ldap_ipnetwork_name", "cn", SYSDB_NAME, NULL }, + { "ldap_ipnetwork_number", "ipNetworkNumber", SYSDB_IP_NETWORK_ATTR_NUMBER, NULL }, + { "ldap_ipnetwork_entry_usn", NULL, SYSDB_USN, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "nisMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "nisMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "nisObject", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "cn", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "nisMapEntry", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "automountMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "automountMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map rfc2307bis_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "automount", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "automountKey", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "automountInformation", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; diff --git a/src/providers/ldap/ldap_opts.h b/src/providers/ldap/ldap_opts.h new file mode 100644 index 0000000..a1c80c3 --- /dev/null +++ b/src/providers/ldap/ldap_opts.h @@ -0,0 +1,69 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef LDAP_OPTS_H_ +#define LDAP_OPTS_H_ + +#include "src/providers/data_provider.h" +#include "providers/ldap/ldap_common.h" + +extern struct dp_option default_basic_opts[]; + +extern struct sdap_attr_map generic_attr_map[]; + +extern struct sdap_attr_map gen_ipa_attr_map[]; + +extern struct sdap_attr_map gen_ad_attr_map[]; + +extern struct sdap_attr_map rfc2307_user_map[]; + +extern struct sdap_attr_map rfc2307_group_map[]; + +extern struct sdap_attr_map rfc2307bis_user_map[]; + +extern struct sdap_attr_map rfc2307bis_group_map[]; + +extern struct sdap_attr_map gen_ad2008r2_user_map[]; + +extern struct sdap_attr_map gen_ad2008r2_group_map[]; + +extern struct sdap_attr_map netgroup_map[]; + +extern struct sdap_attr_map host_map[]; + +extern struct sdap_attr_map native_sudorule_map[]; + +extern struct sdap_attr_map service_map[]; + +extern struct sdap_attr_map iphost_map[]; + +extern struct sdap_attr_map ipnetwork_map[]; + +extern struct sdap_attr_map rfc2307_autofs_mobject_map[]; + +extern struct sdap_attr_map rfc2307_autofs_entry_map[]; + +extern struct sdap_attr_map rfc2307bis_autofs_mobject_map[]; + +extern struct sdap_attr_map rfc2307bis_autofs_entry_map[]; + +#endif /* LDAP_OPTS_H_ */ diff --git a/src/providers/ldap/ldap_resolver_cleanup.c b/src/providers/ldap/ldap_resolver_cleanup.c new file mode 100644 index 0000000..67b1c04 --- /dev/null +++ b/src/providers/ldap/ldap_resolver_cleanup.c @@ -0,0 +1,230 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/ldap_common.h" +#include "db/sysdb_iphosts.h" +#include "db/sysdb_ipnetworks.h" + +static errno_t +cleanup_iphosts(struct sdap_options *opts, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + struct ldb_message **msgs; + size_t count; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmp_ctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmp_ctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_hosts(tmp_ctx, domain, /* subfilter, */ + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to search ip hosts [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired ip host entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete ip host %s\n", name); + ret = sysdb_host_delete(domain, name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IP host delete returned [%d]: (%s)\n", + ret, sss_strerror(ret)); + continue; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +cleanup_ipnetworks(struct sdap_options *opts, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + char *ts_subfilter; + struct ldb_message **msgs; + size_t count; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + subfilter = talloc_asprintf(tmp_ctx, "(!(%s=0))", SYSDB_CACHE_EXPIRE); + if (subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ts_subfilter = talloc_asprintf(tmp_ctx, "(&(!(%s=0))(%s<=%"SPRItime"))", + SYSDB_CACHE_EXPIRE, SYSDB_CACHE_EXPIRE, now); + if (ts_subfilter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_ipnetworks(tmp_ctx, domain, /* subfilter, */ + ts_subfilter, attrs, &count, &msgs); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to search IP networks [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found %zu expired IP network entries!\n", count); + + if (count == 0) { + ret = EOK; + goto done; + } + + for (i = 0; i < count; i++) { + const char *name; + + name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(msgs[i]->dn)); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to delete IP network %s\n", name); + ret = sysdb_ipnetwork_delete(domain, name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IP network delete returned [%d]: (%s)\n", + ret, sss_strerror(ret)); + continue; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ldap_resolver_cleanup(struct sdap_resolver_ctx *ctx) +{ + TALLOC_CTX *tmp_ctx; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + bool in_transaction = false; + errno_t ret, tret; + + tmp_ctx = talloc_new(ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + id_ctx = ctx->id_ctx; + sdom = id_ctx->opts->sdom; + + ret = sysdb_transaction_start(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = cleanup_iphosts(id_ctx->opts, sdom->dom); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = cleanup_ipnetworks(id_ctx->opts, sdom->dom); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = sysdb_transaction_commit(sdom->dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ctx->last_purge = tevent_timeval_current(); + ret = EOK; + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sdom->dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/ldap_resolver_enum.c b/src/providers/ldap/ldap_resolver_enum.c new file mode 100644 index 0000000..3098255 --- /dev/null +++ b/src/providers/ldap/ldap_resolver_enum.c @@ -0,0 +1,299 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/ldap_common.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/ldap/sdap_async_resolver_enum.h" + +static errno_t +ldap_resolver_setup_enumeration(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + time_t cleanup; + bool has_enumerated; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + + ret = sysdb_has_enumerated(id_ctx->opts->sdom->dom, + SYSDB_HAS_ENUMERATED_RESOLVER, + &has_enumerated); + if (ret == ENOENT) { + /* default value */ + has_enumerated = false; + } else if (ret != EOK) { + return ret; + } + + if (has_enumerated) { + /* At least one enumeration has previously run, + * so clients will get cached data. We will delay + * starting to enumerate by 10s so we don't slow + * down the startup process if this is happening + * during system boot. + */ + first_delay = 10; + } else { + /* This is our first startup. Schedule the + * enumeration to start immediately once we + * enter the mainloop. + */ + first_delay = 0; + } + + cleanup = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (cleanup == 0) { + /* We need to cleanup the cache once in a while when enumerating, otherwise + * enumeration would only download deltas since the previous lastUSN and would + * not detect removed entries + */ + ret = dp_opt_set_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT, + LDAP_ENUM_PURGE_TIMEOUT); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set cleanup timeout, enumeration wouldn't " + "detect removed entries!\n"); + return ret; + } + } + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_ENUM_REFRESH_OFFSET); + + name = talloc_asprintf(resolver_ctx, "Enumeration [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = be_ptask_create(resolver_ctx, be_ctx, + period, /* period */ + first_delay, /* first_delay */ + 5, /* enabled delay */ + offset, /* random offset */ + period, /* timeout */ + 0, /* max_backoff */ + send_fn, recv_fn, + resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP | BE_PTASK_SCHEDULE_FROM_LAST, + &resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize enumeration periodic task\n"); + goto fail; + } + + talloc_free(name); + + return EOK; + +fail: + if (name != NULL) { + talloc_free(name); + } + return ret; +} + +static errno_t +ldap_resolver_cleanup_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_resolver_ctx *resolver_ctx = NULL; + + resolver_ctx = talloc_get_type(pvt, struct sdap_resolver_ctx); + if (resolver_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve sdap_resolver_ctx!\n"); + return EINVAL; + } + + return ldap_resolver_cleanup(resolver_ctx); +} + +static errno_t +ldap_resolver_setup_cleanup(struct sdap_resolver_ctx *resolver_ctx) +{ + errno_t ret; + time_t first_delay; + time_t period; + time_t offset; + char *name = NULL; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + + period = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if (period == 0) { + /* Cleanup has been explicitly disabled, so we won't + * create any cleanup tasks. */ + ret = EOK; + goto done; + } + offset = dp_opt_get_int(id_ctx->opts->basic, SDAP_PURGE_CACHE_OFFSET); + + /* Run the first one in a couple of seconds so that we have time to + * finish initializations first. */ + first_delay = 10; + + name = talloc_asprintf(resolver_ctx, "Cleanup [resolver] of %s", + id_ctx->opts->sdom->dom->name); + if (name == NULL) { + return ENOMEM; + } + + ret = be_ptask_create_sync(resolver_ctx, id_ctx->be, period, first_delay, + 5 /* enabled delay */, offset /* random offset */, + period /* timeout */, 0, + ldap_resolver_cleanup_task, resolver_ctx, name, + BE_PTASK_OFFLINE_SKIP, + &resolver_ctx->task); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to initialize cleanup periodic task for %s\n", + id_ctx->opts->sdom->dom->name); + goto done; + } + + ret = EOK; + +done: + if (name != NULL) { + talloc_free(name); + } + + return ret; +} + +errno_t +ldap_resolver_setup_tasks(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *resolver_ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn) +{ + errno_t ret; + struct sdap_id_ctx *id_ctx = resolver_ctx->id_ctx; + struct sdap_domain *sdom = id_ctx->opts->sdom; + + /* set up enumeration task */ + if (sdom->dom->enumerate) { + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver enumeration for %s\n", + sdom->dom->name); + ret = ldap_resolver_setup_enumeration(be_ctx, resolver_ctx, + send_fn, recv_fn); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not running we need to schedule it */ + DEBUG(SSSDBG_TRACE_FUNC, "Setting up resolver cleanup task for %s\n", + sdom->dom->name); + ret = ldap_resolver_setup_cleanup(resolver_ctx); + } + + return ret; +} + +struct ldap_resolver_enum_state { + struct sdap_resolver_ctx *resolver_ctx; +}; + +static void ldap_resolver_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ldap_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ldap_resolver_enum_state *state; + struct sdap_resolver_ctx *resolver_ctx; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ldap_resolver_enum_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + resolver_ctx = talloc_get_type(pvt, struct sdap_resolver_ctx); + if (resolver_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve sdap_resolver_ctx!\n"); + ret = EFAULT; + goto fail; + } + + state->resolver_ctx = resolver_ctx; + + subreq = sdap_dom_resolver_enum_send(state, ev, state->resolver_ctx, + state->resolver_ctx->id_ctx, + state->resolver_ctx->id_ctx->opts->sdom, + state->resolver_ctx->id_ctx->conn); + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + ret = EIO; + goto fail; + } + + tevent_req_set_callback(subreq, ldap_resolver_enumeration_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ldap_resolver_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_dom_resolver_enum_recv(subreq); + + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not enumerate domain\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ldap_resolver_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/providers/ldap/ldap_resolver_enum.h b/src/providers/ldap/ldap_resolver_enum.h new file mode 100644 index 0000000..0c8063a --- /dev/null +++ b/src/providers/ldap/ldap_resolver_enum.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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/>. +*/ + +#ifndef LDAP_RESOLVER_ENUM_H_ +#define LDAP_RESOLVER_ENUM_H_ + +errno_t ldap_resolver_setup_tasks(struct be_ctx *be_ctx, + struct sdap_resolver_ctx *ctx, + be_ptask_send_t send_fn, + be_ptask_recv_t recv_fn); + +struct tevent_req * +ldap_resolver_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +errno_t +ldap_resolver_enumeration_recv(struct tevent_req *req); + +errno_t +ldap_resolver_cleanup(struct sdap_resolver_ctx *ctx); + +#endif /* LDAP_RESOLVER_ENUM_H_ */ diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c new file mode 100644 index 0000000..f5637c5 --- /dev/null +++ b/src/providers/ldap/sdap.c @@ -0,0 +1,2129 @@ +/* + SSSD + + LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> + + 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 "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "confdb/confdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_range.h" +#include "util/probes.h" + +/* =Retrieve-Options====================================================== */ + +errno_t sdap_copy_map_entry(const struct sdap_attr_map *src_map, + struct sdap_attr_map *dst_map, + int entry_index) +{ + if (src_map[entry_index].name != NULL) { + dst_map[entry_index].name = talloc_strdup(dst_map, + src_map[entry_index].name); + if (dst_map[entry_index].name == NULL) { + return ENOMEM; + } + } else { + dst_map->name = NULL; + } + + return EOK; +} + +int sdap_copy_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + int num_entries, + struct sdap_attr_map **_map) +{ + struct sdap_attr_map *map; + int i; + + map = talloc_array(memctx, struct sdap_attr_map, num_entries + 1); + if (!map) { + return ENOMEM; + } + + for (i = 0; i < num_entries; i++) { + map[i].opt_name = talloc_strdup(map, src_map[i].opt_name); + map[i].sys_name = talloc_strdup(map, src_map[i].sys_name); + if (map[i].opt_name == NULL || map[i].sys_name == NULL) { + return ENOMEM; + } + + if (src_map[i].def_name != NULL) { + map[i].def_name = talloc_strdup(map, src_map[i].def_name); + map[i].name = talloc_strdup(map, src_map[i].def_name); + if (map[i].def_name == NULL || map[i].name == NULL) { + return ENOMEM; + } + } else { + map[i].def_name = NULL; + map[i].name = NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + map[i].opt_name, map[i].name ? "" : " no", + map[i].name ? map[i].name : ""); + } + + /* Include the sentinel */ + memset(&map[num_entries], 0, sizeof(struct sdap_attr_map)); + + *_map = map; + return EOK; +} + +static errno_t split_extra_attr(TALLOC_CTX *mem_ctx, + const char *conf_attr, + char **_sysdb_attr, + char **_ldap_attr) +{ + char *ldap_attr; + char *sysdb_attr; + char *sep; + + sep = strchr(conf_attr, ':'); + if (sep == NULL) { + sysdb_attr = talloc_strdup(mem_ctx, conf_attr); + ldap_attr = talloc_strdup(mem_ctx, conf_attr); + } else { + if (sep == conf_attr || *(sep + 1) == '\0') { + return ERR_INVALID_EXTRA_ATTR; + } + + sysdb_attr = talloc_strndup(mem_ctx, conf_attr, + sep - conf_attr); + ldap_attr = talloc_strdup(mem_ctx, sep+1); + } + + if (sysdb_attr == NULL || ldap_attr == NULL) { + return ENOMEM; + } + + *_sysdb_attr = sysdb_attr; + *_ldap_attr = ldap_attr; + return EOK; +} + +enum duplicate_t { + NOT_FOUND = 0, + ALREADY_IN_MAP, /* nothing to add */ + CONFLICT_WITH_MAP /* attempt to redefine attribute */ +}; + +static enum duplicate_t check_duplicate(struct sdap_attr_map *map, + int num_entries, + const char *sysdb_attr, + const char *ldap_attr) +{ + int i; + + for (i = 0; i < num_entries; i++) { + if (strcmp(map[i].sys_name, sysdb_attr) == 0) { + if (map[i].name != NULL && strcmp(map[i].name, ldap_attr) == 0) { + return ALREADY_IN_MAP; + } else { + return CONFLICT_WITH_MAP; + } + } + } + + return NOT_FOUND; +} + +int sdap_extend_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + size_t num_entries, + char **extra_attrs, + struct sdap_attr_map **_map, + size_t *_new_size) +{ + struct sdap_attr_map *map; + size_t nextra = 0; + size_t i; + char *ldap_attr; + char *sysdb_attr; + errno_t ret; + + *_map = src_map; + if (extra_attrs == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "No extra attributes\n"); + *_new_size = num_entries; + return EOK; + } + + for (nextra = 0; extra_attrs[nextra]; nextra++) ; + DEBUG(SSSDBG_FUNC_DATA, "%zu extra attributes\n", nextra); + + map = talloc_realloc(memctx, src_map, struct sdap_attr_map, + num_entries + nextra + 1); + if (map == NULL) { + return ENOMEM; + } + *_map = map; + + for (i = 0; *extra_attrs != NULL; extra_attrs++) { + ret = split_extra_attr(map, *extra_attrs, &sysdb_attr, &ldap_attr); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot split %s\n", *extra_attrs); + continue; + } + + ret = check_duplicate(map, num_entries, sysdb_attr, ldap_attr); + if (ret == ALREADY_IN_MAP) { + DEBUG(SSSDBG_TRACE_FUNC, + "Attribute %s (%s in LDAP) is already in map.\n", + sysdb_attr, ldap_attr); + continue; + } else if (ret == CONFLICT_WITH_MAP) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Attribute %s (%s in LDAP) is already used by SSSD, please " + "choose a different cache name\n", sysdb_attr, ldap_attr); + return ERR_DUP_EXTRA_ATTR; + } + + map[num_entries+i].name = ldap_attr; + map[num_entries+i].sys_name = sysdb_attr; + map[num_entries+i].opt_name = talloc_strdup(map, + map[num_entries+i].name); + map[num_entries+i].def_name = talloc_strdup(map, + map[num_entries+i].name); + if (map[num_entries+i].opt_name == NULL || + map[num_entries+i].sys_name == NULL || + map[num_entries+i].name == NULL || + map[num_entries+i].def_name == NULL) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_FUNC, "Extending map with %s\n", *extra_attrs); + + /* index must be incremented only for appended entry. */ + i++; + } + + nextra = i; + + /* Sentinel */ + memset(&map[num_entries+nextra], 0, sizeof(struct sdap_attr_map)); + + *_new_size = num_entries + nextra; + return EOK; +} + +int sdap_extend_map_with_list(TALLOC_CTX *mem_ctx, + const struct sdap_options *opts, + int extra_attr_index, + struct sdap_attr_map *src_map, + size_t num_entries, + struct sdap_attr_map **_map, + size_t *_new_size) +{ + const char *extra_attrs; + char **extra_attrs_list; + errno_t ret; + + *_map = src_map; + extra_attrs = dp_opt_get_string(opts->basic, extra_attr_index); + if (extra_attrs == NULL) { + *_new_size = num_entries; + return EOK; + } + + /* split server parm into a list */ + ret = split_on_separator(mem_ctx, extra_attrs, ',', true, true, + &extra_attrs_list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse server list!\n"); + return ret; + } + + + ret = sdap_extend_map(mem_ctx, src_map, + num_entries, extra_attrs_list, + _map, _new_size); + talloc_free(extra_attrs_list); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static void sdap_inherit_basic_options(char **inherit_opt_list, + struct dp_option *parent_opts, + struct dp_option *subdom_opts) +{ + int inherit_options[] = { + SDAP_SEARCH_TIMEOUT, + SDAP_NETWORK_TIMEOUT, + SDAP_OPT_TIMEOUT, + SDAP_OFFLINE_TIMEOUT, + SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_ENUM_REFRESH_OFFSET, + SDAP_PURGE_CACHE_TIMEOUT, + SDAP_PURGE_CACHE_OFFSET, + SDAP_KRB5_KEYTAB, + SDAP_KRB5_TICKET_LIFETIME, + SDAP_ENUM_SEARCH_TIMEOUT, + SDAP_EXPIRE_TIMEOUT, + SDAP_EXPIRE_OFFSET, + SDAP_IDLE_TIMEOUT, + SDAP_AD_USE_TOKENGROUPS, + SDAP_OPTS_BASIC /* sentinel */ + }; + int i; + + for (i = 0; inherit_options[i] != SDAP_OPTS_BASIC; i++) { + dp_option_inherit_match(inherit_opt_list, + inherit_options[i], + parent_opts, + subdom_opts); + } +} + +static void sdap_inherit_user_options(char **inherit_opt_list, + struct sdap_attr_map *parent_user_map, + struct sdap_attr_map *child_user_map) +{ + int inherit_options[] = { + SDAP_AT_USER_PRINC, + SDAP_OPTS_USER /* sentinel */ + }; + int i; + int opt_index; + bool inherit_option; + + for (i = 0; inherit_options[i] != SDAP_OPTS_USER; i++) { + opt_index = inherit_options[i]; + + inherit_option = string_in_list(parent_user_map[opt_index].opt_name, + inherit_opt_list, + false); + if (inherit_option == false) { + continue; + } + + sdap_copy_map_entry(parent_user_map, child_user_map, opt_index); + } +} + +void sdap_inherit_options(char **inherit_opt_list, + struct sdap_options *parent_sdap_opts, + struct sdap_options *child_sdap_opts) +{ + sdap_inherit_basic_options(inherit_opt_list, + parent_sdap_opts->basic, + child_sdap_opts->basic); + + sdap_inherit_user_options(inherit_opt_list, + parent_sdap_opts->user_map, + child_sdap_opts->user_map); +} + +int sdap_get_map(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_attr_map *def_map, + int num_entries, + struct sdap_attr_map **_map) +{ + struct sdap_attr_map *map; + char *name; + int i, ret; + + map = talloc_zero_array(memctx, struct sdap_attr_map, num_entries + 1); + if (!map) { + return ENOMEM; + } + + for (i = 0; i < num_entries; i++) { + + map[i].opt_name = def_map[i].opt_name; + map[i].def_name = def_map[i].def_name; + map[i].sys_name = def_map[i].sys_name; + + ret = confdb_get_string(cdb, map, conf_path, + map[i].opt_name, + map[i].def_name, + &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to retrieve value for %s\n", map[i].opt_name); + talloc_zfree(map); + return EINVAL; + } + + if (name) { + ret = sss_filter_sanitize(map, name, &map[i].name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not sanitize attribute [%s]\n", name); + talloc_zfree(map); + return EINVAL; + } + talloc_zfree(name); + } else { + map[i].name = NULL; + } + + if (map[i].def_name && !map[i].name) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to process value for %s\n", map[i].opt_name); + talloc_zfree(map); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s has%s value %s\n", + map[i].opt_name, map[i].name ? "" : " no", + map[i].name ? map[i].name : ""); + } + + *_map = map; + return EOK; +} + +/* =Parse-msg============================================================= */ + +static bool objectclass_matched(struct sdap_attr_map *map, + const char *objcl, int len); +int sdap_parse_entry(TALLOC_CTX *memctx, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sdap_attr_map *map, int attrs_num, + struct sysdb_attrs **_attrs, + bool disable_range_retrieval) +{ + struct sysdb_attrs *attrs; + BerElement *ber = NULL; + struct berval **vals; + struct ldb_val v; + char *str; + int lerrno; + int i, ret, ai; + int base_attr_idx = 0; + const char *name = NULL; + bool store; + bool base64; + char *base_attr; + uint32_t range_offset; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + lerrno = 0; + ret = ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_set_option failed [%s], ignored.\n", + sss_ldap_err2string(ret)); + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + str = ldap_get_dn(sh->ldap, sm->msg); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_get_dn failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "OriginalDN: [%s].\n", str); + PROBE(SDAP_PARSE_ENTRY, "OriginalDN", str, strlen(str)); + ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, str); + ldap_memfree(str); + if (ret) goto done; + + if (map) { + vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass"); + if (!vals) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown entry type, no objectClasses found!\n"); + ret = EINVAL; + goto done; + } + + for (i = 0; vals[i]; i++) { + if (objectclass_matched(map, vals[i]->bv_val, vals[i]->bv_len)) { + /* ok it's an entry of the right type */ + break; + } + } + if (!vals[i]) { + DEBUG(SSSDBG_CRIT_FAILURE, "objectClass not matching: %s\n", + map[0].name); + ldap_value_free_len(vals); + ret = EINVAL; + goto done; + } + ldap_value_free_len(vals); + } + + str = ldap_first_attribute(sh->ldap, sm->msg, &ber); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(lerrno == LDAP_SUCCESS + ? SSSDBG_TRACE_LIBS + : SSSDBG_MINOR_FAILURE, + "Entry has no attributes [%d(%s)]!?\n", + lerrno, sss_ldap_err2string(lerrno)); + if (map) { + ret = EINVAL; + goto done; + } + } + while (str) { + base64 = false; + + ret = sdap_parse_range(tmp_ctx, str, &base_attr, &range_offset, + disable_range_retrieval); + switch(ret) { + case EAGAIN: + /* This attribute contained range values and needs more to + * be retrieved + */ + /* TODO: return the set of attributes that need additional retrieval + * For now, we'll continue below and treat it as regular values. + */ + /* FALLTHROUGH */ + case ECANCELED: + /* FALLTHROUGH */ + case EOK: + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine if attribute [%s] was ranged\n", str); + goto done; + } + + if (ret == ECANCELED) { + store = false; + } else if (map) { + for (i = 1; i < attrs_num; i++) { + /* check if this attr is valid with the chosen schema */ + if (!map[i].name) continue; + /* check if it is an attr we are interested in */ + if (strcasecmp(base_attr, map[i].name) == 0) break; + } + /* interesting attr */ + if (i < attrs_num) { + store = true; + name = map[i].sys_name; + base_attr_idx = i; + if (strcmp(name, SYSDB_SSH_PUBKEY) == 0) { + base64 = true; + } + } else { + store = false; + } + } else { + name = base_attr; + store = true; + } + + if (store) { + vals = ldap_get_values_len(sh->ldap, sm->msg, str); + if (!vals) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (lerrno != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_get_values_len() failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Attribute [%s] has no values, skipping.\n", str); + + } else { + if (!vals[0]) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing value after ldap_get_values() ??\n"); + ldap_value_free_len(vals); + ret = EINVAL; + goto done; + } + for (i = 0; vals[i]; i++) { + if (vals[i]->bv_len == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Value of attribute [%s] is empty. " + "Skipping this value.\n", str); + continue; + } + if (base64) { + v.data = (uint8_t *) sss_base64_encode(attrs, + (uint8_t *) vals[i]->bv_val, vals[i]->bv_len); + if (!v.data) { + ldap_value_free_len(vals); + ret = ENOMEM; + goto done; + } + v.length = strlen((const char *)v.data); + } else { + v.data = (uint8_t *)vals[i]->bv_val; + v.length = vals[i]->bv_len; + } + PROBE(SDAP_PARSE_ENTRY, str, v.data, v.length); + + if (map) { + /* The same LDAP attr might be used for more sysdb + * attrs in case there is a map. Find all that match + * and copy the value + */ + for (ai = base_attr_idx; ai < attrs_num; ai++) { + /* check if this attr is valid with the chosen + * schema */ + if (!map[ai].name) continue; + + /* check if it is an attr we are interested in */ + if (strcasecmp(base_attr, map[ai].name) == 0) { + ret = sysdb_attrs_add_val(attrs, + map[ai].sys_name, + &v); + if (ret) { + ldap_value_free_len(vals); + goto done; + } + } + } + } else { + /* No map, just store the attribute */ + ret = sysdb_attrs_add_val(attrs, name, &v); + if (ret) { + ldap_value_free_len(vals); + goto done; + } + } + } + ldap_value_free_len(vals); + } + } + + ldap_memfree(str); + str = ldap_next_attribute(sh->ldap, sm->msg, ber); + } + ber_free(ber, 0); + ber = NULL; + + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (lerrno) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_get_option() failed: %d(%s)\n", + lerrno, sss_ldap_err2string(lerrno)); + ret = EIO; + goto done; + } + + PROBE(SDAP_PARSE_ENTRY_DONE); + *_attrs = talloc_steal(memctx, attrs); + ret = EOK; + +done: + if (ber) ber_free(ber, 0); + talloc_free(tmp_ctx); + return ret; +} + +static bool objectclass_matched(struct sdap_attr_map *map, + const char *objcl, int len) +{ + if (len == 0) { + len = strlen(objcl) + 1; + } + + if (strncasecmp(map[SDAP_OC_GROUP].name, objcl, len) == 0) { + return true; + } + + if (map[SDAP_OC_GROUP_ALT].name != NULL + && strncasecmp(map[SDAP_OC_GROUP_ALT].name, objcl, len) == 0) { + return true; + } + + return false; +} + +/* Parses an LDAPDerefRes into sdap_deref_attrs structure */ +errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, + struct sdap_attr_map_info *minfo, + size_t num_maps, + LDAPDerefRes *dref, + struct sdap_deref_attrs ***_deref_res) +{ + TALLOC_CTX *tmp_ctx; + LDAPDerefVal *dval; + const char *orig_dn; + const char **ocs; + struct sdap_attr_map *map; + int num_attrs = 0; + int ret, i, a, mi; + const char *name; + size_t len; + struct sdap_deref_attrs **res; + + if (!dref || !minfo) return EINVAL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, num_maps); + if (!res) { + ret = ENOMEM; + goto done; + } + + for (i=0; i < num_maps; i++) { + res[i] = talloc_zero(res, struct sdap_deref_attrs); + if (!res[i]) { + ret = ENOMEM; + goto done; + } + + res[i]->map = minfo[i].map; + } + + if (!dref->derefVal.bv_val) { + DEBUG(SSSDBG_OP_FAILURE, "Entry has no DN?\n"); + ret = EINVAL; + goto done; + } + + orig_dn = dref->derefVal.bv_val; + DEBUG(SSSDBG_TRACE_LIBS, + "Dereferenced DN: %s\n", orig_dn); + + if (!dref->attrVals) { + DEBUG(SSSDBG_FUNC_DATA, + "Dereferenced entry [%s] has no attributes, skipping\n", + orig_dn); + *_deref_res = NULL; + ret = EOK; + goto done; + } + + ocs = NULL; + for (dval = dref->attrVals; dval != NULL; dval = dval->next) { + if (strcasecmp("objectClass", dval->type) == 0) { + if (dval->vals == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No value for objectClass, skipping\n"); + continue; + } + + for(len=0; dval->vals[len].bv_val; len++); + + ocs = talloc_array(tmp_ctx, const char *, len+1); + if (!ocs) { + ret = ENOMEM; + goto done; + } + + for (i=0; i<len; i++) { + DEBUG(SSSDBG_TRACE_ALL, "Dereferenced objectClass value: %s\n", + dval->vals[i].bv_val); + ocs[i] = talloc_strdup(ocs, dval->vals[i].bv_val); + if (!ocs[i]) { + ret = ENOMEM; + goto done; + } + } + ocs[i] = NULL; + break; + } + } + if (!ocs) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown entry type, no objectClasses found!\n"); + ret = EINVAL; + goto done; + } + + for (mi = 0; mi < num_maps; mi++) { + map = NULL; + + for (i=0; ocs[i]; i++) { + /* the objectclass is always the first name in the map */ + if (objectclass_matched(minfo[mi].map, ocs[i], 0)) { + DEBUG(SSSDBG_TRACE_ALL, + "Found map for objectclass '%s'\n", ocs[i]); + map = minfo[mi].map; + num_attrs = minfo[mi].num_attrs; + break; + } + } + if (!map) continue; + + res[mi]->attrs = sysdb_new_attrs(res[mi]); + if (!res[mi]->attrs) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(res[mi]->attrs, SYSDB_ORIG_DN, + orig_dn); + if (ret) { + goto done; + } + + /* The dereference control seems to return the DN from the dereference + * attribute (e.g. member) so we can use it as key for the hash table + * later. */ + ret = sysdb_attrs_add_string(res[mi]->attrs, + SYSDB_DN_FOR_MEMBER_HASH_TABLE, orig_dn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + for (dval = dref->attrVals; dval != NULL; dval = dval->next) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Dereferenced attribute: %s\n", dval->type); + + for (a = 1; a < num_attrs; a++) { + /* check if this attr is valid with the chosen schema */ + if (!map[a].name) continue; + /* check if it is an attr we are interested in */ + if (strcasecmp(dval->type, map[a].name) == 0) break; + } + + /* interesting attr */ + if (a < num_attrs) { + name = map[a].sys_name; + } else { + continue; + } + + if (dval->vals == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No value for attribute %s, skipping\n", name); + continue; + } + + for (i=0; dval->vals[i].bv_val; i++) { + DEBUG(SSSDBG_TRACE_ALL, "Dereferenced attribute value: %s\n", + dval->vals[i].bv_val); + ret = sysdb_attrs_add_mem(res[mi]->attrs, name, + dval->vals[i].bv_val, + dval->vals[i].bv_len); + if (ret) goto done; + } + } + } + + + *_deref_res = talloc_steal(mem_ctx, res); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static void sss_ldap_debug(const char *buf) +{ + sss_debug_fn(__FILE__, __LINE__, __FUNCTION__, SSSDBG_TRACE_ALL, + "libldap: %s", buf); +} + +void setup_ldap_debug(struct dp_option *basic_opts) +{ + int ret; + int ldap_debug_level; + + ldap_debug_level = dp_opt_get_int(basic_opts, SDAP_LIBRARY_DEBUG_LEVEL); + if (ldap_debug_level == 0) { + return; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Setting LDAP library debug level [%d].\n", + ldap_debug_level); + + ret = ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &ldap_debug_level); + if (ret != LBER_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LBER_OPT_DEBUG_LEVEL, ignored .\n"); + } + + ret = ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, sss_ldap_debug); + if (ret != LBER_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LBER_OPT_LOG_PRINT_FN, ignored .\n"); + } + + ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldap_debug_level); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set LDAP_OPT_DEBUG_LEVEL, ignored .\n"); + } +} + +errno_t setup_tls_config(struct dp_option *basic_opts) +{ + int ret; + int ldap_opt_x_tls_require_cert; + const char *tls_opt; + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_REQCERT); + if (tls_opt) { + if (strcasecmp(tls_opt, "never") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_NEVER; + } + else if (strcasecmp(tls_opt, "allow") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_ALLOW; + } + else if (strcasecmp(tls_opt, "try") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_TRY; + } + else if (strcasecmp(tls_opt, "demand") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_DEMAND; + } + else if (strcasecmp(tls_opt, "hard") == 0) { + ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_HARD; + } + else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown value for tls_reqcert '%s'.\n", tls_opt); + return EINVAL; + } + /* LDAP_OPT_X_TLS_REQUIRE_CERT has to be set as a global option, + * because the SSL/TLS context is initialized from this value. */ + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, + &ldap_opt_x_tls_require_cert); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(req_cert) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERT); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cacertfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERTDIR); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cacertdir) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CERT); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(certfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_KEY); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(keyfile) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CIPHER_SUITE); + if (tls_opt) { + ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, tls_opt); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_set_option(cipher) failed: %s\n", + sss_ldap_err2string(ret)); + return EIO; + } + } + + return EOK; +} + +bool sdap_sasl_mech_needs_kinit(const char *sasl_mech) +{ + if (strcasecmp(sasl_mech, "GSSAPI") == 0 + || strcasecmp(sasl_mech, "GSS-SPNEGO") == 0) { + return true; + } + + return false; +} + +bool sdap_check_sup_list(struct sup_list *l, const char *val) +{ + int i; + + if (!val) { + return false; + } + + for (i = 0; i < l->num_vals; i++) { + if (strcasecmp(val, (char *)l->vals[i])) { + continue; + } + return true; + } + + return false; +} + +static int sdap_init_sup_list(TALLOC_CTX *memctx, + struct sup_list *list, + int num, struct ldb_val *vals) +{ + int i; + + list->vals = talloc_array(memctx, char *, num); + if (!list->vals) { + return ENOMEM; + } + + for (i = 0; i < num; i++) { + list->vals[i] = talloc_strndup(list->vals, + (char *)vals[i].data, vals[i].length); + if (!list->vals[i]) { + return ENOMEM; + } + } + + list->num_vals = num; + + return EOK; +} + +int sdap_set_rootdse_supported_lists(struct sysdb_attrs *rootdse, + struct sdap_handle *sh) +{ + struct ldb_message_element *el = NULL; + int ret; + int i; + + for (i = 0; i < rootdse->num; i++) { + el = &rootdse->a[i]; + if (strcasecmp(el->name, "supportedControl") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_controls, + el->num_values, el->values); + if (ret) { + return ret; + } + } else if (strcasecmp(el->name, "supportedExtension") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_extensions, + el->num_values, el->values); + if (ret) { + return ret; + } + } else if (strcasecmp(el->name, "supportedSASLMechanisms") == 0) { + + ret = sdap_init_sup_list(sh, &sh->supported_saslmechs, + el->num_values, el->values); + if (ret) { + return ret; + } + } + } + + return EOK; + +} + +static char *get_single_value_as_string(TALLOC_CTX *mem_ctx, + struct ldb_message_element *el) +{ + char *str = NULL; + + if (el->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing value.\n"); + } else if (el->num_values == 1) { + str = talloc_strndup(mem_ctx, (char *) el->values[0].data, + el->values[0].length); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "More than one value found.\n"); + } + + return str; +} + +static char *get_naming_context(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *rootdse) +{ + struct ldb_message_element *nc = NULL; + struct ldb_message_element *dnc = NULL; + int i; + char *naming_context = NULL; + + for (i = 0; i < rootdse->num; i++) { + if (strcasecmp(rootdse->a[i].name, + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS) == 0) { + nc = &rootdse->a[i]; + } else if (strcasecmp(rootdse->a[i].name, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT) == 0) { + dnc = &rootdse->a[i]; + } + } + + if (dnc == NULL && nc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No attributes [%s] or [%s] found in rootDSE.\n", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT); + } else { + if (dnc != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Using value from [%s] as naming context.\n", + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT); + naming_context = get_single_value_as_string(mem_ctx, dnc); + } + + if (naming_context == NULL && nc != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Using value from [%s] as naming context.\n", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS); + naming_context = get_single_value_as_string(mem_ctx, nc); + } + } + + /* Some directory servers such as Novell eDirectory will return + * a zero-length namingContexts value in some situations. In this + * case, we should return it as NULL so things fail gracefully. + */ + if (naming_context && naming_context[0] == '\0') { + talloc_zfree(naming_context); + } + + return naming_context; +} + +errno_t +sdap_create_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *unparsed_base, + int scope, + const char *filter, + struct sdap_search_base **_base) +{ + struct sdap_search_base *base; + TALLOC_CTX *tmp_ctx; + errno_t ret; + struct ldb_dn *ldn; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + base = talloc_zero(tmp_ctx, struct sdap_search_base); + if (base == NULL) { + ret = ENOMEM; + goto done; + } + + base->basedn = talloc_strdup(base, unparsed_base); + if (base->basedn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Validate the basedn */ + ldn = ldb_dn_new(base, ldb, unparsed_base); + if (!ldn) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(ldn)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid base DN [%s]\n", unparsed_base); + ret = EINVAL; + goto done; + } + + base->ldb = ldb; + base->ldb_basedn = ldn; + base->scope = scope; + base->filter = filter; + + *_base = talloc_steal(mem_ctx, base); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sdap_set_search_base(struct sdap_options *opts, + struct sdap_domain *sdom, + enum sdap_basic_opt class, + char *naming_context) +{ + errno_t ret; + struct sdap_search_base ***bases; + + switch(class) { + case SDAP_SEARCH_BASE: + bases = &sdom->search_bases; + break; + case SDAP_USER_SEARCH_BASE: + bases = &sdom->user_search_bases; + break; + case SDAP_GROUP_SEARCH_BASE: + bases = &sdom->group_search_bases; + break; + case SDAP_NETGROUP_SEARCH_BASE: + bases = &sdom->netgroup_search_bases; + break; + case SDAP_HOST_SEARCH_BASE: + bases = &sdom->host_search_bases; + break; + case SDAP_SUDO_SEARCH_BASE: + bases = &sdom->sudo_search_bases; + break; + case SDAP_SERVICE_SEARCH_BASE: + bases = &sdom->service_search_bases; + break; + case SDAP_AUTOFS_SEARCH_BASE: + bases = &sdom->autofs_search_bases; + break; + case SDAP_IPHOST_SEARCH_BASE: + bases = &sdom->iphost_search_bases; + break; + case SDAP_IPNETWORK_SEARCH_BASE: + bases = &sdom->ipnetwork_search_bases; + break; + default: + return EINVAL; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting option [%s] to [%s].\n", + opts->basic[class].opt_name, naming_context); + + ret = dp_opt_set_string(opts->basic, class, naming_context); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); + goto done; + } + + ret = sdap_parse_search_base(opts, sysdb_ctx_get_ldb(sdom->dom->sysdb), + opts->basic, class, bases); + if (ret != EOK) goto done; + + ret = EOK; +done: + return ret; +} + +errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_domain *sdom) +{ + int ret; + char *naming_context = NULL; + + if (!sdom->search_bases + || !sdom->user_search_bases + || !sdom->group_search_bases + || !sdom->netgroup_search_bases + || !sdom->host_search_bases + || !sdom->sudo_search_bases + || !sdom->iphost_search_bases + || !sdom->ipnetwork_search_bases + || !sdom->autofs_search_bases) { + naming_context = get_naming_context(opts->basic, rootdse); + if (naming_context == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_naming_context failed.\n"); + + /* This has to be non-fatal, since some servers offer + * multiple namingContexts entries. We will just + * add NULL checks for the search bases in the lookups. + */ + ret = EOK; + goto done; + } + } + + /* Default */ + if (!sdom->search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Users */ + if (!sdom->user_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_USER_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Groups */ + if (!sdom->group_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_GROUP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Netgroups */ + if (!sdom->netgroup_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_NETGROUP_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Hosts */ + if (!sdom->host_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_HOST_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Sudo */ + if (!sdom->sudo_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SUDO_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* Services */ + if (!sdom->service_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_SERVICE_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* autofs */ + if (!sdom->autofs_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_AUTOFS_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* IP host */ + if (!sdom->iphost_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_IPHOST_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + /* IP network */ + if (!sdom->ipnetwork_search_bases) { + ret = sdap_set_search_base(opts, sdom, + SDAP_IPNETWORK_SEARCH_BASE, + naming_context); + if (ret != EOK) goto done; + } + + ret = EOK; + +done: + talloc_free(naming_context); + return ret; +} + +int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx, + const char *server, + struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_server_opts **srv_opts) +{ + struct sdap_server_opts *so; + struct { + const char *last_name; + const char *entry_name; + } usn_attrs[] = { { SDAP_IPA_LAST_USN, SDAP_IPA_USN }, + { SDAP_AD_LAST_USN, SDAP_AD_USN }, + { NULL, NULL } }; + const char *last_usn_name; + const char *last_usn_value; + const char *entry_usn_name; + const char *schema_nc = NULL; + char *endptr = NULL; + int ret; + int i; + uint32_t dc_level; + + so = talloc_zero(memctx, struct sdap_server_opts); + if (!so) { + return ENOMEM; + } + so->server_id = talloc_strdup(so, server); + if (!so->server_id) { + talloc_zfree(so); + return ENOMEM; + } + + last_usn_name = opts->gen_map[SDAP_AT_LAST_USN].name; + entry_usn_name = opts->gen_map[SDAP_AT_ENTRY_USN].name; + if (rootdse) { + if (last_usn_name) { + ret = sysdb_attrs_get_string(rootdse, + last_usn_name, &last_usn_value); + if (ret != EOK) { + switch (ret) { + case ENOENT: + DEBUG(SSSDBG_CRIT_FAILURE, + "%s configured but not found in rootdse!\n", + opts->gen_map[SDAP_AT_LAST_USN].opt_name); + break; + case ERANGE: + DEBUG(SSSDBG_CRIT_FAILURE, + "Multiple values of %s found in rootdse!\n", + opts->gen_map[SDAP_AT_LAST_USN].opt_name); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown error (%d) checking rootdse!\n", ret); + } + } else { + if (!entry_usn_name) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s found in rootdse but %s is not set!\n", + last_usn_name, + opts->gen_map[SDAP_AT_ENTRY_USN].opt_name); + } else { + so->supports_usn = true; + errno = 0; + so->last_usn = strtoul(last_usn_value, &endptr, 10); + if (errno || !endptr || *endptr || (endptr == last_usn_value)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "USN is not valid (value: %s)\n", last_usn_value); + so->last_usn = 0; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "USN value: %s (int: %lu)\n", last_usn_value, so->last_usn); + } + } + } + } else { + /* no usn option configure, let's try to autodetect. */ + for (i = 0; usn_attrs[i].last_name; i++) { + ret = sysdb_attrs_get_string(rootdse, + usn_attrs[i].last_name, + &last_usn_value); + if (ret == EOK) { + /* Fixate discovered configuration */ + opts->gen_map[SDAP_AT_LAST_USN].name = + talloc_strdup(opts->gen_map, usn_attrs[i].last_name); + opts->gen_map[SDAP_AT_ENTRY_USN].name = + talloc_strdup(opts->gen_map, usn_attrs[i].entry_name); + so->supports_usn = true; + errno = 0; + so->last_usn = strtoul(last_usn_value, &endptr, 10); + if (errno || !endptr || *endptr || (endptr == last_usn_value)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "USN is not valid (value: %s)\n", last_usn_value); + so->last_usn = 0; + } else { + DEBUG(SSSDBG_TRACE_ALL, + "USN value: %s (int: %lu)\n", last_usn_value, so->last_usn); + } + last_usn_name = usn_attrs[i].last_name; + break; + } + } + } + + /* Detect Active Directory version if available */ + ret = sysdb_attrs_get_uint32_t(rootdse, + SDAP_ROOTDSE_ATTR_AD_VERSION, + &dc_level); + if (ret == EOK) { + /* Validate that the DC level matches an expected value */ + switch(dc_level) { + case DS_BEHAVIOR_WIN2000: + case DS_BEHAVIOR_WIN2003: + case DS_BEHAVIOR_WIN2008: + case DS_BEHAVIOR_WIN2008R2: + case DS_BEHAVIOR_WIN2012: + case DS_BEHAVIOR_WIN2012R2: + case DS_BEHAVIOR_WIN2016: + opts->dc_functional_level = dc_level; + DEBUG(SSSDBG_CONF_SETTINGS, + "Setting AD compatibility level to [%d]\n", + opts->dc_functional_level); + break; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Received invalid value [%d] for AD compatibility level. " + "Using the lowest-common compatibility level\n", + dc_level); + opts->dc_functional_level = DS_BEHAVIOR_WIN2003; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error detecting Active Directory compatibility level " + "(%s). Continuing without AD performance enhancements\n", + strerror(ret)); + } + + ret = sysdb_attrs_get_string(rootdse, + SDAP_ROOTDSE_ATTR_AD_SCHEMA_NC, + &schema_nc); + if (ret == EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Will look for schema at [%s]\n", schema_nc); + opts->schema_basedn = talloc_strdup(opts, schema_nc); + } + } + + if (!last_usn_name) { + DEBUG(SSSDBG_FUNC_DATA, + "No known USN scheme is supported by this server!\n"); + if (!entry_usn_name) { + DEBUG(SSSDBG_FUNC_DATA, + "Will use modification timestamp as usn!\n"); + opts->gen_map[SDAP_AT_ENTRY_USN].name = + talloc_strdup(opts->gen_map, "modifyTimestamp"); + } + } + + if (!opts->user_map[SDAP_AT_USER_USN].name) { + opts->user_map[SDAP_AT_USER_USN].name = + talloc_strdup(opts->user_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (!opts->group_map[SDAP_AT_GROUP_USN].name) { + opts->group_map[SDAP_AT_GROUP_USN].name = + talloc_strdup(opts->group_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (!opts->service_map[SDAP_AT_SERVICE_USN].name) { + opts->service_map[SDAP_AT_SERVICE_USN].name = + talloc_strdup(opts->service_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->sudorule_map && + !opts->sudorule_map[SDAP_AT_SUDO_USN].name) { + opts->sudorule_map[SDAP_AT_SUDO_USN].name = + talloc_strdup(opts->sudorule_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->iphost_map && + !opts->iphost_map[SDAP_AT_IPHOST_USN].name) { + opts->iphost_map[SDAP_AT_IPHOST_USN].name = + talloc_strdup(opts->iphost_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + if (opts->ipnetwork_map && + !opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name) { + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name = + talloc_strdup(opts->ipnetwork_map, + opts->gen_map[SDAP_AT_ENTRY_USN].name); + } + + *srv_opts = so; + return EOK; +} + +void sdap_steal_server_opts(struct sdap_id_ctx *id_ctx, + struct sdap_server_opts **srv_opts) +{ + if (!id_ctx || !srv_opts || !*srv_opts) { + return; + } + + if (!id_ctx->srv_opts) { + id_ctx->srv_opts = talloc_move(id_ctx, srv_opts); + return; + } + + /* discard if same as previous so we do not reset max usn values + * unnecessarily, only update last_usn. */ + if (strcmp(id_ctx->srv_opts->server_id, (*srv_opts)->server_id) == 0) { + id_ctx->srv_opts->last_usn = (*srv_opts)->last_usn; + talloc_zfree(*srv_opts); + return; + } + + talloc_zfree(id_ctx->srv_opts); + id_ctx->srv_opts = talloc_move(id_ctx, srv_opts); +} + +static bool attr_is_filtered(const char *attr, const char **filter) +{ + int i; + + if (filter) { + i = 0; + while (filter[i]) { + if (filter[i] == attr || + strcasecmp(filter[i], attr) == 0) { + return true; + } + i++; + } + } + + return false; +} + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, + const char **filter, + const char ***_attrs, + size_t *attr_count) +{ + errno_t ret; + const char **attrs; + int i, j; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Assume that all entries in the map have values */ + attrs = talloc_zero_array(tmp_ctx, const char *, size + 1); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + /* first attribute is "objectclass" not the specific one */ + attrs[0] = talloc_strdup(memctx, "objectClass"); + if (!attrs[0]) return ENOMEM; + + /* add the others */ + for (i = j = 1; i < size; i++) { + if (map[i].name && !attr_is_filtered(map[i].name, filter)) { + attrs[j] = map[i].name; + j++; + } + } + attrs[j] = NULL; + + /* Trim down the used memory if some attributes were NULL */ + attrs = talloc_realloc(tmp_ctx, attrs, const char *, j + 1); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + *_attrs = talloc_steal(memctx, attrs); + if (attr_count) *attr_count = j; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int sdap_control_create(struct sdap_handle *sh, const char *oid, int iscritical, + struct berval *value, int dupval, LDAPControl **ctrlp) +{ + int ret; + + if (sdap_is_control_supported(sh, oid)) { + ret = sss_ldap_control_create(oid, iscritical, value, dupval, ctrlp); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ldap_control_create failed [%d][%s].\n", + ret, sss_ldap_err2string(ret)); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Server does not support the requested control [%s].\n", oid); + ret = LDAP_NOT_SUPPORTED; + } + + return ret; +} + +int sdap_replace_id(struct sysdb_attrs *entry, const char *attr, id_t val) +{ + char *str; + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el_ext(entry, attr, false, &el); + if (ret == ENOENT) { + return sysdb_attrs_add_uint32(entry, attr, val); + } else if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get attribute [%s]\n", attr); + return ret; + } + + if (el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Expected 1 value for %s, got %d\n", attr, el->num_values); + return EINVAL; + } + + str = talloc_asprintf(entry, "%llu", (unsigned long long) val); + if (!str) { + return ENOMEM; + } + + el->values[0].data = (uint8_t *) str; + el->values[0].length = strlen(str); + + return EOK; +} + +static errno_t sdap_get_rdn_multi(TALLOC_CTX *mem_ctx, const char *dn, + const char *name, char **_val) +{ + int ret; + size_t c; + LDAPDN ldapdn = NULL; + + ret = ldap_str2dn(dn, &ldapdn, LDAP_DN_FORMAT_LDAPV3); + if (ret != LDAP_SUCCESS || ldapdn == NULL || ldapdn[0] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse DN [%s].\n", dn); + ret = EINVAL; + goto done; + } + + ret = ENOENT; + for (c = 0; ldapdn[0][c] != NULL; c++) { + if (strncasecmp(name, ldapdn[0][c]->la_attr.bv_val, + ldapdn[0][c]->la_attr.bv_len) == 0) { + *_val = talloc_strndup(mem_ctx, ldapdn[0][c]->la_value.bv_val, + ldapdn[0][c]->la_value.bv_len); + if (*_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy AVA value.\n"); + ret = ENOMEM; + goto done; + } + ret = EOK; + break; + } + } + +done: + ldap_dnfree(ldapdn); + + return ret; +} + +errno_t sdap_get_primary_name(const char *attr_name, + struct sysdb_attrs *attrs, + const char **_primary_name) +{ + errno_t ret; + const char *orig_name = NULL; + char *rdn_val = NULL; + struct ldb_message_element *sysdb_name_el; + struct ldb_message_element *orig_dn_el; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = sysdb_attrs_get_el(attrs, + SYSDB_NAME, + &sysdb_name_el); + if (ret != EOK || sysdb_name_el->num_values == 0) { + ret = EINVAL; + goto done; + } + + if (sysdb_name_el->num_values == 1) { + /* Entry contains only one name. Just return that */ + orig_name = (const char *)sysdb_name_el->values[0].data; + ret = EOK; + goto done; + } + + /* Multiple values for name. Check whether one matches the RDN */ + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &orig_dn_el); + if (ret) { + goto done; + } + if (orig_dn_el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Original DN is not available.\n"); + ret = EINVAL; + goto done; + } else if (orig_dn_el->num_values == 1) { + ret = sdap_get_rdn_multi(tmp_ctx, + (const char *) orig_dn_el->values[0].data, + attr_name, &rdn_val); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The entry has multiple names and the RDN attribute does " + "not match. Will use the first value [%s] as fallback.\n", + (const char *)sysdb_name_el->values[0].data); + orig_name = (const char *)sysdb_name_el->values[0].data; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get rdn from [%s]\n", + (const char *) orig_dn_el->values[0].data); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Should not have more than one origDN\n"); + ret = EINVAL; + goto done; + } + + for (i = 0; i < sysdb_name_el->num_values; i++) { + if (strcasecmp(rdn_val, + (const char *)sysdb_name_el->values[i].data) == 0) { + /* This name matches the RDN. Use it */ + break; + } + } + if (i < sysdb_name_el->num_values) { + /* Match was found */ + orig_name = (const char *)sysdb_name_el->values[i].data; + } else { + /* If we can't match the name to the RDN, we just have to + * throw up our hands. There's no deterministic way to + * decide which name is correct. + */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Can't match the name to the RDN\n"); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine primary name: [%d][%s]\n", + ret, strerror(ret)); + } + talloc_free(tmp_ctx); + + DEBUG(SSSDBG_TRACE_FUNC, "Processing object %s\n", orig_name); + + *_primary_name = orig_name; + + return ret; +} + +static errno_t +sdap_get_primary_fqdn(TALLOC_CTX *mem_ctx, + struct sdap_idmap_ctx *idmap_ctx, + const char *attr_name, + const char *sid_attr_name, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_primary_fqdn) +{ + errno_t ret; + const char *shortname = NULL; + const char *primary_fqdn = NULL; + TALLOC_CTX *tmp_ctx; + char *sid_str = NULL; + struct sss_domain_info *subdomain = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sdap_get_primary_name(attr_name, attrs, &shortname); + if (ret != EOK) { + goto done; + } + + /* In AD scenarion, the object can be from subdomain - identify it by SID */ + if (sid_attr_name != NULL) { + ret = sdap_attrs_get_sid_str(tmp_ctx, + idmap_ctx, + attrs, + sid_attr_name, + &sid_str); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Group has objectSID [%s]\n", sid_str); + subdomain = find_domain_by_sid(dom, sid_str); + talloc_free(sid_str); + if (subdomain != NULL) { + dom = subdomain; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Group has name [%s]\n", dom->name); + } + + primary_fqdn = sss_create_internal_fqname(tmp_ctx, shortname, dom->name); + if (primary_fqdn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *_primary_fqdn = talloc_steal(mem_ctx, primary_fqdn); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sdap_get_user_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_user_name) +{ + return sdap_get_primary_fqdn(memctx, + opts->idmap_ctx, + opts->user_map[SDAP_AT_USER_NAME].name, + opts->group_map[SDAP_AT_USER_OBJECTSID].name, + attrs, dom, _user_name); +} + +errno_t sdap_get_group_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_group_name) +{ + return sdap_get_primary_fqdn(memctx, + opts->idmap_ctx, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + attrs, dom, _group_name); +} + +errno_t sdap_get_netgroup_primary_name(struct sdap_options *opts, + struct sysdb_attrs *attrs, + const char **_netgroup_name) +{ + return sdap_get_primary_name(opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name, + attrs, _netgroup_name); +} + +static errno_t +_sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + bool qualify_names, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list) +{ + errno_t ret; + size_t i, j; + char **list; + const char *name; + + /* Assume that every entry has a primary name */ + list = talloc_array(mem_ctx, char *, attr_count+1); + if (!list) { + return ENOMEM; + } + + j = 0; + for (i = 0; i < attr_count; i++) { + if (qualify_names == false) { + ret = sdap_get_primary_name(ldap_attr, attr_list[i], &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary name\n"); + /* Skip and continue. Don't advance 'j' */ + continue; + } + list[j] = talloc_strdup(list, name); + } else { + ret = sdap_get_primary_fqdn(mem_ctx, + idmap_ctx, + ldap_attr, + sid_attr, + attr_list[i], + domain, + &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine primary fqdn name\n"); + /* Skip and continue. Don't advance 'j' */ + continue; + } + list[j] = talloc_strdup(list, name); + } + if (!list[j]) { + ret = ENOMEM; + goto done; + } + + j++; + } + + /* NULL-terminate the list */ + list[j] = NULL; + + *name_list = list; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(list); + } + return ret; +} + +errno_t sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + char ***name_list) +{ + return _sdap_get_primary_name_list(domain, mem_ctx, attr_list, attr_count, + ldap_attr, false, NULL, NULL, name_list); +} + +errno_t sdap_get_primary_fqdn_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list) +{ + return _sdap_get_primary_name_list(domain, mem_ctx, attr_list, attr_count, + ldap_attr, true, sid_attr, idmap_ctx, name_list); +} + + +char *sdap_make_oc_list(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map) +{ + if (map[SDAP_OC_GROUP_ALT].name == NULL) { + return talloc_asprintf(mem_ctx, "objectClass=%s", + map[SDAP_OC_GROUP].name); + } else { + return talloc_asprintf(mem_ctx, + "|(objectClass=%s)(objectClass=%s)", + map[SDAP_OC_GROUP].name, + map[SDAP_OC_GROUP_ALT].name); + } +} + +struct sss_domain_info *sdap_get_object_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom) +{ + errno_t ret; + const char *original_dn = NULL; + struct sdap_domain *sdmatch = NULL; + + ret = sysdb_attrs_get_string(obj, SYSDB_ORIG_DN, &original_dn); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no original DN, assuming our domain\n"); + return dom; + } + + sdmatch = sdap_domain_get_by_dn(opts, original_dn); + if (sdmatch == NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "The original DN of the group cannot " + "be related to any search base\n"); + return dom; + } + + return sdmatch->dom; +} + +bool sdap_object_in_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom) +{ + struct sss_domain_info *obj_dom; + + obj_dom = sdap_get_object_domain(opts, obj, dom); + if (obj_dom == NULL) { + return false; + } + + return (obj_dom == dom); +} + +size_t sdap_steal_objects_in_dom(struct sdap_options *opts, + struct sysdb_attrs **dom_objects, + size_t offset, + struct sss_domain_info *dom, + struct sysdb_attrs **all_objects, + size_t count, + bool filter) +{ + size_t copied = 0; + + /* Own objects from all_objects by dom_objects in case they belong + * to domain dom. + * + * Don't copy objects from other domains in case + * the search was for parent domain but a child domain would match, + * too, such as: + * dc=example,dc=com + * dc=child,dc=example,dc=com + * while searching for an object from dc=example. + */ + for (size_t i = 0; i < count; i++) { + if (filter && + sdap_object_in_domain(opts, all_objects[i], dom) == false) { + continue; + } + + dom_objects[offset + copied] = + talloc_steal(dom_objects, all_objects[i]); + copied++; + } + + return copied; +} + +void sdap_domain_copy_search_bases(struct sdap_domain *to, + struct sdap_domain *from) +{ + to->search_bases = from->search_bases; + to->user_search_bases = from->user_search_bases; + to->group_search_bases = from->group_search_bases; + to->netgroup_search_bases = from->netgroup_search_bases; + to->sudo_search_bases = from->sudo_search_bases; + to->service_search_bases = from->service_search_bases; + to->iphost_search_bases = from->iphost_search_bases; + to->ipnetwork_search_bases = from->ipnetwork_search_bases; + to->autofs_search_bases = from->autofs_search_bases; +} diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h new file mode 100644 index 0000000..161bc5c --- /dev/null +++ b/src/providers/ldap/sdap.h @@ -0,0 +1,746 @@ +/* + SSSD + + LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> + + 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/>. +*/ + +#ifndef _SDAP_H_ +#define _SDAP_H_ + +#include <ldb.h> +#include "providers/backend.h" +#include <ldap.h> +#include "util/sss_ldap.h" +#include "lib/certmap/sss_certmap.h" + +struct sdap_msg { + struct sdap_msg *next; + LDAPMessage *msg; +}; + +struct sdap_op; + +typedef void (sdap_op_callback_t)(struct sdap_op *op, + struct sdap_msg *, int, void *); + +struct sdap_handle; + +struct fd_event_item { + struct fd_event_item *prev; + struct fd_event_item *next; + + int fd; + struct tevent_fd *fde; +}; + +struct ldap_cb_data { + struct sdap_handle *sh; + struct tevent_context *ev; + struct fd_event_item *fd_list; +}; + +struct sup_list { + int num_vals; + char **vals; +}; + +struct sdap_handle { + LDAP *ldap; + bool connected; + /* Authentication ticket expiration time (if any) */ + time_t expire_time; + /* Time when the connection became idle (if any) */ + time_t idle_time; + /* Configured idle timeout */ + int idle_timeout; + ber_int_t page_size; + bool disable_deref; + + struct sdap_fd_events *sdap_fd_events; + + struct sup_list supported_saslmechs; + struct sup_list supported_controls; + struct sup_list supported_extensions; + + struct sdap_op *ops; + + /* during release we need to lock access to the handler + * from the destructor to avoid recursion */ + bool destructor_lock; + /* mark when it is safe to finally release the handler memory */ + bool release_memory; +}; + +struct sdap_service { + char *name; + char *uri; + char *kinit_service_name; + struct sockaddr *sockaddr; + socklen_t sockaddr_len; +}; + +struct sdap_ppolicy_data { + int grace; + int expire; +}; + +#define SYSDB_SHADOWPW_LASTCHANGE "shadowLastChange" +#define SYSDB_SHADOWPW_MIN "shadowMin" +#define SYSDB_SHADOWPW_MAX "shadowMax" +#define SYSDB_SHADOWPW_WARNING "shadowWarning" +#define SYSDB_SHADOWPW_INACTIVE "shadowInactive" +#define SYSDB_SHADOWPW_EXPIRE "shadowExpire" +#define SYSDB_SHADOWPW_FLAG "shadowFlag" + +#define SYSDB_NS_ACCOUNT_LOCK "nsAccountLock" + +#define SYSDB_KRBPW_LASTCHANGE "krbLastPwdChange" +#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration" + +#define SYSDB_PWD_ATTRIBUTE "pwdAttribute" + +#define SYSDB_NDS_LOGIN_DISABLED "ndsLoginDisabled" +#define SYSDB_NDS_LOGIN_EXPIRATION_TIME "ndsLoginExpirationTime" +#define SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP "ndsLoginAllowedTimeMap" + +#define SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS "namingContexts" +#define SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT "defaultNamingContext" +#define SDAP_ROOTDSE_ATTR_AD_VERSION "domainControllerFunctionality" +#define SDAP_ROOTDSE_ATTR_AD_SCHEMA_NC "schemaNamingContext" + +#define SDAP_IPA_USN "entryUSN" +#define SDAP_IPA_LAST_USN "lastUSN" +#define SDAP_AD_USN "uSNChanged" +#define SDAP_AD_LAST_USN "highestCommittedUSN" + +#define SDAP_AD_GROUP_TYPE_BUILTIN 0x00000001 +#define SDAP_AD_GROUP_TYPE_GLOBAL 0x00000002 +#define SDAP_AD_GROUP_TYPE_DOMAIN_LOCAL 0x00000004 +#define SDAP_AD_GROUP_TYPE_UNIVERSAL 0x00000008 +#define SDAP_AD_GROUP_TYPE_APP_BASIC 0x00000010 +#define SDAP_AD_GROUP_TYPE_APP_QUERY 0x00000020 +#define SDAP_AD_GROUP_TYPE_SECURITY 0x80000000 + +enum sdap_basic_opt { + SDAP_URI = 0, + SDAP_BACKUP_URI, + SDAP_SEARCH_BASE, + SDAP_DEFAULT_BIND_DN, + SDAP_DEFAULT_AUTHTOK_TYPE, + SDAP_DEFAULT_AUTHTOK, + SDAP_SEARCH_TIMEOUT, + SDAP_NETWORK_TIMEOUT, + SDAP_OPT_TIMEOUT, + SDAP_TLS_REQCERT, + SDAP_USER_SEARCH_BASE, + SDAP_USER_SEARCH_SCOPE, + SDAP_USER_SEARCH_FILTER, + SDAP_USER_EXTRA_ATTRS, + SDAP_GROUP_SEARCH_BASE, + SDAP_GROUP_SEARCH_SCOPE, + SDAP_GROUP_SEARCH_FILTER, + SDAP_HOST_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + SDAP_SUDO_SEARCH_BASE, + SDAP_SUDO_FULL_REFRESH_INTERVAL, + SDAP_SUDO_SMART_REFRESH_INTERVAL, + SDAP_SUDO_RANDOM_OFFSET, + SDAP_SUDO_USE_HOST_FILTER, + SDAP_SUDO_HOSTNAMES, + SDAP_SUDO_IP, + SDAP_SUDO_INCLUDE_NETGROUPS, + SDAP_SUDO_INCLUDE_REGEXP, + SDAP_AUTOFS_SEARCH_BASE, + SDAP_AUTOFS_MAP_MASTER_NAME, + SDAP_IPHOST_SEARCH_BASE, + SDAP_IPNETWORK_SEARCH_BASE, + SDAP_SCHEMA, + SDAP_PWMODIFY_MODE, + SDAP_OFFLINE_TIMEOUT, + SDAP_FORCE_UPPER_CASE_REALM, + SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_ENUM_REFRESH_OFFSET, + SDAP_PURGE_CACHE_TIMEOUT, + SDAP_PURGE_CACHE_OFFSET, + SDAP_TLS_CACERT, + SDAP_TLS_CACERTDIR, + SDAP_TLS_CERT, + SDAP_TLS_KEY, + SDAP_TLS_CIPHER_SUITE, + SDAP_ID_TLS, + SDAP_ID_MAPPING, + SDAP_SASL_MECH, + SDAP_SASL_AUTHID, + SDAP_SASL_REALM, + SDAP_SASL_MINSSF, + SDAP_SASL_MAXSSF, + SDAP_KRB5_KEYTAB, + SDAP_KRB5_KINIT, + SDAP_KRB5_KDC, + SDAP_KRB5_BACKUP_KDC, + SDAP_KRB5_REALM, + SDAP_KRB5_CANONICALIZE, + SDAP_KRB5_USE_KDCINFO, + SDAP_KRB5_KDCINFO_LOOKAHEAD, + SDAP_PWD_POLICY, + SDAP_REFERRALS, + SDAP_ACCOUNT_CACHE_EXPIRATION, + SDAP_DNS_SERVICE_NAME, + SDAP_KRB5_TICKET_LIFETIME, + SDAP_ACCESS_FILTER, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_NESTING_LEVEL, + SDAP_DEREF, + SDAP_ACCOUNT_EXPIRE_POLICY, + SDAP_ACCESS_ORDER, + SDAP_CHPASS_URI, + SDAP_CHPASS_BACKUP_URI, + SDAP_CHPASS_DNS_SERVICE_NAME, + SDAP_CHPASS_UPDATE_LAST_CHANGE, + SDAP_ENUM_SEARCH_TIMEOUT, + SDAP_DISABLE_AUTH_TLS, + SDAP_PAGE_SIZE, + SDAP_DEREF_THRESHOLD, + SDAP_IGNORE_UNREADABLE_REFERENCES, + SDAP_SASL_CANONICALIZE, + SDAP_EXPIRE_TIMEOUT, + SDAP_EXPIRE_OFFSET, + SDAP_IDLE_TIMEOUT, + SDAP_DISABLE_PAGING, + SDAP_IDMAP_LOWER, + SDAP_IDMAP_UPPER, + SDAP_IDMAP_RANGESIZE, + SDAP_IDMAP_AUTORID_COMPAT, + SDAP_IDMAP_DEFAULT_DOMAIN, + SDAP_IDMAP_DEFAULT_DOMAIN_SID, + SDAP_IDMAP_EXTRA_SLICE_INIT, + SDAP_AD_USE_TOKENGROUPS, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS, + SDAP_DISABLE_RANGE_RETRIEVAL, + SDAP_MIN_ID, + SDAP_MAX_ID, + SDAP_PWDLOCKOUT_DN, + SDAP_WILDCARD_LIMIT, + SDAP_LIBRARY_DEBUG_LEVEL, + + SDAP_OPTS_BASIC /* opts counter */ +}; + +enum sdap_gen_attrs { + SDAP_AT_ENTRY_USN = 0, + SDAP_AT_LAST_USN, + + SDAP_AT_GENERAL /* attrs counter */ +}; + +/* the objectclass must be the first attribute. + * Functions depend on this */ +enum sdap_user_attrs { + SDAP_OC_USER = 0, + SDAP_AT_USER_NAME, + SDAP_AT_USER_PWD, + SDAP_AT_USER_UID, + SDAP_AT_USER_GID, + SDAP_AT_USER_GECOS, + SDAP_AT_USER_HOME, + SDAP_AT_USER_SHELL, + SDAP_AT_USER_PRINC, + SDAP_AT_USER_FULLNAME, + SDAP_AT_USER_MEMBEROF, + SDAP_AT_USER_UUID, + SDAP_AT_USER_OBJECTSID, + SDAP_AT_USER_PRIMARY_GROUP, + SDAP_AT_USER_MODSTAMP, + SDAP_AT_USER_USN, + SDAP_AT_SP_LSTCHG, + SDAP_AT_SP_MIN, + SDAP_AT_SP_MAX, + SDAP_AT_SP_WARN, + SDAP_AT_SP_INACT, + SDAP_AT_SP_EXPIRE, + SDAP_AT_SP_FLAG, + SDAP_AT_KP_LASTCHANGE, + SDAP_AT_KP_EXPIRATION, + SDAP_AT_PWD_ATTRIBUTE, + SDAP_AT_AUTH_SVC, + SDAP_AT_AD_ACCOUNT_EXPIRES, + SDAP_AT_AD_USER_ACCOUNT_CONTROL, + SDAP_AT_NS_ACCOUNT_LOCK, + SDAP_AT_AUTHORIZED_HOST, + SDAP_AT_AUTHORIZED_RHOST, + SDAP_AT_NDS_LOGIN_DISABLED, + SDAP_AT_NDS_LOGIN_EXPIRATION_TIME, + SDAP_AT_NDS_LOGIN_ALLOWED_TIME_MAP, + SDAP_AT_USER_SSH_PUBLIC_KEY, + SDAP_AT_USER_AUTH_TYPE, + SDAP_AT_USER_CERT, + SDAP_AT_USER_EMAIL, + SDAP_AT_USER_PASSKEY, + + SDAP_OPTS_USER /* attrs counter */ +}; + +#define SDAP_FIRST_EXTRA_USER_AT SDAP_AT_SP_LSTCHG + +/* the objectclass must be the first attribute. + * Functions depend on this */ +enum sdap_group_attrs { + SDAP_OC_GROUP = 0, + SDAP_OC_GROUP_ALT, + SDAP_AT_GROUP_NAME, + SDAP_AT_GROUP_PWD, + SDAP_AT_GROUP_GID, + SDAP_AT_GROUP_MEMBER, + SDAP_AT_GROUP_UUID, + SDAP_AT_GROUP_OBJECTSID, + SDAP_AT_GROUP_MODSTAMP, + SDAP_AT_GROUP_USN, + SDAP_AT_GROUP_TYPE, + SDAP_AT_GROUP_EXT_MEMBER, + + SDAP_OPTS_GROUP /* attrs counter */ +}; + +enum sdap_netgroup_attrs { + SDAP_OC_NETGROUP = 0, + SDAP_AT_NETGROUP_NAME, + SDAP_AT_NETGROUP_MEMBER, + SDAP_AT_NETGROUP_TRIPLE, + SDAP_AT_NETGROUP_MODSTAMP, + + SDAP_OPTS_NETGROUP /* attrs counter */ +}; + +enum sdap_sudorule_attrs { + SDAP_OC_SUDORULE = 0, + SDAP_AT_SUDO_OC, + SDAP_AT_SUDO_NAME, + SDAP_AT_SUDO_COMMAND, + SDAP_AT_SUDO_HOST, + SDAP_AT_SUDO_USER, + SDAP_AT_SUDO_OPTION, + SDAP_AT_SUDO_RUNAS, + SDAP_AT_SUDO_RUNASUSER, + SDAP_AT_SUDO_RUNASGROUP, + SDAP_AT_SUDO_NOTBEFORE, + SDAP_AT_SUDO_NOTAFTER, + SDAP_AT_SUDO_ORDER, + SDAP_AT_SUDO_USN, + + SDAP_OPTS_SUDO /* attrs counter */ +}; + +enum sdap_host_attrs { + SDAP_OC_HOST = 0, + SDAP_AT_HOST_NAME, + SDAP_AT_HOST_FQDN, + SDAP_AT_HOST_SERVERHOSTNAME, + SDAP_AT_HOST_MEMBER_OF, + SDAP_AT_HOST_SSH_PUBLIC_KEY, + SDAP_AT_HOST_UUID, + + SDAP_OPTS_HOST /* attrs counter */ +}; + +enum sdap_service_attrs { + SDAP_OC_SERVICE = 0, + SDAP_AT_SERVICE_NAME, + SDAP_AT_SERVICE_PORT, + SDAP_AT_SERVICE_PROTOCOL, + SDAP_AT_SERVICE_USN, + SDAP_OPTS_SERVICES /* attrs counter */ +}; + +enum sdap_iphost_entry_attrs { + SDAP_OC_IPHOST = 0, + SDAP_AT_IPHOST_NAME, + SDAP_AT_IPHOST_NUMBER, + SDAP_AT_IPHOST_USN, + + SDAP_OPTS_IPHOST /* attrs counter */ +}; + +enum sdap_ipnetwork_entry_attrs { + SDAP_OC_IPNETWORK = 0, + SDAP_AT_IPNETWORK_NAME, + SDAP_AT_IPNETWORK_NUMBER, + SDAP_AT_IPNETWORK_USN, + + SDAP_OPTS_IPNETWORK /* attrs counter */ +}; + +#ifdef BUILD_SUBID +enum sdap_subid_range_attrs { + SDAP_OC_SUBID_RANGE = 0, + SDAP_AT_SUBID_RANGE_UID_COUNT, + SDAP_AT_SUBID_RANGE_GID_COUNT, + SDAP_AT_SUBID_RANGE_UID_NUMBER, + SDAP_AT_SUBID_RANGE_GID_NUMBER, + SDAP_AT_SUBID_RANGE_OWNER, + + SDAP_OPTS_SUBID_RANGE /* attrs counter */ +}; +#endif + +enum sdap_autofs_map_attrs { + SDAP_OC_AUTOFS_MAP, + SDAP_AT_AUTOFS_MAP_NAME, + + SDAP_OPTS_AUTOFS_MAP /* attrs counter */ +}; + +enum sdap_autofs_entry_attrs { + SDAP_OC_AUTOFS_ENTRY, + SDAP_AT_AUTOFS_ENTRY_KEY, + SDAP_AT_AUTOFS_ENTRY_VALUE, + + SDAP_OPTS_AUTOFS_ENTRY /* attrs counter */ +}; + +struct sdap_attr_map { + const char *opt_name; + const char *def_name; + const char *sys_name; + char *name; +}; +#define SDAP_ATTR_MAP_TERMINATOR { NULL, NULL, NULL, NULL } + +struct sdap_search_base { + const char *basedn; + struct ldb_context *ldb; + struct ldb_dn *ldb_basedn; + int scope; + const char *filter; +}; + +errno_t +sdap_create_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *unparsed_base, + int scope, + const char *filter, + struct sdap_search_base **_base); + +/* Values from + * http://msdn.microsoft.com/en-us/library/cc223272%28v=prot.13%29.aspx + */ +enum dc_functional_level { + DS_BEHAVIOR_WIN2000 = 0, + DS_BEHAVIOR_WIN2003 = 2, + DS_BEHAVIOR_WIN2008 = 3, + DS_BEHAVIOR_WIN2008R2 = 4, + DS_BEHAVIOR_WIN2012 = 5, + DS_BEHAVIOR_WIN2012R2 = 6, + DS_BEHAVIOR_WIN2016 = 7, +}; + +struct sdap_domain { + struct sss_domain_info *dom; + + char *basedn; + + struct sdap_search_base **search_bases; + struct sdap_search_base **user_search_bases; + struct sdap_search_base **group_search_bases; + struct sdap_search_base **netgroup_search_bases; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **sudo_search_bases; + struct sdap_search_base **service_search_bases; + struct sdap_search_base **iphost_search_bases; + struct sdap_search_base **ipnetwork_search_bases; + struct sdap_search_base **autofs_search_bases; + struct sdap_search_base **ignore_user_search_bases; +#ifdef BUILD_SUBID + struct sdap_search_base **subid_ranges_search_bases; +#endif + + struct sdap_domain *next, *prev; + /* Need to modify the list from a talloc destructor */ + struct sdap_domain **head; + + void *pvt; +}; + +typedef struct tevent_req * +(*ext_member_send_fn_t)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt); +typedef errno_t +(*ext_member_recv_fn_t)(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member); + +struct sdap_ext_member_ctx { + /* Typically ID context of the external ID provider */ + void *pvt; + + ext_member_send_fn_t ext_member_resolve_send; + ext_member_recv_fn_t ext_member_resolve_recv; +}; + +struct sdap_certmap_ctx; + +struct sdap_options { + struct dp_option *basic; + struct data_provider *dp; + struct sdap_attr_map *gen_map; + struct sdap_attr_map *user_map; + size_t user_map_cnt; + struct sdap_attr_map *group_map; + struct sdap_attr_map *netgroup_map; + struct sdap_attr_map *host_map; + struct sdap_attr_map *service_map; + struct sdap_attr_map *iphost_map; + struct sdap_attr_map *ipnetwork_map; +#ifdef BUILD_SUBID + struct sdap_attr_map *subid_map; +#endif + + /* ID-mapping support */ + struct sdap_idmap_ctx *idmap_ctx; + + /* Resolving external members */ + struct sdap_ext_member_ctx *ext_ctx; + + /* FIXME - should this go to a special struct to avoid mixing with name-service-switch maps? */ + struct sdap_attr_map *sudorule_map; + struct sdap_attr_map *autofs_mobject_map; + struct sdap_attr_map *autofs_entry_map; + + /* supported schema types */ + enum schema_type { + SDAP_SCHEMA_RFC2307 = 1, /* memberUid = uid */ + SDAP_SCHEMA_RFC2307BIS = 2, /* member = dn */ + SDAP_SCHEMA_IPA_V1 = 3, /* member/memberof */ + SDAP_SCHEMA_AD = 4 /* AD's member/memberof */ + } schema_type; + + /* password modify mode */ + enum pwmodify_mode { + SDAP_PWMODIFY_EXOP = 1, /* pwmodify extended operation */ + SDAP_PWMODIFY_LDAP = 2 /* ldap_modify of userPassword */ + } pwmodify_mode; + + /* The search bases for the domain or its subdomain */ + struct sdap_domain *sdom; + + /* The options below are normally only used with AD */ + bool support_matching_rule; + enum dc_functional_level dc_functional_level; + const char *schema_basedn; + bool allow_remote_domain_local_groups; + + /* Certificate mapping support */ + struct sdap_certmap_ctx *sdap_certmap_ctx; +}; + +struct sdap_server_opts { + char *server_id; + bool supports_usn; + unsigned long last_usn; + char *max_user_value; + char *max_group_value; + char *max_service_value; + char *max_sudo_value; + char *max_iphost_value; + char *max_ipnetwork_value; +}; + +struct sdap_id_ctx; + +struct sdap_attr_map_info { + struct sdap_attr_map *map; + int num_attrs; +}; + +struct sdap_deref_attrs { + struct sdap_attr_map *map; + struct sysdb_attrs *attrs; +}; + +errno_t sdap_copy_map_entry(const struct sdap_attr_map *src_map, + struct sdap_attr_map *dst_map, + int entry_index); + +int sdap_copy_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + int num_entries, + struct sdap_attr_map **_map); + +/** + * @brief Add attributes to a map + * + * sdap_extend_map() will call talloc_realloc() on the second argument so the + * original storage location might change. The return value _map will always + * contain the current memory location which can be used with talloc_free() + * even if there is an error. + * + * @param[in] memctx Talloc memory context + * @param[in] src_map Original map, should not be accessed anymore + * @param[in] num_entries Number of entries in the original map + * @param[in] extra_attrs NULL-terminated array of extra attribute pairs + * sysdb_attr:ldap_attr + * @param[out] _map New map + * @param[out] _new_size Number of entries in the new map + * + * @return + * - EOK success + * - ENOMEM memory allocation failed + * - ERR_DUP_EXTRA_ATTR sysdb attribute is already used + */ +int sdap_extend_map(TALLOC_CTX *memctx, + struct sdap_attr_map *src_map, + size_t num_entries, + char **extra_attrs, + struct sdap_attr_map **_map, + size_t *_new_size); + +int sdap_extend_map_with_list(TALLOC_CTX *mem_ctx, + const struct sdap_options *opts, + int extra_attr_index, + struct sdap_attr_map *src_map, + size_t num_entries, + struct sdap_attr_map **_map, + size_t *_new_size); + +void sdap_inherit_options(char **inherit_opt_list, + struct sdap_options *parent_sdap_opts, + struct sdap_options *child_sdap_opts); + +int sdap_get_map(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_attr_map *def_map, + int num_entries, + struct sdap_attr_map **_map); + +int sdap_parse_entry(TALLOC_CTX *memctx, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sdap_attr_map *map, int attrs_num, + struct sysdb_attrs **_attrs, + bool disable_range_retrieval); + +errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, + struct sdap_attr_map_info *minfo, + size_t num_maps, + LDAPDerefRes *dref, + struct sdap_deref_attrs ***_deref_res); + +void setup_ldap_debug(struct dp_option *basic_opts); + +errno_t setup_tls_config(struct dp_option *basic_opts); + +int sdap_set_rootdse_supported_lists(struct sysdb_attrs *rootdse, + struct sdap_handle *sh); +bool sdap_check_sup_list(struct sup_list *l, const char *val); + +#define sdap_is_sasl_mech_supported(sh, sasl_mech) \ + sdap_check_sup_list(&((sh)->supported_saslmechs), sasl_mech) + +#define sdap_is_control_supported(sh, ctrl_oid) \ + sdap_check_sup_list(&((sh)->supported_controls), ctrl_oid) + +#define sdap_is_extension_supported(sh, ext_oid) \ + sdap_check_sup_list(&((sh)->supported_extensions), ext_oid) + +bool sdap_sasl_mech_needs_kinit(const char *mech); + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, + const char **filter, + const char ***_attrs, + size_t *attr_count); + +int sdap_control_create(struct sdap_handle *sh, const char *oid, int iscritical, + struct berval *value, int dupval, LDAPControl **ctrlp); + +int sdap_replace_id(struct sysdb_attrs *entry, const char *attr, id_t val); + +errno_t sdap_get_group_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_group_name); + +errno_t sdap_get_user_primary_name(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + struct sss_domain_info *dom, + const char **_user_name); + +errno_t sdap_get_netgroup_primary_name(struct sdap_options *opts, + struct sysdb_attrs *attrs, + const char **_netgroup_name); + +errno_t sdap_get_primary_name(const char *attr_name, + struct sysdb_attrs *attrs, + const char **_primary_name); + +errno_t sdap_get_primary_name_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + char ***name_list); + +errno_t sdap_get_primary_fqdn_list(struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attr_list, + size_t attr_count, + const char *ldap_attr, + const char *sid_attr, + struct sdap_idmap_ctx *idmap_ctx, + char ***name_list); + +errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_domain *sdom); +int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx, + const char *server, + struct sysdb_attrs *rootdse, + struct sdap_options *opts, + struct sdap_server_opts **srv_opts); +void sdap_steal_server_opts(struct sdap_id_ctx *id_ctx, + struct sdap_server_opts **srv_opts); + +char *sdap_make_oc_list(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map); + +size_t sdap_steal_objects_in_dom(struct sdap_options *opts, + struct sysdb_attrs **dom_objects, + size_t offset, + struct sss_domain_info *dom, + struct sysdb_attrs **all_objects, + size_t count, + bool filter); + +struct sss_domain_info *sdap_get_object_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom); + +bool sdap_object_in_domain(struct sdap_options *opts, + struct sysdb_attrs *obj, + struct sss_domain_info *dom); + +void sdap_domain_copy_search_bases(struct sdap_domain *to, + struct sdap_domain *from); + +#endif /* _SDAP_H_ */ 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; +} diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h new file mode 100644 index 0000000..2a13a9f --- /dev/null +++ b/src/providers/ldap/sdap_access.h @@ -0,0 +1,114 @@ +/* + SSSD + + sdap_access.h + + 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/>. +*/ + +#ifndef SDAP_ACCESS_H_ +#define SDAP_ACCESS_H_ + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_id_op.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_ACESS_LOCKOUT_DURATION "pwdLockoutDuration" +#define SYSDB_LDAP_ACCESS_LOCKOUT "pwdLockout" + +#define LDAP_ACCESS_FILTER_NAME "filter" +#define LDAP_ACCESS_EXPIRE_NAME "expire" +#define LDAP_ACCESS_EXPIRE_POLICY_REJECT_NAME "pwd_expire_policy_reject" +#define LDAP_ACCESS_EXPIRE_POLICY_WARN_NAME "pwd_expire_policy_warn" +#define LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME "pwd_expire_policy_renew" +#define LDAP_ACCESS_SERVICE_NAME "authorized_service" +#define LDAP_ACCESS_HOST_NAME "host" +#define LDAP_ACCESS_RHOST_NAME "rhost" +#define LDAP_ACCESS_LOCK_NAME "lockout" +#define LDAP_ACCESS_PPOLICY_NAME "ppolicy" + +#define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow" +#define LDAP_ACCOUNT_EXPIRE_AD "ad" +#define LDAP_ACCOUNT_EXPIRE_RHDS "rhds" +#define LDAP_ACCOUNT_EXPIRE_IPA "ipa" +#define LDAP_ACCOUNT_EXPIRE_389DS "389ds" +#define LDAP_ACCOUNT_EXPIRE_NDS "nds" + +enum ldap_access_rule { + LDAP_ACCESS_EMPTY = -1, + LDAP_ACCESS_FILTER = 0, + LDAP_ACCESS_EXPIRE, + LDAP_ACCESS_SERVICE, + LDAP_ACCESS_HOST, + LDAP_ACCESS_RHOST, + LDAP_ACCESS_LOCKOUT, + LDAP_ACCESS_EXPIRE_POLICY_REJECT, + LDAP_ACCESS_EXPIRE_POLICY_WARN, + LDAP_ACCESS_EXPIRE_POLICY_RENEW, + LDAP_ACCESS_PPOLICY, + LDAP_ACCESS_LAST +}; + +enum sdap_access_type { + SDAP_TYPE_LDAP, + SDAP_TYPE_IPA +}; + +struct sdap_access_ctx { + enum sdap_access_type type; + struct sdap_id_ctx *id_ctx; + const char *filter; + int access_rule[LDAP_ACCESS_LAST + 1]; +}; + +struct tevent_req * +sdap_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +sdap_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +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 sdap_access_recv(struct tevent_req *req); + +/* Set the access rules based on ldap_access_order */ +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); + +#endif /* SDAP_ACCESS_H_ */ diff --git a/src/providers/ldap/sdap_ad_groups.c b/src/providers/ldap/sdap_ad_groups.c new file mode 100644 index 0000000..e8c6280 --- /dev/null +++ b/src/providers/ldap/sdap_ad_groups.c @@ -0,0 +1,69 @@ +/* + SSSD + + AD groups helper routines + + Authors: + Lukas Slebodnik <lslebodn@redhat.com> + + Copyright (C) 2013 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 "db/sysdb.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async_private.h" + +/* ==Group-Parsing Routines=============================================== */ + +errno_t sdap_check_ad_group_type(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + const char *group_name, + bool *_need_filter) +{ + int32_t ad_group_type; + errno_t ret = EOK; + *_need_filter = false; + + if (opts->schema_type == SDAP_SCHEMA_AD + && !opts->allow_remote_domain_local_groups) { + ret = sysdb_attrs_get_int32_t(group_attrs, SYSDB_GROUP_TYPE, + &ad_group_type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_int32_t failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, + "AD group [%s] has type flags %#x.\n", + group_name, ad_group_type); + + /* Only security groups from AD are considered for POSIX groups. + * Additionally only global and universal group are taken to account + * for trusted domains. */ + if (!(ad_group_type & SDAP_AD_GROUP_TYPE_SECURITY) + || (IS_SUBDOMAIN(dom) + && (!((ad_group_type & SDAP_AD_GROUP_TYPE_GLOBAL) + || (ad_group_type & SDAP_AD_GROUP_TYPE_UNIVERSAL))))) { + DEBUG(SSSDBG_TRACE_FUNC, + "Filtering AD group [%s].\n", group_name); + + *_need_filter = true; + } + } + + return ret; +} diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c new file mode 100644 index 0000000..ab3572d --- /dev/null +++ b/src/providers/ldap/sdap_async.c @@ -0,0 +1,3169 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009 + + 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 <ctype.h> +#include "util/util.h" +#include "util/strtonum.h" +#include "util/probes.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async_private.h" + +#define REPLY_REALLOC_INCREMENT 10 + +struct sdap_op { + struct sdap_op *prev, *next; + struct sdap_handle *sh; + uint64_t chain_id; + + int msgid; + char *stat_info; + uint64_t start_time; + int timeout; + bool done; + + sdap_op_callback_t *callback; + void *data; + + struct tevent_context *ev; + struct sdap_msg *list; + struct sdap_msg *last; +}; + +int sdap_op_get_msgid(struct sdap_op *op) +{ + return op != NULL ? op->msgid : 0; +} + +/* ==LDAP-Memory-Handling================================================= */ + +static int lmsg_destructor(void *mem) +{ + ldap_msgfree((LDAPMessage *)mem); + return 0; +} + +/* ==sdap-handle-utility-functions======================================== */ + +static inline void sdap_handle_release(struct sdap_handle *sh); +static int sdap_handle_destructor(void *mem); + +struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx) +{ + struct sdap_handle *sh; + + sh = talloc_zero(memctx, struct sdap_handle); + if (!sh) return NULL; + + talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor); + + return sh; +} + +static int sdap_handle_destructor(void *mem) +{ + struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle); + + /* if the structure is currently locked, then mark it to be released + * and prevent talloc from freeing the memory */ + if (sh->destructor_lock) { + sh->release_memory = true; + return -1; + } + + sdap_handle_release(sh); + return 0; +} + +static void sdap_call_op_callback(struct sdap_op *op, struct sdap_msg *reply, + int error) +{ + uint64_t time_spend; + const char *info = (op->stat_info == NULL ? "-" : op->stat_info); + + if (op->start_time != 0) { + time_spend = get_spend_time_us(op->start_time); + DEBUG(SSSDBG_PERF_STAT, + "Handling LDAP operation [%d][%s] took %s.\n", + op->msgid, info, sss_format_time(time_spend)); + + /* time_spend is in us and timeout in s */ + if (op->timeout != 0 && (time_spend / op->timeout) >= (80 * 10000)) { + DEBUG(SSSDBG_IMPORTANT_INFO, "LDAP operation [%d][%s] seems slow, " + "took more than 80%% of timeout [%d].\n", + op->msgid, info, op->timeout); + } + + /* Avoid multiple outputs for the same operation if multiple results + * are returned */ + op->start_time = 0; + } + + op->callback(op, reply, error, op->data); +} + +static void sdap_handle_release(struct sdap_handle *sh) +{ + struct sdap_op *op; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], ldap[%p], " + "destructor_lock[%d], release_memory[%d]\n", + sh, (int)sh->connected, sh->ops, sh->ldap, + (int)sh->destructor_lock, (int)sh->release_memory); + + if (sh->destructor_lock) return; + sh->destructor_lock = true; + + /* make sure nobody tries to reuse this connection from now on */ + sh->connected = false; + + remove_ldap_connection_callbacks(sh); + + while (sh->ops) { + op = sh->ops; + sdap_call_op_callback(op, NULL, EIO); + /* calling the callback may result in freeing the op */ + /* check if it is still the same or avoid freeing */ + if (op == sh->ops) talloc_free(op); + } + + if (sh->ldap) { + ldap_unbind_ext(sh->ldap, NULL, NULL); + sh->ldap = NULL; + } + + /* ok, we have done the job, unlock now */ + sh->destructor_lock = false; + + /* finally if a destructor was ever called, free sh before + * exiting */ + if (sh->release_memory) { + /* neutralize the destructor as we already handled + * all was needed to be released */ + talloc_set_destructor((TALLOC_CTX *)sh, NULL); + talloc_free(sh); + } +} + +/* ==Parse-Results-And-Handle-Disconnections============================== */ +static void sdap_process_message(struct tevent_context *ev, + struct sdap_handle *sh, LDAPMessage *msg); +static void sdap_process_result(struct tevent_context *ev, void *pvt); +static void sdap_process_next_reply(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *pvt) +{ + sdap_process_result(ev, pvt); +} + +static void sdap_ldap_next_result(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + sdap_process_result(ev, pvt); +} + +static struct sdap_op *sdap_get_message_op(struct sdap_handle *sh, + LDAPMessage *msg) +{ + struct sdap_op *op; + int msgid; + + msgid = ldap_msgid(msg); + if (msgid == -1) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid message id!\n"); + return NULL; + } + + for (op = sh->ops; op; op = op->next) { + if (op->msgid == msgid) { + return op; + } + } + + return NULL; +} + +static void sdap_process_result(struct tevent_context *ev, void *pvt) +{ + struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle); + uint64_t old_chain_id; + struct timeval no_timeout = {0, 0}; + struct tevent_timer *te; + struct sdap_op *op; + LDAPMessage *msg; + int ret; + + /* This is a top level event, always use chain id 0. We set a proper id + * later in this function once we can match the reply with an operation. */ + old_chain_id = sss_chain_id_set(0); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->ldap); + + if (!sh->connected || !sh->ldap) { + DEBUG(SSSDBG_OP_FAILURE, "ERROR: LDAP connection is not connected!\n"); + sdap_handle_release(sh); + return; + } + + ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg); + if (ret == 0) { + /* this almost always means we have reached the end of + * the list of received messages */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Trace: end of ldap_result list\n"); + return; + } + + if (ret == -1) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &ret); + DEBUG(SSSDBG_OP_FAILURE, + "ldap_result error: [%s]\n", ldap_err2string(ret)); + sdap_handle_release(sh); + return; + } + + /* We don't know if this will be the last result. + * + * important: we must do this before actually processing the message + * because the message processing might even free the sdap_handler + * so it must be the last operation. + * FIXME: use tevent_immediate/tevent_queues, when available */ + memset(&no_timeout, 0, sizeof(struct timeval)); + + te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh); + if (!te) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add critical timer to fetch next result!\n"); + } + + /* Set the chain id if we can match the operation. */ + op = sdap_get_message_op(sh, msg); + if (op != NULL) { + sss_chain_id_set(op->chain_id); + } + + /* now process this message */ + sdap_process_message(ev, sh, msg); + + /* Restore the chain id. */ + sss_chain_id_set(old_chain_id); +} + +static const char *sdap_ldap_result_str(int msgtype) +{ + switch (msgtype) { + case LDAP_RES_BIND: + return "LDAP_RES_BIND"; + + case LDAP_RES_SEARCH_ENTRY: + return "LDAP_RES_SEARCH_ENTRY"; + + case LDAP_RES_SEARCH_REFERENCE: + return "LDAP_RES_SEARCH_REFERENCE"; + + case LDAP_RES_SEARCH_RESULT: + return "LDAP_RES_SEARCH_RESULT"; + + case LDAP_RES_MODIFY: + return "LDAP_RES_MODIFY"; + + case LDAP_RES_ADD: + return "LDAP_RES_ADD"; + + case LDAP_RES_DELETE: + return "LDAP_RES_DELETE"; + + case LDAP_RES_MODDN: + /* These are the same result + case LDAP_RES_MODRDN: + case LDAP_RES_RENAME: + */ + return "LDAP_RES_RENAME"; + + case LDAP_RES_COMPARE: + return "LDAP_RES_COMPARE"; + + case LDAP_RES_EXTENDED: + return "LDAP_RES_EXTENDED"; + + case LDAP_RES_INTERMEDIATE: + return "LDAP_RES_INTERMEDIATE"; + + case LDAP_RES_ANY: + return "LDAP_RES_ANY"; + + case LDAP_RES_UNSOLICITED: + return "LDAP_RES_UNSOLICITED"; + + default: + /* Unmatched, fall through */ + break; + } + + /* Unknown result type */ + return "Unknown result type!"; +} + +/* process a message calling the right operation callback. + * msg is completely taken care of (including freeing it) + * NOTE: this function may even end up freeing the sdap_handle + * so sdap_handle must not be used after this function is called + */ +static void sdap_process_message(struct tevent_context *ev, + struct sdap_handle *sh, LDAPMessage *msg) +{ + struct sdap_msg *reply; + struct sdap_op *op; + int msgtype; + int ret; + + msgtype = ldap_msgtype(msg); + + op = sdap_get_message_op(sh, msg); + if (op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unmatched msgid, discarding message (type: %0x)\n", + msgtype); + ldap_msgfree(msg); + return; + } + + /* shouldn't happen */ + if (op->done) { + DEBUG(SSSDBG_OP_FAILURE, + "Operation [%p] already handled (type: %0x)\n", op, msgtype); + ldap_msgfree(msg); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Message type: [%s]\n", sdap_ldap_result_str(msgtype)); + + switch (msgtype) { + case LDAP_RES_SEARCH_ENTRY: + case LDAP_RES_SEARCH_REFERENCE: + /* go and process entry */ + break; + + case LDAP_RES_BIND: + case LDAP_RES_SEARCH_RESULT: + case LDAP_RES_MODIFY: + case LDAP_RES_ADD: + case LDAP_RES_DELETE: + case LDAP_RES_MODDN: + case LDAP_RES_COMPARE: + case LDAP_RES_EXTENDED: + case LDAP_RES_INTERMEDIATE: + /* no more results expected with this msgid */ + op->done = true; + break; + + default: + /* unknown msg type?? */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Couldn't figure out the msg type! [%0x]\n", msgtype); + ldap_msgfree(msg); + return; + } + + reply = talloc_zero(op, struct sdap_msg); + if (!reply) { + ldap_msgfree(msg); + ret = ENOMEM; + } else { + reply->msg = msg; + ret = sss_mem_attach(reply, msg, lmsg_destructor); + if (ret != EOK) { + ldap_msgfree(msg); + talloc_zfree(reply); + } + } + + if (op->list) { + /* list exist, queue it */ + + op->last->next = reply; + op->last = reply; + + } else { + /* create list, then call callback */ + op->list = op->last = reply; + + /* must be the last operation as it may end up freeing all memory + * including all ops handlers */ + sdap_call_op_callback(op, reply, ret); + } +} + +static void sdap_unlock_next_reply(struct sdap_op *op) +{ + struct timeval tv; + struct tevent_timer *te; + struct sdap_msg *next_reply; + + if (op->list) { + next_reply = op->list->next; + /* get rid of the previous reply, it has been processed already */ + talloc_zfree(op->list); + op->list = next_reply; + } + + /* if there are still replies to parse, queue a new operation */ + if (op->list) { + /* use a very small timeout, so that fd operations have a chance to be + * served while processing a long reply */ + tv = tevent_timeval_current(); + + /* wait 5 microsecond */ + tv.tv_usec += 5; + tv.tv_sec += tv.tv_usec / 1000000; + tv.tv_usec = tv.tv_usec % 1000000; + + te = tevent_add_timer(op->ev, op, tv, + sdap_process_next_reply, op); + if (!te) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add critical timer for next reply!\n"); + sdap_call_op_callback(op, NULL, EFAULT); + } + } +} + +static void sdap_process_next_reply(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct sdap_op *op = talloc_get_type(pvt, struct sdap_op); + + sdap_call_op_callback(op, op->list, EOK); +} + +/* ==LDAP-Operations-Helpers============================================== */ + +static int sdap_op_destructor(void *mem) +{ + struct sdap_op *op = (struct sdap_op *)mem; + + DLIST_REMOVE(op->sh->ops, op); + + if (op->done) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Operation %d finished\n", op->msgid); + return 0; + } + + /* we don't check the result here, if a message was really abandoned, + * hopefully the server will get an abandon. + * If the operation was already fully completed, this is going to be + * just a noop */ + DEBUG(SSSDBG_TRACE_LIBS, "Abandoning operation %d\n", op->msgid); + ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL); + + return 0; +} + +static void sdap_op_timeout(struct tevent_req *req) +{ + struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op); + + /* should never happen, but just in case */ + if (op->done) { + DEBUG(SSSDBG_OP_FAILURE, "Timeout happened after op was finished !?\n"); + return; + } + + /* signal the caller that we have a timeout */ + DEBUG(SSSDBG_TRACE_LIBS, "Issuing timeout [ldap_opt_timeout] for message id %d\n", op->msgid); + sdap_call_op_callback(op, NULL, ETIMEDOUT); +} + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, const char *stat_info, + sdap_op_callback_t *callback, void *data, + int timeout, struct sdap_op **_op) +{ + struct sdap_op *op; + + op = talloc_zero(memctx, struct sdap_op); + if (!op) return ENOMEM; + + op->start_time = get_start_time(); + op->timeout = timeout; + op->sh = sh; + op->msgid = msgid; + if (stat_info != NULL) { + op->stat_info = talloc_strdup(op, stat_info); + if (op->stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy stat_info, ignored.\n"); + } + } + op->callback = callback; + op->data = data; + op->ev = ev; + op->chain_id = sss_chain_id_get(); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "New operation %d timeout %d\n", op->msgid, timeout); + + /* check if we need to set a timeout */ + if (timeout) { + struct tevent_req *req; + struct timeval tv; + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, timeout, 0); + + /* allocate on op, so when it get freed the timeout is removed */ + req = tevent_wakeup_send(op, ev, tv); + if (!req) { + talloc_zfree(op); + return ENOMEM; + } + tevent_req_set_callback(req, sdap_op_timeout, op); + } + + DLIST_ADD(sh->ops, op); + + talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor); + + *_op = op; + return EOK; +} + +/* ==Modify-Password====================================================== */ + +static errno_t +sdap_chpass_result(TALLOC_CTX *mem_ctx, + int ldap_result, + const char *ldap_msg, + char **_user_msg) +{ + errno_t ret; + + switch (ldap_result) { + case LDAP_SUCCESS: + /* There's no need to set _user_msg here. */ + return EOK; + case LDAP_CONSTRAINT_VIOLATION: + if (ldap_msg == NULL || *ldap_msg == '\0') { + ldap_msg = "Please make sure the password " + "meets the complexity constraints."; + } + ret = ERR_CHPASS_DENIED; + break; + default: + ret = ERR_NETWORK_IO; + } + + if (ldap_msg != NULL) { + *_user_msg = talloc_strdup(mem_ctx, ldap_msg); + if (*_user_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + } + + return ret; +} + +struct sdap_exop_modify_passwd_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + char *user_error_message; +}; + +static void sdap_exop_modify_passwd_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + char *user_dn, + const char *password, + const char *new_password, + int timeout) +{ + struct tevent_req *req = NULL; + struct sdap_exop_modify_passwd_state *state; + int ret; + BerElement *ber = NULL; + struct berval *bv = NULL; + int msgid; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + char *stat_info; + + req = tevent_req_create(memctx, &state, + struct sdap_exop_modify_passwd_state); + if (!req) return NULL; + + state->sh = sh; + state->user_error_message = NULL; + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "ber_alloc_t failed.\n"); + talloc_zfree(req); + return NULL; + } + + ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, + user_dn, + LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password, + LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + talloc_zfree(req); + return NULL; + } + + ret = ber_flatten(ber, &bv); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + talloc_zfree(req); + return NULL; + } + + ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create " + "Password Policy control.\n"); + ret = ERR_INTERNAL; + goto fail; + } + request_controls = ctrls; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing extended operation\n"); + + ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD, + bv, request_controls, NULL, &msgid); + ber_bvfree(bv); + if (ctrls[0]) ldap_control_free(ctrls[0]); + if (ret == -1 || msgid == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_extended_operation failed\n"); + ret = ERR_NETWORK_IO; + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "ldap_extended_operation sent, msgid = %d\n", msgid); + + stat_info = talloc_asprintf(state, "server: [%s] modify passwd dn: [%s]", + sdap_get_server_peer_str_safe(state->sh), + user_dn); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, ev, state->sh, msgid, stat_info, + sdap_exop_modify_passwd_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + ret = ERR_INTERNAL; + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_exop_modify_passwd_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, + struct sdap_exop_modify_passwd_state); + char *errmsg = NULL; + int ret; + LDAPControl **response_controls = NULL; + int c; + ber_int_t pp_grace; + ber_int_t pp_expire; + LDAPPasswordPolicyError pp_error; + int result; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, NULL, + &response_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", state->op->msgid); + ret = ERR_INTERNAL; + goto done; + } + + if (response_controls == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Server returned no controls.\n"); + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Server returned control [%s].\n", + response_controls[c]->ldctl_oid); + if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) { + ret = ldap_parse_passwordpolicy_control(state->sh->ldap, + response_controls[c], + &pp_expire, &pp_grace, + &pp_error); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_parse_passwordpolicy_control failed.\n"); + ret = ERR_NETWORK_IO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error)); + } + } + } + + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_extended_operation result: %s(%d), %s\n", + sss_ldap_err2string(result), result, errmsg); + + ret = sdap_chpass_result(state, result, errmsg, &state->user_error_message); + +done: + ldap_controls_free(response_controls); + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **user_error_message) +{ + struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, + struct sdap_exop_modify_passwd_state); + + /* We want to return the error message even on failure */ + *user_error_message = talloc_steal(mem_ctx, state->user_error_message); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_modify_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_op *op; + + int ldap_result; + char *ldap_msg; +}; + +static void sdap_modify_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req * +sdap_modify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *dn, + char *attr, + char **values) +{ + struct tevent_req *req; + struct sdap_modify_state *state; + LDAPMod **mods; + errno_t ret; + int msgid; + char *stat_info; + + req = tevent_req_create(mem_ctx, &state, struct sdap_modify_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + + mods = talloc_zero_array(state, LDAPMod *, 2); + if (mods == NULL) { + ret = ENOMEM; + goto done; + } + + mods[0] = talloc_zero(mods, LDAPMod); + if (mods[0] == NULL) { + ret = ENOMEM; + goto done; + } + + mods[0]->mod_op = LDAP_MOD_REPLACE; + mods[0]->mod_type = attr; + mods[0]->mod_vals.modv_strvals = values; + mods[1] = NULL; + + ret = ldap_modify_ext(state->sh->ldap, dn, mods, NULL, NULL, &msgid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_modify_ext() failed [%d]\n", ret); + goto done; + } + + stat_info = talloc_asprintf(state, "server: [%s] modify dn: [%s] attr: [%s]", + sdap_get_server_peer_str_safe(state->sh), dn, + attr); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_modify_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_modify_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req; + struct sdap_modify_state *state; + char *errmsg; + errno_t ret; + int result; + int lret; + + req = talloc_get_type(pvt, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_state); + + if (error) { + tevent_req_error(req, error); + return; + } + + lret = ldap_parse_result(state->sh->ldap, reply->msg, &result, + NULL, &errmsg, NULL, NULL, 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", + state->op->msgid); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "ldap_modify result: %s(%d), %s\n", + sss_ldap_err2string(result), + result, errmsg); + + state->ldap_result = result; + if (errmsg != NULL) { + state->ldap_msg = talloc_strdup(state, errmsg); + if (state->ldap_msg == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_modify_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *_ldap_result, + char **_ldap_msg) +{ + struct sdap_modify_state *state; + + state = tevent_req_data(req, struct sdap_modify_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_ldap_result != NULL) { + *_ldap_result = state->ldap_result; + } + + if (_ldap_msg != NULL) { + *_ldap_msg = talloc_steal(mem_ctx, state->ldap_msg); + } + + return EOK; +} + +struct sdap_modify_passwd_state { + const char *dn; + char *user_msg; +}; + +static void sdap_modify_passwd_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_modify_passwd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + char *attr, + const char *user_dn, + const char *new_password) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_modify_passwd_state *state; + char **values; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_modify_passwd_state); + if (req == NULL) { + return NULL; + } + + state->dn = user_dn; + + values = talloc_zero_array(state, char *, 2); + if (values == NULL) { + ret = ENOMEM; + goto done; + } + + values[0] = talloc_strdup(values, new_password); + if (values[0] == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_modify_send(state, ev, sh, timeout, user_dn, attr, values); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_modify_passwd_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_modify_passwd_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_modify_passwd_state *state; + int ldap_result; + char *ldap_msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_passwd_state); + + ret = sdap_modify_recv(state, subreq, &ldap_result, &ldap_msg); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = sdap_chpass_result(state, ldap_result, ldap_msg, &state->user_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Password change for [%s] was successful\n", + state->dn); + + tevent_req_done(req); +} + +errno_t sdap_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **_user_error_message) +{ + struct sdap_modify_passwd_state *state; + + state = tevent_req_data(req, struct sdap_modify_passwd_state); + + /* We want to return the error message even on failure */ + *_user_error_message = talloc_steal(mem_ctx, state->user_msg); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Update-passwordLastChanged-attribute====================== */ +struct sdap_modify_shadow_lastchange_state { + const char *dn; +}; + +static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *dn, + char *attr) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_modify_shadow_lastchange_state *state; + char **values; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_modify_shadow_lastchange_state); + if (req == NULL) { + return NULL; + } + + state->dn = dn; + values = talloc_zero_array(state, char *, 2); + if (values == NULL) { + ret = ENOMEM; + goto done; + } + + /* The attribute contains number of days since the epoch */ + values[0] = talloc_asprintf(values, "%"SPRItime, time(NULL)/86400); + if (values[0] == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_modify_send(state, ev, sh, 5, dn, attr, values); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_modify_shadow_lastchange_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_modify_shadow_lastchange_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_modify_shadow_lastchange_state); + + ret = sdap_modify_recv(state, subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "shadowLastChange change for [%s] failed [%d]: %s\n", + state->dn, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "shadowLastChange change for [%s] was successful\n", + state->dn); + + tevent_req_done(req); +} + +errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* ==Fetch-RootDSE============================================= */ + +struct sdap_get_rootdse_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + + struct sysdb_attrs *rootdse; +}; + +static void sdap_get_rootdse_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh) +{ + struct tevent_req *req, *subreq; + struct sdap_get_rootdse_state *state; + const char *attrs[] = { + "*", + "altServer", + SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS, + "supportedControl", + "supportedExtension", + "supportedFeatures", + "supportedLDAPVersion", + "supportedSASLMechanisms", + SDAP_ROOTDSE_ATTR_AD_VERSION, + SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT, + SDAP_IPA_LAST_USN, SDAP_AD_LAST_USN, + NULL + }; + + DEBUG(SSSDBG_TRACE_ALL, "Getting rootdse\n"); + + req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->rootdse = NULL; + + subreq = sdap_get_generic_send(state, ev, opts, sh, + "", LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_rootdse_done, req); + + return req; +} + +/* This is not a real attribute, it's just there to avoid + * actually pulling real data down, to save bandwidth + */ +static void sdap_get_rootdse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_rootdse_state *state = tevent_req_data(req, + struct sdap_get_rootdse_state); + struct sysdb_attrs **results; + size_t num_results; + int ret; + + ret = sdap_get_generic_recv(subreq, state, &num_results, &results); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (num_results == 0 || !results) { + DEBUG(SSSDBG_OP_FAILURE, "RootDSE could not be retrieved. " + "Please check that anonymous access to RootDSE is allowed\n" + ); + tevent_req_error(req, ENOENT); + return; + } + + if (num_results > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Multiple replies when searching for RootDSE??\n"); + tevent_req_error(req, EIO); + return; + } + + state->rootdse = talloc_steal(state, results[0]); + talloc_zfree(results); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got rootdse\n"); + + tevent_req_done(req); + return; +} + +int sdap_get_rootdse_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sysdb_attrs **rootdse) +{ + struct sdap_get_rootdse_state *state = tevent_req_data(req, + struct sdap_get_rootdse_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *rootdse = talloc_steal(memctx, state->rootdse); + + return EOK; +} + +/* ==Helpers for parsing replies============================== */ +struct sdap_reply { + size_t reply_max; + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static errno_t add_to_reply(TALLOC_CTX *mem_ctx, + struct sdap_reply *sreply, + struct sysdb_attrs *msg) +{ + if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) { + sreply->reply_max += REPLY_REALLOC_INCREMENT; + sreply->reply = talloc_realloc(mem_ctx, sreply->reply, + struct sysdb_attrs *, + sreply->reply_max); + if (sreply->reply == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); + return ENOMEM; + } + } + + sreply->reply[sreply->reply_count++] = talloc_steal(sreply->reply, msg); + + return EOK; +} + +struct sdap_deref_reply { + size_t reply_max; + size_t reply_count; + struct sdap_deref_attrs **reply; +}; + +static errno_t add_to_deref_reply(TALLOC_CTX *mem_ctx, + int num_maps, + struct sdap_deref_reply *dreply, + struct sdap_deref_attrs **res) +{ + int i; + + if (res == NULL) { + /* Nothing to add, probably ACIs prevented us from dereferencing + * the attribute */ + return EOK; + } + + for (i=0; i < num_maps; i++) { + if (res[i]->attrs == NULL) continue; /* Nothing in this map */ + + if (dreply->reply == NULL || + dreply->reply_max == dreply->reply_count) { + dreply->reply_max += REPLY_REALLOC_INCREMENT; + dreply->reply = talloc_realloc(mem_ctx, dreply->reply, + struct sdap_deref_attrs *, + dreply->reply_max); + if (dreply->reply == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); + return ENOMEM; + } + } + + dreply->reply[dreply->reply_count++] = + talloc_steal(dreply->reply, res[i]); + } + + return EOK; +} + +const char *sdap_get_server_peer_str(struct sdap_handle *sh) +{ + int ret; + int fd; + struct sockaddr sa; + socklen_t sa_len = sizeof(sa); + char ip[NI_MAXHOST]; + static char out[NI_MAXHOST + 8]; + int port; + + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "cannot get sdap fd\n"); + return NULL; + } + + ret = getpeername(fd, &sa, &sa_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + switch (sa.sa_family) { + case AF_INET: { + struct sockaddr_in in; + socklen_t in_len = sizeof(in); + + ret = getpeername(fd, (struct sockaddr *)(&in), &in_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = getnameinfo((struct sockaddr *)(&in), in_len, + ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); + return NULL; + } + + port = ntohs(in.sin_port); + ret = snprintf(out, sizeof(out), "%s:%d", ip, port); + break; + } + case AF_INET6: { + struct sockaddr_in6 in6; + socklen_t in6_len = sizeof(in6); + + ret = getpeername(fd, (struct sockaddr *)(&in6), &in6_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = getnameinfo((struct sockaddr *)(&in6), in6_len, + ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); + return NULL; + } + + port = ntohs(in6.sin6_port); + ret = snprintf(out, sizeof(out), "[%s]:%d", ip, port); + break; + } + case AF_UNIX: { + struct sockaddr_un un; + socklen_t un_len = sizeof(un); + + ret = getpeername(fd, (struct sockaddr *)(&un), &un_len); + if (ret == -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); + return NULL; + } + + ret = snprintf(out, sizeof(out), "%.*s", + (int)strnlen(un.sun_path, un_len - offsetof(struct sockaddr_un, + sun_path)), un.sun_path); + break; + } + default: + return NULL; + } + + if (ret < 0 || ret >= sizeof(out)) { + return NULL; + } + + return out; +} + +const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh) +{ + const char *ip = sdap_get_server_peer_str(sh); + return ip != NULL ? ip : "- IP not available -"; +} + +static void sdap_print_server(struct sdap_handle *sh) +{ + const char *ip; + + /* The purpose of the call is to add the server IP to the debug output if + * debug_level is SSSDBG_TRACE_INTERNAL or higher */ + if (DEBUG_IS_SET(SSSDBG_TRACE_INTERNAL)) { + ip = sdap_get_server_peer_str(sh); + if (ip != NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Searching %s\n", ip); + } else { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_server_peer_str failed.\n"); + } + } +} + +/* ==Generic Search exposing all options======================= */ +typedef errno_t (*sdap_parse_cb)(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); + +struct sdap_get_generic_ext_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *search_base; + int scope; + const char *filter; + const char **attrs; + int timeout; + int sizelimit; + + struct sdap_op *op; + + struct berval cookie; + + LDAPControl **serverctrls; + int nserverctrls; + LDAPControl **clientctrls; + + size_t ref_count; + char **refs; + + sdap_parse_cb parse_cb; + void *cb_data; + + unsigned int flags; +}; + +static errno_t sdap_get_generic_ext_step(struct tevent_req *req); + +static void sdap_get_generic_op_finished(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +enum { + /* Be silent about exceeded size limit */ + SDAP_SRCH_FLG_SIZELIMIT_SILENT = 1 << 0, + + /* Allow paging */ + SDAP_SRCH_FLG_PAGING = 1 << 1, + + /* Only attribute descriptions are requested */ + SDAP_SRCH_FLG_ATTRS_ONLY = 1 << 2, +}; + +static struct tevent_req * +sdap_get_generic_ext_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + sdap_parse_cb parse_cb, + void *cb_data, + unsigned int flags) +{ + errno_t ret; + struct sdap_get_generic_ext_state *state; + struct tevent_req *req; + int i; + LDAPControl *control; + + req = tevent_req_create(memctx, &state, struct sdap_get_generic_ext_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->search_base = search_base; + state->scope = scope; + state->filter = filter; + state->attrs = attrs; + state->op = NULL; + state->sizelimit = sizelimit; + state->timeout = timeout; + state->cookie.bv_len = 0; + state->cookie.bv_val = NULL; + state->parse_cb = parse_cb; + state->cb_data = cb_data; + state->clientctrls = clientctrls; + state->flags = flags; + + if (state->sh == NULL || state->sh->ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP search while not connected.\n"); + tevent_req_error(req, EIO); + tevent_req_post(req, ev); + return req; + } + + sdap_print_server(sh); + + /* Be extra careful and never allow paging for BASE searches, + * even if requested. + */ + if (scope == LDAP_SCOPE_BASE && (flags & SDAP_SRCH_FLG_PAGING)) { + /* Disable paging */ + state->flags &= ~SDAP_SRCH_FLG_PAGING; + DEBUG(SSSDBG_TRACE_FUNC, + "WARNING: Disabling paging because scope is set to base.\n"); + } + + /* Also check for deref/asq requests and force + * paging on for those requests + */ + /* X-DEREF */ + control = ldap_control_find(LDAP_CONTROL_X_DEREF, + serverctrls, + NULL); + if (control) { + state->flags |= SDAP_SRCH_FLG_PAGING; + } + + /* ASQ */ + control = ldap_control_find(LDAP_SERVER_ASQ_OID, + serverctrls, + NULL); + if (control) { + state->flags |= SDAP_SRCH_FLG_PAGING; + } + + for (state->nserverctrls=0; + serverctrls && serverctrls[state->nserverctrls]; + state->nserverctrls++) ; + + /* One extra space for NULL, one for page control */ + state->serverctrls = talloc_array(state, LDAPControl *, + state->nserverctrls+2); + if (!state->serverctrls) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, ev); + return req; + } + + for (i=0; i < state->nserverctrls; i++) { + state->serverctrls[i] = serverctrls[i]; + } + state->serverctrls[i] = NULL; + + PROBE(SDAP_GET_GENERIC_EXT_SEND, state->search_base, + state->scope, state->filter, state->attrs); + + ret = sdap_get_generic_ext_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; + } + + return req; +} + +static errno_t sdap_get_generic_ext_step(struct tevent_req *req) +{ + struct sdap_get_generic_ext_state *state = + tevent_req_data(req, struct sdap_get_generic_ext_state); + char *errmsg; + int lret; + int optret; + errno_t ret; + int msgid; + bool disable_paging; + char *stat_info; + + LDAPControl *page_control = NULL; + + /* Make sure to free any previous operations so + * if we are handling a large number of pages we + * don't waste memory. + */ + talloc_zfree(state->op); + + DEBUG(SSSDBG_TRACE_FUNC, + "calling ldap_search_ext with [%s][%s].\n", + state->filter ? state->filter : "no filter", + state->search_base); + if (state->attrs) { + for (int i = 0; state->attrs[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Requesting attrs: [%s]\n", state->attrs[i]); + } + } + + disable_paging = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_PAGING); + + if (!disable_paging + && (state->flags & SDAP_SRCH_FLG_PAGING) + && sdap_is_control_supported(state->sh, + LDAP_CONTROL_PAGEDRESULTS)) { + lret = ldap_create_page_control(state->sh->ldap, + state->sh->page_size, + state->cookie.bv_val ? + &state->cookie : + NULL, + false, + &page_control); + if (lret != LDAP_SUCCESS) { + ret = EIO; + goto done; + } + state->serverctrls[state->nserverctrls] = page_control; + state->serverctrls[state->nserverctrls+1] = NULL; + } + + lret = ldap_search_ext(state->sh->ldap, state->search_base, + state->scope, state->filter, + discard_const(state->attrs), + (state->flags & SDAP_SRCH_FLG_ATTRS_ONLY), + state->serverctrls, + state->clientctrls, NULL, state->sizelimit, &msgid); + ldap_control_free(page_control); + state->serverctrls[state->nserverctrls] = NULL; + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_search_ext failed: %s\n", sss_ldap_err2string(lret)); + if (lret == LDAP_SERVER_DOWN) { + ret = ETIMEDOUT; + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &errmsg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "Connection error: %s\n", errmsg); + sss_log(SSS_LOG_ERR, "LDAP connection error: %s", errmsg); + } else { + sss_log(SSS_LOG_ERR, "LDAP connection error, %s", + sss_ldap_err2string(lret)); + } + } else if (lret == LDAP_FILTER_ERROR) { + ret = ERR_INVALID_FILTER; + } else { + ret = EIO; + } + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_search_ext called, msgid = %d\n", msgid); + + stat_info = talloc_asprintf(state, "server: [%s] filter: [%s] base: [%s]", + sdap_get_server_peer_str_safe(state->sh), + state->filter, state->search_base); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_get_generic_op_finished, req, + state->timeout, + &state->op); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto done; + } + +done: + return ret; +} + +static errno_t +sdap_get_generic_ext_add_references(struct sdap_get_generic_ext_state *state, + char **refs) +{ + int i; + + if (refs == NULL) { + /* Rare, but it's possible that we might get a reference result with + * no references attached. + */ + return EOK; + } + + for (i = 0; refs[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, "Additional References: %s\n", refs[i]); + } + + /* Extend the size of the ref array */ + state->refs = talloc_realloc(state, state->refs, char *, + state->ref_count + i); + if (state->refs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_realloc failed extending ref_array.\n"); + return ENOMEM; + } + + /* Copy in all the references */ + for (i = 0; refs[i]; i++) { + state->refs[state->ref_count + i] = + talloc_strdup(state->refs, refs[i]); + + if (state->refs[state->ref_count + i] == NULL) { + return ENOMEM; + } + } + + state->ref_count += i; + + return EOK; +} + +static void sdap_get_generic_op_finished(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_generic_ext_state *state = tevent_req_data(req, + struct sdap_get_generic_ext_state); + char *errmsg = NULL; + char **refs = NULL; + int result; + int ret; + int lret; + ber_int_t total_count; + struct berval cookie; + LDAPControl **returned_controls = NULL; + LDAPControl *page_control; + + if (error) { + tevent_req_error(req, error); + return; + } + + switch (ldap_msgtype(reply->msg)) { + case LDAP_RES_SEARCH_REFERENCE: + ret = ldap_parse_reference(state->sh->ldap, reply->msg, + &refs, NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_reference failed (%d)\n", state->op->msgid); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_get_generic_ext_add_references(state, refs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_ext_add_references failed: %s(%d)\n", + sss_strerror(ret), ret); + ldap_memvfree((void **)refs); + tevent_req_error(req, ret); + return; + } + + /* Remove the original strings */ + ldap_memvfree((void **)refs); + + /* unlock the operation so that we can proceed with the next result */ + sdap_unlock_next_reply(state->op); + break; + + case LDAP_RES_SEARCH_ENTRY: + ret = state->parse_cb(state->sh, reply, state->cb_data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "reply parsing callback failed.\n"); + tevent_req_error(req, ret); + return; + } + + sdap_unlock_next_reply(state->op); + break; + + case LDAP_RES_SEARCH_RESULT: + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, &refs, + &returned_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", state->op->msgid); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search result: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + + if (result == LDAP_SIZELIMIT_EXCEEDED + || result == LDAP_ADMINLIMIT_EXCEEDED) { + /* Try to return what we've got */ + + if ( ! (state->flags & SDAP_SRCH_FLG_SIZELIMIT_SILENT)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "LDAP sizelimit was exceeded, " + "returning incomplete data\n"); + } + } else if (result == LDAP_INAPPROPRIATE_MATCHING) { + /* This error should only occur when we're testing for + * specialized functionality like the LDAP matching rule + * filter for Active Directory. Warn at a higher log + * level and return EIO. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "LDAP_INAPPROPRIATE_MATCHING: %s\n", + errmsg ? errmsg : "no errmsg set"); + ldap_memfree(errmsg); + tevent_req_error(req, EIO); + return; + } else if (result == LDAP_UNAVAILABLE_CRITICAL_EXTENSION) { + ldap_memfree(errmsg); + tevent_req_error(req, ENOTSUP); + return; + } else if (result == LDAP_REFERRAL) { + ret = sdap_get_generic_ext_add_references(state, refs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_ext_add_references failed: %s(%d)\n", + sss_strerror(ret), ret); + tevent_req_error(req, ret); + } + /* For referrals, we need to fall through as if it was LDAP_SUCCESS */ + } else if (result != LDAP_SUCCESS && result != LDAP_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected result from ldap: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + ldap_memfree(errmsg); + tevent_req_error(req, EIO); + return; + } + ldap_memfree(errmsg); + + /* Determine if there are more pages to retrieve */ + page_control = ldap_control_find(LDAP_CONTROL_PAGEDRESULTS, + returned_controls, NULL ); + if (!page_control) { + /* No paging support. We are done */ + tevent_req_done(req); + return; + } + + lret = ldap_parse_pageresponse_control(state->sh->ldap, page_control, + &total_count, &cookie); + ldap_controls_free(returned_controls); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine page control\n"); + tevent_req_error(req, EIO); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Total count [%d]\n", total_count); + + if (cookie.bv_val != NULL && cookie.bv_len > 0) { + /* Cookie contains data, which means there are more requests + * to be processed. + */ + talloc_zfree(state->cookie.bv_val); + state->cookie.bv_len = cookie.bv_len; + state->cookie.bv_val = talloc_memdup(state, + cookie.bv_val, + cookie.bv_len); + if (!state->cookie.bv_val) { + tevent_req_error(req, ENOMEM); + return; + } + ber_memfree(cookie.bv_val); + + ret = sdap_get_generic_ext_step(req); + if (ret != EOK) { + tevent_req_error(req, ENOMEM); + return; + } + + return; + } + /* The cookie must be freed even if len == 0 */ + ber_memfree(cookie.bv_val); + + /* This was the last page. We're done */ + + tevent_req_done(req); + return; + + default: + /* what is going on here !? */ + tevent_req_error(req, EIO); + return; + } +} + +static int +sdap_get_generic_ext_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *ref_count, + char ***refs) +{ + struct sdap_get_generic_ext_state *state = + tevent_req_data(req, struct sdap_get_generic_ext_state); + + PROBE(SDAP_GET_GENERIC_EXT_RECV, state->search_base, + state->scope, state->filter); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (ref_count) { + *ref_count = state->ref_count; + } + + if (refs) { + *refs = talloc_steal(mem_ctx, state->refs); + } + + return EOK; +} + +/* This search handler can be used by most calls */ +static void generic_ext_search_handler(struct tevent_req *subreq, + struct sdap_options *opts) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + size_t ref_count, i; + char **refs; + + ret = sdap_get_generic_ext_recv(subreq, req, &ref_count, &refs); + talloc_zfree(subreq); + + if (ret != EOK) { + if (ret == ETIMEDOUT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv failed: [%d]: %s " + "[ldap_search_timeout]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + if (ref_count > 0) { + /* We will ignore referrals in the generic handler */ + DEBUG(SSSDBG_TRACE_ALL, + "Request included referrals which were ignored.\n"); + if (debug_level & SSSDBG_TRACE_ALL) { + for(i = 0; i < ref_count; i++) { + DEBUG(SSSDBG_TRACE_ALL, + " Ref: %s\n", refs[i]); + } + } + } + + talloc_free(refs); + tevent_req_done(req); +} + +/* ==Generic Search exposing all options======================= */ +struct sdap_get_and_parse_generic_state { + struct sdap_attr_map *map; + int map_num_attrs; + + struct sdap_reply sreply; + struct sdap_options *opts; +}; + +static void sdap_get_and_parse_generic_done(struct tevent_req *subreq); +static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); + +struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int attrsonly, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + bool allow_paging) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_get_and_parse_generic_state *state = NULL; + unsigned int flags = 0; + + req = tevent_req_create(memctx, &state, + struct sdap_get_and_parse_generic_state); + if (!req) return NULL; + + state->map = map; + state->map_num_attrs = map_num_attrs; + state->opts = opts; + + if (allow_paging) { + flags |= SDAP_SRCH_FLG_PAGING; + } + + if (attrsonly) { + flags |= SDAP_SRCH_FLG_ATTRS_ONLY; + } + + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base, + scope, filter, attrs, serverctrls, + clientctrls, sizelimit, timeout, + sdap_get_and_parse_generic_parse_entry, + state, flags); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_and_parse_generic_done, req); + + return req; +} + +static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sysdb_attrs *attrs; + struct sdap_get_and_parse_generic_state *state = + talloc_get_type(pvt, struct sdap_get_and_parse_generic_state); + + bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(state, sh, msg, + state->map, state->map_num_attrs, + &attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + return ret; + } + + ret = add_to_reply(state, &state->sreply, attrs); + if (ret != EOK) { + talloc_free(attrs); + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); + return ret; + } + + /* add_to_reply steals attrs, no need to free them here */ + return EOK; +} + +static void sdap_get_and_parse_generic_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_and_parse_generic_state *state = + tevent_req_data(req, struct sdap_get_and_parse_generic_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +int sdap_get_and_parse_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_and_parse_generic_state *state = tevent_req_data(req, + struct sdap_get_and_parse_generic_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->sreply.reply_count; + *reply = talloc_steal(mem_ctx, state->sreply.reply); + + return EOK; +} + + +/* ==Simple generic search============================================== */ +struct sdap_get_generic_state { + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static void sdap_get_generic_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int timeout, + bool allow_paging) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_get_generic_state *state = NULL; + + req = tevent_req_create(memctx, &state, struct sdap_get_generic_state); + if (!req) return NULL; + + subreq = sdap_get_and_parse_generic_send(memctx, ev, opts, sh, search_base, + scope, filter, attrs, + map, map_num_attrs, + false, NULL, NULL, 0, timeout, + allow_paging); + if (subreq == NULL) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_generic_done, req); + + return req; +} + +static void sdap_get_generic_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_generic_state *state = + tevent_req_data(req, struct sdap_get_generic_state); + errno_t ret; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &state->reply_count, &state->reply); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + tevent_req_done(req); +} + +int sdap_get_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_generic_state *state = + tevent_req_data(req, struct sdap_get_generic_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +/* ==OpenLDAP deref search============================================== */ +static int sdap_x_deref_create_control(struct sdap_handle *sh, + const char *deref_attr, + const char **attrs, + LDAPControl **ctrl); + +static void sdap_x_deref_search_done(struct tevent_req *subreq); +static int sdap_x_deref_search_ctrls_destructor(void *ptr); + +static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +struct sdap_x_deref_search_state { + struct sdap_handle *sh; + struct sdap_op *op; + struct sdap_attr_map_info *maps; + LDAPControl **ctrls; + struct sdap_options *opts; + bool ldap_ignore_unreadable_references; + + struct sdap_deref_reply dreply; + int num_maps; +}; + +static struct tevent_req * +sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, const char *filter, + const char *deref_attr, const char **attrs, + struct sdap_attr_map_info *maps, int num_maps, + int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_x_deref_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_x_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->maps = maps; + state->op = NULL; + state->opts = opts; + state->num_maps = num_maps; + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + if (state->ctrls == NULL) { + talloc_zfree(req); + return NULL; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_x_deref_search_ctrls_destructor); + + state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, + SDAP_IGNORE_UNREADABLE_REFERENCES); + + ret = sdap_x_deref_create_control(sh, deref_attr, + attrs, &state->ctrls[0]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create OpenLDAP deref control\n"); + talloc_zfree(req); + return NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Dereferencing entry [%s] using OpenLDAP deref\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + filter == NULL ? LDAP_SCOPE_BASE + : LDAP_SCOPE_SUBTREE, + filter, attrs, + state->ctrls, NULL, 0, timeout, + sdap_x_deref_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_x_deref_search_done, req); + + return req; +} + +static int sdap_x_deref_create_control(struct sdap_handle *sh, + const char *deref_attr, + const char **attrs, + LDAPControl **ctrl) +{ + struct berval derefval; + int ret; + struct LDAPDerefSpec ds[2]; + + ds[0].derefAttr = discard_const(deref_attr); + ds[0].attributes = discard_const(attrs); + + ds[1].derefAttr = NULL; /* sentinel */ + + ret = ldap_create_deref_control_value(sh->ldap, ds, &derefval); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_create_deref_control_value failed: %s\n", + ldap_err2string(ret)); + return ret; + } + + ret = sdap_control_create(sh, LDAP_CONTROL_X_DEREF, + 1, &derefval, 1, ctrl); + ldap_memfree(derefval.bv_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed %d\n", ret); + return ret; + } + + return EOK; +} + +static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + LDAPControl **ctrls = NULL; + LDAPControl *derefctrl = NULL; + LDAPDerefRes *deref_res = NULL; + LDAPDerefRes *dref; + struct sdap_deref_attrs **res; + TALLOC_CTX *tmp_ctx; + + struct sdap_x_deref_search_state *state = talloc_get_type(pvt, + struct sdap_x_deref_search_state); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = ldap_get_entry_controls(state->sh->ldap, msg->msg, + &ctrls); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed\n"); + goto done; + } + + if (!ctrls) { + /* When we attempt to request attributes that are not present in + * the dereferenced links, some serves might not send the dereference + * control back at all. Be permissive and treat the search as if + * it didn't find anything. + */ + DEBUG(SSSDBG_MINOR_FAILURE, "No controls found for entry\n"); + ret = EOK; + goto done; + } + + res = NULL; + + derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL); + if (!derefctrl) { + DEBUG(SSSDBG_FUNC_DATA, "No deref controls found\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got deref control\n"); + + ret = ldap_parse_derefresponse_control(state->sh->ldap, + derefctrl, + &deref_res); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_derefresponse_control failed: %s\n", + ldap_err2string(ret)); + goto done; + } + + for (dref = deref_res; dref; dref=dref->next) { + ret = sdap_parse_deref(tmp_ctx, state->maps, state->num_maps, + dref, &res); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_parse_deref failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = add_to_deref_reply(state, state->num_maps, + &state->dreply, res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_to_deref_reply failed.\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, + "All deref results from a single control parsed\n"); + ldap_derefresponse_free(deref_res); + deref_res = NULL; + + ret = EOK; +done: + if (ret != EOK && ret != ENOMEM) { + if (state->ldap_ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference\n"); + ret = EOK; + } + } + + talloc_zfree(tmp_ctx); + ldap_controls_free(ctrls); + ldap_derefresponse_free(deref_res); + return ret; +} + +static void sdap_x_deref_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_x_deref_search_state *state = + tevent_req_data(req, struct sdap_x_deref_search_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +static int sdap_x_deref_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +static int +sdap_x_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_x_deref_search_state *state = tevent_req_data(req, + struct sdap_x_deref_search_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->dreply.reply_count; + *reply = talloc_steal(mem_ctx, state->dreply.reply); + + return EOK; +} + +/* ==Security Descriptor (ACL) search=================================== */ +struct sdap_sd_search_state { + LDAPControl **ctrls; + struct sdap_options *opts; + size_t reply_count; + struct sysdb_attrs **reply; + struct sdap_reply sreply; + + /* Referrals returned by the search */ + size_t ref_count; + char **refs; +}; + +static int sdap_sd_search_create_control(struct sdap_handle *sh, + int val, + LDAPControl **ctrl); +static int sdap_sd_search_ctrls_destructor(void *ptr); +static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +static void sdap_sd_search_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, int sd_flags, + const char **attrs, int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sd_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_sd_search_state); + if (!req) return NULL; + + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + state->opts = opts; + if (state->ctrls == NULL) { + ret = EIO; + goto fail; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_sd_search_ctrls_destructor); + + ret = sdap_sd_search_create_control(sh, sd_flags, &state->ctrls[0]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create SD control\n"); + ret = EIO; + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + LDAP_SCOPE_BASE, "(objectclass=*)", attrs, + state->ctrls, NULL, 0, timeout, + sdap_sd_search_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + ret = EIO; + goto fail; + } + tevent_req_set_callback(subreq, sdap_sd_search_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int sdap_sd_search_create_control(struct sdap_handle *sh, + int val, + LDAPControl **ctrl) +{ + struct berval *sdval; + int ret; + BerElement *ber = NULL; + ber = ber_alloc_t(LBER_USE_DER); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); + return ENOMEM; + } + + ret = ber_printf(ber, "{i}", val); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + return EIO; + } + + ret = ber_flatten(ber, &sdval); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + return EIO; + } + + ret = sdap_control_create(sh, LDAP_SERVER_SD_OID, 1, sdval, 1, ctrl); + ber_bvfree(sdval); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); + return ret; + } + + return EOK; +} + +static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sysdb_attrs *attrs; + struct sdap_sd_search_state *state = + talloc_get_type(pvt, struct sdap_sd_search_state); + + bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(state, sh, msg, + NULL, 0, + &attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + return ret; + } + + ret = add_to_reply(state, &state->sreply, attrs); + if (ret != EOK) { + talloc_free(attrs); + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); + return ret; + } + + /* add_to_reply steals attrs, no need to free them here */ + return EOK; +} + +static void sdap_sd_search_done(struct tevent_req *subreq) +{ + int ret; + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_sd_search_state *state = + tevent_req_data(req, struct sdap_sd_search_state); + + ret = sdap_get_generic_ext_recv(subreq, state, + &state->ref_count, + &state->refs); + talloc_zfree(subreq); + + if (ret != EOK) { + if (ret == ETIMEDOUT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s " + "[ldap_network_timeout]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_get_generic_ext_recv request failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_sd_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +int sdap_sd_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply, + size_t *_ref_count, + char ***_refs) +{ + struct sdap_sd_search_state *state = tevent_req_data(req, + struct sdap_sd_search_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_reply_count = state->sreply.reply_count; + *_reply = talloc_steal(mem_ctx, state->sreply.reply); + + if(_ref_count) { + *_ref_count = state->ref_count; + } + + if (_refs) { + *_refs = talloc_steal(mem_ctx, state->refs); + } + + return EOK; +} + +/* ==Attribute scoped search============================================ */ +struct sdap_asq_search_state { + struct sdap_attr_map_info *maps; + int num_maps; + LDAPControl **ctrls; + struct sdap_options *opts; + bool ldap_ignore_unreadable_references; + + struct sdap_deref_reply dreply; +}; + +static int sdap_asq_search_create_control(struct sdap_handle *sh, + const char *attr, + LDAPControl **ctrl); +static int sdap_asq_search_ctrls_destructor(void *ptr); +static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt); +static void sdap_asq_search_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_asq_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_options *opts, struct sdap_handle *sh, + const char *base_dn, const char *deref_attr, + const char **attrs, struct sdap_attr_map_info *maps, + int num_maps, int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_asq_search_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_asq_search_state); + if (!req) return NULL; + + state->maps = maps; + state->num_maps = num_maps; + state->ctrls = talloc_zero_array(state, LDAPControl *, 2); + state->opts = opts; + if (state->ctrls == NULL) { + talloc_zfree(req); + return NULL; + } + talloc_set_destructor((TALLOC_CTX *) state->ctrls, + sdap_asq_search_ctrls_destructor); + + state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, + SDAP_IGNORE_UNREADABLE_REFERENCES); + + ret = sdap_asq_search_create_control(sh, deref_attr, &state->ctrls[0]); + if (ret != EOK) { + talloc_zfree(req); + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create ASQ control\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using ASQ\n", base_dn); + subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, + LDAP_SCOPE_BASE, NULL, attrs, + state->ctrls, NULL, 0, timeout, + sdap_asq_search_parse_entry, + state, SDAP_SRCH_FLG_PAGING); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_asq_search_done, req); + + return req; +} + + +static int sdap_asq_search_create_control(struct sdap_handle *sh, + const char *attr, + LDAPControl **ctrl) +{ + struct berval *asqval; + int ret; + BerElement *ber = NULL; + + ber = ber_alloc_t(LBER_USE_DER); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); + return ENOMEM; + } + + ret = ber_printf(ber, "{s}", attr); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); + ber_free(ber, 1); + return EIO; + } + + ret = ber_flatten(ber, &asqval); + ber_free(ber, 1); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); + return EIO; + } + + ret = sdap_control_create(sh, LDAP_SERVER_ASQ_OID, 1, asqval, 1, ctrl); + ber_bvfree(asqval); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); + return ret; + } + + return EOK; +} + +static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, + struct sdap_msg *msg, + void *pvt) +{ + errno_t ret; + struct sdap_asq_search_state *state = + talloc_get_type(pvt, struct sdap_asq_search_state); + struct berval **vals; + int i, mi; + struct sdap_attr_map *map; + int num_attrs = 0; + struct sdap_deref_attrs **res; + char *tmp; + char *dn = NULL; + TALLOC_CTX *tmp_ctx; + bool disable_range_rtrvl; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, + state->num_maps); + if (!res) { + ret = ENOMEM; + goto done; + } + + for (mi =0; mi < state->num_maps; mi++) { + res[mi] = talloc_zero(res, struct sdap_deref_attrs); + if (!res[mi]) { + ret = ENOMEM; + goto done; + } + res[mi]->map = state->maps[mi].map; + res[mi]->attrs = NULL; + } + + + tmp = ldap_get_dn(sh->ldap, msg->msg); + if (!tmp) { + ret = EINVAL; + goto done; + } + + dn = talloc_strdup(tmp_ctx, tmp); + ldap_memfree(tmp); + if (!dn) { + ret = ENOMEM; + goto done; + } + + /* Find all suitable maps in the list */ + vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass"); + if (!vals) { + DEBUG(SSSDBG_OP_FAILURE, + "Unknown entry type, no objectClass found for DN [%s]!\n", dn); + ret = EINVAL; + goto done; + } + for (mi =0; mi < state->num_maps; mi++) { + map = NULL; + for (i = 0; vals[i]; i++) { + /* the objectclass is always the first name in the map */ + if (strncasecmp(state->maps[mi].map[0].name, + vals[i]->bv_val, vals[i]->bv_len) == 0) { + /* it's an entry of the right type */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Matched objectclass [%s] on DN [%s], will use associated map\n", + state->maps[mi].map[0].name, dn); + map = state->maps[mi].map; + num_attrs = state->maps[mi].num_attrs; + break; + } + } + if (!map) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "DN [%s] did not match the objectClass [%s]\n", + dn, state->maps[mi].map[0].name); + continue; + } + + disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, + SDAP_DISABLE_RANGE_RETRIEVAL); + + ret = sdap_parse_entry(res[mi], sh, msg, + map, num_attrs, + &res[mi]->attrs, disable_range_rtrvl); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + } + ldap_value_free_len(vals); + + ret = add_to_deref_reply(state, state->num_maps, + &state->dreply, res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "add_to_deref_reply failed.\n"); + goto done; + } + + ret = EOK; +done: + if (ret != EOK && ret != ENOMEM) { + if (state->ldap_ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", + dn != NULL ? dn : "(null)"); + ret = EOK; + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +static void sdap_asq_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_asq_search_state *state = + tevent_req_data(req, struct sdap_asq_search_state); + + return generic_ext_search_handler(subreq, state->opts); +} + +static int sdap_asq_search_ctrls_destructor(void *ptr) +{ + LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); + + if (ctrls && ctrls[0]) { + ldap_control_free(ctrls[0]); + } + + return 0; +} + +int sdap_asq_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_asq_search_state *state = tevent_req_data(req, + struct sdap_asq_search_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->dreply.reply_count; + *reply = talloc_steal(mem_ctx, state->dreply.reply); + + return EOK; +} + +/* ==Generic Deref Search============================================ */ +enum sdap_deref_type { + SDAP_DEREF_OPENLDAP, + SDAP_DEREF_ASQ +}; + +struct sdap_deref_search_state { + struct sdap_handle *sh; + const char *base_dn; + const char *deref_attr; + + size_t reply_count; + struct sdap_deref_attrs **reply; + enum sdap_deref_type deref_type; + unsigned flags; +}; + +static void sdap_deref_search_done(struct tevent_req *subreq); +static void sdap_deref_search_with_filter_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_deref_search_with_filter_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + const char *filter, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout, + unsigned flags) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_deref_search_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->reply_count = 0; + state->reply = NULL; + state->flags = flags; + + if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); + state->deref_type = SDAP_DEREF_OPENLDAP; + + subreq = sdap_x_deref_search_send(state, ev, opts, sh, search_base, + filter, deref_attr, attrs, maps, + num_maps, timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); + goto fail; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Server does not support any known deref method!\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_deref_search_with_filter_done, req); + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void sdap_deref_search_with_filter_done(struct tevent_req *subreq) +{ + sdap_deref_search_done(subreq); +} + +int sdap_deref_search_with_filter_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + return sdap_deref_search_recv(req, mem_ctx, reply_count, reply); +} + +struct tevent_req * +sdap_deref_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_deref_search_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); + if (!req) return NULL; + + state->sh = sh; + state->reply_count = 0; + state->reply = NULL; + state->base_dn = base_dn; + state->deref_attr = deref_attr; + + PROBE(SDAP_DEREF_SEARCH_SEND, state->base_dn, state->deref_attr); + + if (sdap_is_control_supported(sh, LDAP_SERVER_ASQ_OID)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports ASQ\n"); + state->deref_type = SDAP_DEREF_ASQ; + + subreq = sdap_asq_search_send(state, ev, opts, sh, base_dn, + deref_attr, attrs, maps, num_maps, + timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start ASQ search\n"); + goto fail; + } + } else if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); + state->deref_type = SDAP_DEREF_OPENLDAP; + + subreq = sdap_x_deref_search_send(state, ev, opts, sh, base_dn, NULL, + deref_attr, attrs, maps, num_maps, + timeout); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); + goto fail; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Server does not support any known deref method!\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_deref_search_done, req); + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void sdap_deref_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_deref_search_state *state = tevent_req_data(req, + struct sdap_deref_search_state); + int ret; + + switch (state->deref_type) { + case SDAP_DEREF_OPENLDAP: + ret = sdap_x_deref_search_recv(subreq, state, + &state->reply_count, &state->reply); + break; + case SDAP_DEREF_ASQ: + ret = sdap_asq_search_recv(subreq, state, + &state->reply_count, &state->reply); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown deref method %d\n", state->deref_type); + tevent_req_error(req, EINVAL); + return; + } + + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "dereference processing failed [%d]: %s\n", ret, strerror(ret)); + if (ret == ENOTSUP) { + state->sh->disable_deref = true; + } + + if (!(state->flags & SDAP_DEREF_FLG_SILENT)) { + if (ret == ENOTSUP) { + sss_log(SSS_LOG_WARNING, + "LDAP server claims to support deref, but deref search " + "failed. Disabling deref for further requests. You can " + "permanently disable deref by setting " + "ldap_deref_threshold to 0 in domain configuration."); + } else { + sss_log(SSS_LOG_WARNING, + "dereference processing failed : %s", strerror(ret)); + } + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_deref_search_state *state = tevent_req_data(req, + struct sdap_deref_search_state); + + PROBE(SDAP_DEREF_SEARCH_RECV, state->base_dn, state->deref_attr); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +bool sdap_has_deref_support_ex(struct sdap_handle *sh, + struct sdap_options *opts, + bool ignore_client) +{ + const char *deref_oids[][2] = { { LDAP_SERVER_ASQ_OID, "ASQ" }, + { LDAP_CONTROL_X_DEREF, "OpenLDAP" }, + { NULL, NULL } + }; + int i; + int deref_threshold; + + if (sh->disable_deref) { + return false; + } + + if (ignore_client == false) { + deref_threshold = dp_opt_get_int(opts->basic, SDAP_DEREF_THRESHOLD); + if (deref_threshold == 0) { + return false; + } + } + + for (i=0; deref_oids[i][0]; i++) { + if (sdap_is_control_supported(sh, deref_oids[i][0])) { + DEBUG(SSSDBG_TRACE_FUNC, "The server supports deref method %s\n", + deref_oids[i][1]); + return true; + } + } + + return false; +} + +bool sdap_has_deref_support(struct sdap_handle *sh, struct sdap_options *opts) +{ + return sdap_has_deref_support_ex(sh, opts, false); +} diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h new file mode 100644 index 0000000..5458d21 --- /dev/null +++ b/src/providers/ldap/sdap_async.h @@ -0,0 +1,455 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> + + 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/>. +*/ + +#ifndef _SDAP_ASYNC_H_ +#define _SDAP_ASYNC_H_ + +#include <sys/types.h> +#include <sys/socket.h> +#include <talloc.h> +#include <tevent.h> +#include "providers/backend.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/fail_over.h" + +#define AD_TOKENGROUPS_ATTR "tokenGroups" + +struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *uri, + struct sockaddr *sockaddr, + socklen_t sockaddr_len, + bool use_start_tls); +int sdap_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh); + +struct tevent_req *sdap_connect_host_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_db, + const char *protocol, + const char *host, + int port, + bool use_start_tls); + +errno_t sdap_connect_host_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sdap_handle **_sh); + +/* Search users in LDAP, return them as attrs */ +enum sdap_entry_lookup_type { + SDAP_LOOKUP_SINGLE, /* Direct single-user/group lookup */ + SDAP_LOOKUP_WILDCARD, /* Multiple entries with a limit */ + SDAP_LOOKUP_ENUMERATE, /* Fetch all entries from the server */ +}; + +struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type); +int sdap_search_user_recv(TALLOC_CTX *memctx, struct tevent_req *req, + char **higher_usn, struct sysdb_attrs ***users, + size_t *count); + +/* Search users in LDAP using the request above, save them to cache */ +struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + struct sysdb_attrs *mapped_attrs); +int sdap_get_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp); + +struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + bool no_members); +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp); + +struct tevent_req *sdap_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout); +int sdap_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req * +sdap_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_search_base **search_bases); + +errno_t +sdap_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts); + +struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + const char *user_dn, + struct sss_auth_token *authtok, + int simple_bind_timeout); + +errno_t sdap_auth_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy); + +struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_handle *sh, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + const char *name, + int filter_type, + const char *extra_value, + const char **grp_attrs, + bool set_non_posix); +int sdap_get_initgr_recv(struct tevent_req *req); + +struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + char *user_dn, + const char *password, + const char *new_password, + int timeout); +errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **user_error_msg); + +struct tevent_req * +sdap_modify_passwd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + char *attr, + const char *user_dn, + const char *new_password); + +errno_t sdap_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + char **_user_error_message); + +struct tevent_req * +sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *dn, + char *attr); + +errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req); + +enum connect_tls { + CON_TLS_DFL, + CON_TLS_ON, + CON_TLS_OFF +}; + +struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_ctx *be, + struct sdap_service *service, + bool skip_rootdse, + enum connect_tls force_tls, + bool skip_auth); +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + bool *can_retry, + struct sdap_handle **gsh, + struct sdap_server_opts **srv_opts); + +/* Exposes all options of generic send while allowing to parse by map */ +struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int attrsonly, + LDAPControl **serverctrls, + LDAPControl **clientctrls, + int sizelimit, + int timeout, + bool allow_paging); +int sdap_get_and_parse_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + int scope, + const char *filter, + const char **attrs, + struct sdap_attr_map *map, + int map_num_attrs, + int timeout, + bool allow_paging); +int sdap_get_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, size_t *reply_count, + struct sysdb_attrs ***reply_list); + +bool sdap_has_deref_support_ex(struct sdap_handle *sh, + struct sdap_options *opts, + bool ignore_client); +bool sdap_has_deref_support(struct sdap_handle *sh, + struct sdap_options *opts); + +enum sdap_deref_flags { + SDAP_DEREF_FLG_SILENT = 1 << 0, /* Do not warn if dereference fails */ +}; + +struct tevent_req * +sdap_deref_search_with_filter_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *search_base, + const char *filter, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout, + unsigned flags); +int sdap_deref_search_with_filter_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply); + +struct tevent_req * +sdap_deref_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + const char *deref_attr, + const char **attrs, + int num_maps, + struct sdap_attr_map_info *maps, + int timeout); +int sdap_deref_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply); + + +struct tevent_req * +sdap_sd_search_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *base_dn, + int sd_flags, + const char **attrs, + int timeout); +int sdap_sd_search_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply, + size_t *_ref_count, + char ***_refs); + +errno_t +sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs, + const char *attr_name, + const char *attr_desc, + bool multivalued, + const char *name, + struct sysdb_attrs *attrs); + +#define sdap_attrs_add_string(ldap_attrs, attr_name, attr_desc, name, attrs) \ + sdap_attrs_add_ldap_attr(ldap_attrs, attr_name, attr_desc, \ + false, name, attrs) + +#define sdap_attrs_add_list(ldap_attrs, attr_name, attr_desc, name, attrs) \ + sdap_attrs_add_ldap_attr(ldap_attrs, attr_name, attr_desc, \ + true, name, attrs) + +errno_t +sdap_save_all_names(const char *name, + struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *dom, + enum sysdb_member_type entry_type, + struct sysdb_attrs *attrs); + +struct tevent_req * +sdap_get_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); +errno_t +sdap_get_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_services_recv(struct tevent_req *req); + +struct tevent_req * +sdap_get_iphost_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); +errno_t +sdap_get_iphost_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_iphosts_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_iphosts_recv(struct tevent_req *req); + +struct tevent_req * +sdap_get_ipnetwork_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration); + +errno_t +sdap_get_ipnetwork_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value); + +struct tevent_req * +enum_ipnetworks_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge); + +errno_t +enum_ipnetworks_recv(struct tevent_req *req); + +struct tevent_req * +sdap_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout, + bool use_id_mapping); + +errno_t +sdap_ad_tokengroups_initgroups_recv(struct tevent_req *req); + +errno_t +sdap_handle_id_collision_for_incomplete_groups(struct data_provider *dp, + struct sss_domain_info *domain, + const char *name, + gid_t gid, + const char *original_dn, + const char *sid_str, + const char *uuid, + bool posix, + time_t now); + +struct sdap_id_conn_ctx *get_ldap_conn_from_sdom_pvt(struct sdap_options *opts, + struct sdap_domain *sdom); +#endif /* _SDAP_ASYNC_H_ */ diff --git a/src/providers/ldap/sdap_async_ad.h b/src/providers/ldap/sdap_async_ad.h new file mode 100644 index 0000000..a5f47a1 --- /dev/null +++ b/src/providers/ldap/sdap_async_ad.h @@ -0,0 +1,59 @@ +/* + SSSD - header files for AD specific enhancement in the common LDAP/SDAP + code + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef SDAP_ASYNC_AD_H_ +#define SDAP_ASYNC_AD_H_ + +errno_t sdap_ad_save_group_membership_with_idmapping(const char *username, + struct sdap_options *opts, + struct sss_domain_info *user_dom, + struct sdap_idmap_ctx *idmap_ctx, + size_t num_sids, + char **sids); + +errno_t +sdap_ad_tokengroups_get_posix_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *user_domain, + size_t num_sids, + char **sids, + size_t *_num_missing, + char ***_missing, + size_t *_num_valid, + char ***_valid_groups); + +errno_t +sdap_ad_tokengroups_update_members(const char *username, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + char **ldap_groups); +struct tevent_req * +sdap_ad_resolve_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sss_domain_info *domain, + char **sids); + +errno_t sdap_ad_resolve_sids_recv(struct tevent_req *req); +#endif /* SDAP_ASYNC_AD_H_ */ diff --git a/src/providers/ldap/sdap_async_autofs.c b/src/providers/ldap/sdap_async_autofs.c new file mode 100644 index 0000000..8a542f9 --- /dev/null +++ b/src/providers/ldap/sdap_async_autofs.c @@ -0,0 +1,1479 @@ +/* + SSSD + + Async LDAP Helper routines for autofs + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "db/sysdb_autofs.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap_ops.h" + +/* ====== Utility functions ====== */ +static const char * +get_autofs_map_name(struct sysdb_attrs *map, struct sdap_options *opts) +{ + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el(map, + opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].sys_name, + &el); + if (ret) return NULL; + if (el->num_values == 0) return NULL; + + return (const char *)el->values[0].data; +} + +static const char * +get_autofs_entry_attr(struct sysdb_attrs *entry, struct sdap_options *opts, + enum sdap_autofs_entry_attrs attr) +{ + errno_t ret; + struct ldb_message_element *el; + + ret = sysdb_attrs_get_el(entry, + opts->autofs_entry_map[attr].sys_name, + &el); + if (ret) return NULL; + if (el->num_values != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected one entry got %d\n", el->num_values); + return NULL; + } + + return (const char *)el->values[0].data; +} + +static const char * +get_autofs_entry_key(struct sysdb_attrs *entry, struct sdap_options *opts) +{ + return get_autofs_entry_attr(entry, opts, SDAP_AT_AUTOFS_ENTRY_KEY); +} + +static const char * +get_autofs_entry_value(struct sysdb_attrs *entry, struct sdap_options *opts) +{ + return get_autofs_entry_attr(entry, opts, SDAP_AT_AUTOFS_ENTRY_VALUE); +} + +static errno_t +add_autofs_entry(struct sss_domain_info *domain, + const char *map, + struct sdap_options *opts, + struct sysdb_attrs *entry, + time_t now) +{ + const char *key; + const char *value; + + key = get_autofs_entry_key(entry, opts); + if (!key) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs entry key\n"); + return EINVAL; + } + + value = get_autofs_entry_value(entry, opts); + if (!value) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs entry value\n"); + return EINVAL; + } + + return sysdb_save_autofsentry(domain, map, key, value, NULL, + domain->autofsmap_timeout, now); +} + +static errno_t +save_autofs_entries(struct sss_domain_info *domain, + struct sdap_options *opts, + const char *map, + char **add_dn_list, + hash_table_t *entry_hash) +{ + hash_key_t key; + hash_value_t value; + size_t i; + int hret; + errno_t ret; + struct sysdb_attrs *entry; + time_t now; + + if (!add_dn_list) { + return EOK; + } + + now = time(NULL); + + for (i=0; add_dn_list[i]; i++) { + key.type = HASH_KEY_STRING; + key.str = (char *) add_dn_list[i]; + + hret = hash_lookup(entry_hash, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve entry [%s] from hash\n", add_dn_list[i]); + continue; + } + + entry = talloc_get_type(value.ptr, struct sysdb_attrs); + if (!entry) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve entry [%s] from ptr\n", add_dn_list[i]); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Saving autofs entry [%s]\n", add_dn_list[i]); + ret = add_autofs_entry(domain, map, opts, entry, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save entry [%s] to cache\n", add_dn_list[i]); + continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Saved entry [%s]\n", add_dn_list[i]); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All entries saved\n"); + return EOK; +} + +static errno_t +del_autofs_entries(struct sss_domain_info *dom, + char **del_dn_list) +{ + size_t i; + errno_t ret; + + for (i=0; del_dn_list[i]; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Removing autofs entry [%s]\n", del_dn_list[i]); + + ret = sysdb_del_autofsentry(dom, del_dn_list[i]); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot delete entry %s\n", del_dn_list[i]); + continue; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All entries removed\n"); + return EOK; +} + +static errno_t +save_autofs_map(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *map, + bool enumerated) +{ + const char *mapname; + const char *origdn; + errno_t ret; + time_t now; + + mapname = get_autofs_map_name(map, opts); + if (!mapname) return EINVAL; + + ret = sysdb_attrs_get_string(map, SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original dn [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + now = time(NULL); + + ret = sysdb_save_autofsmap(dom, mapname, mapname, origdn, + NULL, dom->autofsmap_timeout, now, enumerated); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +struct automntmaps_process_members_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + int timeout; + + const char *orig_dn; + char *base_filter; + char *filter; + const char **attrs; + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sysdb_attrs *map; + + struct sysdb_attrs **entries; + size_t entries_count; +}; + +static void +automntmaps_process_members_done(struct tevent_req *subreq); +static errno_t +automntmaps_process_members_next_base(struct tevent_req *req); + +static struct tevent_req * +automntmaps_process_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sss_domain_info *dom, + struct sdap_search_base **search_bases, + int timeout, + struct sysdb_attrs *map) +{ + errno_t ret; + struct tevent_req *req; + struct automntmaps_process_members_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct automntmaps_process_members_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->timeout = timeout; + state->base_iter = 0; + state->map = map; + state->search_bases = search_bases; + + state->base_filter = talloc_asprintf(state, "(&(%s=*)(objectclass=%s))", + opts->autofs_entry_map[SDAP_AT_AUTOFS_ENTRY_KEY].name, + opts->autofs_entry_map[SDAP_OC_AUTOFS_ENTRY].name); + if (!state->base_filter) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto immediate; + } + + ret = build_attrs_from_map(state, opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, NULL, + &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + ret = ENOMEM; + goto immediate; + } + + + ret = sysdb_attrs_get_string(state->map, SYSDB_ORIG_DN, &state->orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get originalDN\n"); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Examining autofs map [%s]\n", state->orig_dn); + + ret = automntmaps_process_members_next_base(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "search failed [%d]: %s\n", ret, strerror(ret)); + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +automntmaps_process_members_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct automntmaps_process_members_state *state = + tevent_req_data(req, struct automntmaps_process_members_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for automount map entries with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->orig_dn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + state->timeout, true); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot start search for entries\n"); + return EIO; + } + tevent_req_set_callback(subreq, automntmaps_process_members_done, req); + + return EOK; +} + +static void +automntmaps_process_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct automntmaps_process_members_state *state = + tevent_req_data(req, struct automntmaps_process_members_state); + errno_t ret; + struct sysdb_attrs **entries; + size_t entries_count, i; + + ret = sdap_get_generic_recv(subreq, state, + &entries_count, &entries); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (entries_count > 0) { + state->entries = talloc_realloc(state, state->entries, + struct sysdb_attrs *, + state->entries_count + entries_count + 1); + if (state->entries == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i=0; i < entries_count; i++) { + state->entries[state->entries_count + i] = + talloc_steal(state->entries, entries[i]); + } + + state->entries_count += entries_count; + state->entries[state->entries_count] = NULL; + } + + state->base_iter++; + if (state->search_bases[state->base_iter]) { + ret = automntmaps_process_members_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "No more search bases to try\n"); + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for autofs entries, returned %zu results.\n", + state->entries_count); + + tevent_req_done(req); + return; +} + +static errno_t +automntmaps_process_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *entries_count, + struct sysdb_attrs ***entries) +{ + struct automntmaps_process_members_state *state; + state = tevent_req_data(req, struct automntmaps_process_members_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (entries_count) { + *entries_count = state->entries_count; + } + + if (entries) { + *entries = talloc_steal(mem_ctx, state->entries); + } + + return EOK; +} + +struct sdap_get_automntmap_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + + char *higher_timestamp; + + struct sysdb_attrs **map; + size_t count; + + struct sysdb_attrs **entries; + size_t entries_count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_automntmap_next_base(struct tevent_req *req); +static void +sdap_get_automntmap_process(struct tevent_req *subreq); + +static struct tevent_req * +sdap_get_automntmap_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_automntmap_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_automntmap_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->map = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + + ret = sdap_get_automntmap_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + return req; +} + +static errno_t +sdap_get_automntmap_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_automntmap_state *state; + + state = tevent_req_data(req, struct sdap_get_automntmap_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for automount maps with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->autofs_mobject_map, SDAP_OPTS_AUTOFS_MAP, + state->timeout, + false); + if (!subreq) { + return EIO; + } + tevent_req_set_callback(subreq, sdap_get_automntmap_process, req); + + return EOK; +} + +static void +sdap_get_automntmap_done(struct tevent_req *subreq); + +static void +sdap_get_automntmap_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, + &state->count, &state->map); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for autofs maps, returned %zu results.\n", state->count); + + if (state->count == 0) { + /* No maps found in this search */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_automntmap_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + tevent_req_error(req, ENOENT); + return; + } else if (state->count > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "The search yielded more than one autofs map\n"); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Processing autofs maps\n"); + subreq = automntmaps_process_members_send(state, state->ev, state->opts, + state->sh, state->dom, + state->search_bases, + state->timeout, + state->map[0]); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_automntmap_done, req); + + return; +} + +static void +sdap_get_automntmap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + errno_t ret; + + ret = automntmaps_process_members_recv(subreq, state, &state->entries_count, + &state->entries); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "automount map members received\n"); + tevent_req_done(req); + return; +} + +static errno_t +sdap_get_automntmap_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **map, + size_t *entries_count, + struct sysdb_attrs ***entries) +{ + struct sdap_get_automntmap_state *state = tevent_req_data(req, + struct sdap_get_automntmap_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (map) { + *map = talloc_steal(mem_ctx, state->map[0]); + } + + if (entries_count) { + *entries_count = state->entries_count; + } + + if (entries) { + *entries = talloc_steal(mem_ctx, state->entries); + } + + return EOK; +} + +struct sdap_autofs_setautomntent_state { + char *filter; + const char **attrs; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sdap_id_op *sdap_op; + struct sss_domain_info *dom; + + const char *mapname; + struct sysdb_attrs *map; + struct sysdb_attrs **entries; + size_t entries_count; + + int dp_error; +}; + +static void +sdap_autofs_setautomntent_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_setautomntent_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_id_op *op, + struct sdap_options *opts, + const char *mapname) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_autofs_setautomntent_state *state; + char *clean_mapname; + errno_t ret; + + req = tevent_req_create(memctx, &state, + struct sdap_autofs_setautomntent_state); + if (!req) return NULL; + + if (!mapname) { + DEBUG(SSSDBG_CRIT_FAILURE, "No map name given\n"); + ret = EINVAL; + goto fail; + } + + state->sh = sh; + state->sysdb = sysdb; + state->opts = opts; + state->sdap_op = op; + state->dom = dom; + state->mapname = mapname; + + ret = sss_filter_sanitize(state, mapname, &clean_mapname); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].name, + clean_mapname, + state->opts->autofs_mobject_map[SDAP_OC_AUTOFS_MAP].name); + if (!state->filter) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_free(clean_mapname); + + ret = build_attrs_from_map(state, state->opts->autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, NULL, + &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_get_automntmap_send(state, ev, dom, + state->opts, + state->opts->sdom->autofs_search_bases, + state->sh, + state->attrs, state->filter, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT)); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_automntmap_send failed\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_autofs_setautomntent_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_autofs_setautomntent_save(struct tevent_req *req); + +static void +sdap_autofs_setautomntent_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_setautomntent_state *state = tevent_req_data(req, + struct sdap_autofs_setautomntent_state); + + ret = sdap_get_automntmap_recv(subreq, state, &state->map, + &state->entries_count, &state->entries); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not find automount map\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_automntmap_recv failed [%d]: %s\n", + ret, strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + ret = sdap_autofs_setautomntent_save(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not save automount map\n"); + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +static errno_t +sdap_autofs_setautomntent_save(struct tevent_req *req) +{ + struct sdap_autofs_setautomntent_state *state = tevent_req_data(req, + struct sdap_autofs_setautomntent_state); + errno_t ret, tret; + bool in_transaction = false; + TALLOC_CTX *tmp_ctx; + struct ldb_message **entries = NULL; + size_t count; + const char *key; + const char *val; + char **sysdb_entrylist = NULL; + char **ldap_entrylist = NULL; + char **add_entries = NULL; + char **del_entries = NULL; + size_t i, j; + + hash_table_t *entry_hash = NULL; + hash_key_t hkey; + hash_value_t value; + int hret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_LIBS, + "Got %zu map entries from LDAP\n", state->entries_count); + if (state->entries_count == 0) { + /* No entries for this map in LDAP. + * We need to ensure that there are no entries + * in the sysdb either. + */ + ldap_entrylist = NULL; + } else { + ldap_entrylist = talloc_array(tmp_ctx, char *, + state->entries_count+1); + if (!ldap_entrylist) { + ret = ENOMEM; + goto done; + } + + ret = sss_hash_create(state, 0, &entry_hash); + if (ret) { + goto done; + } + + /* Get a list of the map members by DN */ + for (i=0, j=0; i < state->entries_count; i++) { + key = get_autofs_entry_key(state->entries[i], state->opts); + val = get_autofs_entry_value(state->entries[i], state->opts); + if (!key || !val) { + DEBUG(SSSDBG_MINOR_FAILURE, "Malformed entry, skipping\n"); + continue; + } + + ldap_entrylist[j] = sysdb_autofsentry_strdn(ldap_entrylist, + state->dom, + state->mapname, + key, val); + if (!ldap_entrylist[j]) { + ret = ENOMEM; + goto done; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = ldap_entrylist[j]; + value.type = HASH_VALUE_PTR; + value.ptr = state->entries[i]; + + hret = hash_enter(entry_hash, &hkey, &value); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + j++; + } + /* terminate array with NULL after the last retrieved entry */ + ldap_entrylist[j] = NULL; + } + + ret = sysdb_autofs_entries_by_map(tmp_ctx, state->dom, state->mapname, + &count, &entries); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "cache lookup for the map failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Got %zu map entries from sysdb\n", count); + if (count == 0) { + /* No map members for this map in sysdb currently */ + sysdb_entrylist = NULL; + } else { + sysdb_entrylist = talloc_array(state, char *, count+1); + if (!sysdb_entrylist) { + ret = ENOMEM; + goto done; + } + + /* Get a list of the map members by DN */ + for (i=0; i < count; i++) { + sysdb_entrylist[i] = talloc_strdup(sysdb_entrylist, + ldb_dn_get_linearized(entries[i]->dn)); + if (!sysdb_entrylist[i]) { + ret = ENOMEM; + goto done; + } + } + sysdb_entrylist[count] = NULL; + } + + /* Find the differences between the sysdb and LDAP lists + * Entries in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, ldap_entrylist, sysdb_entrylist, + &add_entries, &del_entries, NULL); + if (ret != EOK) goto done; + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot start sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = true; + + /* Save the map itself */ + ret = save_autofs_map(state->dom, state->opts, state->map, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs map entry [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* Create entries that don't exist yet */ + if (add_entries && add_entries[0]) { + ret = save_autofs_entries(state->dom, state->opts, + state->mapname, add_entries, + entry_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs entries [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + /* Delete entries that don't exist anymore */ + if (del_entries && del_entries[0]) { + ret = del_autofs_entries(state->dom, del_entries); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs entries [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot commit sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot cancel sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +errno_t +sdap_autofs_setautomntent_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_autofs_get_map_state { + struct sdap_id_ctx *id_ctx; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + const char *mapname; + int dp_error; +}; + +static errno_t sdap_autofs_get_map_retry(struct tevent_req *req); +static void sdap_autofs_get_map_connect_done(struct tevent_req *subreq); +static void sdap_autofs_get_map_done(struct tevent_req *subreq); + +struct tevent_req *sdap_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_get_map_state); + if (!req) { + return NULL; + } + + state->id_ctx = id_ctx; + state->opts = id_ctx->opts; + state->mapname = mapname; + state->dp_error = DP_ERR_FATAL; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_autofs_get_map_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_autofs_get_map_retry(struct tevent_req *req) +{ + struct sdap_autofs_get_map_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_connect_done, req); + + return EAGAIN; +} + +static void sdap_autofs_get_map_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + char *filter; + char *safe_mapname; + const char **attrs; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP connection successful\n"); + + ret = sss_filter_sanitize(state, state->mapname, &safe_mapname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_mobject_map[SDAP_AT_AUTOFS_MAP_NAME].name, + safe_mapname, + state->opts->autofs_mobject_map[SDAP_OC_AUTOFS_MAP].name); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + tevent_req_error(req, ret); + return; + } + + ret = build_attrs_from_map(state, state->opts->autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, NULL, &attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + tevent_req_error(req, ret); + return; + } + + subreq = sdap_search_bases_return_first_send(state, state->id_ctx->be->ev, + state->opts, sdap_id_op_handle(state->sdap_op), + state->opts->sdom->autofs_search_bases, + state->opts->autofs_mobject_map, false, + dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), + filter, attrs, NULL); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_done, req); +} + +static void sdap_autofs_get_map_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_map_state *state; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, &reply_count, + &reply); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_get_map_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (reply_count == 0) { + ret = sysdb_delete_autofsmap(state->id_ctx->be->domain, state->mapname); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs map %s [%d]: %s\n", + state->mapname, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; + } + + ret = save_autofs_map(state->id_ctx->be->domain, state->opts, reply[0], false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs map %s [%d]: %s\n", + state->mapname, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_autofs_get_map_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_autofs_get_map_state *state; + + state = tevent_req_data(req, struct sdap_autofs_get_map_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_autofs_get_entry_state { + struct sdap_id_ctx *id_ctx; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + const char *mapname; + const char *entryname; + int dp_error; +}; + +static errno_t sdap_autofs_get_entry_retry(struct tevent_req *req); +static void sdap_autofs_get_entry_connect_done(struct tevent_req *subreq); +static void sdap_autofs_get_entry_done(struct tevent_req *subreq); + +struct tevent_req *sdap_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname, + const char *entryname) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_get_entry_state); + if (!req) { + return NULL; + } + + state->id_ctx = id_ctx; + state->opts = id_ctx->opts; + state->mapname = mapname; + state->entryname = entryname; + state->dp_error = DP_ERR_FATAL; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sdap_autofs_get_entry_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_autofs_get_entry_retry(struct tevent_req *req) +{ + struct sdap_autofs_get_entry_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_connect_done, req); + + return EAGAIN; +} + +static void sdap_autofs_get_entry_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + struct ldb_message *map; + char *filter; + char *safe_entryname; + const char **attrs; + const char *base_dn; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP connection successful\n"); + + ret = sysdb_get_map_byname(state, state->id_ctx->be->domain, + state->mapname, &map); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Map %s does not exist!\n", state->mapname); + tevent_req_error(req, ret); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get map %s [%d]: %s\n", + state->mapname, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + base_dn = ldb_msg_find_attr_as_string(map, SYSDB_ORIG_DN, NULL); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get originalDN\n"); + tevent_req_error(req, ERR_INTERNAL); + return; + } + + ret = sss_filter_sanitize(state, state->entryname, &safe_entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->autofs_entry_map[SDAP_AT_AUTOFS_ENTRY_KEY].name, + safe_entryname, + state->opts->autofs_entry_map[SDAP_OC_AUTOFS_ENTRY].name); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build filter\n"); + tevent_req_error(req, ret); + return; + } + + ret = build_attrs_from_map(state, state->opts->autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, NULL, &attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build attributes from map\n"); + tevent_req_error(req, ret); + return; + } + + subreq = sdap_search_bases_return_first_send(state, state->id_ctx->be->ev, + state->opts, sdap_id_op_handle(state->sdap_op), + state->opts->sdom->autofs_search_bases, + state->opts->autofs_entry_map, false, + dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), + filter, attrs, base_dn); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_done, req); +} + +static errno_t sdap_autofs_save_entry(struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs *newentry, + const char *mapname, + const char *entryname); + +static void sdap_autofs_get_entry_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_autofs_get_entry_state *state; + struct sysdb_attrs **reply; + size_t reply_count; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, &reply_count, + &reply); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_get_entry_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* This will delete the entry if it already exist. */ + if (reply_count == 0) { + ret = sdap_autofs_save_entry(state->id_ctx->be->domain, state->opts, + NULL, state->mapname, state->entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + goto done; + } + + /* If other attribute then automountKey is in the distinguished name and + * there are multiple entries with different casing then we may get more + * then one result. */ + for (i = 0; i < reply_count; i++) { + ret = sdap_autofs_save_entry(state->id_ctx->be->domain, state->opts, + reply[i], state->mapname, + state->entryname); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } + +done: + tevent_req_done(req); + return; +} + +errno_t sdap_autofs_get_entry_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_autofs_get_entry_state *state; + + state = tevent_req_data(req, struct sdap_autofs_get_entry_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +static errno_t sdap_autofs_save_entry(struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs *newentry, + const char *mapname, + const char *entryname) +{ + bool in_transaction = false; + errno_t ret; + int tret; + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot start sysdb transaction [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + in_transaction = true; + + /* Delete existing entry to cover case where new entry has the same key + * but different automountInformation. Because the dn is created from the + * combination of key and information it would be possible to end up with + * two entries with same key but different information otherwise. + */ + ret = sysdb_del_autofsentry_by_key(domain, mapname, entryname); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot delete entry %s:%s\n", + mapname, entryname); + goto done; + } + + if (newentry != NULL) { + ret = add_autofs_entry(domain, mapname, opts, newentry, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save autofs entry %s:%s [%d]: %s\n", + mapname, entryname, ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot commit sysdb transaction [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(domain->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot cancel sysdb transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + return ret; +} diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c new file mode 100644 index 0000000..e863872 --- /dev/null +++ b/src/providers/ldap/sdap_async_connection.c @@ -0,0 +1,2386 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009 + Copyright (C) 2010, rhafer@suse.de, Novell Inc. + + 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 <unistd.h> +#include <fcntl.h> +#include <sasl/sasl.h> +#include "util/util.h" +#include "util/sss_krb5.h" +#include "util/sss_ldap.h" +#include "util/strtonum.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +#define MAX_RETRY_ATTEMPTS 1 + +/* ==Connect-to-LDAP-Server=============================================== */ + +struct sdap_rebind_proc_params { + struct sdap_options *opts; + struct sdap_handle *sh; + bool use_start_tls; +}; + +static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params); + +struct sdap_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *uri; + bool use_start_tls; + + struct sdap_op *op; + + struct sdap_msg *reply; + int result; +}; + +static void sdap_sys_connect_done(struct tevent_req *subreq); +static void sdap_connect_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *uri, + struct sockaddr *sockaddr, + socklen_t sockaddr_len, + bool use_start_tls) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_connect_state *state; + int ret; + int timeout; + + req = tevent_req_create(memctx, &state, struct sdap_connect_state); + if (!req) return NULL; + + if (uri == NULL || sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid uri or sockaddr\n"); + ret = EINVAL; + goto fail; + } + + state->reply = talloc(state, struct sdap_msg); + if (!state->reply) { + talloc_zfree(req); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->use_start_tls = use_start_tls; + + state->uri = talloc_asprintf(state, "%s", uri); + if (!state->uri) { + talloc_zfree(req); + return NULL; + } + + state->sh = sdap_handle_create(state); + if (!state->sh) { + talloc_zfree(req); + return NULL; + } + + state->sh->page_size = dp_opt_get_int(state->opts->basic, + SDAP_PAGE_SIZE); + + timeout = dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT); + + subreq = sss_ldap_init_send(state, ev, state->uri, sockaddr, + sockaddr_len, timeout); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_init_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sdap_sys_connect_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_sys_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + struct timeval tv; + int ver; + int lret = 0; + int optret; + int ret = EOK; + int msgid; + char *errmsg = NULL; + bool ldap_referrals; + const char *ldap_deref; + int ldap_deref_val; + struct sdap_rebind_proc_params *rebind_proc_params; + int sd; + bool sasl_nocanon; + const char *sasl_mech; + int sasl_minssf; + ber_len_t ber_sasl_minssf; + int sasl_maxssf; + ber_len_t ber_sasl_maxssf; + char *stat_info; + + ret = sss_ldap_init_recv(subreq, &state->sh->ldap, &sd); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_async_connect_call request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = setup_ldap_connection_callbacks(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "setup_ldap_connection_callbacks failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + goto fail; + } + + /* If sss_ldap_init_recv() does not return a valid file descriptor we have + * to assume that the connection callback will be called by internally by + * the OpenLDAP client library. */ + if (sd != -1) { + ret = sdap_call_conn_cb(state->uri, sd, state->sh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_call_conn_cb failed.\n"); + goto fail; + } + } + + /* Force ldap version to 3 */ + ver = LDAP_VERSION3; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_PROTOCOL_VERSION, &ver); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set ldap version to 3\n"); + goto fail; + } + + /* TODO: maybe this can be remove when we go async, currently we need it + * to handle EINTR during poll(). */ + ret = ldap_set_option(state->sh->ldap, LDAP_OPT_RESTART, LDAP_OPT_ON); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set restart option.\n"); + } + + /* Set Network Timeout */ + tv.tv_sec = dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT); + tv.tv_usec = 0; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set network timeout to %d\n", + dp_opt_get_int(state->opts->basic, SDAP_NETWORK_TIMEOUT)); + goto fail; + } + + /* Set Default Timeout */ + tv.tv_sec = dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT); + tv.tv_usec = 0; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_TIMEOUT, &tv); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set default timeout to %d\n", + dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT)); + goto fail; + } + + /* Set Referral chasing */ + ldap_referrals = dp_opt_get_bool(state->opts->basic, SDAP_REFERRALS); + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_REFERRALS, + (ldap_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set referral chasing to %s\n", + (ldap_referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF")); + goto fail; + } + + if (ldap_referrals) { + rebind_proc_params = talloc_zero(state->sh, + struct sdap_rebind_proc_params); + if (rebind_proc_params == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + rebind_proc_params->opts = state->opts; + rebind_proc_params->sh = state->sh; + rebind_proc_params->use_start_tls = state->use_start_tls; + + lret = ldap_set_rebind_proc(state->sh->ldap, sdap_rebind_proc, + rebind_proc_params); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_set_rebind_proc failed.\n"); + goto fail; + } + } + + /* Set alias dereferencing */ + ldap_deref = dp_opt_get_string(state->opts->basic, SDAP_DEREF); + if (ldap_deref != NULL) { + ret = deref_string_to_val(ldap_deref, &ldap_deref_val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "deref_string_to_val failed.\n"); + goto fail; + } + + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_DEREF, &ldap_deref_val); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set deref option to %d\n", ldap_deref_val); + goto fail; + } + + } + + /* Set host name canonicalization for LDAP SASL bind */ + sasl_nocanon = !dp_opt_get_bool(state->opts->basic, SDAP_SASL_CANONICALIZE); + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_NOCANON, + sasl_nocanon ? LDAP_OPT_ON : LDAP_OPT_OFF); + if (lret != LDAP_OPT_SUCCESS) { + /* Do not fail, just warn into both debug logs and syslog */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set LDAP SASL nocanon option to %s. If your system " + "is configured to use SASL, LDAP operations might fail.\n", + sasl_nocanon ? "true" : "false"); + sss_log(SSS_LOG_INFO, + "Failed to set LDAP SASL nocanon option to %s. If your system " + "is configured to use SASL, LDAP operations might fail.\n", + sasl_nocanon ? "true" : "false"); + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + if (sasl_mech != NULL) { + sasl_minssf = dp_opt_get_int(state->opts->basic, SDAP_SASL_MINSSF); + if (sasl_minssf >= 0) { + ber_sasl_minssf = (ber_len_t)sasl_minssf; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_SSF_MIN, + &ber_sasl_minssf); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set LDAP MIN SSF option " + "to %d\n", sasl_minssf); + goto fail; + } + } + + sasl_maxssf = dp_opt_get_int(state->opts->basic, SDAP_SASL_MAXSSF); + if (sasl_maxssf >= 0) { + ber_sasl_maxssf = (ber_len_t)sasl_maxssf; + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_X_SASL_SSF_MAX, + &ber_sasl_maxssf); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set LDAP MAX SSF option " + "to %d\n", sasl_maxssf); + goto fail; + } + } + } + + /* if we do not use start_tls the connection is not really connected yet + * just fake an async procedure and leave connection to the bind call */ + if (!state->use_start_tls) { + tevent_req_done(req); + return; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing START TLS\n"); + + lret = ldap_start_tls(state->sh->ldap, NULL, NULL, &msgid); + if (lret != LDAP_SUCCESS) { + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &errmsg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), + errmsg); + sss_log(SSS_LOG_ERR, "Could not start TLS. %s", errmsg); + } + else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s]\n", + sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS. " + "Check for certificate issues."); + } + goto fail; + } + + stat_info = talloc_asprintf(state, "server: [%s] START TLS", + sdap_get_server_peer_str_safe(state->sh)); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_set_connected(state->sh, state->ev); + if (ret) goto fail; + + ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, + sdap_connect_done, req, + dp_opt_get_int(state->opts->basic, SDAP_OPT_TIMEOUT), + &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto fail; + } + + return; + +fail: + if (ret) { + tevent_req_error(req, ret); + } else { + if (lret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, EIO); + } + } + return; +} + +static void sdap_connect_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + char *errmsg = NULL; + char *tlserr; + int ret; + int optret; + + if (error) { + tevent_req_error(req, error); + return; + } + + state->reply = talloc_steal(state, reply); + + ret = ldap_parse_result(state->sh->ldap, state->reply->msg, + &state->result, NULL, &errmsg, NULL, NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d)\n", sdap_op_get_msgid(state->op)); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "START TLS result: %s(%d), %s\n", + sss_ldap_err2string(state->result), state->result, errmsg); + ldap_memfree(errmsg); + + if (ldap_tls_inplace(state->sh->ldap)) { + DEBUG(SSSDBG_TRACE_ALL, "SSL/TLS handler already in place.\n"); + tevent_req_done(req); + return; + } + +/* FIXME: take care that ldap_install_tls might block */ + ret = ldap_install_tls(state->sh->ldap); + if (ret != LDAP_SUCCESS) { + + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &tlserr); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s] [%s]\n", + sss_ldap_err2string(ret), + tlserr); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. %s", tlserr); + } + else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s]\n", + sss_ldap_err2string(ret)); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. " + "Check for certificate issues."); + } + + state->result = ret; + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +int sdap_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh) +{ + struct sdap_connect_state *state = tevent_req_data(req, + struct sdap_connect_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *sh = talloc_steal(memctx, state->sh); + if (!*sh) { + return ENOMEM; + } + return EOK; +} + +struct sdap_connect_host_state { + struct tevent_context *ev; + struct sdap_options *opts; + char *uri; + char *protocol; + char *host; + int port; + bool use_start_tls; + + struct sdap_handle *sh; +}; + +static void sdap_connect_host_resolv_done(struct tevent_req *subreq); +static void sdap_connect_host_done(struct tevent_req *subreq); + +struct tevent_req *sdap_connect_host_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct resolv_ctx *resolv_ctx, + enum restrict_family family_order, + enum host_database *host_db, + const char *protocol, + const char *host, + int port, + bool use_start_tls) +{ + struct sdap_connect_host_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_connect_host_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->port = port; + state->use_start_tls = use_start_tls; + + state->protocol = talloc_strdup(state, protocol); + if (state->protocol == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->host = talloc_strdup(state, host); + if (state->host == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->uri = talloc_asprintf(state, "%s://%s:%d", protocol, host, port); + if (state->uri == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host %s\n", host); + + subreq = resolv_gethostbyname_send(state, state->ev, resolv_ctx, + host, family_order, host_db); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_connect_host_resolv_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void sdap_connect_host_resolv_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_connect_host_state *state = NULL; + struct resolv_hostent *hostent = NULL; + struct sockaddr *sockaddr = NULL; + socklen_t sockaddr_len; + int status; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_connect_host_state); + + ret = resolv_gethostbyname_recv(subreq, state, &status, NULL, &hostent); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to resolve host %s: %s\n", + state->host, resolv_strerror(status)); + goto done; + } + + sockaddr = resolv_get_sockaddr_address(state, hostent, state->port, + &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "resolv_get_sockaddr_address() failed\n"); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Connecting to %s\n", state->uri); + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->uri, sockaddr, sockaddr_len, + state->use_start_tls); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_connect_host_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void sdap_connect_host_done(struct tevent_req *subreq) +{ + struct sdap_connect_host_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_connect_host_state); + + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* if TLS was used, the sdap handle is already marked as connected */ + if (!state->use_start_tls) { + /* we need to mark handle as connected to allow anonymous bind */ + ret = sdap_set_connected(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_set_connected() failed\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful connection to %s\n", state->uri); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_connect_host_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sdap_handle **_sh) +{ + struct sdap_connect_host_state *state = NULL; + state = tevent_req_data(req, struct sdap_connect_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_sh = talloc_steal(mem_ctx, state->sh); + + return EOK; +} + + +/* ==Simple-Bind========================================================== */ + +struct simple_bind_state { + struct tevent_context *ev; + struct sdap_handle *sh; + const char *user_dn; + + struct sdap_op *op; + + struct sdap_msg *reply; + struct sdap_ppolicy_data *ppolicy; +}; + +static void simple_bind_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *user_dn, + struct berval *pw) +{ + struct tevent_req *req; + struct simple_bind_state *state; + int ret = EOK; + int msgid; + int ldap_err; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + char *stat_info; + + req = tevent_req_create(memctx, &state, struct simple_bind_state); + if (!req) return NULL; + + state->reply = talloc(state, struct sdap_msg); + if (!state->reply) { + talloc_zfree(req); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->user_dn = user_dn; + + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldap_control_create failed to create " + "Password Policy control.\n"); + goto fail; + } + request_controls = ctrls; + + DEBUG(SSSDBG_CONF_SETTINGS, + "Executing simple bind as: %s\n", state->user_dn); + + ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE, + pw, request_controls, NULL, &msgid); + if (ctrls[0]) ldap_control_free(ctrls[0]); + if (ret == -1 || msgid == -1) { + ret = ldap_get_option(state->sh->ldap, + LDAP_OPT_RESULT_CODE, &ldap_err); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_bind failed (couldn't get ldap error)\n"); + ret = LDAP_LOCAL_ERROR; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "ldap_sasl_bind failed (%d)[%s]\n", + ldap_err, sss_ldap_err2string(ldap_err)); + ret = ldap_err; + } + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "ldap simple bind sent, msgid = %d\n", msgid); + + if (!sh->connected) { + ret = sdap_set_connected(sh, ev); + if (ret) goto fail; + } + + stat_info = talloc_asprintf(state, "server: [%s] simple bind: [%s]", + sdap_get_server_peer_str_safe(state->sh), + state->user_dn); + if (stat_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); + } + + ret = sdap_op_add(state, ev, sh, msgid, stat_info, + simple_bind_done, req, timeout, &state->op); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); + goto fail; + } + + return req; + +fail: + if (ret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, ERR_NETWORK_IO); + } + tevent_req_post(req, ev); + return req; +} + +static void simple_bind_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct simple_bind_state *state = tevent_req_data(req, + struct simple_bind_state); + char *errmsg = NULL; + char *nval; + errno_t ret = ERR_INTERNAL; + int lret; + LDAPControl **response_controls; + int c; + ber_int_t pp_grace; + ber_int_t pp_expire; + LDAPPasswordPolicyError pp_error; + int result = LDAP_OTHER; + bool on_grace_login_limit = false; + + if (error) { + tevent_req_error(req, error); + return; + } + + state->reply = talloc_steal(state, reply); + + lret = ldap_parse_result(state->sh->ldap, state->reply->msg, + &result, NULL, &errmsg, NULL, + &response_controls, 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_parse_result failed (%d)\n", sdap_op_get_msgid(state->op)); + ret = ERR_INTERNAL; + goto done; + } + + if (result == LDAP_SUCCESS) { + ret = EOK; + } else if (result == LDAP_INVALID_CREDENTIALS + && errmsg != NULL && strstr(errmsg, "data 775,") != NULL) { + /* Value 775 is described in + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms681386%28v=vs.85%29.aspx + * for more details please see commit message. */ + ret = ERR_ACCOUNT_LOCKED; + } else { + ret = ERR_AUTH_FAILED; + } + + if (response_controls == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "Server returned no controls.\n"); + state->ppolicy = NULL; + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Server returned control [%s].\n", + response_controls[c]->ldctl_oid); + + if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) { + lret = ldap_parse_passwordpolicy_control(state->sh->ldap, + response_controls[c], + &pp_expire, &pp_grace, + &pp_error); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_parse_passwordpolicy_control failed.\n"); + ret = ERR_INTERNAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error)); + if (!state->ppolicy) + state->ppolicy = talloc_zero(state, + struct sdap_ppolicy_data); + if (state->ppolicy == NULL) { + ret = ENOMEM; + goto done; + } + state->ppolicy->grace = pp_grace; + state->ppolicy->expire = pp_expire; + if (result == LDAP_SUCCESS) { + /* We have to set the on_grace_login_limit as when going + * through the response controls 389-ds may return both + * an warning and an error (and the order is not ensured) + * for the GraceLimit: + * - [1.3.6.1.4.1.42.2.27.8.5.1] for the GraceLimit itself + * - [2.16.840.1.113730.3.4.4] for the PasswordExpired + * + * So, in order to avoid bulldozing the GraceLimit, let's + * set it to true when pp_grace >= 0 and, in the end of + * this function, just return EOK when LDAP returns the + * PasswordExpired error but the GraceLimit is still valid. + */ + on_grace_login_limit = false; + if (pp_error == PP_changeAfterReset) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password was reset. " + "User must set a new password.\n"); + ret = ERR_PASSWORD_EXPIRED; + } else if (pp_grace >= 0) { + on_grace_login_limit = true; + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired. " + "[%d] grace logins remaining.\n", + pp_grace); + } else if (pp_expire > 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password will expire in [%d] seconds.\n", + pp_expire); + } + } else if (result == LDAP_INVALID_CREDENTIALS && + pp_error == PP_passwordExpired) { + /* According to + * https://www.ietf.org/archive/id/draft-behera-ldap-password-policy-11.txt + * section 8.1.2.3.2. this condition means "No Remaining + * Grace Authentications". */ + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, grace logins exhausted.\n"); + ret = ERR_AUTH_FAILED; + } + } else if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PWEXPIRED) == 0) { + /* I haven't found a proper documentation of this control only + * the Red Hat Directory Server documentation has a short + * description in the section "Understanding Password + * Expiration Controls", e.g. + * https://access.redhat.com/documentation/en-us/red_hat_directory_server/11/html/administration_guide/understanding_password_expiration_controls + */ + if (result == LDAP_INVALID_CREDENTIALS) { + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, grace logins exhausted.\n"); + ret = ERR_AUTH_FAILED; + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Password expired, user must set a new password.\n"); + ret = ERR_PASSWORD_EXPIRED; + } + } else if (strcmp(response_controls[c]->ldctl_oid, + LDAP_CONTROL_PWEXPIRING) == 0) { + /* ignore controls with suspiciously long values */ + if (response_controls[c]->ldctl_value.bv_len > 32) { + continue; + } + + if (!state->ppolicy) { + state->ppolicy = talloc(state, struct sdap_ppolicy_data); + } + + if (state->ppolicy == NULL) { + ret = ENOMEM; + goto done; + } + /* ensure that bv_val is a null-terminated string */ + nval = talloc_strndup(NULL, + response_controls[c]->ldctl_value.bv_val, + response_controls[c]->ldctl_value.bv_len); + if (nval == NULL) { + ret = ENOMEM; + goto done; + } + state->ppolicy->expire = strtouint32(nval, NULL, 10); + lret = errno; + talloc_zfree(nval); + if (lret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Couldn't convert control response " + "to an integer [%s].\n", strerror(lret)); + ret = ERR_INTERNAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Password will expire in [%d] seconds.\n", + state->ppolicy->expire); + } + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Bind result: %s(%d), %s\n", + sss_ldap_err2string(result), result, + errmsg ? errmsg : "no errmsg set"); + + if (result != LDAP_SUCCESS && ret == EOK) { + ret = ERR_AUTH_FAILED; + } + + if (ret == ERR_PASSWORD_EXPIRED && on_grace_login_limit) { + ret = EOK; + } + +done: + ldap_controls_free(response_controls); + ldap_memfree(errmsg); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t simple_bind_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy) +{ + struct simple_bind_state *state = tevent_req_data(req, + struct simple_bind_state); + + if (ppolicy != NULL) { + *ppolicy = talloc_steal(memctx, state->ppolicy); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==SASL-Bind============================================================ */ + +struct sasl_bind_state { + struct tevent_context *ev; + struct sdap_handle *sh; + + const char *sasl_mech; + const char *sasl_user; + struct berval *sasl_cred; +}; + +static int sdap_sasl_interact(LDAP *ld, unsigned flags, + void *defaults, void *interact); + +static struct tevent_req *sasl_bind_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + struct berval *sasl_cred) +{ + struct tevent_req *req; + struct sasl_bind_state *state; + int ret = EOK; + int optret; + char *diag_msg = NULL; + + req = tevent_req_create(memctx, &state, struct sasl_bind_state); + if (!req) return NULL; + + state->ev = ev; + state->sh = sh; + state->sasl_mech = sasl_mech; + state->sasl_user = sasl_user; + state->sasl_cred = sasl_cred; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing sasl bind mech: %s, user: %s\n", + sasl_mech, sasl_user); + + /* FIXME: Warning, this is a sync call! + * No async variant exist in openldap libraries yet */ + + if (state->sh == NULL || state->sh->ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP search while not connected.\n"); + ret = ERR_NETWORK_IO; + goto fail; + } + + ret = ldap_sasl_interactive_bind_s(state->sh->ldap, NULL, + sasl_mech, NULL, NULL, + LDAP_SASL_QUIET, + (*sdap_sasl_interact), state); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_interactive_bind_s failed (%d)[%s]\n", + ret, sss_ldap_err2string(ret)); + + optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, + &diag_msg); + if (optret == EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Extended failure message: [%s]\n", diag_msg); + } + talloc_zfree(diag_msg); + + goto fail; + } + + if (!sh->connected) { + ret = sdap_set_connected(sh, ev); + if (ret) goto fail; + } + + /* This is a hack, relies on the fact that tevent_req_done() will always + * set the state but will not complain if no callback has been set. + * tevent_req_post() will only set the immediate event and then just call + * the async callback set by the caller right after we return using the + * state value set previously by tevent_req_done() */ + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + +fail: + if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, ERR_AUTH_FAILED); + } + tevent_req_post(req, ev); + return req; +} + +static int sdap_sasl_interact(LDAP *ld, unsigned flags, + void *defaults, void *interact) +{ + struct sasl_bind_state *state = talloc_get_type(defaults, + struct sasl_bind_state); + sasl_interact_t *in = (sasl_interact_t *)interact; + + if (!ld) return LDAP_PARAM_ERROR; + + while (in->id != SASL_CB_LIST_END) { + + switch (in->id) { + case SASL_CB_GETREALM: + case SASL_CB_USER: + case SASL_CB_PASS: + if (in->defresult) { + in->result = in->defresult; + } else { + in->result = ""; + } + in->len = strlen(in->result); + break; + case SASL_CB_AUTHNAME: + if (state->sasl_user) { + in->result = state->sasl_user; + } else if (in->defresult) { + in->result = in->defresult; + } else { + in->result = ""; + } + in->len = strlen(in->result); + break; + case SASL_CB_NOECHOPROMPT: + case SASL_CB_ECHOPROMPT: + goto fail; + } + + in++; + } + + return LDAP_SUCCESS; + +fail: + return LDAP_UNAVAILABLE; +} + +static errno_t sasl_bind_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Perform-Kinit-given-keytab-and-principal============================= */ + +struct sdap_kinit_state { + const char *keytab; + const char *principal; + const char *realm; + int timeout; + int lifetime; + + const char *krb_service_name; + struct tevent_context *ev; + struct be_ctx *be; + + struct fo_server *kdc_srv; + time_t expire_time; +}; + +static void sdap_kinit_done(struct tevent_req *subreq); +static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req); +static void sdap_kinit_kdc_resolved(struct tevent_req *subreq); + +static +struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct be_ctx *be, + struct sdap_handle *sh, + const char *krb_service_name, + int timeout, + const char *keytab, + const char *principal, + const char *realm, + bool canonicalize, + int lifetime) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_kinit_state *state; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Attempting kinit (%s, %s, %s, %d)\n", + keytab ? keytab : "default", + principal, realm, lifetime); + + if (lifetime < 0 || lifetime > INT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Ticket lifetime out of range.\n"); + return NULL; + } + + req = tevent_req_create(memctx, &state, struct sdap_kinit_state); + if (!req) return NULL; + + state->keytab = keytab; + state->principal = principal; + state->realm = realm; + state->ev = ev; + state->be = be; + state->timeout = timeout; + state->lifetime = lifetime; + state->krb_service_name = krb_service_name; + + if (canonicalize) { + ret = setenv("KRB5_CANONICALIZE", "true", 1); + } else { + ret = setenv("KRB5_CANONICALIZE", "false", 1); + } + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to set KRB5_CANONICALIZE to %s\n", + ((canonicalize)?"true":"false")); + talloc_free(req); + return NULL; + } + + subreq = sdap_kinit_next_kdc(req); + if (!subreq) { + talloc_free(req); + return NULL; + } + + return req; +} + +static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req) +{ + struct tevent_req *next_req; + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + + DEBUG(SSSDBG_TRACE_LIBS, + "Resolving next KDC for service %s\n", state->krb_service_name); + + next_req = be_resolve_server_send(state, state->ev, + state->be, + state->krb_service_name, + state->kdc_srv == NULL ? true : false); + if (next_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_resolve_server_send failed.\n"); + return NULL; + } + tevent_req_set_callback(next_req, sdap_kinit_kdc_resolved, req); + + return next_req; +} + +static void sdap_kinit_kdc_resolved(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + struct tevent_req *tgtreq; + int ret; + + ret = be_resolve_server_recv(subreq, state, &state->kdc_srv); + talloc_zfree(subreq); + if (ret != EOK) { + /* all servers have been tried and none + * was found good, go offline */ + tevent_req_error(req, ERR_NETWORK_IO); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "KDC resolved, attempting to get TGT...\n"); + + tgtreq = sdap_get_tgt_send(state, state->ev, state->realm, + state->principal, state->keytab, + state->lifetime, state->timeout); + if (!tgtreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(tgtreq, sdap_kinit_done, req); +} + +static void sdap_kinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + + int ret; + int result; + char *ccname = NULL; + time_t expire_time = 0; + krb5_error_code kerr; + struct tevent_req *nextreq; + + ret = sdap_get_tgt_recv(subreq, state, &result, + &kerr, &ccname, &expire_time); + talloc_zfree(subreq); + if (ret == ETIMEDOUT) { + /* The child didn't even respond. Perhaps the KDC is too busy, + * retry with another KDC */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Communication with KDC timed out, trying the next one\n"); + be_fo_set_port_status(state->be, state->krb_service_name, + state->kdc_srv, PORT_NOT_WORKING); + nextreq = sdap_kinit_next_kdc(req); + if (!nextreq) { + tevent_req_error(req, ENOMEM); + } + return; + } else if (ret != EOK) { + /* A severe error while executing the child. Abort the operation. */ + DEBUG(SSSDBG_CRIT_FAILURE, + "child failed (%d [%s])\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (result == EOK) { + ret = setenv("KRB5CCNAME", ccname, 1); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to set env. variable KRB5CCNAME!\n"); + tevent_req_error(req, ERR_AUTH_FAILED); + return; + } + + state->expire_time = expire_time; + tevent_req_done(req); + return; + } else { + if (kerr == KRB5_KDC_UNREACH) { + be_fo_set_port_status(state->be, state->krb_service_name, + state->kdc_srv, PORT_NOT_WORKING); + nextreq = sdap_kinit_next_kdc(req); + if (!nextreq) { + tevent_req_error(req, ENOMEM); + } + return; + } + + } + if (result == EFAULT || result == EIO || result == EPERM) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Could not get TGT from server %s: %d [%s]\n", + state->kdc_srv ? fo_get_server_name(state->kdc_srv) : "NULL", + result, sss_strerror(result)); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Could not get TGT: %d [%s]\n", result, sss_strerror(result)); + } + tevent_req_error(req, ERR_AUTH_FAILED); +} + +static errno_t sdap_kinit_recv(struct tevent_req *req, + time_t *expire_time) +{ + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + enum tevent_req_state tstate; + uint64_t err_uint64 = ERR_INTERNAL; + errno_t err; + + if (tevent_req_is_error(req, &tstate, &err_uint64)) { + if (tstate != TEVENT_REQ_IN_PROGRESS) { + err = (errno_t)err_uint64; + if (err == EOK) { + return ERR_INTERNAL; + } + return err; + } + } + + *expire_time = state->expire_time; + return EOK; +} + + +/* ==Authenticaticate-User-by-DN========================================== */ + +struct sdap_auth_state { + struct sdap_ppolicy_data *ppolicy; + bool is_sasl; +}; + +static void sdap_auth_done(struct tevent_req *subreq); + +/* TODO: handle sasl_cred */ +struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + const char *sasl_mech, + const char *sasl_user, + const char *user_dn, + struct sss_auth_token *authtok, + int simple_bind_timeout) +{ + struct tevent_req *req, *subreq; + struct sdap_auth_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_auth_state); + if (!req) return NULL; + + if (sasl_mech) { + state->is_sasl = true; + subreq = sasl_bind_send(state, ev, sh, sasl_mech, sasl_user, NULL); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + } else { + const char *password = NULL; + struct berval pw; + size_t pwlen; + errno_t ret; + + /* this code doesn't make copies of password + * but only uses pointer to authtok internals + */ + ret = sss_authtok_get_password(authtok, &password, &pwlen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot parse authtok.\n"); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + /* Treat a zero-length password as a failure */ + if (*password == '\0') { + tevent_req_error(req, ENOENT); + return tevent_req_post(req, ev); + } + pw.bv_val = discard_const(password); + pw.bv_len = pwlen; + + state->is_sasl = false; + subreq = simple_bind_send(state, ev, sh, simple_bind_timeout, user_dn, &pw); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + } + + tevent_req_set_callback(subreq, sdap_auth_done, req); + return req; +} + +static int sdap_auth_get_authtok(const char *authtok_type, + struct dp_opt_blob authtok, + struct berval *pw) +{ + if (!authtok_type) return EOK; + if (!pw) return EINVAL; + + if (strcasecmp(authtok_type,"password") == 0) { + pw->bv_len = authtok.length; + pw->bv_val = (char *) authtok.data; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Authentication token type [%s] is not supported\n", + authtok_type); + return EINVAL; + } + + return EOK; +} + +static void sdap_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_auth_state *state = tevent_req_data(req, + struct sdap_auth_state); + int ret; + + if (state->is_sasl) { + ret = sasl_bind_recv(subreq); + state->ppolicy = NULL; + } else { + ret = simple_bind_recv(subreq, state, &state->ppolicy); + } + + if (tevent_req_error(req, ret)) { + return; + } + + tevent_req_done(req); +} + +errno_t sdap_auth_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_ppolicy_data **ppolicy) +{ + struct sdap_auth_state *state = tevent_req_data(req, + struct sdap_auth_state); + + if (ppolicy != NULL) { + *ppolicy = talloc_steal(memctx, state->ppolicy); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Client connect============================================ */ + +struct sdap_cli_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_service *service; + struct be_ctx *be; + + bool use_rootdse; + struct sysdb_attrs *rootdse; + + struct sdap_handle *sh; + + struct fo_server *srv; + + struct sdap_server_opts *srv_opts; + + enum connect_tls force_tls; + bool do_auth; + bool use_tls; + + int retry_attempts; +}; + +static int sdap_cli_resolve_next(struct tevent_req *req); +static void sdap_cli_resolve_done(struct tevent_req *subreq); +static void sdap_cli_connect_done(struct tevent_req *subreq); +static void sdap_cli_rootdse_step(struct tevent_req *req); +static void sdap_cli_rootdse_done(struct tevent_req *subreq); +static errno_t sdap_cli_use_rootdse(struct sdap_cli_connect_state *state); +static void sdap_cli_kinit_step(struct tevent_req *req); +static void sdap_cli_kinit_done(struct tevent_req *subreq); +static void sdap_cli_auth_step(struct tevent_req *req); +static void sdap_cli_auth_done(struct tevent_req *subreq); +static errno_t sdap_cli_auth_reconnect(struct tevent_req *subreq); +static void sdap_cli_auth_reconnect_done(struct tevent_req *subreq); +static void sdap_cli_rootdse_auth_done(struct tevent_req *subreq); + +static errno_t +decide_tls_usage(enum connect_tls force_tls, struct dp_option *basic, + const char *uri, bool *_use_tls) +{ + bool use_tls = true; + + switch (force_tls) { + case CON_TLS_DFL: + use_tls = dp_opt_get_bool(basic, SDAP_ID_TLS); + break; + case CON_TLS_ON: + use_tls = true; + break; + case CON_TLS_OFF: + use_tls = false; + break; + default: + return EINVAL; + break; + } + + if (use_tls && sdap_is_secure_uri(uri)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] is a secure channel. No need to run START_TLS\n", uri); + use_tls = false; + } + + *_use_tls = use_tls; + return EOK; +} + +struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct be_ctx *be, + struct sdap_service *service, + bool skip_rootdse, + enum connect_tls force_tls, + bool skip_auth) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *req; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_cli_connect_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->service = service; + state->be = be; + state->srv = NULL; + state->srv_opts = NULL; + state->use_rootdse = !skip_rootdse; + state->force_tls = force_tls; + state->do_auth = !skip_auth; + + ret = sdap_cli_resolve_next(req); + if (ret) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static int sdap_cli_resolve_next(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + + /* Before stepping to next server destroy any connection from previous attempt */ + talloc_zfree(state->sh); + + /* NOTE: this call may cause service->uri to be refreshed + * with a new valid server. Do not use service->uri before */ + subreq = be_resolve_server_send(state, state->ev, + state->be, state->service->name, + state->srv == NULL ? true : false); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_cli_resolve_done, req); + return EOK; +} + +static void sdap_cli_resolve_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + int ret; + + ret = be_resolve_server_recv(subreq, state, &state->srv); + talloc_zfree(subreq); + if (ret) { + state->srv = NULL; + /* all servers have been tried and none + * was found good, go offline */ + tevent_req_error(req, EIO); + return; + } + + ret = decide_tls_usage(state->force_tls, state->opts->basic, + state->service->uri, &state->use_tls); + + if (ret != EOK) { + tevent_req_error(req, EINVAL); + return; + } + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_connect_done, req); +} + +static void sdap_cli_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + const char *sasl_mech; + int ret; + + talloc_zfree(state->sh); + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret == ERR_TLS_HANDSHAKE_INTERRUPTED && + state->retry_attempts < MAX_RETRY_ATTEMPTS) { + DEBUG(SSSDBG_OP_FAILURE, + "TLS handshake was interruped, provider will retry\n"); + state->retry_attempts++; + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_cli_connect_done, req); + return; + } else if (ret != EOK) { + state->retry_attempts = 0; + /* retry another server */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + + return; + } + state->retry_attempts = 0; + + if (state->use_rootdse) { + /* fetch the rootDSE this time */ + sdap_cli_rootdse_step(req); + return; + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + + if (state->do_auth && sasl_mech && state->use_rootdse) { + /* check if server claims to support the configured SASL MECH */ + if (!sdap_is_sasl_mech_supported(state->sh, sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (state->do_auth && sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) { + sdap_cli_kinit_step(req); + return; + } + } + + sdap_cli_auth_step(req); +} + +static void sdap_cli_rootdse_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + int ret; + + subreq = sdap_get_rootdse_send(state, state->ev, state->opts, state->sh); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_rootdse_done, req); + + if (!state->sh->connected) { + /* this rootdse search is performed before we actually do a bind, + * so we need to set up the callbacks or we will never get notified + * of a reply */ + + ret = sdap_set_connected(state->sh, state->ev); + if (ret) { + tevent_req_error(req, ret); + } + } +} + +static void sdap_cli_rootdse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + const char *sasl_mech; + int ret; + + ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse); + talloc_zfree(subreq); + if (ret) { + if (ret == ETIMEDOUT) { /* retry another server */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* RootDSE was not available on + * the server. + * Continue, and just assume that the + * features requested by the config + * work properly. + */ + state->rootdse = NULL; + } + + + ret = sdap_cli_use_rootdse(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_cli_use_rootdse failed\n"); + tevent_req_error(req, ret); + return; + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + + if (state->do_auth && sasl_mech && state->rootdse) { + /* check if server claims to support the configured SASL MECH */ + if (!sdap_is_sasl_mech_supported(state->sh, sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (state->do_auth && sasl_mech && sdap_sasl_mech_needs_kinit(sasl_mech)) { + if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) { + sdap_cli_kinit_step(req); + return; + } + } + + sdap_cli_auth_step(req); +} + +static errno_t sdap_cli_use_rootdse(struct sdap_cli_connect_state *state) +{ + errno_t ret; + + if (state->rootdse) { + /* save rootdse data about supported features */ + ret = sdap_set_rootdse_supported_lists(state->rootdse, state->sh); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_set_rootdse_supported_lists failed\n"); + return ret; + } + + ret = sdap_set_config_options_with_rootdse(state->rootdse, state->opts, + state->opts->sdom); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_set_config_options_with_rootdse failed.\n"); + return ret; + } + + } + + ret = sdap_get_server_opts_from_rootdse(state, + state->service->uri, + state->rootdse, + state->opts, &state->srv_opts); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_server_opts_from_rootdse failed.\n"); + return ret; + } + + return EOK; +} + +static void sdap_cli_kinit_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + + subreq = sdap_kinit_send(state, state->ev, + state->be, + state->sh, + state->service->kinit_service_name, + dp_opt_get_int(state->opts->basic, + SDAP_OPT_TIMEOUT), + dp_opt_get_string(state->opts->basic, + SDAP_KRB5_KEYTAB), + dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID), + sdap_gssapi_realm(state->opts->basic), + dp_opt_get_bool(state->opts->basic, + SDAP_KRB5_CANONICALIZE), + dp_opt_get_int(state->opts->basic, + SDAP_KRB5_TICKET_LIFETIME)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_kinit_done, req); +} + +static void sdap_cli_kinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + time_t expire_time = 0; + errno_t ret; + + ret = sdap_kinit_recv(subreq, &expire_time); + talloc_zfree(subreq); + if (ret != EOK) { + /* We're not able to authenticate to the LDAP server. + * There's not much we can do except for going offline */ + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot get a TGT: ret [%d](%s)\n", ret, sss_strerror(ret)); + tevent_req_error(req, EACCES); + return; + } + state->sh->expire_time = expire_time; + + sdap_cli_auth_step(req); +} + +static void sdap_cli_auth_step(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + struct tevent_req *subreq; + time_t now; + int expire_timeout; + int expire_offset; + + const char *sasl_mech = dp_opt_get_string(state->opts->basic, + SDAP_SASL_MECH); + const char *user_dn = dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_BIND_DN); + const char *authtok_type; + struct dp_opt_blob authtok_blob; + struct sss_auth_token *authtok; + errno_t ret; + + /* It's possible that connection was terminated by server (e.g. #2435), + to overcome this try to connect again. */ + if (state->sh == NULL || !state->sh->connected) { + DEBUG(SSSDBG_TRACE_FUNC, "No connection available. " + "Trying to reconnect.\n"); + ret = sdap_cli_auth_reconnect(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_cli_auth_reconnect failed: %d:[%s]\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + /* Set the LDAP expiration time + * If SASL has already set it, use the sooner of the two + */ + now = time(NULL); + expire_timeout = dp_opt_get_int(state->opts->basic, SDAP_EXPIRE_TIMEOUT); + expire_offset = dp_opt_get_int(state->opts->basic, SDAP_EXPIRE_OFFSET); + if (expire_offset > 0) { + expire_timeout += sss_rand() % (expire_offset + 1); + } else if (expire_offset < 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negative value [%d] of ldap_connection_expire_offset " + "is not allowed.\n", + expire_offset); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "expire timeout is %d\n", expire_timeout); + if (!state->sh->expire_time + || (state->sh->expire_time > (now + expire_timeout))) { + state->sh->expire_time = now + expire_timeout; + DEBUG(SSSDBG_TRACE_LIBS, + "the connection will expire at %"SPRItime"\n", + state->sh->expire_time); + } + + if (!state->do_auth || + (sasl_mech == NULL && user_dn == NULL)) { + DEBUG(SSSDBG_TRACE_LIBS, + "No authentication requested or SASL auth forced off\n"); + tevent_req_done(req); + return; + } + + authtok_type = dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_AUTHTOK_TYPE); + authtok = sss_authtok_new(state); + if(authtok == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + if (authtok_type != NULL) { + if (strcasecmp(authtok_type, "password") != 0) { + DEBUG(SSSDBG_TRACE_LIBS, "Invalid authtoken type\n"); + tevent_req_error(req, EINVAL); + return; + } + + authtok_blob = dp_opt_get_blob(state->opts->basic, + SDAP_DEFAULT_AUTHTOK); + if (authtok_blob.data) { + ret = sss_authtok_set_password(authtok, + (const char *)authtok_blob.data, + authtok_blob.length); + if (ret) { + tevent_req_error(req, ret); + return; + } + } + } + + subreq = sdap_auth_send(state, state->ev, + state->sh, sasl_mech, + dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID), + user_dn, authtok, + dp_opt_get_int(state->opts->basic, + SDAP_OPT_TIMEOUT)); + talloc_free(authtok); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_auth_done, req); +} + +static errno_t sdap_cli_auth_reconnect(struct tevent_req *req) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_cli_connect_state); + + ret = decide_tls_usage(state->force_tls, state->opts->basic, + state->service->uri, &state->use_tls); + if (ret != EOK) { + goto done; + } + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + state->service->sockaddr, + state->service->sockaddr_len, + state->use_tls); + + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_cli_auth_reconnect_done, req); + + ret = EOK; + +done: + return ret; +} + +static void sdap_cli_auth_reconnect_done(struct tevent_req *subreq) +{ + struct sdap_cli_connect_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_cli_connect_state); + + talloc_zfree(state->sh); + + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* if TLS was used, the sdap handle is already marked as connected */ + if (!state->use_tls) { + /* we need to mark handle as connected to allow anonymous bind */ + ret = sdap_set_connected(state->sh, state->ev); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_set_connected() failed.\n"); + goto done; + } + } + + /* End request if reconnecting failed to avoid endless loop */ + if (state->sh == NULL || !state->sh->connected) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to reconnect.\n"); + ret = EIO; + goto done; + } + + sdap_cli_auth_step(req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void sdap_cli_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + int ret; + + ret = sdap_auth_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (state->use_rootdse && !state->rootdse) { + /* We weren't able to read rootDSE during unauthenticated bind. + * Let's try again now that we are authenticated */ + subreq = sdap_get_rootdse_send(state, state->ev, + state->opts, state->sh); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_rootdse_auth_done, req); + return; + } + + tevent_req_done(req); +} + +static void sdap_cli_rootdse_auth_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + + ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse); + talloc_zfree(subreq); + if (ret) { + if (ret == ETIMEDOUT) { + /* The server we authenticated against went down. Retry another + * one */ + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + ret = sdap_cli_resolve_next(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* RootDSE was not available on + * the server. + * Continue, and just assume that the + * features requested by the config + * work properly. + */ + state->use_rootdse = false; + state->rootdse = NULL; + tevent_req_done(req); + return; + } + + /* We were able to get rootDSE after authentication */ + ret = sdap_cli_use_rootdse(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_cli_use_rootdse failed\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + bool *can_retry, + struct sdap_handle **gsh, + struct sdap_server_opts **srv_opts) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + enum tevent_req_state tstate; + uint64_t err_uint64; + int err; + + if (can_retry) { + *can_retry = true; + } + if (tevent_req_is_error(req, &tstate, &err_uint64)) { + /* mark the server as bad if connection failed */ + if (state->srv) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish connection " + "[%"PRIu64"]: %s\n", err_uint64, sss_strerror(err_uint64)); + + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_NOT_WORKING); + } else { + if (can_retry) { + *can_retry = false; + } + } + + if (tstate == TEVENT_REQ_USER_ERROR) { + err = (int)err_uint64; + if (err == EOK) { + return EINVAL; + } + return err; + } + return EIO; + } else if (state->srv) { + DEBUG(SSSDBG_TRACE_FUNC, "Connection established.\n"); + + be_fo_set_port_status(state->be, state->service->name, + state->srv, PORT_WORKING); + } + + if (gsh) { + if (*gsh) { + talloc_zfree(*gsh); + } + *gsh = talloc_steal(memctx, state->sh); + if (!*gsh) { + return ENOMEM; + } + } else { + talloc_zfree(state->sh); + } + + if (srv_opts) { + *srv_opts = talloc_steal(memctx, state->srv_opts); + } + + return EOK; +} + +static int synchronous_tls_setup(LDAP *ldap) +{ + int lret; + int optret; + int ldaperr; + int msgid; + char *errmsg = NULL; + char *diag_msg; + LDAPMessage *result = NULL; + TALLOC_CTX *tmp_ctx; + + DEBUG(SSSDBG_CONF_SETTINGS, "Executing START TLS\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return LDAP_NO_MEMORY; + + lret = ldap_start_tls(ldap, NULL, NULL, &msgid); + if (lret != LDAP_SUCCESS) { + optret = sss_ldap_get_diagnostic_msg(tmp_ctx, ldap, &diag_msg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_start_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), diag_msg); + sss_log(SSS_LOG_ERR, "Could not start TLS. %s", diag_msg); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_start_tls failed: [%s]\n", sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS. " + "Check for certificate issues."); + } + goto done; + } + + lret = ldap_result(ldap, msgid, 1, NULL, &result); + if (lret != LDAP_RES_EXTENDED) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected ldap_result, expected [%lu] got [%d].\n", + LDAP_RES_EXTENDED, lret); + lret = LDAP_PARAM_ERROR; + goto done; + } + + lret = ldap_parse_result(ldap, result, &ldaperr, NULL, &errmsg, NULL, NULL, + 0); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "ldap_parse_result failed (%d) [%d][%s]\n", msgid, lret, + sss_ldap_err2string(lret)); + goto done; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "START TLS result: %s(%d), %s\n", + sss_ldap_err2string(ldaperr), ldaperr, errmsg); + + if (ldap_tls_inplace(ldap)) { + DEBUG(SSSDBG_TRACE_ALL, "SSL/TLS handler already in place.\n"); + lret = LDAP_SUCCESS; + goto done; + } + + lret = ldap_install_tls(ldap); + if (lret != LDAP_SUCCESS) { + + optret = sss_ldap_get_diagnostic_msg(tmp_ctx, ldap, &diag_msg); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), diag_msg); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. %s", diag_msg); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_install_tls failed: [%s]\n", + sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. " + "Check for certificate issues."); + } + + goto done; + } + + lret = LDAP_SUCCESS; +done: + if (result) ldap_msgfree(result); + if (errmsg) ldap_memfree(errmsg); + talloc_zfree(tmp_ctx); + return lret; +} + +static int sdap_rebind_proc(LDAP *ldap, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params) +{ + struct sdap_rebind_proc_params *p = talloc_get_type(params, + struct sdap_rebind_proc_params); + const char *sasl_mech; + const char *user_dn; + struct berval password = {0, NULL}; + LDAPControl **request_controls = NULL; + LDAPControl *ctrls[2] = { NULL, NULL }; + TALLOC_CTX *tmp_ctx = NULL; + struct sasl_bind_state *sasl_bind_state; + int ret; + + if (ldap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Trying LDAP rebind while not connected.\n"); + return ERR_NETWORK_IO; + } + + if (p->use_start_tls) { + ret = synchronous_tls_setup(ldap); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "synchronous_tls_setup failed.\n"); + return ret; + } + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return LDAP_NO_MEMORY; + } + + sasl_mech = dp_opt_get_string(p->opts->basic, SDAP_SASL_MECH); + + if (sasl_mech == NULL) { + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &ctrls[0]); + if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ldap_control_create failed to create " + "Password Policy control.\n"); + goto done; + } + request_controls = ctrls; + + user_dn = dp_opt_get_string(p->opts->basic, SDAP_DEFAULT_BIND_DN); + if (user_dn != NULL) { + /* this code doesn't make copies of password + * but only keeps pointer to opts internals + */ + ret = sdap_auth_get_authtok(dp_opt_get_string(p->opts->basic, + SDAP_DEFAULT_AUTHTOK_TYPE), + dp_opt_get_blob(p->opts->basic, + SDAP_DEFAULT_AUTHTOK), + &password); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_auth_get_authtok failed.\n"); + ret = LDAP_LOCAL_ERROR; + goto done; + } + } + + ret = ldap_sasl_bind_s(ldap, user_dn, LDAP_SASL_SIMPLE, &password, + request_controls, NULL, NULL); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_bind_s failed (%d)[%s]\n", ret, + sss_ldap_err2string(ret)); + } + } else { + sasl_bind_state = talloc_zero(tmp_ctx, struct sasl_bind_state); + if (sasl_bind_state == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = LDAP_NO_MEMORY; + goto done; + } + sasl_bind_state->sasl_user = dp_opt_get_string(p->opts->basic, + SDAP_SASL_AUTHID); + ret = ldap_sasl_interactive_bind_s(ldap, NULL, + sasl_mech, NULL, NULL, + LDAP_SASL_QUIET, + (*sdap_sasl_interact), + sasl_bind_state); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_sasl_interactive_bind_s failed (%d)[%s]\n", ret, + sss_ldap_err2string(ret)); + } + } + + DEBUG(SSSDBG_TRACE_LIBS, "%s bind to [%s].\n", + (ret == LDAP_SUCCESS ? "Successfully" : "Failed to"), url); + +done: + if (ctrls[0]) ldap_control_free(ctrls[0]); + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c new file mode 100644 index 0000000..44cec84 --- /dev/null +++ b/src/providers/ldap/sdap_async_enum.c @@ -0,0 +1,773 @@ +/* + SSSD + + LDAP Enumeration Module + + Authors: + Simo Sorce <ssorce@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 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 <errno.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_enum.h" +#include "providers/ldap/sdap_idmap.h" + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge); +static errno_t enum_users_recv(struct tevent_req *req); + +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge); +static errno_t enum_groups_recv(struct tevent_req *req); + +/* ==Enumeration-Request-with-connections=================================== */ +struct sdap_dom_enum_ex_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + + struct sdap_id_conn_ctx *user_conn; + struct sdap_id_conn_ctx *group_conn; + struct sdap_id_conn_ctx *svc_conn; + struct sdap_id_op *user_op; + struct sdap_id_op *group_op; + struct sdap_id_op *svc_op; + + bool purge; +}; + +static errno_t sdap_dom_enum_ex_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb); +static bool sdap_dom_enum_ex_connected(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq); +static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_groups(struct tevent_req *subreq); +static void sdap_dom_enum_ex_groups_done(struct tevent_req *subreq); +static void sdap_dom_enum_ex_get_svcs(struct tevent_req *subreq); +static void sdap_dom_enum_ex_svcs_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_dom_enum_ex_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *user_conn, + struct sdap_id_conn_ctx *group_conn, + struct sdap_id_conn_ctx *svc_conn) +{ + struct tevent_req *req; + struct sdap_dom_enum_ex_state *state; + int t; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct sdap_dom_enum_ex_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sdom = sdom; + state->user_conn = user_conn; + state->group_conn = group_conn; + state->svc_conn = svc_conn; + ctx->last_enum = tevent_timeval_current(); + + t = dp_opt_get_int(ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if ((ctx->last_purge.tv_sec + t) < ctx->last_enum.tv_sec) { + state->purge = true; + } + + state->user_op = sdap_id_op_create(state, user_conn->conn_cache); + if (state->user_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for users\n"); + ret = EIO; + goto fail; + } + + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_users); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_dom_enum_ex_retry failed\n"); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_dom_enum_ex_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb) +{ + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + struct tevent_req *subreq; + errno_t ret; + + subreq = sdap_id_op_connect_send(op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d\n", ret); + return ret; + } + + tevent_req_set_callback(subreq, tcb, req); + return EOK; +} + +static bool sdap_dom_enum_ex_connected(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return false; + } + + return true; +} + +static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_users_send(state, state->ev, + state->ctx, state->sdom, + state->user_op, state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_users_done, req); +} + +static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + errno_t ret; + int dp_error; + + ret = enum_users_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->user_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_users); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "User enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->group_op = sdap_id_op_create(state, state->group_conn->conn_cache); + if (state->group_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for groups\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_enum_ex_retry(req, state->group_op, + sdap_dom_enum_ex_get_groups); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Continues to sdap_dom_enum_ex_get_groups */ +} + +static void sdap_dom_enum_ex_get_groups(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_groups_send(state, state->ev, state->ctx, + state->sdom, + state->group_op, state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_groups_done, req); +} + +static void sdap_dom_enum_ex_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + int ret; + int dp_error; + + ret = enum_groups_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->group_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->group_op, + sdap_dom_enum_ex_get_groups); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "Group enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + + state->svc_op = sdap_id_op_create(state, state->svc_conn->conn_cache); + if (state->svc_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for svcs\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_enum_ex_retry(req, state->svc_op, + sdap_dom_enum_ex_get_svcs); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } +} + +static void sdap_dom_enum_ex_get_svcs(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + + if (sdap_dom_enum_ex_connected(subreq) == false) { + return; + } + + subreq = enum_services_send(state, state->ev, state->ctx, + state->svc_op, state->purge); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_dom_enum_ex_svcs_done, req); +} + +static void sdap_dom_enum_ex_svcs_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_enum_ex_state *state = tevent_req_data(req, + struct sdap_dom_enum_ex_state); + int ret; + int dp_error; + + ret = enum_services_recv(subreq); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->svc_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_enum_ex_retry(req, state->user_op, + sdap_dom_enum_ex_get_svcs); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "Service enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->sdom->dom, SYSDB_HAS_ENUMERATED_ID, + true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not mark domain as having enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + if (state->purge) { + ret = ldap_id_cleanup(state->ctx, state->sdom); + if (ret != EOK) { + /* Not fatal, worst case we'll have stale entries that would be + * removed on a subsequent online lookup + */ + DEBUG(SSSDBG_MINOR_FAILURE, "Cleanup failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + } + + tevent_req_done(req); +} + +errno_t sdap_dom_enum_ex_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Enumeration-Request==================================================== */ +struct tevent_req * +sdap_dom_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn) +{ + return sdap_dom_enum_ex_send(memctx, ev, ctx, sdom, conn, conn, conn); +} + +errno_t sdap_dom_enum_recv(struct tevent_req *req) +{ + return sdap_dom_enum_ex_recv(req); +} + +/* ==User-Enumeration===================================================== */ +struct enum_users_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + + char *filter; + const char **attrs; +}; + +static void enum_users_done(struct tevent_req *subreq); + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_users_state *state; + int ret; + bool use_mapping; + + req = tevent_req_create(memctx, &state, struct enum_users_state); + if (!req) return NULL; + + state->ev = ev; + state->sdom = sdom; + state->ctx = ctx; + state->op = op; + + use_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + /* We always want to filter on objectclass and an available name */ + state->filter = talloc_asprintf(state, + "(&(objectclass=%s)(%s=*)", + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_NAME].name); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (use_mapping) { + /* If we're ID-mapping, check for the objectSID as well */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)", + ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* We're not ID-mapping, so make sure to only get entries + * that have UID and GID + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)(%s=*)", + ctx->opts->user_map[SDAP_AT_USER_UID].name, + ctx->opts->user_map[SDAP_AT_USER_GID].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (ctx->srv_opts && ctx->srv_opts->max_user_value && !purge) { + /* If we have lastUSN available and we're not doing a full + * refresh, limit to changes with a higher entryUSN value. + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, + "(%s>=%s)(!(%s=%s))", + ctx->opts->user_map[SDAP_AT_USER_USN].name, + ctx->srv_opts->max_user_value, + ctx->opts->user_map[SDAP_AT_USER_USN].name, + ctx->srv_opts->max_user_value); + + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + } + + /* Terminate the search filter */ + state->filter = talloc_asprintf_append_buffer(state->filter, ")"); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, ctx->opts->user_map, + ctx->opts->user_map_cnt, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + /* TODO: restrict the enumerations to using a single + * search base at a time. + */ + + subreq = sdap_get_users_send(state, state->ev, + state->sdom->dom, + state->sdom->dom->sysdb, + state->ctx->opts, + state->sdom->user_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + SDAP_LOOKUP_ENUMERATE, NULL); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_users_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct enum_users_state *state = tevent_req_data(req, + struct enum_users_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_users_recv(subreq, state, &usn_value); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->ctx->srv_opts->max_user_value); + state->ctx->srv_opts->max_user_value = + talloc_steal(state->ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->ctx->srv_opts->last_usn)) { + state->ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Users higher USN value: [%s]\n", + state->ctx->srv_opts->max_user_value); + + tevent_req_done(req); +} + +static errno_t enum_users_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* =Group-Enumeration===================================================== */ +struct enum_groups_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + + char *filter; + const char **attrs; +}; + +static void enum_groups_done(struct tevent_req *subreq); + +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_op *op, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_groups_state *state; + int ret; + bool use_mapping; + bool non_posix = false; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct enum_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->sdom = sdom; + state->ctx = ctx; + state->op = op; + + if (sdom->dom->type == DOM_TYPE_APPLICATION) { + non_posix = true; + } + + use_mapping = sdap_idmap_domain_has_algorithmic_mapping( + ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + /* We always want to filter on objectclass and an available name */ + oc_list = sdap_make_oc_list(state, ctx->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s)(%s=*)", oc_list, + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (!non_posix && use_mapping) { + /* If we're ID-mapping, check for the objectSID as well */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(%s=*)", + ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name); + } else { + /* We're not ID-mapping, so make sure to only get entries + * that have a non-zero GID. + */ + state->filter = talloc_asprintf_append_buffer( + state->filter, "(&(%s=*)(!(%s=0)))", + ctx->opts->group_map[SDAP_AT_GROUP_GID].name, + ctx->opts->group_map[SDAP_AT_GROUP_GID].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + if (ctx->srv_opts && ctx->srv_opts->max_group_value && !purge) { + state->filter = talloc_asprintf_append_buffer( + state->filter, + "(%s>=%s)(!(%s=%s))", + ctx->opts->group_map[SDAP_AT_GROUP_USN].name, + ctx->srv_opts->max_group_value, + ctx->opts->group_map[SDAP_AT_GROUP_USN].name, + ctx->srv_opts->max_group_value); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + } + + /* Terminate the search filter */ + state->filter = talloc_asprintf_append_buffer(state->filter, ")"); + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP, + NULL, &state->attrs, NULL); + if (ret != EOK) goto fail; + + /* TODO: restrict the enumerations to using a single + * search base at a time. + */ + + subreq = sdap_get_groups_send(state, state->ev, + state->sdom, + state->ctx->opts, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + SDAP_LOOKUP_ENUMERATE, false); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_groups_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct enum_groups_state *state = tevent_req_data(req, + struct enum_groups_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_groups_recv(subreq, state, &usn_value); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->ctx->srv_opts->max_group_value); + state->ctx->srv_opts->max_group_value = + talloc_steal(state->ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->ctx->srv_opts->last_usn)) { + state->ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Groups higher USN value: [%s]\n", + state->ctx->srv_opts->max_group_value); + + tevent_req_done(req); +} + +static errno_t enum_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_enum.h b/src/providers/ldap/sdap_async_enum.h new file mode 100644 index 0000000..2da38f9 --- /dev/null +++ b/src/providers/ldap/sdap_async_enum.h @@ -0,0 +1,49 @@ +/* + SSSD + + LDAP Enumeration Module + + Authors: + Simo Sorce <ssorce@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 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/>. +*/ + +#ifndef _SDAP_ASYNC_ENUM_H_ +#define _SDAP_ASYNC_ENUM_H_ + +struct tevent_req * +sdap_dom_enum_ex_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *user_conn, + struct sdap_id_conn_ctx *group_conn, + struct sdap_id_conn_ctx *svc_conn); + +errno_t sdap_dom_enum_ex_recv(struct tevent_req *req); + +struct tevent_req * +sdap_dom_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn); + +errno_t sdap_dom_enum_recv(struct tevent_req *req); + +#endif /* _SDAP_ASYNC_ENUM_H_ */ diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c new file mode 100644 index 0000000..f36e5c5 --- /dev/null +++ b/src/providers/ldap/sdap_async_groups.c @@ -0,0 +1,2471 @@ +/* + SSSD + + Async LDAP Helper routines - retrieving groups + + Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009 + Copyright (C) 2010, Ralf Haferkamp <rhafer@suse.de>, Novell Inc. + Copyright (C) Jan Zeleny <jzeleny@redhat.com> - 2011 + + 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 "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" + +/* ==Group-Parsing Routines=============================================== */ + +static int sdap_find_entry_by_origDN(TALLOC_CTX *memctx, + struct sysdb_ctx *ctx, + struct sss_domain_info *domain, + const char *orig_dn, + char **_localdn, + bool *_is_group) +{ + TALLOC_CTX *tmpctx; + const char *attrs[] = {SYSDB_OBJECTCLASS, SYSDB_OBJECTCATEGORY, NULL}; + struct ldb_dn *base_dn; + char *filter; + struct ldb_message **msgs; + size_t num_msgs; + int ret; + char *sanitized_dn; + const char *objectclass; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_filter_sanitize_dn(tmpctx, orig_dn, &sanitized_dn); + if (ret != EOK) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(tmpctx, "%s=%s", SYSDB_ORIG_DN, sanitized_dn); + if (!filter) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(tmpctx, domain); + if (!base_dn) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Searching cache for [%s].\n", sanitized_dn); + ret = sysdb_search_entry(tmpctx, ctx, + base_dn, LDB_SCOPE_SUBTREE, filter, attrs, + &num_msgs, &msgs); + if (ret) { + goto done; + } + if (num_msgs != 1) { + ret = ENOENT; + goto done; + } + + *_localdn = talloc_strdup(memctx, ldb_dn_get_linearized(msgs[0]->dn)); + if (!*_localdn) { + ret = ENOENT; + goto done; + } + + if (_is_group != NULL) { + objectclass = ldb_msg_find_attr_as_string(msgs[0], SYSDB_OBJECTCATEGORY, + NULL); + if (objectclass == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "An entry without a %s?\n", + SYSDB_OBJECTCATEGORY); + ret = EINVAL; + goto done; + } + + *_is_group = strcmp(SYSDB_GROUP_CLASS, objectclass) == 0; + } + + ret = EOK; + +done: + talloc_zfree(tmpctx); + return ret; +} + +static errno_t +sdap_get_members_with_primary_gid(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + gid_t gid, char ***_localdn, size_t *_ndn) +{ + static const char *search_attrs[] = { SYSDB_NAME, NULL }; + char *filter; + struct ldb_message **msgs; + size_t count; + size_t i; + errno_t ret; + char **localdn; + + /* Don't search if the group is non-POSIX */ + if (!gid) return EOK; + + filter = talloc_asprintf(mem_ctx, "(%s=%llu)", SYSDB_GIDNUM, + (unsigned long long) gid); + if (!filter) { + return ENOMEM; + } + + ret = sysdb_search_users(mem_ctx, domain, filter, + search_attrs, &count, &msgs); + talloc_free(filter); + if (ret == ENOENT) { + *_localdn = NULL; + *_ndn = 0; + return EOK; + } else if (ret != EOK) { + return ret; + } + + localdn = talloc_array(mem_ctx, char *, count); + if (!localdn) { + talloc_free(msgs); + return ENOMEM; + } + + for (i=0; i < count; i++) { + localdn[i] = talloc_strdup(localdn, + ldb_dn_get_linearized(msgs[i]->dn)); + if (!localdn[i]) { + talloc_free(localdn); + talloc_free(msgs); + return ENOMEM; + } + } + + talloc_free(msgs); + *_localdn = localdn; + *_ndn = count; + return EOK; +} + +static errno_t +sdap_dn_by_primary_gid(TALLOC_CTX *mem_ctx, struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *domain, + struct sdap_options *opts, + char ***_dn_list, size_t *_count) +{ + gid_t gid; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(ldap_attrs, + opts->group_map[SDAP_AT_GROUP_GID].sys_name, + &gid); + if (ret == ENOENT) { + /* Non-POSIX AD group. Skip. */ + *_dn_list = NULL; + *_count = 0; + return EOK; + } else if (ret && ret != ENOENT) { + return ret; + } + + ret = sdap_get_members_with_primary_gid(mem_ctx, domain, gid, + _dn_list, _count); + if (ret) return ret; + + return EOK; +} + +static bool has_member(struct ldb_message_element *member_el, + char *member) +{ + struct ldb_val val; + + val.data = (uint8_t *) member; + val.length = strlen(member); + + /* This is bad complexity, but this loop should only be invoked in + * the very rare scenario of AD POSIX group that is primary group of + * some users but has user member attributes at the same time + */ + if (ldb_msg_find_val(member_el, &val) != NULL) { + return true; + } + + return false; +} + +static void link_pgroup_members(struct sysdb_attrs *group_attrs, + struct ldb_message_element *member_el, + char **userdns, + size_t nuserdns) +{ + int i, j; + + j = 0; + for (i=0; i < nuserdns; i++) { + if (has_member(member_el, userdns[i])) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Member %s already included, skipping\n", userdns[i]); + continue; + } + + member_el->values[member_el->num_values + j].data = (uint8_t *) \ + talloc_steal(group_attrs, userdns[i]); + member_el->values[member_el->num_values + j].length = \ + strlen(userdns[i]); + j++; + } + member_el->num_values += j; +} + +static int sdap_fill_memberships(struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + struct sysdb_ctx *ctx, + struct sss_domain_info *domain, + hash_table_t *ghosts, + struct ldb_val *values, + int num_values, + char **userdns, + size_t nuserdns) +{ + struct ldb_message_element *el; + int i, j; + int ret; + errno_t hret; + hash_key_t key; + hash_value_t value; + struct sdap_domain *sdom; + struct sysdb_ctx *member_sysdb; + struct sss_domain_info *member_dom; + + ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_get_el failed\n"); + goto done; + } + + /* Just allocate both big enough to contain all members for now */ + el->values = talloc_realloc(group_attrs, el->values, struct ldb_val, + el->num_values + num_values + nuserdns); + if (!el->values) { + DEBUG(SSSDBG_MINOR_FAILURE, "No memory to allocate group attrs\n"); + ret = ENOMEM; + goto done; + } + + j = el->num_values; + for (i = 0; i < num_values; i++) { + if (ghosts == NULL) { + hret = HASH_ERROR_KEY_NOT_FOUND; + } else { + key.type = HASH_KEY_STRING; + key.str = (char *)values[i].data; + hret = hash_lookup(ghosts, &key, &value); + } + + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + sdom = sdap_domain_get_by_dn(opts, (char *)values[i].data); + if (sdom == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Member [%s] is it out of domain " + "scope?\n", (char *)values[i].data); + member_sysdb = ctx; + member_dom = domain; + } else { + member_sysdb = sdom->dom->sysdb; + member_dom = sdom->dom; + } + + /* sync search entry with this as origDN */ + ret = sdap_find_entry_by_origDN(el->values, member_sysdb, + member_dom, (char *)values[i].data, + (char **)&el->values[j].data, + NULL); + if (ret == ENOENT) { + /* member may be outside of the configured search bases + * or out of scope of nesting limit */ + DEBUG(SSSDBG_MINOR_FAILURE, "Member [%s] was not found in " + "cache. Is it out of scope?\n", (char *)values[i].data); + continue; + } + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "'sdap_find_entry_by_origDN' failed for member [%s].\n", + (char *)values[i].data); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, " member #%d (%s): [%s]\n", + i, (char *)values[i].data, + (char *)el->values[j].data); + + el->values[j].length = strlen((char *)el->values[j].data); + j++; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "hash_lookup failed: [%d]: %s\n", hret, strerror(hret)); + ret = EFAULT; + goto done; + } + + /* If the member is in ghost table, it has + * already been processed - just skip it */ + } + el->num_values = j; + + link_pgroup_members(group_attrs, el, userdns, nuserdns); + ret = EOK; + +done: + return ret; +} + +/* ==Save-Group-Entry===================================================== */ + + /* FIXME: support non legacy */ + /* FIXME: support storing additional attributes */ + +static errno_t +sdap_store_group_with_gid(struct sss_domain_info *domain, + const char *name, + gid_t gid, + struct sysdb_attrs *group_attrs, + uint64_t cache_timeout, + bool posix_group, + time_t now) +{ + errno_t ret; + + /* make sure that non-POSIX (empty or explicit gid=0) groups have the + * gidNumber set to zero even if updating existing group */ + if (!posix_group) { + ret = sysdb_attrs_add_uint32(group_attrs, SYSDB_GIDNUM, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not set explicit GID 0 for %s\n", name); + return ret; + } + } + + ret = sysdb_store_group(domain, name, gid, group_attrs, + cache_timeout, now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not store group %s\n", name); + return ret; + } + + return ret; +} + +static errno_t +sdap_process_ghost_members(struct sysdb_attrs *attrs, + struct sdap_options *opts, + hash_table_t *ghosts, + bool populate_members, + bool store_original_member, + struct sysdb_attrs *sysdb_attrs) +{ + errno_t ret; + struct ldb_message_element *gh; + struct ldb_message_element *memberel; + struct ldb_message_element *sysdb_memberel; + struct ldb_message_element *ghostel; + size_t cnt; + int i; + int hret; + hash_key_t key; + hash_value_t value; + + ret = sysdb_attrs_get_el(attrs, SYSDB_GHOST, &gh); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading ghost attributes: [%s]\n", + strerror(ret)); + return ret; + } + + ret = sysdb_attrs_get_el_ext(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + false, &memberel); + if (ret == ENOENT) { + /* Create a dummy element with no values in order for the loop to just + * fall through and make sure the attrs array is not reallocated. + */ + memberel = talloc(attrs, struct ldb_message_element); + if (memberel == NULL) { + return ENOMEM; + } + memberel->num_values = 0; + memberel->values = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading members: [%s]\n", strerror(ret)); + return ret; + } + + if (store_original_member) { + DEBUG(SSSDBG_TRACE_FUNC, "The group has %d members\n", memberel->num_values); + for (i = 0; i < memberel->num_values; i++) { + ret = sysdb_attrs_add_string(sysdb_attrs, SYSDB_ORIG_MEMBER, + (const char *) memberel->values[i].data); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add member [%s]\n", + (const char *) memberel->values[i].data); + return ret; + } + } + } + + if (populate_members) { + ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_MEMBER, &sysdb_memberel); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading group members from group_attrs: [%s]\n", + strerror(ret)); + return ret; + } + sysdb_memberel->values = memberel->values; + sysdb_memberel->num_values = memberel->num_values; + } + + ret = sysdb_attrs_get_el(sysdb_attrs, SYSDB_GHOST, &ghostel); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error getting ghost element: [%s]\n", strerror(ret)); + return ret; + } + ghostel->values = gh->values; + ghostel->num_values = gh->num_values; + + cnt = ghostel->num_values + memberel->num_values; + DEBUG(SSSDBG_TRACE_FUNC, "Group has %zu members\n", cnt); + + /* Now process RFC2307bis ghost hash table */ + if (ghosts && cnt > 0) { + ghostel->values = talloc_realloc(sysdb_attrs, ghostel->values, + struct ldb_val, cnt); + if (ghostel->values == NULL) { + return ENOMEM; + } + + for (i = 0; i < memberel->num_values; i++) { + key.type = HASH_KEY_STRING; + key.str = (char *) memberel->values[i].data; + hret = hash_lookup(ghosts, &key, &value); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + continue; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error checking hash table: [%s]\n", + hash_error_string(hret)); + return EFAULT; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Adding ghost member for group [%s]\n", (char *) value.ptr); + ghostel->values[ghostel->num_values].data = \ + (uint8_t *) talloc_strdup(ghostel->values, value.ptr); + if (ghostel->values[ghostel->num_values].data == NULL) { + return ENOMEM; + } + ghostel->values[ghostel->num_values].length = strlen(value.ptr); + ghostel->num_values++; + } + } + + return EOK; +} + +static int sdap_save_group(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + bool populate_members, + bool store_original_member, + hash_table_t *ghosts, + char **_usn_value, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs; + const char *group_name = NULL; + gid_t gid = 0; + errno_t ret; + char *usn_value = NULL; + TALLOC_CTX *tmpctx = NULL; + bool posix_group; + bool use_id_mapping; + bool need_filter; + char *sid_str; + struct sss_domain_info *subdomain; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + group_attrs = sysdb_new_attrs(tmpctx); + if (group_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Always store SID string if available */ + ret = sdap_attrs_get_sid_str(tmpctx, opts->idmap_ctx, attrs, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + &sid_str); + if (ret == EOK) { + ret = sysdb_attrs_add_string(group_attrs, SYSDB_SID_STR, sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not add SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "objectSID: not available for group [%s].\n", + group_name); + sid_str = NULL; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not identify objectSID: [%s]\n", + sss_strerror(ret)); + sid_str = NULL; + } + + /* Always store UUID if available */ + ret = sysdb_handle_original_uuid( + opts->group_map[SDAP_AT_GROUP_UUID].def_name, + attrs, + opts->group_map[SDAP_AT_GROUP_UUID].sys_name, + group_attrs, SYSDB_UUID); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", ret, sss_strerror(ret)); + } + + /* If this object has a SID available, we will determine the correct + * domain by its SID. */ + if (sid_str != NULL) { + subdomain = sss_get_domain_by_sid_ldap_fallback(get_domains_head(dom), + sid_str); + if (subdomain) { + dom = subdomain; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "SID %s does not belong to any known " + "domain\n", sid_str); + } + } + + ret = sdap_get_group_primary_name(tmpctx, opts, attrs, dom, &group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get group name\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", group_name); + + posix_group = true; + ret = sdap_check_ad_group_type(dom, opts, attrs, group_name, + &need_filter); + if (ret != EOK) { + goto done; + } + if (need_filter) { + posix_group = false; + gid = 0; + + ret = sysdb_attrs_add_bool(group_attrs, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark group as non-POSIX!\n"); + goto done; + } + } + + if (posix_group) { + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + dom->name, + sid_str); + if (use_id_mapping) { + posix_group = true; + + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID not available, cannot map a " \ + "unix ID to group [%s].\n", group_name); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping group [%s] objectSID [%s] to unix ID\n", + group_name, sid_str); + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, &gid); + if (ret == ENOTSUP) { + /* ENOTSUP is returned if built-in SID was provided + * => do not store the group, but return EOK */ + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + + /* Store the GID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sdap_replace_id(attrs, SYSDB_GIDNUM, gid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped GID\n"); + goto done; + } + } else { + ret = sysdb_attrs_get_bool(attrs, SYSDB_POSIX, &posix_group); + if (ret == ENOENT) { + posix_group = true; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error reading posix attribute: [%s]\n", + sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "This is%s a posix group\n", (posix_group)?"":" not"); + ret = sysdb_attrs_add_bool(group_attrs, SYSDB_POSIX, posix_group); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting posix attribute: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(attrs, + opts->group_map[SDAP_AT_GROUP_GID].sys_name, + &gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "no gid provided for [%s] in domain [%s].\n", + group_name, dom->name); + ret = EINVAL; + goto done; + } + } + } + + /* check that the gid is valid for this domain */ + if (posix_group) { + if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Group [%s] filtered out! (id out of range)\n", group_name); + ret = EINVAL; + goto done; + } + /* Group ID OK */ + } + + ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN, "original DN", + group_name, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting original DN: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sdap_attrs_add_string(attrs, + opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, + "original mod-Timestamp", + group_name, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting mod timestamp: [%s]\n", + sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_USN].sys_name, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error looking up group USN: [%s]\n", + sss_strerror(ret)); + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original USN value is not available for [%s].\n", group_name); + } else { + ret = sysdb_attrs_add_string(group_attrs, + opts->group_map[SDAP_AT_GROUP_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error setting group USN: [%s]\n", + sss_strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmpctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + ret = sdap_process_ghost_members(attrs, opts, ghosts, + populate_members, store_original_member, + group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save ghost members\n"); + goto done; + } + + ret = sdap_save_all_names(group_name, attrs, dom, + SYSDB_MEMBER_GROUP, group_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save group names\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for group %s\n", group_name); + + ret = sdap_store_group_with_gid(dom, group_name, gid, group_attrs, + dom->group_timeout, + posix_group, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not store group with GID: [%s]\n", + sss_strerror(ret)); + goto done; + } + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, usn_value); + } + + talloc_steal(memctx, group_attrs); + ret = EOK; + +done: + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to save group [%s]: [%s]\n", + group_name ? group_name : "Unknown", + sss_strerror(ret)); + } + talloc_free(tmpctx); + return ret; +} + +static errno_t +are_sids_from_same_dom(const char *sid1, const char *sid2, bool *_result) +{ + size_t len_prefix_sid1; + size_t len_prefix_sid2; + char *rid1, *rid2; + bool result; + + rid1 = strrchr(sid1, '-'); + if (rid1 == NULL) { + return EINVAL; + } + + rid2 = strrchr(sid2, '-'); + if (rid2 == NULL) { + return EINVAL; + } + + len_prefix_sid1 = rid1 - sid1; + len_prefix_sid2 = rid2 - sid2; + + result = (len_prefix_sid1 == len_prefix_sid2) && + (strncmp(sid1, sid2, len_prefix_sid1) == 0); + + *_result = result; + + return EOK; +} + +static errno_t +retain_extern_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *group_name, + const char *group_sid, + char ***_userdns, + size_t *_nuserdns) +{ + TALLOC_CTX *tmp_ctx; + const char **sids, **dns; + bool same_domain; + errno_t ret; + size_t i, n; + size_t nuserdns = 0; + const char **userdns = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_get_sids_of_members(tmp_ctx, dom, group_name, &sids, &dns, &n); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, + "get_sids_of_members failed: %d [%s]\n", + ret, sss_strerror(ret)); + } + goto done; + } + + for (i=0; i < n; i++) { + ret = are_sids_from_same_dom(group_sid, sids[i], &same_domain); + if (ret == EOK && !same_domain) { + DEBUG(SSSDBG_TRACE_ALL, "extern member: %s\n", dns[i]); + nuserdns++; + userdns = talloc_realloc(tmp_ctx, userdns, const char*, nuserdns); + if (userdns == NULL) { + ret = ENOMEM; + goto done; + } + userdns[nuserdns-1] = talloc_steal(userdns, dns[i]); + } + } + *_nuserdns = nuserdns; + *_userdns = discard_const(talloc_steal(mem_ctx, userdns)); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* ==Save-Group-Members=================================================== */ + + /* FIXME: support non-legacy */ + /* FIXME: support storing additional attributes */ + +static int sdap_save_grpmem(TALLOC_CTX *memctx, + struct sysdb_ctx *ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + hash_table_t *ghosts, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs = NULL; + const char *group_sid; + const char *group_name; + char **userdns = NULL; + size_t nuserdns = 0; + struct sss_domain_info *group_dom = NULL; + int ret; + const char *remove_attrs[] = {SYSDB_MEMBER, SYSDB_ORIG_MEMBER, SYSDB_GHOST, + NULL}; + const char *check_dom; + const char *check_name; + + if (dom->ignore_group_members) { + DEBUG(SSSDBG_TRACE_FUNC, "Group members are ignored, nothing to do.\n"); + return EOK; + } + + ret = sysdb_attrs_get_string(attrs, SYSDB_SID_STR, &group_sid); + if (ret != EOK) { + /* Try harder. */ + ret = sdap_attrs_get_sid_str(memctx, opts->idmap_ctx, attrs, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + discard_const(&group_sid)); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to get group sid\n"); + group_sid = NULL; + } + } + + if (group_sid != NULL) { + group_dom = sss_get_domain_by_sid_ldap_fallback(get_domains_head(dom), + group_sid); + if (group_dom == NULL) { + ret = well_known_sid_to_name(group_sid, &check_dom, &check_name); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Skipping group with SID [%s][%s\\%s] which is " + "currently not handled by SSSD.\n", + group_sid, check_dom, check_name); + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SID [%s] does not belong to any known " + "domain, using [%s].\n", group_sid, + dom->name); + } + } + + if (group_dom == NULL) { + group_dom = dom; + } + + ret = sdap_get_group_primary_name(memctx, opts, attrs, group_dom, + &group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get group name\n"); + goto fail; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", group_name); + + /* With AD we also want to merge in parent groups of primary GID as they + * are reported with tokenGroups, too + */ + if (opts->schema_type == SDAP_SCHEMA_AD) { + ret = sdap_dn_by_primary_gid(memctx, attrs, group_dom, opts, + &userdns, &nuserdns); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_dn_by_primary_gid failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + } + + /* This is a temporal solution until the IPA provider is able to + * resolve external group membership. + * https://fedorahosted.org/sssd/ticket/2522 + */ + if (opts->schema_type == SDAP_SCHEMA_IPA_V1) { + if (group_sid != NULL) { + ret = retain_extern_members(memctx, group_dom, group_name, + group_sid, &userdns, &nuserdns); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "retain_extern_members failed: %d:[%s].\n", + ret, sss_strerror(ret)); + } + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_attrs_get_el failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + if (el->num_values == 0 && nuserdns == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "No members for group [%s]\n", group_name); + + ret = sysdb_remove_attrs(group_dom, group_name, SYSDB_MEMBER_GROUP, + discard_const(remove_attrs)); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_attrs failed.\n"); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_remove_attrs failed for missing entry\n"); + } + goto fail; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Adding member users to group [%s]\n", group_name); + + group_attrs = sysdb_new_attrs(memctx); + if (!group_attrs) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_new_attrs failed\n"); + ret = ENOMEM; + goto fail; + } + + ret = sdap_fill_memberships(opts, group_attrs, ctx, group_dom, ghosts, + el->values, el->num_values, + userdns, nuserdns); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_fill_memberships failed with [%d]: %s\n", ret, + strerror(ret)); + goto fail; + } + } + + ret = sysdb_store_group(group_dom, group_name, 0, group_attrs, + group_dom->group_timeout, now); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "sysdb_store_group failed: [%d][%s].\n", + ret, strerror(ret)); + goto fail; + } + + return EOK; + +fail: + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to save members of group %s\n", group_name); + return ret; +} + + +/* ==Generic-Function-to-save-multiple-groups============================= */ + +static int sdap_save_groups(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **groups, + int num_groups, + bool populate_members, + hash_table_t *ghosts, + bool save_orig_member, + char **_usn_value) +{ + TALLOC_CTX *tmpctx; + char *higher_usn = NULL; + char *usn_value; + bool twopass; + bool has_nesting = false; + int ret; + errno_t sret; + int i; + struct sysdb_attrs **saved_groups = NULL; + int nsaved_groups = 0; + time_t now; + bool in_transaction = false; + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + twopass = false; + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + twopass = true; + has_nesting = true; + break; + + default: + return EINVAL; + } + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (twopass && !populate_members) { + saved_groups = talloc_array(tmpctx, struct sysdb_attrs *, + num_groups); + if (!saved_groups) { + ret = ENOMEM; + goto done; + } + } + + now = time(NULL); + for (i = 0; i < num_groups; i++) { + usn_value = NULL; + + /* if 2 pass savemembers = false */ + ret = sdap_save_group(tmpctx, opts, dom, groups[i], + populate_members, + has_nesting && save_orig_member, + ghosts, &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %d. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_ALL, "Group %d processed!\n", i); + if (twopass && !populate_members) { + saved_groups[nsaved_groups] = groups[i]; + nsaved_groups++; + } + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + if (twopass && !populate_members) { + + for (i = 0; i < nsaved_groups; i++) { + + ret = sdap_save_grpmem(tmpctx, sysdb, opts, dom, saved_groups[i], + ghosts, now); + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %d members: %d\n", i, ret); + } else { + DEBUG(SSSDBG_FUNC_DATA, + "Can't save members of missing group %d\n", i); + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "Group %d members processed!\n", i); + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmpctx); + return ret; +} + + +/* ==Process-Groups======================================================= */ + +struct sdap_process_group_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + + struct sysdb_attrs *group; + struct ldb_message_element* sysdb_dns; + struct ldb_message_element* ghost_dns; + const char **attrs; + const char *filter; + size_t check_count; + + bool enumeration; +}; + +static void sdap_process_group_members(struct tevent_req *subreq); + +static int sdap_process_group_members_2307bis(struct tevent_req *req, + struct sdap_process_group_state *state, + struct ldb_message_element *memberel); +static int sdap_process_group_members_2307(struct sdap_process_group_state *state, + struct ldb_message_element *memberel, + struct ldb_message_element *ghostel); + +static errno_t sdap_process_group_create_dns(TALLOC_CTX *mem_ctx, + size_t num_values, + struct ldb_message_element **_dns) +{ + struct ldb_message_element *dns; + + dns = talloc(mem_ctx, struct ldb_message_element); + if (dns == NULL) { + return ENOMEM; + } + + dns->num_values = 0; + dns->values = talloc_array(dns, struct ldb_val, + num_values); + if (dns->values == NULL) { + talloc_zfree(dns); + return ENOMEM; + } + + *_dns = dns; + + return EOK; +} + +static struct tevent_req * +sdap_process_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group, + bool enumeration) +{ + struct ldb_message_element *el; + struct ldb_message_element *ghostel; + struct sdap_process_group_state *grp_state; + struct tevent_req *req = NULL; + const char **attrs; + char* filter; + int ret; + + req = tevent_req_create(memctx, &grp_state, + struct sdap_process_group_state); + if (!req) return NULL; + + ret = build_attrs_from_map(grp_state, opts->user_map, opts->user_map_cnt, + NULL, &attrs, NULL); + if (ret) { + goto done; + } + + /* FIXME: we ignore nested rfc2307bis groups for now */ + filter = talloc_asprintf(grp_state, "(objectclass=%s)", + opts->user_map[SDAP_OC_USER].name); + if (!filter) { + talloc_zfree(req); + return NULL; + } + + grp_state->ev = ev; + grp_state->opts = opts; + grp_state->dom = dom; + grp_state->sh = sh; + grp_state->sysdb = sysdb; + grp_state->group = group; + grp_state->check_count = 0; + grp_state->filter = filter; + grp_state->attrs = attrs; + grp_state->enumeration = enumeration; + + ret = sysdb_attrs_get_el(group, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret) { + goto done; + } + + /* Group without members */ + if (el->num_values == 0) { + DEBUG(SSSDBG_FUNC_DATA, "No Members. Done!\n"); + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_el(group, + SYSDB_GHOST, + &ghostel); + if (ret) { + goto done; + } + + if (ghostel->num_values == 0) { + /* Element was probably newly created, look for "member" again */ + ret = sysdb_attrs_get_el(group, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + goto done; + } + } + + + ret = sdap_process_group_create_dns(grp_state, el->num_values, + &grp_state->sysdb_dns); + if (ret != EOK) { + goto done; + } + + ret = sdap_process_group_create_dns(grp_state, el->num_values, + &grp_state->ghost_dns); + if (ret != EOK) { + goto done; + } + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + ret = sdap_process_group_members_2307(grp_state, el, ghostel); + break; + + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + case SDAP_SCHEMA_RFC2307BIS: + /* Note that this code branch will be used only if + * ldap_nesting_level = 0 is set in config file + */ + ret = sdap_process_group_members_2307bis(req, grp_state, el); + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown schema type %d\n", opts->schema_type); + ret = EINVAL; + break; + } + +done: + /* We managed to process all the entries */ + /* EBUSY means we need to wait for entries in LDAP */ + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "All group members processed\n"); + tevent_req_done(req); + tevent_req_post(req, ev); + } + + if (ret != EOK && ret != EBUSY) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static int +sdap_process_missing_member_2307bis(struct tevent_req *req, + char *user_dn) +{ + struct sdap_process_group_state *grp_state = + tevent_req_data(req, struct sdap_process_group_state); + struct tevent_req *subreq; + + subreq = sdap_get_generic_send(grp_state, + grp_state->ev, + grp_state->opts, + grp_state->sh, + user_dn, + LDAP_SCOPE_BASE, + grp_state->filter, + grp_state->attrs, + grp_state->opts->user_map, + grp_state->opts->user_map_cnt, + dp_opt_get_int(grp_state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_process_group_members, req); + + grp_state->check_count++; + return EOK; +} + +static int +sdap_process_group_members_2307bis(struct tevent_req *req, + struct sdap_process_group_state *state, + struct ldb_message_element *memberel) +{ + char *member_dn; + char *strdn; + int ret; + int i; + int nesting_level; + bool is_group; + + nesting_level = dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL); + + for (i=0; i < memberel->num_values; i++) { + member_dn = (char *)memberel->values[i].data; + + ret = sdap_find_entry_by_origDN(state->sysdb_dns->values, + state->sysdb, + state->dom, + member_dn, + &strdn, + &is_group); + + if (ret == EOK) { + if (nesting_level == 0 && is_group) { + /* Ignore group members which are groups themselves. */ + continue; + } + + /* + * User already cached in sysdb. Remember the sysdb DN for later + * use by sdap_save_groups() + */ + DEBUG(SSSDBG_TRACE_LIBS, "sysdbdn: %s\n", strdn); + state->sysdb_dns->values[state->sysdb_dns->num_values].data = + (uint8_t*) strdn; + state->sysdb_dns->values[state->sysdb_dns->num_values].length = + strlen(strdn); + state->sysdb_dns->num_values++; + } else if (ret == ENOENT) { + if (!state->enumeration) { + /* The user is not in sysdb, need to add it + * We don't need to do this if we're in an enumeration, + * because all real members should all be populated + * already by the first pass of the enumeration. + * Also, we don't want to be holding the sysdb + * transaction while we're performing LDAP lookups. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "Searching LDAP for missing user entry\n"); + ret = sdap_process_missing_member_2307bis(req, + member_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error processing missing member #%d (%s):\n", + i, member_dn); + return ret; + } + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error checking cache for member #%d (%s):\n", + i, (char *)memberel->values[i].data); + return ret; + } + } + + if (state->check_count == 0) { + /* + * All group members are already cached in sysdb, we are done + * with this group. To avoid redundant sysdb lookups, populate the + * "member" attribute of the group entry with the sysdb DNs of + * the members. + */ + ret = EOK; + memberel->values = talloc_steal(state->group, state->sysdb_dns->values); + memberel->num_values = state->sysdb_dns->num_values; + } else { + ret = EBUSY; + } + + return ret; +} + +static int +sdap_add_group_member_2307(struct ldb_message_element *sysdb_dns, + const char *username) +{ + sysdb_dns->values[sysdb_dns->num_values].data = + (uint8_t *) talloc_strdup(sysdb_dns->values, username); + if (sysdb_dns->values[sysdb_dns->num_values].data == NULL) { + return ENOMEM; + } + sysdb_dns->values[sysdb_dns->num_values].length = + strlen(username); + sysdb_dns->num_values++; + + return EOK; +} + +static int +sdap_process_missing_member_2307(struct sdap_process_group_state *state, + char *member_name) +{ + int ret; + TALLOC_CTX *tmp_ctx; + const char *filter; + const char *username; + const char *user_dn; + char *sanitized_name; + size_t count; + struct ldb_message **msgs = NULL; + static const char *attrs[] = { SYSDB_NAME, NULL }; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sss_filter_sanitize(tmp_ctx, member_name, &sanitized_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to sanitize the given name:'%s'.\n", member_name); + goto done; + } + + /* Check for the alias in the sysdb */ + filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_NAME_ALIAS, + sanitized_name); + if (!filter) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_users(tmp_ctx, state->dom, filter, + attrs, &count, &msgs); + if (ret == EOK && count > 0) { + /* Entry exists but the group references it with an alias. */ + + if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one entry with this alias?\n"); + ret = EIO; + goto done; + } + + /* fill username with primary name */ + username = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (username == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, "Inconsistent sysdb: user " + "without primary name?\n"); + goto done; + } + user_dn = sysdb_user_strdn(tmp_ctx, state->dom->name, username); + if (user_dn == NULL) { + return ENOMEM; + } + + ret = sdap_add_group_member_2307(state->sysdb_dns, user_dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group member %s\n", username); + } + } else if (ret == ENOENT) { + /* The entry really does not exist, add a ghost */ + DEBUG(SSSDBG_TRACE_FUNC, "Adding a ghost entry\n"); + ret = sdap_add_group_member_2307(state->ghost_dns, member_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group member %s\n", member_name); + } + } else { + ret = EIO; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int +sdap_process_group_members_2307(struct sdap_process_group_state *state, + struct ldb_message_element *memberel, + struct ldb_message_element *ghostel) +{ + struct ldb_message *msg; + char *member_attr_val; + char *member_name; + char *userdn; + int ret; + int i; + + for (i=0; i < memberel->num_values; i++) { + member_attr_val = (char *)memberel->values[i].data; + + /* We need to skip over zero-length usernames */ + if (member_attr_val[0] == '\0') continue; + + /* RFC2307 stores members as plain usernames in the member attribute. + * Internally, we use FQDNs in the cache. + */ + member_name = sss_create_internal_fqname(state, member_attr_val, + state->dom->name); + if (member_name == NULL) { + return ENOMEM; + } + + ret = sysdb_search_user_by_name(state, state->dom, member_name, + NULL, &msg); + if (ret == EOK) { + /* + * User already cached in sysdb. Remember the sysdb DN for later + * use by sdap_save_groups() + */ + DEBUG(SSSDBG_TRACE_LIBS, + "Member already cached in sysdb: %s\n", member_name); + + userdn = sysdb_user_strdn(state->sysdb_dns, state->dom->name, member_name); + if (userdn == NULL) { + return ENOMEM; + } + + ret = sdap_add_group_member_2307(state->sysdb_dns, userdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add member %s into sysdb\n", member_name); + goto done; + } + } else if (ret == ENOENT) { + /* The user is not in sysdb, need to add it */ + DEBUG(SSSDBG_TRACE_LIBS, "member #%d (%s): not found in sysdb\n", + i, member_name); + + ret = sdap_process_missing_member_2307(state, member_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error processing missing member #%d (%s):\n", + i, member_name); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error checking cache for member #%d (%s):\n", + i, (char *) memberel->values[i].data); + goto done; + } + } + + ret = EOK; + talloc_free(memberel->values); + memberel->values = talloc_steal(state->group, state->sysdb_dns->values); + memberel->num_values = state->sysdb_dns->num_values; + talloc_free(ghostel->values); + ghostel->values = talloc_steal(state->group, state->ghost_dns->values); + ghostel->num_values = state->ghost_dns->num_values; + +done: + return ret; +} + +static void sdap_process_group_members(struct tevent_req *subreq) +{ + struct sysdb_attrs **usr_attrs; + size_t count; + int ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_process_group_state *state = + tevent_req_data(req, struct sdap_process_group_state); + struct ldb_message_element *el; + char *name_string; + + state->check_count--; + DEBUG(SSSDBG_TRACE_ALL, "Members remaining: %zu\n", state->check_count); + + ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs); + talloc_zfree(subreq); + if (ret) { + goto next; + } + if (count != 1) { + ret = EINVAL; + DEBUG(SSSDBG_TRACE_LIBS, + "Expected one user entry and got %zu\n", count); + goto next; + } + ret = sysdb_attrs_get_el(usr_attrs[0], + state->opts->user_map[SDAP_AT_USER_NAME].sys_name, &el); + if (el->num_values == 0) { + ret = EINVAL; + } + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get the member's name\n"); + goto next; + } + + name_string = sss_create_internal_fqname(state, + (const char *) el[0].values[0].data, + state->dom->name); + if (name_string == NULL) { + ret = ENOMEM; + goto next; + } + + state->ghost_dns->values[state->ghost_dns->num_values].data = + talloc_steal(state->ghost_dns->values, (uint8_t *) name_string); + state->ghost_dns->values[state->ghost_dns->num_values].length = + strlen(name_string); + state->ghost_dns->num_values++; + +next: + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, + "Error reading group member[%d]: %s. Skipping\n", + ret, strerror(ret)); + } + + if (state->check_count == 0) { + /* + * To avoid redundant sysdb lookups, populate the "member" attribute + * of the group entry with the sysdb DNs of the members. + */ + ret = sysdb_attrs_get_el(state->group, + state->opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get the group member attribute [%d]: %s\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + el->values = talloc_steal(state->group, state->sysdb_dns->values); + el->num_values = state->sysdb_dns->num_values; + + ret = sysdb_attrs_get_el(state->group, SYSDB_GHOST, &el); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + el->values = talloc_steal(state->group, state->ghost_dns->values); + el->num_values = state->ghost_dns->num_values; + DEBUG(SSSDBG_TRACE_ALL, "Processed Group - Done\n"); + tevent_req_done(req); + } +} + +static int sdap_process_group_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* ==Search-Groups-with-filter============================================ */ + +struct sdap_get_groups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sdap_domain *sdom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + enum sdap_entry_lookup_type lookup_type; + bool no_members; + + char *higher_usn; + struct sysdb_attrs **groups; + size_t count; + size_t check_count; + hash_table_t *missing_external; + + hash_table_t *user_hash; + hash_table_t *group_hash; + + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sdap_handle *ldap_sh; + struct sdap_id_op *op; +}; + +static errno_t sdap_get_groups_next_base(struct tevent_req *req); +static void sdap_get_groups_ldap_connect_done(struct tevent_req *subreq); +static void sdap_get_groups_process(struct tevent_req *subreq); +static void sdap_get_groups_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + bool no_members) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_get_groups_state *state; + struct sdap_id_conn_ctx *ldap_conn = NULL; + + req = tevent_req_create(memctx, &state, struct sdap_get_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sdom = sdom; + state->dom = sdom->dom; + state->sh = sh; + state->sysdb = sdom->dom->sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->groups = NULL; + state->count = 0; + state->timeout = timeout; + state->lookup_type = lookup_type; + state->no_members = no_members; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = sdom->group_search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Group lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + /* With AD by default the Global Catalog is used for lookup. But the GC + * group object might not have full group membership data. To make sure we + * connect to an LDAP server of the group's domain. */ + ldap_conn = get_ldap_conn_from_sdom_pvt(state->opts, sdom); + if (ldap_conn != NULL) { + state->op = sdap_id_op_create(state, ldap_conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, + sdap_get_groups_ldap_connect_done, + req); + return req; + } + + ret = sdap_get_groups_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_get_groups_ldap_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_groups_state *state; + int ret; + int dp_error; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_groups_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->ldap_sh = sdap_id_op_handle(state->op); + + ret = sdap_get_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_get_groups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_groups_state *state; + bool need_paging = false; + int sizelimit = 0; + + state = tevent_req_data(req, struct sdap_get_groups_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for groups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + switch (state->lookup_type) { + case SDAP_LOOKUP_SINGLE: + break; + /* Only requests that can return multiple entries should require + * the paging control + */ + case SDAP_LOOKUP_WILDCARD: + sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT); + need_paging = true; + break; + case SDAP_LOOKUP_ENUMERATE: + need_paging = true; + break; + } + + subreq = sdap_get_and_parse_generic_send( + state, state->ev, state->opts, + state->ldap_sh != NULL ? state->ldap_sh : state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + 0, NULL, NULL, sizelimit, state->timeout, + need_paging); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_groups_process, req); + + return EOK; +} + +static void sdap_nested_done(struct tevent_req *req); +static void sdap_search_group_copy_batch(struct sdap_get_groups_state *state, + struct sysdb_attrs **groups, + size_t count); + +static void sdap_get_groups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_groups_state *state = + tevent_req_data(req, struct sdap_get_groups_state); + int ret; + int i; + bool next_base = false; + size_t count; + struct sysdb_attrs **groups; + char **sysdb_groupnamelist; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &count, &groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for groups, returned %zu results.\n", count); + + if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \ + state->lookup_type == SDAP_LOOKUP_ENUMERATE || \ + count == 0) { + /* No users found in this search or looking up multiple entries */ + next_base = true; + } + + /* Add this batch of groups to the list */ + if (count > 0) { + state->groups = + talloc_realloc(state, + state->groups, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->groups) { + tevent_req_error(req, ENOMEM); + return; + } + + sdap_search_group_copy_batch(state, groups, count); + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no groups were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + if (state->no_members) { + ret = sdap_get_primary_fqdn_list(state->dom, state, + state->groups, state->count, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + state->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + state->opts->idmap_ctx, + &sysdb_groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_primary_name_list failed.\n"); + tevent_req_error(req, ret); + return; + } + + ret = sdap_add_incomplete_groups(state->sysdb, state->dom, state->opts, + sysdb_groupnamelist, state->groups, + state->count); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "Writing only group data without members was successful.\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, "sdap_add_incomplete_groups failed.\n"); + tevent_req_error(req, ret); + } + return; + } + + /* Check whether we need to do nested searches + * for RFC2307bis/FreeIPA/ActiveDirectory + * We don't need to do this for enumeration, + * because all groups will be picked up anyway. + * + * We can also skip this if we're using the + * LDAP_MATCHING_RULE_IN_CHAIN available in + * AD 2008 and later + */ + if (state->lookup_type == SDAP_LOOKUP_SINGLE) { + if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307) + && (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)) { + subreq = sdap_nested_group_send(state, state->ev, state->sdom, + state->opts, state->sh, + state->groups[0]); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_set_callback(subreq, sdap_nested_done, req); + return; + } + } + + /* We have all of the groups. Save them to the sysdb */ + state->check_count = state->count; + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to start transaction\n"); + tevent_req_error(req, ret); + return; + } + + if ((state->lookup_type == SDAP_LOOKUP_ENUMERATE + || state->lookup_type == SDAP_LOOKUP_WILDCARD) + && state->opts->schema_type != SDAP_SCHEMA_RFC2307 + && dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Saving groups without members first " + "to allow unrolling of nested groups.\n"); + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + state->groups, state->count, false, + NULL, true, NULL); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store groups.\n"); + tevent_req_error(req, ret); + return; + } + } + + for (i = 0; i < state->count; i++) { + subreq = sdap_process_group_send(state, state->ev, state->dom, + state->sysdb, state->opts, + state->sh, state->groups[i], + state->lookup_type == SDAP_LOOKUP_ENUMERATE); + + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_groups_done, req); + } +} + +static void sdap_search_group_copy_batch(struct sdap_get_groups_state *state, + struct sysdb_attrs **groups, + size_t count) +{ + size_t copied; + bool filter; + + /* Always copy all objects for wildcard lookups. */ + filter = state->lookup_type == SDAP_LOOKUP_SINGLE ? true : false; + + copied = sdap_steal_objects_in_dom(state->opts, + state->groups, + state->count, + state->dom, + groups, count, filter); + + state->count += copied; + state->groups[state->count] = NULL; +} + +static void sdap_get_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_groups_state *state = + tevent_req_data(req, struct sdap_get_groups_state); + + int ret; + errno_t sysret; + + ret = sdap_process_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + sysret = sysdb_transaction_cancel(state->sysdb); + if (sysret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel sysdb transaction\n"); + } + tevent_req_error(req, ret); + return; + } + + state->check_count--; + DEBUG(SSSDBG_TRACE_ALL, "Groups remaining: %zu\n", state->check_count); + + + if (state->check_count == 0) { + DEBUG(SSSDBG_TRACE_ALL, "All groups processed\n"); + + /* If ignore_group_members is set for the domain, don't update + * group memberships in the cache. + * + * If enumeration is on, don't overwrite orig_members as they've been + * saved earlier. + */ + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + state->groups, state->count, + !state->dom->ignore_group_members, NULL, + state->lookup_type == SDAP_LOOKUP_SINGLE, + &state->higher_usn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store groups.\n"); + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Groups - Done\n", state->count); + sysret = sysdb_transaction_commit(state->sysdb); + if (sysret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Couldn't commit transaction\n"); + tevent_req_error(req, sysret); + } else { + tevent_req_done(req); + } + } +} + +static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + hash_table_t **_ghosts); + + +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **usn_value) +{ + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +static void sdap_nested_ext_done(struct tevent_req *subreq); + +static void sdap_nested_done(struct tevent_req *subreq) +{ + errno_t ret, tret; + unsigned long user_count; + unsigned long group_count; + bool in_transaction = false; + struct sysdb_attrs **users = NULL; + struct sysdb_attrs **groups = NULL; + hash_table_t *ghosts; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + ret = sdap_nested_group_recv(state, subreq, &user_count, &users, + &group_count, &groups, + &state->missing_external); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Nested group processing failed: [%d][%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Save all of the users first so that they are in + * place for the groups to add them. + */ + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + PROBE(SDAP_NESTED_GROUP_POPULATE_PRE); + ret = sdap_nested_group_populate_users(state, state->sysdb, + state->dom, state->opts, + users, user_count, &ghosts); + PROBE(SDAP_NESTED_GROUP_POPULATE_POST); + if (ret != EOK) { + goto fail; + } + + PROBE(SDAP_NESTED_GROUP_SAVE_PRE); + ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts, + groups, group_count, false, ghosts, true, + &state->higher_usn); + PROBE(SDAP_NESTED_GROUP_SAVE_POST); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + if (hash_count(state->missing_external) == 0) { + /* No external members. Processing complete */ + DEBUG(SSSDBG_TRACE_INTERNAL, "No external members, done\n"); + tevent_req_done(req); + return; + } + + /* At the moment, we need to save the direct groups & members in one + * transaction and then query the others in a separate requests + */ + subreq = sdap_nested_group_lookup_external_send(state, state->ev, + state->dom, + state->opts->ext_ctx, + state->missing_external); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_nested_ext_done, req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); +} + +static void sdap_nested_ext_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + ret = sdap_nested_group_lookup_external_recv(state, subreq); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot resolve external members [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + hash_table_t **_ghosts) +{ + int i; + errno_t ret, sret; + struct ldb_message_element *el; + const char *username; + const char *original_dn; + const char *hash_key_dn; + struct sss_domain_info *user_dom; + struct sdap_domain *sdap_dom; + + TALLOC_CTX *tmp_ctx; + struct ldb_message **msgs; + const char *sysdb_name; + struct sysdb_attrs *attrs; + static const char *search_attrs[] = { SYSDB_NAME, NULL }; + hash_table_t *ghosts; + hash_key_t key; + hash_value_t value; + size_t count; + bool in_transaction = false; + + if (_ghosts == NULL) { + return EINVAL; + } + + if (num_users == 0) { + /* Nothing to do if there are no users */ + *_ghosts = NULL; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sss_hash_create(tmp_ctx, num_users, &ghosts); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction!\n"); + goto done; + } + in_transaction = true; + + for (i = 0; i < num_users; i++) { + ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN, &el); + if (el->num_values == 0) { + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User entry %d has no originalDN attribute\n", i); + goto done; + } + original_dn = (const char *) el->values[0].data; + + sdap_dom = sdap_domain_get_by_dn(opts, original_dn); + user_dom = sdap_dom == NULL ? domain : sdap_dom->dom; + + ret = sdap_get_user_primary_name(tmp_ctx, opts, users[i], + user_dom, &username); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "User entry %d has no name attribute. Skipping\n", i); + continue; + } + + /* Check for the specified origDN in the sysdb */ + PROBE(SDAP_NESTED_GROUP_POPULATE_SEARCH_USERS_PRE); + ret = sysdb_search_users_by_orig_dn(tmp_ctx, user_dom, original_dn, + search_attrs, &count, &msgs); + PROBE(SDAP_NESTED_GROUP_POPULATE_SEARCH_USERS_POST); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error checking cache for user entry\n"); + goto done; + } else if (ret == EOK) { + /* The entry is cached but expired. Update the username + * if needed. */ + if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one entry with this origDN? Skipping\n"); + continue; + } + + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (strcmp(sysdb_name, username) == 0) { + /* Username is correct, continue */ + continue; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, username); + if (ret) goto done; + ret = sysdb_set_entry_attr(user_dom->sysdb, msgs[0]->dn, attrs, + SYSDB_MOD_REP); + if (ret != EOK) goto done; + } else { + /* The DN of the user object and the DN in the member attribute + * might differ, e.g. in case. Since we later search the hash with + * DNs from the member attribute we should try to use DN from the + * member attribute here as well. This should be added earlier in + * the SYSDB_DN_FOR_MEMBER_HASH_TABLE attribute. If this does not + * exists we fall-back to original_dn which should work in the + * most cases as well. */ + ret = sysdb_attrs_get_string(users[i], + SYSDB_DN_FOR_MEMBER_HASH_TABLE, + &hash_key_dn); + if (ret != EOK) { + hash_key_dn = original_dn; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_steal(ghosts, discard_const(hash_key_dn)); + value.type = HASH_VALUE_PTR; + /* Already qualified from sdap_get_user_primary_name() */ + value.ptr = talloc_steal(ghosts, discard_const(username)); + ret = hash_enter(ghosts, &key, &value); + if (ret != HASH_SUCCESS) { + talloc_free(key.str); + talloc_free(value.ptr); + ret = ENOMEM; + goto done; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + if (ret != EOK) { + *_ghosts = NULL; + } else { + *_ghosts = talloc_steal(mem_ctx, ghosts); + } + talloc_zfree(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_async_hosts.c b/src/providers/ldap/sdap_async_hosts.c new file mode 100644 index 0000000..0633a3f --- /dev/null +++ b/src/providers/ldap/sdap_async_hosts.c @@ -0,0 +1,209 @@ +/* + SSSD + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_host_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + struct sdap_attr_map *host_map; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *host_filter; + + const char *hostname; + + /* Return values */ + size_t host_count; + struct sysdb_attrs **hosts; +}; + +static void +sdap_host_info_done(struct tevent_req *subreq); + +static errno_t +sdap_host_info_next(struct tevent_req *req, + struct sdap_host_state *state); + +/** + * hostname == NULL -> look up all hosts / host groups + * hostname != NULL -> look up only given host and groups + * it's member of + */ +struct tevent_req * +sdap_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_search_base **search_bases) +{ + errno_t ret; + struct sdap_host_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct sdap_host_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->hostname = hostname; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->cur_filter = NULL; + state->host_map = host_map; + + ret = build_attrs_from_map(state, host_map, SDAP_OPTS_HOST, + NULL, &state->attrs, NULL); + if (ret != EOK) { + goto immediate; + } + + if (hostname == NULL) { + state->host_filter = talloc_asprintf(state, "(objectClass=%s)", + host_map[SDAP_OC_HOST].name); + } else { + state->host_filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=%s))", + host_map[SDAP_OC_HOST].name, + host_map[SDAP_AT_HOST_FQDN].name, + hostname); + } + if (state->host_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = sdap_host_info_next(req, state); + if (ret == EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host search base configured?\n"); + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_host_info_next(struct tevent_req *req, + struct sdap_host_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->host_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, + state->sh, base->basedn, + base->scope, state->cur_filter, + state->attrs, state->host_map, + SDAP_OPTS_HOST, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting host info\n"); + talloc_zfree(state->cur_filter); + return EIO; + } + tevent_req_set_callback(subreq, sdap_host_info_done, req); + + return EAGAIN; +} + +static void +sdap_host_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_host_state *state = tevent_req_data(req, struct sdap_host_state); + + ret = sdap_get_generic_recv(subreq, state, + &state->host_count, + &state->hosts); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->host_count == 0) { + state->search_base_iter++; + ret = sdap_host_info_next(req, state); + if (ret == EOK) { + /* No more search bases to try */ + tevent_req_error(req, ENOENT); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } + + /* Nothing else to do, just complete the req */ + tevent_req_done(req); +} + +errno_t sdap_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts) +{ + struct sdap_host_state *state = tevent_req_data(req, struct sdap_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *host_count = state->host_count; + *hosts = talloc_steal(mem_ctx, state->hosts); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c new file mode 100644 index 0000000..97be594 --- /dev/null +++ b/src/providers/ldap/sdap_async_initgroups.c @@ -0,0 +1,3647 @@ +/* + SSSD + + Async LDAP Helper routines - initgroups operation + + Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009 + Copyright (C) 2010, Ralf Haferkamp <rhafer@suse.de>, Novell Inc. + Copyright (C) Jan Zeleny <jzeleny@redhat.com> - 2011 + + 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 "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +/* ==Save-fake-group-list=====================================*/ +errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + char **sysdb_groupnames, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + int i, mi, ai; + const char *groupname; + const char *original_dn; + const char *uuid = NULL; + char **missing; + gid_t gid; + int ret; + errno_t sret; + bool in_transaction = false; + bool posix; + time_t now; + char *sid_str = NULL; + bool use_id_mapping; + bool need_filter; + struct sss_domain_info *subdomain; + + /* There are no groups in LDAP but we should add user to groups?? */ + if (ldap_groups_count == 0) return EOK; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + missing = talloc_array(tmp_ctx, char *, ldap_groups_count+1); + if (!missing) { + ret = ENOMEM; + goto done; + } + mi = 0; + + for (i=0; sysdb_groupnames[i]; i++) { + subdomain = find_domain_by_object_name(domain, sysdb_groupnames[i]); + if (subdomain == NULL) { + subdomain = domain; + } + ret = sysdb_search_group_by_name(tmp_ctx, subdomain, sysdb_groupnames[i], NULL, + &msg); + if (ret == EOK) { + continue; + } else if (ret == ENOENT) { + missing[mi] = talloc_strdup(missing, sysdb_groupnames[i]); + DEBUG(SSSDBG_TRACE_LIBS, "Group #%d [%s][%s] is not cached, " \ + "need to add a fake entry\n", + i, sysdb_groupnames[i], missing[mi]); + mi++; + continue; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "search for group failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + missing[mi] = NULL; + + /* All groups are cached, nothing to do */ + if (mi == 0) { + ret = EOK; + goto done; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + domain->name, + domain->domain_id); + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot start sysdb transaction [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + in_transaction = true; + + + now = time(NULL); + for (i=0; missing[i]; i++) { + /* The group is not in sysdb, need to add a fake entry */ + for (ai=0; ai < ldap_groups_count; ai++) { + ret = sdap_get_group_primary_name(tmp_ctx, opts, ldap_groups[ai], + domain, &groupname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The group has no name attribute\n"); + goto done; + } + + if (strcmp(groupname, missing[i]) == 0) { + posix = true; + + ret = sdap_attrs_get_sid_str( + tmp_ctx, opts->idmap_ctx, ldap_groups[ai], + opts->group_map[SDAP_AT_GROUP_OBJECTSID].sys_name, + &sid_str); + if (ret != EOK && ret != ENOENT) goto done; + + if (use_id_mapping) { + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No SID for group [%s] " \ + "while id-mapping.\n", + groupname); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping group [%s] objectSID to unix ID\n", groupname); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] has objectSID [%s]\n", + groupname, sid_str); + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, + &gid); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] has mapped gid [%lu]\n", + groupname, (unsigned long)gid); + } else { + posix = false; + gid = 0; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Group [%s] cannot be mapped. " + "Treating as a non-POSIX group\n", + groupname); + } + + } else { + ret = sysdb_attrs_get_uint32_t(ldap_groups[ai], + SYSDB_GIDNUM, + &gid); + if (ret == ENOENT || (ret == EOK && gid == 0)) { + DEBUG(SSSDBG_TRACE_LIBS, "The group %s gid was %s\n", + groupname, ret == ENOENT ? "missing" : "zero"); + DEBUG(SSSDBG_TRACE_FUNC, + "Marking group %s as non-POSIX and setting GID=0!\n", + groupname); + gid = 0; + posix = false; + } else if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The GID attribute is malformed\n"); + goto done; + } + } + + ret = sysdb_attrs_get_string(ldap_groups[ai], + SYSDB_ORIG_DN, + &original_dn); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no original DN\n"); + original_dn = NULL; + } + + ret = sysdb_handle_original_uuid( + opts->group_map[SDAP_AT_GROUP_UUID].def_name, + ldap_groups[ai], + opts->group_map[SDAP_AT_GROUP_UUID].sys_name, + ldap_groups[ai], "uniqueIDstr"); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", + ret, sss_strerror(ret)); + } + + ret = sysdb_attrs_get_string(ldap_groups[ai], + "uniqueIDstr", + &uuid); + if (ret) { + DEBUG(SSSDBG_FUNC_DATA, + "The group has no UUID\n"); + uuid = NULL; + } + + ret = sdap_check_ad_group_type(domain, opts, ldap_groups[ai], + groupname, &need_filter); + if (ret != EOK) { + goto done; + } + + if (need_filter) { + posix = false; + gid = 0; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Adding fake group %s to sysdb\n", groupname); + subdomain = find_domain_by_object_name(domain, groupname); + if (subdomain == NULL) { + subdomain = domain; + } + ret = sysdb_add_incomplete_group(subdomain, groupname, gid, + original_dn, sid_str, + uuid, posix, now); + if (ret == ERR_GID_DUPLICATED) { + /* In case o group id-collision, do: + * - Delete the group from sysdb + * - Add the new incomplete group + * - Notify the NSS responder that the entry has also to be + * removed from the memory cache + */ + ret = sdap_handle_id_collision_for_incomplete_groups( + opts->dp, subdomain, groupname, gid, + original_dn, sid_str, uuid, posix, + now); + } + + if (ret != EOK) { + goto done; + } + break; + } + } + + if (ai == ldap_groups_count) { + DEBUG(SSSDBG_OP_FAILURE, + "Group %s not present in LDAP\n", missing[i]); + ret = EINVAL; + goto done; + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_transaction_commit failed.\n"); + goto done; + } + in_transaction = false; + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +int sdap_initgr_common_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + const char *name, + enum sysdb_member_type type, + char **sysdb_grouplist, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count) +{ + TALLOC_CTX *tmp_ctx; + char **ldap_grouplist = NULL; + char **ldap_fqdnlist = NULL; + char **add_groups; + char **del_groups; + int ret, tret; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + if (ldap_groups_count == 0) { + /* No groups for this user in LDAP. + * We need to ensure that there are no groups + * in the sysdb either. + */ + ldap_grouplist = NULL; + } else { + ret = sdap_get_primary_name_list(domain, tmp_ctx, ldap_groups, + ldap_groups_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + &ldap_grouplist); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + /* Find the differences between the sysdb and LDAP lists + * Groups in the sysdb only must be removed. + */ + if (ldap_grouplist != NULL) { + ldap_fqdnlist = sss_create_internal_fqname_list( + tmp_ctx, + (const char * const *) ldap_grouplist, + domain->name); + if (ldap_fqdnlist == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = diff_string_lists(tmp_ctx, ldap_fqdnlist, sysdb_grouplist, + &add_groups, &del_groups, NULL); + if (ret != EOK) goto done; + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + /* Add fake entries for any groups the user should be added as + * member of but that are not cached in sysdb + */ + if (add_groups && add_groups[0]) { + ret = sdap_add_incomplete_groups(sysdb, domain, opts, + add_groups, ldap_groups, + ldap_groups_count); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Adding incomplete groups failed\n"); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", name); + ret = sysdb_update_members(domain, name, type, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307===================== */ + +struct sdap_initgr_rfc2307_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_options *opts; + struct sdap_handle *sh; + const char **attrs; + const char *name; + char *base_filter; + const char *orig_dn; + char *filter; + int timeout; + + struct sdap_op *op; + + struct sysdb_attrs **ldap_groups; + size_t ldap_groups_count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_initgr_rfc2307_next_base(struct tevent_req *req); +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq); +struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307_state *state; + const char **attr_filter; + char *clean_name; + char *shortname; + errno_t ret; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->domain = domain; + state->sh = sh; + state->op = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->ldap_groups = NULL; + state->ldap_groups_count = 0; + state->base_iter = 0; + state->search_bases = opts->sdom->group_search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a group search base\n"); + ret = EINVAL; + goto done; + } + + state->name = talloc_strdup(state, name); + if (!state->name) { + talloc_zfree(req); + return NULL; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + talloc_free(req); + return NULL; + } + + attr_filter[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + ret = sss_parse_internal_fqname(state, name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", name); + goto done; + } + + ret = sss_filter_sanitize(state, shortname, &clean_name); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + oc_list = sdap_make_oc_list(state, opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)", + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_name, oc_list, + opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + talloc_zfree(req); + return NULL; + } + talloc_zfree(clean_name); + + switch (domain->type) { + case DOM_TYPE_APPLICATION: + state->base_filter = talloc_asprintf_append(state->base_filter, ")"); + break; + case DOM_TYPE_POSIX: + state->base_filter = talloc_asprintf_append(state->base_filter, + "(&(%s=*)(!(%s=0))))", + opts->group_map[SDAP_AT_GROUP_GID].name, + opts->group_map[SDAP_AT_GROUP_GID].name); + break; + } + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + ret = sdap_initgr_rfc2307_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t sdap_initgr_rfc2307_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_initgr_rfc2307_state *state; + + state = tevent_req_data(req, struct sdap_initgr_rfc2307_state); + + talloc_zfree(state->filter); + + state->filter = sdap_combine_filters( state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for groups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_initgr_rfc2307_process, req); + + return EOK; +} + +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307_state *state; + struct sysdb_attrs **ldap_groups; + char **sysdb_grouplist = NULL; + size_t count; + int ret; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_rfc2307_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + /* Add this batch of groups to the list */ + if (count > 0) { + state->ldap_groups = + talloc_realloc(state, + state->ldap_groups, + struct sysdb_attrs *, + state->ldap_groups_count + count + 1); + if (!state->ldap_groups) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + */ + for (i = 0; i < count; i++) { + state->ldap_groups[state->ldap_groups_count + i] = + talloc_steal(state->ldap_groups, ldap_groups[i]); + } + + state->ldap_groups_count += count; + + state->ldap_groups[state->ldap_groups_count] = NULL; + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = sdap_initgr_rfc2307_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* Search for all groups for which this user is a member */ + ret = get_sysdb_grouplist(state, state->sysdb, state->domain, + state->name, &sysdb_grouplist); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* There are no nested groups here so we can just update the + * memberships */ + ret = sdap_initgr_common_store(state->sysdb, + state->domain, + state->opts, + state->name, + SYSDB_MEMBER_USER, + sysdb_grouplist, + state->ldap_groups, + state->ldap_groups_count); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_initgr_rfc2307_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Common code for pure RFC2307bis and IPA/AD========================= */ +errno_t +sdap_nested_groups_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **groups, + unsigned long count) +{ + errno_t ret, tret; + TALLOC_CTX *tmp_ctx; + char **groupnamelist = NULL; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + if (count > 0) { + ret = sdap_get_primary_fqdn_list(domain, tmp_ctx, groups, count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sdap_add_incomplete_groups(sysdb, domain, opts, groupnamelist, + groups, count); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Could not add incomplete groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +struct membership_diff { + struct membership_diff *prev; + struct membership_diff *next; + + const char *name; + char **add; + char **del; +}; + +static errno_t +build_membership_diff(TALLOC_CTX *mem_ctx, const char *name, + char **ldap_parent_names, char **sysdb_parent_names, + struct membership_diff **_mdiff) +{ + TALLOC_CTX *tmp_ctx; + struct membership_diff *mdiff; + errno_t ret; + char **add_groups; + char **del_groups; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + mdiff = talloc_zero(tmp_ctx, struct membership_diff); + if (!mdiff) { + ret = ENOMEM; + goto done; + } + mdiff->name = talloc_strdup(mdiff, name); + if (!mdiff->name) { + ret = ENOMEM; + goto done; + } + + /* Find the differences between the sysdb and ldap lists + * Groups in ldap only must be added to the sysdb; + * groups in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, + ldap_parent_names, sysdb_parent_names, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + mdiff->add = talloc_steal(mdiff, add_groups); + mdiff->del = talloc_steal(mdiff, del_groups); + + ret = EOK; + *_mdiff = talloc_steal(mem_ctx, mdiff); +done: + talloc_free(tmp_ctx); + return ret; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-nested-groups=============== */ + +struct sdap_initgr_nested_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + + struct sysdb_attrs *user; + const char *username; + const char *orig_dn; + + const char **grp_attrs; + + struct ldb_message_element *memberof; + char *filter; + char **group_dns; + int cur; + + struct sdap_op *op; + + struct sysdb_attrs **groups; + int groups_cur; +}; + +static errno_t sdap_initgr_nested_deref_search(struct tevent_req *req); +static errno_t sdap_initgr_nested_noderef_search(struct tevent_req *req); +static void sdap_initgr_nested_search(struct tevent_req *subreq); +static void sdap_initgr_nested_store(struct tevent_req *req); +static struct tevent_req *sdap_initgr_nested_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sysdb_attrs *user, + const char **grp_attrs) +{ + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + errno_t ret; + int deref_threshold; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_nested_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->grp_attrs = grp_attrs; + state->user = user; + state->op = NULL; + + ret = sdap_get_user_primary_name(memctx, opts, user, dom, &state->username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "User entry had no username\n"); + goto immediate; + } + + ret = sysdb_attrs_get_el(state->user, SYSDB_MEMBEROF, &state->memberof); + if (ret || !state->memberof || state->memberof->num_values == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "User entry lacks original memberof ?\n"); + /* We can't find any groups for this user, so we'll + * have to assume there aren't any. Just return + * success here. + */ + ret = EOK; + goto immediate; + } + + state->groups = talloc_zero_array(state, struct sysdb_attrs *, + state->memberof->num_values + 1); + if (!state->groups) { + ret = ENOMEM; + goto immediate; + } + state->groups_cur = 0; + + deref_threshold = dp_opt_get_int(state->opts->basic, + SDAP_DEREF_THRESHOLD); + if (sdap_has_deref_support(state->sh, state->opts) && + deref_threshold < state->memberof->num_values) { + ret = sysdb_attrs_get_string(user, SYSDB_ORIG_DN, + &state->orig_dn); + if (ret != EOK) goto immediate; + + ret = sdap_initgr_nested_deref_search(req); + if (ret != EAGAIN) goto immediate; + } else { + ret = sdap_initgr_nested_noderef_search(req); + if (ret != EAGAIN) goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t sdap_initgr_nested_noderef_search(struct tevent_req *req) +{ + int i; + struct tevent_req *subreq; + struct sdap_initgr_nested_state *state; + char *oc_list; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + state->group_dns = talloc_array(state, char *, + state->memberof->num_values + 1); + if (!state->group_dns) { + return ENOMEM; + } + for (i = 0; i < state->memberof->num_values; i++) { + state->group_dns[i] = talloc_strdup(state->group_dns, + (char *)state->memberof->values[i].data); + if (!state->group_dns[i]) { + return ENOMEM; + } + } + state->group_dns[i] = NULL; /* terminate */ + state->cur = 0; + + oc_list = sdap_make_oc_list(state, state->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + return ENOMEM; + } + + state->filter = talloc_asprintf(state, "(&(%s)(%s=*))", oc_list, + state->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->filter) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->group_dns[state->cur], + LDAP_SCOPE_BASE, + state->filter, state->grp_attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_search, req); + + return EAGAIN; +} + +static void sdap_initgr_nested_deref_done(struct tevent_req *subreq); + +static errno_t sdap_initgr_nested_deref_search(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_attr_map_info *maps; + const int num_maps = 1; + const char **sdap_attrs; + errno_t ret; + int timeout; + struct sdap_initgr_nested_state *state; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps+1); + if (!maps) return ENOMEM; + + maps[0].map = state->opts->group_map; + maps[0].num_attrs = SDAP_OPTS_GROUP; + maps[1].map = NULL; + + ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP, + NULL, &sdap_attrs, NULL); + if (ret != EOK) goto fail; + + timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + + subreq = sdap_deref_search_send(state, state->ev, state->opts, + state->sh, state->orig_dn, + state->opts->user_map[SDAP_AT_USER_MEMBEROF].name, + sdap_attrs, num_maps, maps, timeout); + if (!subreq) { + ret = EIO; + goto fail; + } + talloc_steal(subreq, sdap_attrs); + talloc_steal(subreq, maps); + + tevent_req_set_callback(subreq, sdap_initgr_nested_deref_done, req); + return EAGAIN; + +fail: + talloc_free(sdap_attrs); + talloc_free(maps); + return ret; +} + +static void sdap_initgr_nested_deref_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + size_t num_results; + size_t i; + struct sdap_deref_attrs **deref_result; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sdap_deref_search_recv(subreq, state, + &num_results, + &deref_result); + talloc_zfree(subreq); + if (ret == ENOTSUP) { + ret = sdap_initgr_nested_noderef_search(req); + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } + return; + } else if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } else if (ret == ENOENT || deref_result == NULL) { + /* Nothing could be dereferenced. Done. */ + tevent_req_done(req); + return; + } + + for (i=0; i < num_results; i++) { + state->groups[i] = talloc_steal(state->groups, + deref_result[i]->attrs); + } + + state->groups_cur = num_results; + sdap_initgr_nested_store(req); +} + +static void sdap_initgr_nested_search(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_nested_state *state; + struct sysdb_attrs **groups; + size_t count; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (count == 1) { + state->groups[state->groups_cur] = talloc_steal(state->groups, + groups[0]); + state->groups_cur++; + } else if (count == 0) { + /* this might be HBAC or sudo rule */ + DEBUG(SSSDBG_FUNC_DATA, "Object %s not found. Skipping\n", + state->group_dns[state->cur]); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Search for group %s, returned %zu results. Skipping\n", + state->group_dns[state->cur], count); + } + + state->cur++; + /* note that state->memberof->num_values is the count of original + * memberOf which might not be only groups, but permissions, etc. + * Use state->groups_cur for group index cap */ + if (state->cur < state->memberof->num_values) { + subreq = sdap_get_generic_send(state, state->ev, + state->opts, state->sh, + state->group_dns[state->cur], + LDAP_SCOPE_BASE, + state->filter, state->grp_attrs, + state->opts->group_map, + SDAP_OPTS_GROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_search, req); + } else { + sdap_initgr_nested_store(req); + } +} + +static errno_t +sdap_initgr_store_groups(struct sdap_initgr_nested_state *state); +static errno_t +sdap_initgr_store_group_memberships(struct sdap_initgr_nested_state *state); +static errno_t +sdap_initgr_store_user_memberships(struct sdap_initgr_nested_state *state); + +static void sdap_initgr_nested_store(struct tevent_req *req) +{ + errno_t ret; + struct sdap_initgr_nested_state *state; + bool in_transaction = false; + errno_t tret; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* save the groups if they are not already */ + ret = sdap_initgr_store_groups(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* save the group memberships */ + ret = sdap_initgr_store_group_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save group memberships [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* save the user memberships */ + ret = sdap_initgr_store_user_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save user memberships [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + tevent_req_done(req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); + return; +} + +static errno_t +sdap_initgr_store_groups(struct sdap_initgr_nested_state *state) +{ + return sdap_nested_groups_store(state->sysdb, state->dom, + state->opts, state->groups, + state->groups_cur); +} + +static errno_t +sdap_initgr_nested_get_membership_diff(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *group, + struct sysdb_attrs **all_groups, + int groups_count, + struct membership_diff **mdiff); + +static int sdap_initgr_nested_get_direct_parents(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *attrs, + struct sysdb_attrs **groups, + int ngroups, + struct sysdb_attrs ***_direct_parents, + int *_ndirect); + +static errno_t +sdap_initgr_store_group_memberships(struct sdap_initgr_nested_state *state) +{ + errno_t ret; + int i, tret; + TALLOC_CTX *tmp_ctx; + struct membership_diff *miter = NULL; + struct membership_diff *memberships = NULL; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* Compute the diffs first in order to keep the transaction as small + * as possible + */ + for (i=0; i < state->groups_cur; i++) { + ret = sdap_initgr_nested_get_membership_diff(tmp_ctx, state->sysdb, + state->opts, state->dom, + state->groups[i], + state->groups, + state->groups_cur, + &miter); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not compute memberships for group %d [%d]: %s\n", + i, ret, strerror(ret)); + goto done; + } + + DLIST_ADD(memberships, miter); + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + DLIST_FOR_EACH(miter, memberships) { + ret = sysdb_update_members(state->dom, miter->name, + SYSDB_MEMBER_GROUP, + (const char *const *) miter->add, + (const char *const *) miter->del); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to update memberships\n"); + goto done; + } + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_initgr_store_user_memberships(struct sdap_initgr_nested_state *state) +{ + errno_t ret; + int tret; + const char *orig_dn; + + char **sysdb_parent_name_list = NULL; + char **ldap_parent_name_list = NULL; + char **ldap_fqdnlist = NULL; + + int nparents; + struct sysdb_attrs **ldap_parentlist; + struct ldb_message_element *el; + int i, mi; + char **add_groups; + char **del_groups; + TALLOC_CTX *tmp_ctx; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* Get direct LDAP parents */ + ret = sysdb_attrs_get_string(state->user, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "The user has no original DN\n"); + goto done; + } + + ldap_parentlist = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + state->groups_cur + 1); + if (!ldap_parentlist) { + ret = ENOMEM; + goto done; + } + nparents = 0; + + for (i=0; i < state->groups_cur ; i++) { + ret = sysdb_attrs_get_el(state->groups[i], SYSDB_MEMBER, &el); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "A group with no members during initgroups?\n"); + goto done; + } + + for (mi = 0; mi < el->num_values; mi++) { + if (strcasecmp((const char *) el->values[mi].data, orig_dn) != 0) { + continue; + } + + ldap_parentlist[nparents] = state->groups[i]; + nparents++; + } + } + + DEBUG(SSSDBG_TRACE_LIBS, + "The user %s is a direct member of %d LDAP groups\n", + state->username, nparents); + + if (nparents == 0) { + ldap_parent_name_list = NULL; + } else { + ret = sdap_get_primary_name_list(state->dom, tmp_ctx, ldap_parentlist, + nparents, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + &ldap_parent_name_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + if (ldap_parent_name_list) { + ldap_fqdnlist = sss_create_internal_fqname_list( + tmp_ctx, + (const char * const *) ldap_parent_name_list, + state->dom->name); + if (ldap_fqdnlist == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_get_direct_parents(tmp_ctx, state->dom, state->dom, + SYSDB_MEMBER_USER, + state->username, &sysdb_parent_name_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + state->username, ret, strerror(ret)); + goto done; + } + + ret = diff_string_lists(tmp_ctx, + ldap_fqdnlist, sysdb_parent_name_list, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Updating memberships for %s\n", state->username); + ret = sysdb_update_members(state->dom, state->username, SYSDB_MEMBER_USER, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not update sysdb memberships for %s: %d [%s]\n", + state->username, ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +sdap_initgr_nested_get_membership_diff(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *group, + struct sysdb_attrs **all_groups, + int groups_count, + struct membership_diff **_mdiff) +{ + errno_t ret; + struct membership_diff *mdiff; + const char *group_name; + + struct sysdb_attrs **ldap_parentlist; + int parents_count; + + char **ldap_parent_names_list = NULL; + char **sysdb_parents_names_list = NULL; + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* Get direct sysdb parents */ + ret = sdap_get_group_primary_name(tmp_ctx, opts, group, dom, &group_name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_get_direct_parents(tmp_ctx, dom, dom, SYSDB_MEMBER_GROUP, + group_name, &sysdb_parents_names_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + group_name, ret, strerror(ret)); + goto done; + } + + /* For each group, filter only parents from full set */ + ret = sdap_initgr_nested_get_direct_parents(tmp_ctx, + group, + all_groups, + groups_count, + &ldap_parentlist, + &parents_count); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get parent groups for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "The group %s is a direct member of %d LDAP groups\n", + group_name, parents_count); + + if (parents_count > 0) { + ret = sdap_get_primary_fqdn_list(dom, tmp_ctx, ldap_parentlist, + parents_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &ldap_parent_names_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_primary_name_list failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } + + ret = build_membership_diff(tmp_ctx, group_name, ldap_parent_names_list, + sysdb_parents_names_list, &mdiff); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not build membership diff for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + + ret = EOK; + *_mdiff = talloc_steal(mem_ctx, mdiff); +done: + talloc_free(tmp_ctx); + return ret; +} + +static int sdap_initgr_nested_get_direct_parents(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *attrs, + struct sysdb_attrs **groups, + int ngroups, + struct sysdb_attrs ***_direct_parents, + int *_ndirect) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *member; + int i, mi; + int ret; + const char *orig_dn; + + int ndirect; + struct sysdb_attrs **direct_groups; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + direct_groups = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + ngroups + 1); + if (!direct_groups) { + ret = ENOMEM; + goto done; + } + ndirect = 0; + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing originalDN\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, + "Looking up direct parents for group [%s]\n", orig_dn); + + /* FIXME - Filter only parents from full set to avoid searching + * through all members of huge groups. That requires asking for memberOf + * with the group LDAP search + */ + + /* Filter only direct parents from the list of all groups */ + for (i=0; i < ngroups; i++) { + ret = sysdb_attrs_get_el(groups[i], SYSDB_MEMBER, &member); + if (ret) { + DEBUG(SSSDBG_TRACE_LIBS, + "A group with no members during initgroups?\n"); + continue; + } + + for (mi = 0; mi < member->num_values; mi++) { + if (strcasecmp((const char *) member->values[mi].data, orig_dn) != 0) { + continue; + } + + direct_groups[ndirect] = groups[i]; + ndirect++; + } + } + direct_groups[ndirect] = NULL; + + DEBUG(SSSDBG_TRACE_ALL, + "The group [%s] has %d direct parents\n", orig_dn, ndirect); + + *_direct_parents = talloc_steal(mem_ctx, direct_groups); + *_ndirect = ndirect; + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static int sdap_initgr_nested_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307-BIS================= */ +struct sdap_initgr_rfc2307bis_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + const char *name; + char *base_filter; + char *filter; + const char **attrs; + const char *orig_dn; + + int timeout; + + size_t base_iter; + struct sdap_search_base **search_bases; + + struct sdap_op *op; + + hash_table_t *group_hash; + size_t num_direct_parents; + struct sysdb_attrs **direct_groups; +}; + +struct sdap_nested_group { + struct sysdb_attrs *group; + struct sysdb_attrs **ldap_parents; + size_t parents_count; +}; + +static errno_t sdap_initgr_rfc2307bis_next_base(struct tevent_req *req); +static void sdap_initgr_rfc2307bis_process(struct tevent_req *subreq); +static void sdap_initgr_rfc2307bis_done(struct tevent_req *subreq); +errno_t save_rfc2307bis_user_memberships( + struct sdap_initgr_rfc2307bis_state *state); + +static struct tevent_req *sdap_initgr_rfc2307bis_send( + TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_domain *sdom, + struct sdap_handle *sh, + const char *name, + const char *orig_dn) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_initgr_rfc2307bis_state *state; + const char **attr_filter; + char *clean_orig_dn; + bool use_id_mapping; + char *oc_list; + + req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307bis_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sdom->dom->sysdb; + state->dom = sdom->dom; + state->sh = sh; + state->op = NULL; + state->name = name; + state->direct_groups = NULL; + state->num_direct_parents = 0; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->base_iter = 0; + state->search_bases = sdom->group_search_bases; + state->orig_dn = orig_dn; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a group search base\n"); + ret = EINVAL; + goto done; + } + + ret = sss_hash_create(state, 0, &state->group_hash); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + ret = ENOMEM; + goto done; + } + + attr_filter[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) goto done; + + ret = sss_filter_sanitize_dn(state, orig_dn, &clean_orig_dn); + if (ret != EOK) goto done; + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + oc_list = sdap_make_oc_list(state, opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = + talloc_asprintf(state, + "(&(%s=%s)(%s)(%s=*)", + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_orig_dn, oc_list, + opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + if (use_id_mapping) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to groups with a GID value. But there must be a SID to map + * from. + */ + state->base_filter = talloc_asprintf_append(state->base_filter, + "(%s=*))", + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name); + } else { + state->base_filter = talloc_asprintf_append(state->base_filter, ")"); + } + if (!state->base_filter) { + talloc_zfree(req); + return NULL; + } + + + talloc_zfree(clean_orig_dn); + + ret = sdap_initgr_rfc2307bis_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static errno_t sdap_initgr_rfc2307bis_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_initgr_rfc2307bis_state *state; + + state = tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for parent groups for user [%s] with base [%s]\n", + state->orig_dn, state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307bis_process, req); + + return EOK; +} + +static void sdap_initgr_rfc2307bis_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307bis_state *state; + struct sysdb_attrs **ldap_groups; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + + ret = sdap_get_generic_recv(subreq, state, + &count, + &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Found %zu parent groups for user [%s]\n", count, state->name); + + /* Add this batch of groups to the list */ + if (count > 0) { + state->direct_groups = + talloc_realloc(state, + state->direct_groups, + struct sysdb_attrs *, + state->num_direct_parents + count + 1); + if (!state->direct_groups) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + */ + for (i = 0; i < count; i++) { + state->direct_groups[state->num_direct_parents + i] = + talloc_steal(state->direct_groups, ldap_groups[i]); + } + + state->num_direct_parents += count; + + state->direct_groups[state->num_direct_parents] = NULL; + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = sdap_initgr_rfc2307bis_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + if (state->num_direct_parents == 0) { + /* Start a transaction to look up the groups in the sysdb + * and update them with LDAP data + */ + ret = save_rfc2307bis_user_memberships(state); + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return; + } + + subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts, + state->sysdb, state->dom, + state->sh, + state->search_bases, + state->direct_groups, + state->num_direct_parents, + state->group_hash, 0); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307bis_done, req); +} + +static errno_t +save_rfc2307bis_groups(struct sdap_initgr_rfc2307bis_state *state); +static errno_t +save_rfc2307bis_group_memberships(struct sdap_initgr_rfc2307bis_state *state); + +static void sdap_initgr_rfc2307bis_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_initgr_rfc2307bis_state *state = + tevent_req_data(req, struct sdap_initgr_rfc2307bis_state); + bool in_transaction = false; + errno_t tret; + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* save the groups if they are not cached */ + ret = save_rfc2307bis_groups(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save groups memberships [%d]\n", ret); + goto fail; + } + + /* save the group membership */ + ret = save_rfc2307bis_group_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save group memberships [%d]\n", ret); + goto fail; + } + + /* save the user memberships */ + ret = save_rfc2307bis_user_memberships(state); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not save user memberships [%d]\n", ret); + goto fail; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + tevent_req_done(req); + return; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); + return; +} + +static int sdap_initgr_rfc2307bis_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct rfc2307bis_group_memberships_state { + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + + hash_table_t *group_hash; + + struct membership_diff *memberships; + + int ret; +}; + +static errno_t +save_rfc2307bis_groups(struct sdap_initgr_rfc2307bis_state *state) +{ + struct sysdb_attrs **groups = NULL; + unsigned long count; + hash_value_t *values; + int hret, i; + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct sdap_nested_group *gr; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + hret = hash_values(state->group_hash, &count, &values); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + groups = talloc_array(tmp_ctx, struct sysdb_attrs *, count); + if (!groups) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + gr = talloc_get_type(values[i].ptr, + struct sdap_nested_group); + groups[i] = gr->group; + } + talloc_zfree(values); + + ret = sdap_nested_groups_store(state->sysdb, state->dom, state->opts, + groups, count); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool rfc2307bis_group_memberships_build(hash_entry_t *item, void *user_data); + +static errno_t +save_rfc2307bis_group_memberships(struct sdap_initgr_rfc2307bis_state *state) +{ + errno_t ret, tret; + int hret; + TALLOC_CTX *tmp_ctx; + struct rfc2307bis_group_memberships_state *membership_state; + struct membership_diff *iter; + struct membership_diff *iter_start; + struct membership_diff *iter_tmp; + bool in_transaction = false; + int num_added; + int i; + int grp_count; + char **add = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + membership_state = talloc_zero(tmp_ctx, + struct rfc2307bis_group_memberships_state); + if (!membership_state) { + ret = ENOMEM; + goto done; + } + + membership_state->sysdb = state->sysdb; + membership_state->dom = state->dom; + membership_state->opts = state->opts; + membership_state->group_hash = state->group_hash; + + hret = hash_iterate(state->group_hash, + rfc2307bis_group_memberships_build, + membership_state); + if (hret != HASH_SUCCESS) { + ret = membership_state->ret; + goto done; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + iter_start = membership_state->memberships; + + DLIST_FOR_EACH(iter, membership_state->memberships) { + /* Create a copy of iter->add array but do not include groups outside + * nesting limit. This array must be NULL terminated. + */ + for (grp_count = 0; iter->add[grp_count]; grp_count++); + add = talloc_zero_array(tmp_ctx, char *, grp_count + 1); + if (add == NULL) { + ret = ENOMEM; + goto done; + } + + num_added = 0; + for (i = 0; i < grp_count; i++) { + DLIST_FOR_EACH(iter_tmp, iter_start) { + if (!strcmp(iter_tmp->name,iter->add[i])) { + add[num_added] = iter->add[i]; + num_added++; + break; + } + } + } + + if (num_added == 0) { + add = NULL; + } else { + add[num_added] = NULL; + } + ret = sysdb_update_members(state->dom, iter->name, + SYSDB_MEMBER_GROUP, + (const char *const *) add, + (const char *const *) iter->del); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to update memberships\n"); + goto done; + } + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static bool +rfc2307bis_group_memberships_build(hash_entry_t *item, void *user_data) +{ + struct rfc2307bis_group_memberships_state *mstate = talloc_get_type( + user_data, struct rfc2307bis_group_memberships_state); + struct sdap_nested_group *group; + char *group_name; + TALLOC_CTX *tmp_ctx; + errno_t ret; + char **sysdb_parents_names_list; + char **ldap_parents_names_list = NULL; + + struct membership_diff *mdiff; + + group_name = (char *) item->key.str; + group = (struct sdap_nested_group *) item->value.ptr; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_get_direct_parents(tmp_ctx, mstate->dom, mstate->dom, + SYSDB_MEMBER_GROUP, + group_name, &sysdb_parents_names_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + group_name, ret, strerror(ret)); + goto done; + } + + if (group->parents_count > 0) { + ret = sdap_get_primary_fqdn_list(mstate->dom, tmp_ctx, + group->ldap_parents, group->parents_count, + mstate->opts->group_map[SDAP_AT_GROUP_NAME].name, + mstate->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + mstate->opts->idmap_ctx, + &ldap_parents_names_list); + if (ret != EOK) { + goto done; + } + } + + ret = build_membership_diff(tmp_ctx, group_name, ldap_parents_names_list, + sysdb_parents_names_list, &mdiff); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not build membership diff for %s [%d]: %s\n", + group_name, ret, strerror(ret)); + goto done; + } + + talloc_steal(mstate, mdiff); + DLIST_ADD(mstate->memberships, mdiff); + ret = EOK; +done: + talloc_free(tmp_ctx); + mstate->ret = ret; + return ret == EOK ? true : false; +} + +errno_t save_rfc2307bis_user_memberships( + struct sdap_initgr_rfc2307bis_state *state) +{ + errno_t ret, tret; + char **ldap_grouplist; + char **sysdb_parent_name_list; + char **add_groups; + char **del_groups; + bool in_transaction = false; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if(!tmp_ctx) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Save parent groups to sysdb\n"); + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto error; + } + in_transaction = true; + + ret = sysdb_get_direct_parents(tmp_ctx, state->dom, state->dom, + SYSDB_MEMBER_USER, + state->name, &sysdb_parent_name_list); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not get direct sysdb parents for %s: %d [%s]\n", + state->name, ret, strerror(ret)); + goto error; + } + + if (state->num_direct_parents == 0) { + ldap_grouplist = NULL; + } + else { + ret = sdap_get_primary_fqdn_list(state->dom, tmp_ctx, + state->direct_groups, state->num_direct_parents, + state->opts->group_map[SDAP_AT_GROUP_NAME].name, + state->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + state->opts->idmap_ctx, + &ldap_grouplist); + if (ret != EOK) { + goto error; + } + } + + /* Find the differences between the sysdb and ldap lists + * Groups in ldap only must be added to the sysdb; + * groups in the sysdb only must be removed. + */ + ret = diff_string_lists(tmp_ctx, + ldap_grouplist, sysdb_parent_name_list, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto error; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", state->name); + ret = sysdb_update_members(state->dom, state->name, SYSDB_MEMBER_USER, + (const char *const *)add_groups, + (const char *const *)del_groups); + if (ret != EOK) { + goto error; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto error; + } + in_transaction = false; + + talloc_free(tmp_ctx); + return EOK; + +error: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_rfc2307bis_nested_ctx { + struct tevent_context *ev; + struct sdap_options *opts; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct sdap_handle *sh; + int timeout; + const char *base_filter; + char *filter; + const char *orig_dn; + const char **attrs; + struct sysdb_attrs **groups; + size_t num_groups; + + size_t nesting_level; + + size_t group_iter; + struct sdap_nested_group **processed_groups; + + hash_table_t *group_hash; + const char *primary_name; + + struct sysdb_handle *handle; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req); +struct tevent_req *rfc2307bis_nested_groups_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct sdap_options *opts, struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, struct sdap_handle *sh, + struct sdap_search_base **search_bases, + struct sysdb_attrs **groups, size_t num_groups, + hash_table_t *group_hash, size_t nesting) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_rfc2307bis_nested_ctx *state; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "About to process %zu groups in nesting level %zu\n", + num_groups, nesting); + + req = tevent_req_create(mem_ctx, &state, + struct sdap_rfc2307bis_nested_ctx); + if (!req) return NULL; + + if ((num_groups == 0) || + (nesting > dp_opt_get_int(opts->basic, SDAP_NESTING_LEVEL))) { + /* No parent groups to process or too deep*/ + ret = EOK; + goto done; + } + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->groups = groups; + state->num_groups = num_groups; + state->group_iter = 0; + state->nesting_level = nesting; + state->group_hash = group_hash; + state->filter = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT); + state->base_iter = 0; + state->search_bases = search_bases; + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups nested lookup request " + "without a group search base\n"); + ret = EINVAL; + goto done; + } + + state->processed_groups = talloc_array(state, + struct sdap_nested_group *, + state->num_groups); + if (state->processed_groups == NULL) { + ret = ENOMEM; + goto done; + } + + while (state->group_iter < state->num_groups) { + ret = rfc2307bis_nested_groups_step(req); + if (ret == EOK) { + /* This group had already been looked up. Continue to + * another group in the same level + */ + state->group_iter++; + continue; + } else { + goto done; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + /* All parent groups were already processed */ + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + /* EAGAIN means a lookup is in progress */ + return req; +} + +static errno_t rfc2307bis_nested_groups_next_base(struct tevent_req *req); +static void rfc2307bis_nested_groups_process(struct tevent_req *subreq); +static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **attr_filter; + char *clean_orig_dn; + hash_key_t key; + hash_value_t value; + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + char *oc_list; + const char *class; + + tmp_ctx = talloc_new(state); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_OBJECTCATEGORY, &class); + if (ret == EOK) { + /* If there is a objectClass attribute the object is coming from the + * cache and the name attribute of the object already has the primary + * name. + * If the objectClass attribute is missing the object is coming from + * LDAP and we have to find the primary name first. */ + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_NAME, &state->primary_name); + } else { + ret = sdap_get_group_primary_name(state, state->opts, + state->groups[state->group_iter], + state->dom, &state->primary_name); + } + if (ret != EOK) { + goto done; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(state, state->primary_name); + if (!key.str) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing group [%s]\n", state->primary_name); + + ret = hash_lookup(state->group_hash, &key, &value); + if (ret == HASH_SUCCESS) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Group [%s] was already processed, " + "taking a shortcut\n", state->primary_name); + state->processed_groups[state->group_iter] = + talloc_get_type(value.ptr, struct sdap_nested_group); + talloc_free(key.str); + ret = EOK; + goto done; + } + + /* Need to try to find parent groups for this group. */ + state->processed_groups[state->group_iter] = + talloc_zero(state->processed_groups, struct sdap_nested_group); + if (!state->processed_groups[state->group_iter]) { + ret = ENOMEM; + goto done; + } + + /* this steal doesn't change much now, but will be helpful later on + * if we steal the whole processed_group on the hash table */ + state->processed_groups[state->group_iter]->group = + talloc_steal(state->processed_groups[state->group_iter], + state->groups[state->group_iter]); + + /* Get any parent groups for this group */ + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_ORIG_DN, + &state->orig_dn); + if (ret != EOK) { + goto done; + } + + attr_filter = talloc_array(state, const char *, 2); + if (!attr_filter) { + ret = ENOMEM; + goto done; + } + + attr_filter[0] = state->opts->group_map[SDAP_AT_GROUP_MEMBER].name; + attr_filter[1] = NULL; + + ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP, + attr_filter, &state->attrs, NULL); + if (ret != EOK) { + goto done; + } + + ret = sss_filter_sanitize_dn(tmp_ctx, state->orig_dn, &clean_orig_dn); + if (ret != EOK) { + goto done; + } + + oc_list = sdap_make_oc_list(state, state->opts->group_map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto done; + } + + state->base_filter = talloc_asprintf( + state, "(&(%s=%s)(%s)(%s=*))", + state->opts->group_map[SDAP_AT_GROUP_MEMBER].name, + clean_orig_dn, oc_list, + state->opts->group_map[SDAP_AT_GROUP_NAME].name); + if (!state->base_filter) { + ret = ENOMEM; + goto done; + } + + ret = rfc2307bis_nested_groups_next_base(req); + if (ret != EOK) goto done; + + /* Still processing parent groups */ + ret = EAGAIN; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t rfc2307bis_nested_groups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_rfc2307bis_nested_ctx *state; + + state = tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for parent groups of group [%s] with base [%s]\n", + state->orig_dn, + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, + rfc2307bis_nested_groups_process, + req); + + return EOK; +} + +static void +rfc2307bis_nested_groups_iterate(struct tevent_req *req, + struct sdap_rfc2307bis_nested_ctx *state) +{ + errno_t ret; + + state->group_iter++; + while (state->group_iter < state->num_groups) { + ret = rfc2307bis_nested_groups_step(req); + if (ret == EAGAIN) { + /* Looking up parent groups.. */ + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* EOK means this group has already been processed + * in another nesting level */ + state->group_iter++; + } + + if (state->group_iter == state->num_groups) { + /* All groups processed. Done. */ + tevent_req_done(req); + } +} + +static void rfc2307bis_nested_groups_done(struct tevent_req *subreq); +static void rfc2307bis_nested_groups_process(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + size_t count; + size_t i; + struct sysdb_attrs **ldap_groups; + struct sdap_nested_group *ngr; + hash_value_t value; + hash_key_t key; + int hret; + + ret = sdap_get_generic_recv(subreq, state, + &count, + &ldap_groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Found %zu parent groups of [%s]\n", count, state->orig_dn); + ngr = state->processed_groups[state->group_iter]; + + /* Add this batch of groups to the list */ + if (count > 0) { + ngr->ldap_parents = + talloc_realloc(ngr, + ngr->ldap_parents, + struct sysdb_attrs *, + ngr->parents_count + count + 1); + if (!ngr->ldap_parents) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new groups into the list. + * They're allocated on 'state' so we need to move them + * onto ldap_parents so that the data won't disappear when + * we finish this nesting level. + */ + for (i = 0; i < count; i++) { + ngr->ldap_parents[ngr->parents_count + i] = + talloc_steal(ngr->ldap_parents, ldap_groups[i]); + } + + ngr->parents_count += count; + + ngr->ldap_parents[ngr->parents_count] = NULL; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Total of %zu direct parents after this iteration\n", + ngr->parents_count); + } + + state->base_iter++; + + /* Check for additional search bases, and iterate + * through again. + */ + if (state->search_bases[state->base_iter] != NULL) { + ret = rfc2307bis_nested_groups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* Reset the base iterator for future lookups */ + state->base_iter = 0; + + /* Save the group into the hash table */ + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(state, state->primary_name); + if (!key.str) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the nested group entry on the group_hash context so it can + * outlive this request */ + talloc_steal(state->group_hash, ngr); + + value.type = HASH_VALUE_PTR; + value.ptr = ngr; + + hret = hash_enter(state->group_hash, &key, &value); + if (hret != HASH_SUCCESS) { + talloc_free(key.str); + tevent_req_error(req, EIO); + return; + } + talloc_free(key.str); + + if (ngr->parents_count == 0) { + /* No parent groups for this group in LDAP + * Move on to the next group + */ + rfc2307bis_nested_groups_iterate(req, state); + return; + } + + /* Otherwise, recurse into the groups */ + subreq = rfc2307bis_nested_groups_send( + state, state->ev, state->opts, state->sysdb, + state->dom, state->sh, + state->search_bases, + ngr->ldap_parents, + ngr->parents_count, + state->group_hash, + state->nesting_level+1); + if (!subreq) { + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, rfc2307bis_nested_groups_done, req); +} + +errno_t rfc2307bis_nested_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static void rfc2307bis_nested_groups_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_rfc2307bis_nested_ctx *state = + tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "rfc2307bis_nested failed [%d][%s]\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + rfc2307bis_nested_groups_iterate(req, state); +} + +/* ==Initgr-call-(groups-a-user-is-member-of)============================= */ + +struct sdap_get_initgr_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_domain *sdom; + struct sdap_handle *sh; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *user_op; + const char *filter_value; + const char **grp_attrs; + const char **user_attrs; + char *user_base_filter; + char *shortname; + char *filter; + int timeout; + bool non_posix; + + struct sysdb_attrs *orig_user; + + size_t user_base_iter; + struct sdap_search_base **user_search_bases; + + bool use_id_mapping; +}; + +static errno_t sdap_get_initgr_next_base(struct tevent_req *req); +static errno_t sdap_get_initgr_user_connect(struct tevent_req *req); +static void sdap_get_initgr_user_connect_done(struct tevent_req *subreq); +static void sdap_get_initgr_user(struct tevent_req *subreq); +static void sdap_get_initgr_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_handle *sh, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + const char *filter_value, + int filter_type, + const char *extra_value, + const char **grp_attrs, + bool set_non_posix) +{ + struct tevent_req *req; + struct sdap_get_initgr_state *state; + int ret; + char *clean_name; + bool use_id_mapping; + const char *search_attr = NULL; + char *ep_filter; + + DEBUG(SSSDBG_TRACE_ALL, "Retrieving info for initgroups call\n"); + + req = tevent_req_create(memctx, &state, struct sdap_get_initgr_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = id_ctx->opts; + state->dom = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->sdom = sdom; + state->sh = sh; + state->id_ctx = id_ctx; + state->conn = conn; + state->filter_value = filter_value; + state->grp_attrs = grp_attrs; + state->orig_user = NULL; + state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT); + state->user_base_iter = 0; + state->user_search_bases = sdom->user_search_bases; + if (!state->user_search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Initgroups lookup request without a user search base\n"); + ret = EINVAL; + goto done; + } + + if (state->dom->type == DOM_TYPE_APPLICATION || set_non_posix) { + state->non_posix = true; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + id_ctx->opts->idmap_ctx, + sdom->dom->name, + sdom->dom->domain_id); + + switch (filter_type) { + case BE_FILTER_SECID: + search_attr = state->opts->user_map[SDAP_AT_USER_OBJECTSID].name; + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + break; + case BE_FILTER_UUID: + search_attr = state->opts->user_map[SDAP_AT_USER_UUID].name; + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + break; + case BE_FILTER_NAME: + if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) { + + ret = sss_filter_sanitize(state, state->filter_value, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + + ep_filter = get_enterprise_principal_string_filter(state, + state->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_name, state->opts->basic); + state->user_base_filter = + talloc_asprintf(state, + "(&(|(%s=%s)(%s=%s)%s)(objectclass=%s)", + state->opts->user_map[SDAP_AT_USER_PRINC].name, + clean_name, + state->opts->user_map[SDAP_AT_USER_EMAIL].name, + clean_name, + ep_filter == NULL ? "" : ep_filter, + state->opts->user_map[SDAP_OC_USER].name); + if (state->user_base_filter == NULL) { + talloc_zfree(req); + return NULL; + } + } else { + search_attr = state->opts->user_map[SDAP_AT_USER_NAME].name; + + ret = sss_parse_internal_fqname(state, filter_value, + &state->shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot parse %s\n", filter_value); + goto done; + } + + ret = sss_filter_sanitize(state, state->shortname, &clean_name); + if (ret != EOK) { + talloc_zfree(req); + return NULL; + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported filter type [%d].\n", + filter_type); + return NULL; + } + + if (search_attr == NULL && state->user_base_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing search attribute name or filter.\n"); + talloc_zfree(req); + return NULL; + } + + if (state->user_base_filter == NULL) { + state->user_base_filter = + talloc_asprintf(state, "(&(%s=%s)(objectclass=%s)", + search_attr, clean_name, + state->opts->user_map[SDAP_OC_USER].name); + if (!state->user_base_filter) { + talloc_zfree(req); + return NULL; + } + } + + if (state->non_posix) { + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + ")"); + } else if (use_id_mapping) { + /* When mapping IDs or looking for SIDs, we don't want to limit + * ourselves to users with a UID value. But there must be a SID to map + * from. + */ + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + "(%s=*))", + id_ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name); + } else { + /* When not ID-mapping or looking up app users, make sure there + * is a non-NULL UID */ + state->user_base_filter = talloc_asprintf_append(state->user_base_filter, + "(&(%s=*)(!(%s=0))))", + id_ctx->opts->user_map[SDAP_AT_USER_UID].name, + id_ctx->opts->user_map[SDAP_AT_USER_UID].name); + } + if (!state->user_base_filter) { + talloc_zfree(req); + return NULL; + } + + ret = build_attrs_from_map(state, + state->opts->user_map, + state->opts->user_map_cnt, + NULL, &state->user_attrs, NULL); + if (ret) { + talloc_zfree(req); + return NULL; + } + + state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + state->opts->idmap_ctx, + state->dom->name, + state->dom->domain_id); + + ret = sdap_get_initgr_user_connect(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t sdap_get_initgr_user_connect(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_initgr_state *state; + int ret = EOK; + struct sdap_id_conn_ctx *user_conn = NULL; + + state = tevent_req_data(req, struct sdap_get_initgr_state); + + /* Prefer LDAP over GC for users */ + user_conn = get_ldap_conn_from_sdom_pvt(state->id_ctx->opts, state->sdom); + state->user_op = sdap_id_op_create(state, user_conn == NULL + ? state->conn->conn_cache + : user_conn->conn_cache); + if (state->user_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + return ENOMEM; + } + + subreq = sdap_id_op_connect_send(state->user_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed\n"); + return ret; + } + + tevent_req_set_callback(subreq, sdap_get_initgr_user_connect_done, req); + return EOK; +} + +static void sdap_get_initgr_user_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_get_initgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; +} + +static errno_t sdap_get_initgr_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_initgr_state *state; + + state = tevent_req_data(req, struct sdap_get_initgr_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->user_base_filter, + state->user_search_bases[state->user_base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for users with base [%s]\n", + state->user_search_bases[state->user_base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, sdap_id_op_handle(state->user_op), + state->user_search_bases[state->user_base_iter]->basedn, + state->user_search_bases[state->user_base_iter]->scope, + state->filter, state->user_attrs, + state->opts->user_map, state->opts->user_map_cnt, + state->timeout, + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_initgr_user, req); + return EOK; +} + +static int sdap_search_initgr_user_in_batch(struct sdap_get_initgr_state *state, + struct sysdb_attrs **users, + size_t count) +{ + int ret = EINVAL; + + for (size_t i = 0; i < count; i++) { + if (sdap_object_in_domain(state->opts, users[i], state->dom) == false) { + continue; + } + + state->orig_user = talloc_steal(state, users[i]); + ret = EOK; + break; + } + + return ret; +} + +static void sdap_get_initgr_user(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + struct sysdb_attrs **usr_attrs; + size_t count; + int ret; + errno_t sret; + const char *orig_dn; + const char *cname; + bool in_transaction = false; + + DEBUG(SSSDBG_TRACE_ALL, "Receiving info for the user\n"); + + ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (count == 0) { + /* No users found in this search */ + state->user_base_iter++; + if (state->user_search_bases[state->user_base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_initgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* fallback to fetch a local user if required */ + if ((state->opts->schema_type == SDAP_SCHEMA_RFC2307) && + (dp_opt_get_bool(state->opts->basic, + SDAP_RFC2307_FALLBACK_TO_LOCAL_USERS) == true)) { + ret = sdap_fallback_local_user(state, state->shortname, -1, &usr_attrs); + if (ret == EOK) { + state->orig_user = usr_attrs[0]; + } + } else { + ret = ENOENT; + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + } else if (count == 1) { + state->orig_user = usr_attrs[0]; + } else { + DEBUG(SSSDBG_FUNC_DATA, + "The search returned %zu entries, need to match the correct one\n", + count); + + /* When matching against a search base, it's sufficient to pick only + * the first search base because all bases in a single domain would + * have the same DC= components + */ + ret = sdap_search_initgr_user_in_batch(state, usr_attrs, count); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_search_initgr_user_in_batch failed [%d]: %s :" + "SSSD can't select a user that matches domain %s\n", + ret, sss_strerror(ret), state->dom->name); + tevent_req_error(req, ret); + return; + } + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + DEBUG(SSSDBG_TRACE_ALL, "Storing the user\n"); + + ret = sdap_save_user(state, state->opts, state->dom, state->orig_user, + NULL, NULL, 0, state->non_posix); + if (ret) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, "Commit change\n"); + + ret = sysdb_transaction_commit(state->sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + ret = sysdb_get_real_name(state, state->dom, state->filter_value, &cname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot canonicalize username\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, "Process user's groups\n"); + + switch (state->opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + subreq = sdap_initgr_rfc2307_send(state, state->ev, state->opts, + state->sysdb, state->dom, state->sh, + cname); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_AD: + ret = sysdb_attrs_get_string(state->orig_user, + SYSDB_ORIG_DN, + &orig_dn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2003 + && dp_opt_get_bool(state->opts->basic, SDAP_AD_USE_TOKENGROUPS)) { + /* Take advantage of AD's tokenGroups mechanism to look up all + * parent groups in a single request. + */ + subreq = sdap_ad_tokengroups_initgroups_send(state, state->ev, + state->id_ctx, + state->conn, + state->opts, + state->sysdb, + state->dom, + state->sh, + cname, orig_dn, + state->timeout, + state->use_id_mapping); + } else { + subreq = sdap_initgr_rfc2307bis_send( + state, state->ev, state->opts, + state->sdom, state->sh, + cname, orig_dn); + } + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + talloc_steal(subreq, orig_dn); + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + break; + + case SDAP_SCHEMA_IPA_V1: + subreq = sdap_initgr_nested_send(state, state->ev, state->opts, + state->sysdb, state->dom, state->sh, + state->orig_user, state->grp_attrs); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_done, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + + return; +fail: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + tevent_req_error(req, ret); +} + +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq); + +errno_t sdap_ad_check_domain_local_groups(struct tevent_req *req) +{ + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + int ret; + struct sdap_domain *local_sdom; + const char *orig_name; + const char *sysdb_name; + struct ldb_result *res; + struct tevent_req *subreq; + struct sysdb_attrs **groups; + + /* We only need to check for domain local groups in the AD case and if the + * user is not from our domain, i.e. if the user comes from a sub-domain. + */ + if (state->opts->schema_type != SDAP_SCHEMA_AD + || !IS_SUBDOMAIN(state->dom) + || !dp_target_enabled(state->id_ctx->be->provider, "ad", DPT_ID)) { + return EOK; + } + + local_sdom = sdap_domain_get(state->id_ctx->opts, state->dom->parent); + if (local_sdom == NULL || local_sdom->pvt == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + state->dom->parent->name); + return EINVAL; + } + + ret = sysdb_attrs_get_string(state->orig_user, SYSDB_NAME, &orig_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name in user object.\n"); + return ret; + } + + sysdb_name = sss_create_internal_fqname(state, orig_name, state->dom->name); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + return ENOMEM; + } + + ret = sysdb_initgroups(state, state->dom, sysdb_name, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_initgroups failed for user [%s].\n", + sysdb_name); + return ret; + } + + if (res->count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups returned no results for user [%s].\n", + sysdb_name); + return EINVAL; + } + + /* The user object, the first entry in the res->msgs, is included as well + * to cover the case where the remote user is directly added to + * a domain local group. */ + ret = sysdb_msg2attrs(state, res->count, res->msgs, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_msg2attrs failed.\n"); + return ret; + } + + subreq = sdap_ad_get_domain_local_groups_send(state, state->ev, local_sdom, + state->opts, state->sysdb, state->dom->parent, + groups, res->count); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_get_domain_local_groups_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_ad_check_domain_local_groups_done, + req); + + return EAGAIN; +} + +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = sdap_ad_get_domain_local_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + + return; +} + +static void sdap_get_initgr_pgid(struct tevent_req *req); +static void sdap_get_initgr_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + int ret; + TALLOC_CTX *tmp_ctx; + gid_t primary_gid; + char *gid; + char *sid_str; + char *dom_sid_str; + char *group_sid_str; + struct sdap_options *opts = state->opts; + struct ldb_message *msg; + + DEBUG(SSSDBG_TRACE_ALL, "Initgroups done\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + tevent_req_error(req, ENOMEM); + return; + } + + switch (state->opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + ret = sdap_initgr_rfc2307_recv(subreq); + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_AD: + if (state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2003 + && dp_opt_get_bool(state->opts->basic, SDAP_AD_USE_TOKENGROUPS)) { + + ret = sdap_ad_tokengroups_initgroups_recv(subreq); + } else { + ret = sdap_initgr_rfc2307bis_recv(subreq); + } + break; + + case SDAP_SCHEMA_IPA_V1: + ret = sdap_initgr_nested_recv(subreq); + break; + + default: + + ret = EINVAL; + break; + } + + talloc_zfree(subreq); + if (ret) { + DEBUG(SSSDBG_TRACE_ALL, "Error in initgroups: [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + /* We also need to update the user's primary group, since + * the user may not be an explicit member of that group + */ + + if (state->use_id_mapping) { + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping primary group to unix ID\n"); + + /* The primary group ID is just the RID part of the objectSID + * of the group. Generate the GID by adding this to the domain + * SID value. + */ + + /* Get the user SID so we can extract the domain SID + * from it. + */ + ret = sdap_attrs_get_sid_str( + tmp_ctx, opts->idmap_ctx, state->orig_user, + opts->user_map[SDAP_AT_USER_OBJECTSID].sys_name, + &sid_str); + if (ret != EOK) goto done; + + /* Get the domain SID from the user SID */ + ret = sdap_idmap_get_dom_sid_from_object(tmp_ctx, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + + ret = sysdb_attrs_get_uint32_t( + state->orig_user, + opts->user_map[SDAP_AT_USER_PRIMARY_GROUP].sys_name, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "no primary group ID provided\n"); + ret = EINVAL; + goto done; + } + + /* Add the RID to the end */ + group_sid_str = talloc_asprintf(tmp_ctx, "%s-%lu", + dom_sid_str, + (unsigned long)primary_gid); + if (!group_sid_str) { + ret = ENOMEM; + goto done; + } + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, group_sid_str, + &primary_gid); + if (ret != EOK) goto done; + } else { + ret = sysdb_attrs_get_uint32_t(state->orig_user, SYSDB_GIDNUM, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Could not find user's primary GID\n"); + goto done; + } + } + + ret = sysdb_search_group_by_gid(tmp_ctx, state->dom, primary_gid, NULL, + &msg); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Primary group already cached, nothing to do.\n"); + } else { + gid = talloc_asprintf(state, "%lu", (unsigned long)primary_gid); + if (gid == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = groups_get_send(req, state->ev, state->id_ctx, + state->id_ctx->opts->sdom, state->conn, + gid, BE_FILTER_IDNUM, false, + false, false); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sdap_get_initgr_pgid, req); + + talloc_free(tmp_ctx); + return; + } + + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + talloc_free(tmp_ctx); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "memberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + ret = EOK; + } + +done: + talloc_free(tmp_ctx); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + return; +} + +static void sdap_get_initgr_pgid(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + errno_t ret; + + ret = groups_get_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "memberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + } + + tevent_req_done(req); + return; +} + +int sdap_get_initgr_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t get_sysdb_grouplist_ex(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist, + bool get_dn) +{ + errno_t ret; + const char *attrs[2]; + struct ldb_message *msg; + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *groups; + char **sysdb_grouplist = NULL; + unsigned int i; + + attrs[0] = SYSDB_MEMBEROF; + attrs[1] = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, + attrs, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Error searching user [%s] by name: [%s]\n", + name, strerror(ret)); + goto done; + } + + groups = ldb_msg_find_element(msg, SYSDB_MEMBEROF); + if (!groups || groups->num_values == 0) { + /* No groups for this user in sysdb currently */ + sysdb_grouplist = NULL; + } else { + sysdb_grouplist = talloc_array(tmp_ctx, char *, groups->num_values+1); + if (!sysdb_grouplist) { + ret = ENOMEM; + goto done; + } + + if (get_dn) { + /* Get distinguish name */ + for (i=0; i < groups->num_values; i++) { + sysdb_grouplist[i] = talloc_strdup(sysdb_grouplist, + (const char *)groups->values[i].data); + if (sysdb_grouplist[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + } else { + /* Get a list of the groups by groupname only */ + for (i=0; i < groups->num_values; i++) { + ret = sysdb_group_dn_name(sysdb, + sysdb_grouplist, + (const char *)groups->values[i].data, + &sysdb_grouplist[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine group name from [%s]: [%s]\n", + (const char *)groups->values[i].data, strerror(ret)); + goto done; + } + } + } + + sysdb_grouplist[groups->num_values] = NULL; + } + + *grouplist = talloc_steal(mem_ctx, sysdb_grouplist); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t get_sysdb_grouplist(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist) +{ + return get_sysdb_grouplist_ex(mem_ctx, sysdb, domain, + name, grouplist, false); +} + +errno_t get_sysdb_grouplist_dn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist) +{ + return get_sysdb_grouplist_ex(mem_ctx, sysdb, domain, + name, grouplist, true); +} + +errno_t +sdap_handle_id_collision_for_incomplete_groups(struct data_provider *dp, + struct sss_domain_info *domain, + const char *name, + gid_t gid, + const char *original_dn, + const char *sid_str, + const char *uuid, + bool posix, + time_t now) +{ + errno_t ret; + + ret = sysdb_delete_group(domain, NULL, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Due to an id collision, the new group with gid [\"%"PRIu32"\"] " + "will not be added as the old group (with the same gid) could " + "not be removed from the sysdb!\n", + gid); + return ret; + } + + ret = sysdb_add_incomplete_group(domain, name, gid, original_dn, sid_str, + uuid, posix, now); + if (ret != EOK) { + return ret; + } + + dp_sbus_invalidate_group_memcache(dp, gid); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c new file mode 100644 index 0000000..fb80c92 --- /dev/null +++ b/src/providers/ldap/sdap_async_initgroups_ad.c @@ -0,0 +1,1742 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ad/ad_common.h" +#include "lib/idmap/sss_idmap.h" + +struct sdap_get_ad_tokengroups_state { + struct tevent_context *ev; + struct sss_idmap_ctx *idmap_ctx; + const char *username; + + char **sids; + size_t num_sids; +}; + +static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_get_ad_tokengroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *attrs[] = {AD_TOKENGROUPS_ATTR, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_get_ad_tokengroups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->idmap_ctx = opts->idmap_ctx->map; + state->ev = ev; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send(state, state->ev, opts, sh, orig_dn, + LDAP_SCOPE_BASE, NULL, attrs, + NULL, 0, timeout, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_get_ad_tokengroups_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **users = NULL; + struct ldb_message_element *el = NULL; + enum idmap_error_code err; + char *sid_str = NULL; + size_t num_users; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); + + ret = sdap_get_generic_recv(subreq, state, &num_users, &users); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "LDAP search failed: [%s]\n", sss_strerror(ret)); + goto done; + } + + if (num_users != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, + "More than one result on a base search!\n"); + ret = EINVAL; + goto done; + } + + /* get the list of sids from tokengroups */ + ret = sysdb_attrs_get_el_ext(users[0], AD_TOKENGROUPS_ATTR, false, &el); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, "No tokenGroups entries for [%s]\n", + state->username); + + state->sids = NULL; + state->num_sids = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not read tokenGroups attribute: " + "[%s]\n", strerror(ret)); + goto done; + } + + state->num_sids = 0; + state->sids = talloc_zero_array(state, char*, el->num_values); + if (state->sids == NULL) { + ret = ENOMEM; + goto done; + } + + /* convert binary sid to string */ + for (i = 0; i < el->num_values; i++) { + err = sss_idmap_bin_sid_to_sid(state->idmap_ctx, el->values[i].data, + el->values[i].length, &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert binary SID to string: [%s]. Skipping\n", + idmap_error_string(err)); + continue; + } + + state->sids[i] = talloc_move(state->sids, &sid_str); + state->num_sids++; + } + + /* shrink array to final number of elements */ + state->sids = talloc_realloc(state, state->sids, char*, state->num_sids); + if (state->sids == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_get_ad_tokengroups_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + size_t *_num_sids, + char ***_sids) +{ + struct sdap_get_ad_tokengroups_state *state = NULL; + state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_num_sids != NULL) { + *_num_sids = state->num_sids; + } + + if (_sids != NULL) { + *_sids = talloc_steal(mem_ctx, state->sids); + } + + return EOK; +} + +errno_t +sdap_ad_tokengroups_update_members(const char *username, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + char **ldap_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **sysdb_groups = NULL; + char **add_groups = NULL; + char **del_groups = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* Get the current sysdb group list for this user so we can update it. */ + ret = get_sysdb_grouplist_dn(tmp_ctx, sysdb, domain, + username, &sysdb_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not get the list of groups for " + "[%s] in the sysdb: [%s]\n", username, strerror(ret)); + goto done; + } + + /* Find the differences between the sysdb and LDAP lists. + * Groups in the sysdb only must be removed. */ + ret = diff_string_lists(tmp_ctx, ldap_groups, sysdb_groups, + &add_groups, &del_groups, NULL); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Updating memberships for [%s]\n", username); + + ret = sysdb_update_members_dn(domain, username, SYSDB_MEMBER_USER, + (const char *const *) add_groups, + (const char *const *) del_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_ad_resolve_sids_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sss_domain_info *domain; + char **sids; + + const char *current_sid; + int index; +}; + +static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req); +static void sdap_ad_resolve_sids_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_resolve_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sss_domain_info *domain, + char **sids) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_resolve_sids_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->conn = conn; + state->opts = opts; + state->domain = get_domains_head(domain); + state->sids = sids; + state->index = 0; + + if (state->sids == NULL || state->sids[0] == NULL) { + ret = EOK; + goto immediately; + } + + ret = sdap_ad_resolve_sids_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdap_domain = NULL; + struct sss_domain_info *domain = NULL; + + state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); + + do { + state->current_sid = state->sids[state->index]; + if (state->current_sid == NULL) { + return EOK; + } + state->index++; + + domain = sss_get_domain_by_sid_ldap_fallback(state->domain, + state->current_sid); + + if (domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID %s does not belong to any known " + "domain\n", state->current_sid); + } + } while (domain == NULL); + + sdap_domain = sdap_domain_get(state->opts, domain); + if (sdap_domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "SDAP domain does not exist?\n"); + return ERR_INTERNAL; + } + + subreq = groups_get_send(state, state->ev, state->id_ctx, sdap_domain, + state->conn, state->current_sid, + BE_FILTER_SECID, false, true, false); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_ad_resolve_sids_done, req); + + return EAGAIN; +} + +static void sdap_ad_resolve_sids_done(struct tevent_req *subreq) +{ + struct sdap_ad_resolve_sids_state *state = NULL; + struct tevent_req *req = NULL; + int dp_error; + int sdap_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); + + ret = groups_get_recv(subreq, &dp_error, &sdap_error); + talloc_zfree(subreq); + + if (ret == EOK && sdap_error == ENOENT && dp_error == DP_ERR_OK) { + /* Group was not found, we will ignore the error and continue with + * next group. This may happen for example if the group is built-in, + * but a custom search base is provided. */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to resolve SID %s - will try next sid.\n", + state->current_sid); + } else if (ret != EOK || sdap_error != EOK || dp_error != DP_ERR_OK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve SID %s [dp_error: %d, " + "sdap_error: %d, ret: %d]: %s\n", state->current_sid, dp_error, + sdap_error, ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_resolve_sids_step(req); + if (ret == EAGAIN) { + /* continue with next SID */ + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_ad_resolve_sids_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +struct sdap_ad_tokengroups_initgr_mapping_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sdap_idmap_ctx *idmap_ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + const char *orig_dn; + int timeout; + const char *username; + + struct sdap_id_op *op; +}; + +static void +sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq); +static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq); +static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *orig_dn, + int timeout, + const char *username, + struct sdap_handle *sh, + struct tevent_req *req, + tevent_req_fn callback); + +static struct tevent_req * +sdap_ad_tokengroups_initgr_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdom; + struct ad_id_ctx *subdom_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgr_mapping_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->idmap_ctx = opts->idmap_ctx; + state->sysdb = sysdb; + state->domain = domain; + state->timeout = timeout; + state->orig_dn = orig_dn; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + sdom = sdap_domain_get(opts, domain); + if (sdom == NULL || sdom->pvt == NULL) { + ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, + state->username, sh, req, + sdap_ad_tokengroups_initgr_mapping_done); + if (ret == EOK) { + return req; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + domain->name); + goto immediately; + } + } + + subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, + sdap_ad_tokengroups_initgr_mapping_connect_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + int ret; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_ad_tokengroups_initgr_mapping_state); + + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, + sdap_id_op_handle(state->op), + state->username, + state->orig_dn, state->timeout); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_mapping_done, + req); + + return; +} + +errno_t sdap_ad_save_group_membership_with_idmapping(const char *username, + struct sdap_options *opts, + struct sss_domain_info *user_dom, + struct sdap_idmap_ctx *idmap_ctx, + size_t num_sids, + char **sids) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sss_domain_info *domain = NULL; + struct ldb_message *msg = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + const char *name = NULL; + const char *sid = NULL; + size_t i; + time_t now; + gid_t gid; + char **groups = NULL; + size_t num_groups; + errno_t ret; + errno_t sret; + bool in_transaction = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + num_groups = 0; + groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + now = time(NULL); + ret = sysdb_transaction_start(user_dom->sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + for (i = 0; i < num_sids; i++) { + sid = sids[i]; + DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); + + ret = sdap_idmap_sid_to_unix(idmap_ctx, sid, &gid); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not convert SID to GID: [%s]. " + "Skipping\n", strerror(ret)); + continue; + } + + domain = sss_get_domain_by_sid_ldap_fallback(user_dom, sid); + if (domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); + continue; + } + + DEBUG(SSSDBG_TRACE_LIBS, "SID [%s] maps to GID [%"SPRIgid"]\n", + sid, gid); + + /* Check whether this GID already exists in the sysdb */ + ret = sysdb_search_group_by_gid(tmp_ctx, domain, gid, attrs, &msg); + if (ret == EOK) { + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve group name from sysdb\n"); + ret = EINVAL; + goto done; + } + } else if (ret == ENOENT) { + /* This is a new group. For now, we will store it under the name + * of its SID. When a direct lookup of the group or its GID occurs, + * it will replace this temporary entry. */ + name = sss_create_internal_fqname(tmp_ctx, sid, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_add_incomplete_group(domain, name, gid, + NULL, sid, NULL, false, now); + if (ret == ERR_GID_DUPLICATED) { + /* In case o group id-collision, do: + * - Delete the group from sysdb + * - Add the new incomplete group + * - Notify the NSS responder that the entry has also to be + * removed from the memory cache + */ + ret = sdap_handle_id_collision_for_incomplete_groups( + idmap_ctx->id_ctx->be->provider, + domain, name, gid, NULL, sid, NULL, + false, now); + } + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not create incomplete " + "group: [%s]\n", strerror(ret)); + goto done; + } + } else { + /* Unexpected error */ + DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up group in sysdb: " + "[%s]\n", strerror(ret)); + goto done; + } + + groups[num_groups] = sysdb_group_strdn(tmp_ctx, domain->name, name); + if (groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + num_groups++; + } + + groups[num_groups] = NULL; + + ret = sdap_ad_tokengroups_update_members(username, + user_dom->sysdb, user_dom, + groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(user_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not commit transaction! [%s]\n", + strerror(ret)); + goto done; + } + in_transaction = false; + +done: + talloc_free(tmp_ctx); + + if (in_transaction) { + sret = sysdb_transaction_cancel(user_dom->sysdb); + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + + return ret; +} + +static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; + struct tevent_req *req = NULL; + char **sids = NULL; + size_t num_sids = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_mapping_state); + + ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_save_group_membership_with_idmapping(state->username, + state->opts, + state->domain, + state->idmap_ctx, + num_sids, + sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_save_group_membership_with_idmapping failed.\n"); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_ad_tokengroups_initgr_mapping_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ad_tokengroups_initgr_posix_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + const char *orig_dn; + int timeout; + const char *username; + + struct sdap_id_op *op; + char **missing_sids; + size_t num_missing_sids; + char **cached_groups; + size_t num_cached_groups; +}; + +static void +sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq); + +static void +sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq); +static void +sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_ad_tokengroups_initgr_posix_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_domain *sdom; + struct ad_id_ctx *subdom_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgr_posix_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->conn = conn; + state->opts = opts; + state->sh = sh; + state->sysdb = sysdb; + state->domain = domain; + state->orig_dn = orig_dn; + state->timeout = timeout; + state->username = talloc_strdup(state, name); + if (state->username == NULL) { + ret = ENOMEM; + goto immediately; + } + + sdom = sdap_domain_get(opts, domain); + if (sdom == NULL || sdom->pvt == NULL) { + ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, + state->username, sh, req, + sdap_ad_tokengroups_initgr_posix_tg_done); + if (ret == EOK) { + return req; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + domain->name); + goto immediately; + } + } + subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, + sdap_ad_tokengroups_initgr_posix_sids_connect_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + int ret; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_ad_tokengroups_initgr_posix_state); + + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, + sdap_id_op_handle(state->op), + state->username, state->orig_dn, + state->timeout); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_tg_done, + req); + + return; +} + +errno_t +sdap_ad_tokengroups_get_posix_members(TALLOC_CTX *mem_ctx, + struct sss_domain_info *user_domain, + size_t num_sids, + char **sids, + size_t *_num_missing, + char ***_missing, + size_t *_num_valid, + char ***_valid_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sss_domain_info *domain = NULL; + struct ldb_message *msg = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + const char *name = NULL; + char *sid = NULL; + char **valid_groups = NULL; + size_t num_valid_groups; + char **missing_sids = NULL; + size_t num_missing_sids; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + ret = ENOMEM; + goto done; + } + + num_valid_groups = 0; + valid_groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (valid_groups == NULL) { + ret = ENOMEM; + goto done; + } + + num_missing_sids = 0; + missing_sids = talloc_zero_array(tmp_ctx, char*, num_sids + 1); + if (missing_sids == NULL) { + ret = ENOMEM; + goto done; + } + + /* For each SID check if it is already present in the cache. If yes, we + * will get name of the group and update the membership. Otherwise we need + * to remember the SID and download missing groups one by one. */ + for (i = 0; i < num_sids; i++) { + sid = sids[i]; + DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); + + domain = sss_get_domain_by_sid_ldap_fallback(user_domain, sid); + if (domain == NULL) { + const char *check_dom; + const char *check_name; + + ret = well_known_sid_to_name(sid, &check_dom, &check_name); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Skipping SID [%s][%s\\%s] which is " + "currently not handled by SSSD.\n", + sid, check_dom, check_name); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); + } + continue; + } + + ret = sysdb_search_group_by_sid_str(tmp_ctx, domain, sid, attrs, &msg); + if (ret == EOK) { + /* we will update membership of this group */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve group name from sysdb\n"); + ret = EINVAL; + goto done; + } + + valid_groups[num_valid_groups] = sysdb_group_strdn(valid_groups, + domain->name, + name); + if (valid_groups[num_valid_groups] == NULL) { + ret = ENOMEM; + goto done; + } + num_valid_groups++; + } else if (ret == ENOENT) { + if (_missing != NULL) { + /* we need to download this group */ + missing_sids[num_missing_sids] = talloc_steal(missing_sids, + sid); + num_missing_sids++; + + DEBUG(SSSDBG_TRACE_FUNC, "Missing SID %s will be downloaded\n", + sid); + } + + /* else: We have downloaded missing groups but some of them may + * remained missing because they are outside of search base. We + * will just ignore them and continue with the next group. */ + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up SID %s in sysdb: " + "[%s]\n", sid, strerror(ret)); + goto done; + } + } + + valid_groups[num_valid_groups] = NULL; + missing_sids[num_missing_sids] = NULL; + + /* return list of missing groups */ + if (_missing != NULL) { + *_missing = talloc_steal(mem_ctx, missing_sids); + *_num_missing = num_missing_sids; + } + + /* return list of missing groups */ + if (_valid_groups != NULL) { + *_valid_groups = talloc_steal(mem_ctx, valid_groups); + *_num_valid = num_valid_groups; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void +sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + char **sids = NULL; + size_t num_sids = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); + + ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->domain, + num_sids, sids, + &state->num_missing_sids, + &state->missing_sids, + &state->num_cached_groups, + &state->cached_groups); + if (ret != EOK) { + goto done; + } + + /* download missing SIDs */ + subreq = sdap_ad_resolve_sids_send(state, state->ev, state->id_ctx, + state->conn, + state->opts, state->domain, + state->missing_sids); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_sids_done, + req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void +sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + char **cached_groups; + size_t num_cached_groups; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); + + ret = sdap_ad_resolve_sids_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve missing SIDs " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->domain, + state->num_missing_sids, + state->missing_sids, + NULL, NULL, + &num_cached_groups, + &cached_groups); + if (ret != EOK){ + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + state->cached_groups = concatenate_string_array(state, + state->cached_groups, + state->num_cached_groups, + cached_groups, + num_cached_groups); + if (state->cached_groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* update membership of existing groups */ + ret = sdap_ad_tokengroups_update_members(state->username, + state->sysdb, state->domain, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_ad_tokengroups_initgr_posix_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ad_get_domain_local_groups_state { + struct tevent_context *ev; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + int dp_error; + + struct sdap_search_base **search_bases; + struct sysdb_attrs **groups; + size_t num_groups; + hash_table_t *group_hash; +}; + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq); +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups) +{ + struct sdap_ad_get_domain_local_groups_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_get_domain_local_groups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + ad_id_ctx = talloc_get_type(local_sdom->pvt, struct ad_id_ctx); + state->conn = ad_id_ctx->ldap_ctx; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->search_bases = state->conn->id_ctx->opts->sdom->group_search_bases; + state->groups = groups; + state->num_groups = num_groups; + + ret = sss_hash_create(state, 0, &state->group_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto fail; + } + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_connect_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts, + state->sysdb, state->dom, + sdap_id_op_handle(state->op), + state->search_bases, + state->groups, state->num_groups, + state->group_hash, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rfc2307bis_nested_groups_send failed.\n"); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_done, req); + + return; +} + +struct sdap_nested_group { + struct sysdb_attrs *group; + struct sysdb_attrs **ldap_parents; + size_t parents_count; +}; + +static errno_t +sdap_ad_get_domain_local_groups_parse_parents(TALLOC_CTX *mem_ctx, + struct sdap_nested_group *gr, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + const char **_sysdb_name, + enum sysdb_member_type *_type, + char ***_add_list, + char ***_del_list) +{ + int ret; + size_t c; + char **groupnamelist = NULL; + struct sysdb_attrs *groups[1]; + enum sysdb_member_type type; + const char *sysdb_name; + const char *group_name; + const char *class; + struct sss_domain_info *obj_dom; + char *local_groups_base_dn; + char **cached_local_parents = NULL; + char **add_list = NULL; + char **del_list = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + local_groups_base_dn = talloc_asprintf(tmp_ctx, SYSDB_TMPL_GROUP_BASE, + dom->name); + if (local_groups_base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (gr->parents_count != 0) { + /* Store the parents if needed */ + ret = sdap_nested_groups_store(sysdb, dom, opts, + gr->ldap_parents, gr->parents_count); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sdap_get_primary_fqdn_list(dom, tmp_ctx, gr->ldap_parents, + gr->parents_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + opts->group_map[SDAP_AT_GROUP_OBJECTSID].name, + opts->idmap_ctx, + &groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_primary_fqdn_list failed.\n"); + goto done; + } + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_NAME, &sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed to get SYSDB_NAME, " + "skipping.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_OBJECTCATEGORY, &class); + if (ret != EOK) { + /* If objectcategory is missing, gr->group is a nested parent found during + * the nested group lookup. It might not already be stored in the cache. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "sysdb_attrs_get_string failed to get %s for [%s], assuming " + "group.\n", SYSDB_OBJECTCATEGORY, sysdb_name); + + /* make sure group exists in cache */ + groups[0]= gr->group; + ret = sdap_nested_groups_store(sysdb, dom, opts, groups, 1); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* Since the object is coming from LDAP it cannot have the internal + * fully-qualified name, so we can expand it unconditionally. */ + group_name = NULL; + ret = sdap_get_primary_name(opts->group_map[SDAP_AT_GROUP_NAME].name, + gr->group, &group_name); + if (ret != EOK || group_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not determine primary name\n"); + group_name = sysdb_name; + } + + group_name = sss_create_internal_fqname(tmp_ctx, group_name, + dom->name); + if (group_name != NULL) { + sysdb_name = group_name; + } + + type = SYSDB_MEMBER_GROUP; + } else { + if (class != NULL && strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + } + + /* We need to get the cached list of groups form the local domain the + * object is a member of to compare them with the current list just + * retrieved (groupnamelist). Even if this list is empty we have to + * proceed because the membership might have been removed recently on the + * server. */ + + obj_dom = find_domain_by_object_name(get_domains_head(dom), + sysdb_name); + if (obj_dom == NULL) { + obj_dom = dom; + DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for [%s], " + "trying with local domain [%s].\n", + sysdb_name, obj_dom->name); + } + + ret = sysdb_get_direct_parents(tmp_ctx, obj_dom, dom, type, sysdb_name, + &cached_local_parents); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE,"sysdb_get_direct_parents failed.\n"); + goto done; + } + + if (cached_local_parents != NULL && cached_local_parents[0] == NULL) { + talloc_zfree(cached_local_parents); + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (cached_local_parents != NULL) { + for (c = 0; cached_local_parents[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] cached_local_parents [%s].\n", + sysdb_name, cached_local_parents[c]); + } + } + + if (groupnamelist != NULL) { + for (c = 0; groupnamelist[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] groupnamelist [%s].\n", + sysdb_name, groupnamelist[c]); + } + } + } + + ret = diff_string_lists(tmp_ctx, cached_local_parents, groupnamelist, + &del_list, &add_list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n"); + goto done; + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (add_list != NULL) { + for (c = 0; add_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "add: [%s] will be member of [%s].\n", + sysdb_name, add_list[c]); + } + } + if (del_list != NULL) { + for (c = 0; del_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "del: [%s] was member of [%s].\n", + sysdb_name, del_list[c]); + } + } + } + + *_type = type; + *_sysdb_name = talloc_steal(mem_ctx, sysdb_name); + *_add_list = talloc_steal(mem_ctx, groupnamelist); + *_del_list = talloc_steal(mem_ctx, del_list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int ret; + int hret; + unsigned long count; + hash_value_t *values = NULL; + struct sdap_nested_group *gr; + size_t c; + const char *sysdb_name = NULL; + enum sysdb_member_type type; + char **add_list = NULL; + char **del_list = NULL; + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret == ENOENT) { + /* In case of ENOENT we can just proceed without making + * sdap_get_initgr_user() fail because there's no nested + * groups for this user/group. */ + ret = EOK; + goto done; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + hret = hash_values(state->group_hash, &count, &values); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_values failed.\n"); + ret = EIO; + goto done; + } + + for (c = 0; c < count; c++) { + gr = talloc_get_type(values[c].ptr, + struct sdap_nested_group); + + /* The values from the hash are either user or group objects returned + * by sysdb_initgroups() which where used to start the request or + * nested parents found during the request. The nested parents contain + * the processed LDAP data and can be identified by a missing + * objectclass attribute. */ + ret = sdap_ad_get_domain_local_groups_parse_parents(state, gr, + state->dom, + state->sysdb, + state->opts, + &sysdb_name, + &type, + &add_list, + &del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_get_domain_local_groups_parse_parents failed.\n"); + continue; + } + + if ((add_list == NULL && del_list == NULL) + || (add_list == NULL && del_list != NULL && del_list[0] == NULL) + || (add_list != NULL && add_list[0] == NULL && del_list == NULL) + || (add_list != NULL && add_list[0] == NULL + && del_list != NULL && del_list[0] == NULL) ) { + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating domain local memberships for %s\n", + sysdb_name); + ret = sysdb_update_members(state->dom, sysdb_name, type, + (const char *const *) add_list, + (const char *const *) del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_members failed.\n"); + goto done; + } + } + + ret = EOK; +done: + talloc_zfree(values); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_ad_tokengroups_initgroups_state { + bool use_id_mapping; + bool use_shortcut; + struct sss_domain_info *domain; +}; + +static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_conn_ctx *conn, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_handle *sh, + const char *name, + const char *orig_dn, + int timeout, + bool use_id_mapping) +{ + struct sdap_ad_tokengroups_initgroups_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + char **param = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_tokengroups_initgroups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->use_id_mapping = use_id_mapping; + state->domain = domain; + + /* We can compute the gidNumber attribute from SIDs obtained from + * the tokenGroups lookup in case ID mapping is used for a user from the + * parent domain. For trusted domains, we need to know the group type + * to be able to filter out domain-local groups. Additionally, as a + * temporary workaround until https://fedorahosted.org/sssd/ticket/2656 + * is fixed, we also fetch the group object if group members are ignored + * to avoid having to transfer and retain members when the fake + * tokengroups object without name is replaced by the full group object + */ + state->use_shortcut = false; + if (state->use_id_mapping + && !IS_SUBDOMAIN(state->domain) + && !state->domain->ignore_group_members) { + ret = confdb_get_param(id_ctx->be->cdb, mem_ctx, id_ctx->be->conf_path, + CONFDB_NSS_FILTER_GROUPS, ¶m); + if (ret == EOK) { + state->use_shortcut = (param == NULL || param[0] == NULL); + talloc_free(param); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to access %s: %i (%s)\n", + CONFDB_NSS_FILTER_GROUPS, ret, sss_strerror(ret)); + /* Continue without using the shortcut. Safest option. */ + } + } + if (state->use_shortcut) { + subreq = sdap_ad_tokengroups_initgr_mapping_send(state, ev, opts, + sysdb, domain, sh, + name, orig_dn, + timeout); + } else { + subreq = sdap_ad_tokengroups_initgr_posix_send(state, ev, id_ctx, conn, + opts, sysdb, domain, sh, + name, orig_dn, + timeout); + } + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgroups_done, req); + } + + return req; +} + +static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq) +{ + struct sdap_ad_tokengroups_initgroups_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ad_tokengroups_initgroups_state); + + if (state->use_shortcut) { + ret = sdap_ad_tokengroups_initgr_mapping_recv(subreq); + } else { + ret = sdap_ad_tokengroups_initgr_posix_recv(subreq); + } + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_ad_tokengroups_initgroups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *orig_dn, + int timeout, + const char *username, + struct sdap_handle *sh, + struct tevent_req *req, + tevent_req_fn callback) +{ + struct tevent_req *subreq = NULL; + errno_t ret; + + if (sh != NULL) { + /* plain LDAP provider already has a sdap_handle */ + subreq = sdap_get_ad_tokengroups_send(mem_ctx, ev, opts, sh, username, + orig_dn, timeout); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + goto done; + } + + tevent_req_set_callback(subreq, callback, req); + ret = EOK; + goto done; + + } else { + ret = EINVAL; + goto done; + } + +done: + return ret; +} + +struct sdap_id_conn_ctx *get_ldap_conn_from_sdom_pvt(struct sdap_options *opts, + struct sdap_domain *sdom) +{ + struct ad_id_ctx *ad_id_ctx; + struct sdap_id_conn_ctx *user_conn = NULL; + + if (opts->schema_type == SDAP_SCHEMA_AD && sdom->pvt != NULL) { + ad_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); + if (ad_id_ctx != NULL && ad_id_ctx->ldap_ctx != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Returning LDAP connection for user lookup.\n"); + user_conn = ad_id_ctx->ldap_ctx; + } + } + + return user_conn; +} diff --git a/src/providers/ldap/sdap_async_iphost.c b/src/providers/ldap/sdap_async_iphost.c new file mode 100644 index 0000000..4b4dcad --- /dev/null +++ b/src/providers/ldap/sdap_async_iphost.c @@ -0,0 +1,640 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "db/sysdb_iphosts.h" + +struct sdap_get_iphost_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **iphosts; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_iphost_next_base(struct tevent_req *req); + +struct tevent_req * +sdap_get_iphost_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + struct sdap_get_iphost_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_iphost_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->iphosts = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "IP host lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_iphost_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static void +sdap_get_iphost_process(struct tevent_req *subreq); + +static errno_t +sdap_get_iphost_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_iphost_state *state; + + state = tevent_req_data(req, struct sdap_get_iphost_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for IP host with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->iphost_map, SDAP_OPTS_IPHOST, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_iphost_process, req); + + return EOK; +} + +static errno_t +sdap_save_iphosts(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **iphosts, + size_t num_hosts, + char **_usn_value); +static void +sdap_get_iphost_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_iphost_state *state; + int ret; + size_t count, i; + struct sysdb_attrs **hosts; + bool next_base = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_iphost_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &hosts); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for IP hosts returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No hosts found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of sevices to the list */ + if (count > 0) { + state->iphosts = talloc_realloc(state, state->iphosts, + struct sysdb_attrs *, + state->count + count + 1); + if (state->iphosts == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the new hosts into the list */ + for (i = 0; i < count; i++) { + state->iphosts[state->count + i] = + talloc_steal(state->iphosts, hosts[i]); + } + + state->count += count; + state->iphosts[state->count] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_iphost_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no hosts were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_iphosts(state, state->sysdb, state->dom, state->opts, + state->iphosts, state->count, &state->higher_usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to store IP hosts.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Saved %zu IP hosts\n", state->count); + + tevent_req_done(req); +} + +static errno_t +sdap_save_iphost(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +static errno_t +sdap_save_iphosts(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **hosts, + size_t num_hosts, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_hosts == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_hosts; i++) { + usn_value = NULL; + + ret = sdap_save_iphost(tmp_ctx, sysdb, opts, dom, hosts[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP host %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "IP host [%zu/%zu] saved\n", i, num_hosts); + } + + if (usn_value != NULL) { + if (higher_usn != NULL) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value != NULL) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_iphost(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *host_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char **aliases = NULL; + const char **addresses = NULL; + const char **cased_aliases = NULL; + const char **cased_addresses = NULL; + const char **store_aliases = NULL; + const char **store_addresses = NULL; + char **missing; + uint64_t cache_timeout; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + host_attrs = sysdb_new_attrs(tmp_ctx); + if (host_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this hosts */ + ret = sdap_get_primary_name(opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the IP host\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "IP host primary name: [%s]\n", name); + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP host aliases: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the addresses */ + ret = sysdb_attrs_get_string_array(attrs, SYSDB_IP_HOST_ATTR_ADDRESS, + tmp_ctx, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP host addresses: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive aliases: [%s]\n", + strerror(ret)); + goto done; + } + + ret = sss_get_cased_name_list(tmp_ctx, addresses, + dom->case_sensitive, &cased_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive addresses: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_aliases = dom->case_sensitive ? aliases : cased_aliases; + store_addresses = dom->case_sensitive ? addresses : cased_addresses; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->iphost_map[SDAP_AT_IPHOST_USN].sys_name, + &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(host_attrs, + opts->iphost_map[SDAP_AT_IPHOST_USN].sys_name, + (const char*)el->values[0].data); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (usn_value == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(host_attrs, opts->iphost_map, SDAP_OPTS_IPHOST, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->resolver_timeout; + + ret = sysdb_store_host(dom, name, store_aliases, store_addresses, + host_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP host in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; + +} + +errno_t +sdap_get_iphost_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + struct sdap_get_iphost_state *state; + + state = tevent_req_data(req, struct sdap_get_iphost_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value != NULL) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +/* Enumeration routines */ + +struct enum_iphosts_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_iphosts_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_iphosts_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_iphosts_state *state; + + req = tevent_req_create(memctx, &state, struct enum_iphosts_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_iphost_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_USN].name, + id_ctx->srv_opts->max_iphost_value, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_USN].name, + id_ctx->srv_opts->max_iphost_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name, + id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->iphost_map, + SDAP_OPTS_IPHOST, NULL, + &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + subreq = sdap_get_iphost_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->id_ctx->opts->sdom->iphost_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_iphosts_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_iphosts_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_iphosts_state *state = + tevent_req_data(req, struct enum_iphosts_state); + char *usn_value = NULL; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_iphost_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_iphost_value); + state->id_ctx->srv_opts->max_iphost_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "IP host higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_iphost_value); + + tevent_req_done(req); +} + +errno_t +enum_iphosts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_ipnetwork.c b/src/providers/ldap/sdap_async_ipnetwork.c new file mode 100644 index 0000000..5e5b181 --- /dev/null +++ b/src/providers/ldap/sdap_async_ipnetwork.c @@ -0,0 +1,625 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "db/sysdb_ipnetworks.h" + +struct sdap_get_ipnetwork_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **entries; + size_t num_entries; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_ipnetwork_next_base(struct tevent_req *req); + +struct tevent_req * +sdap_get_ipnetwork_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + struct sdap_get_ipnetwork_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_ipnetwork_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->entries = NULL; + state->num_entries = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "IP network lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_ipnetwork_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static void +sdap_get_ipnetwork_process(struct tevent_req *subreq); + +static errno_t +sdap_get_ipnetwork_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_ipnetwork_state *state; + + state = tevent_req_data(req, struct sdap_get_ipnetwork_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for IP network with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_ipnetwork_process, req); + + return EOK; +} + +static errno_t +sdap_save_ipnetworks(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **entries, + size_t num_entries, + char **_usn_value); + +static void +sdap_get_ipnetwork_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_get_ipnetwork_state *state; + int ret; + size_t count, i; + struct sysdb_attrs **entries; + bool next_base = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_get_ipnetwork_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &entries); + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for IP networks returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No entries found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of entries to the list */ + if (count > 0) { + state->entries = talloc_realloc(state, state->entries, + struct sysdb_attrs *, + state->num_entries + count + 1); + if (state->entries == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the new entries into the list */ + for (i = 0; i < count; i++) { + state->entries[state->num_entries + i] = + talloc_steal(state->entries, entries[i]); + } + + state->num_entries += count; + state->entries[state->num_entries] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_ipnetwork_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no entries were found + */ + if (state->num_entries == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_ipnetworks(state, state->sysdb, state->dom, state->opts, + state->entries, state->num_entries, + &state->higher_usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to store IP networks.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Saved %zu IP networks\n", state->num_entries); + + tevent_req_done(req); +} + + +static errno_t +sdap_save_ipnetwork(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +static errno_t +sdap_save_ipnetworks(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **entries, + size_t num_entries, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_entries == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_entries; i++) { + usn_value = NULL; + + ret = sdap_save_ipnetwork(tmp_ctx, sysdb, opts, dom, entries[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP network %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "IP network [%zu/%zu] saved\n", i, num_entries); + } + + if (usn_value != NULL) { + if (higher_usn != NULL) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value != NULL) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_ipnetwork(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *net_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char *address = NULL; + const char **aliases = NULL; + const char **cased_aliases = NULL; + const char **store_aliases = NULL; + char **missing; + uint64_t cache_timeout; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + net_attrs = sysdb_new_attrs(tmp_ctx); + if (net_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this network */ + ret = sdap_get_primary_name(opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the IP network\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "IP network primary name: [%s]\n", name); + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP network aliases: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the address */ + ret = sysdb_attrs_get_string(attrs, SYSDB_IP_NETWORK_ATTR_NUMBER, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify IP network number: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive aliases: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_aliases = dom->case_sensitive ? aliases : cased_aliases; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].sys_name, + &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(net_attrs, + opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].sys_name, + (const char*)el->values[0].data); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (usn_value == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(net_attrs, opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->resolver_timeout; + + ret = sysdb_store_ipnetwork(dom, name, store_aliases, address, + net_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store IP network in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_get_ipnetwork_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + /* TODO */ + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* Enumeration routines */ + +struct enum_ipnetworks_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_ipnetworks_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_ipnetworks_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_ipnetworks_state *state; + + req = tevent_req_create(memctx, &state, struct enum_ipnetworks_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_ipnetwork_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name, + id_ctx->srv_opts->max_ipnetwork_value, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_USN].name, + id_ctx->srv_opts->max_ipnetwork_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name, + id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, NULL, + &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + subreq = sdap_get_ipnetwork_send(state, state->ev, + state->domain, state->sysdb, state->id_ctx->opts, + state->id_ctx->opts->sdom->ipnetwork_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_ipnetworks_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_ipnetworks_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_ipnetworks_state *state = + tevent_req_data(req, struct enum_ipnetworks_state); + char *usn_value = NULL; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_ipnetwork_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_ipnetwork_value); + state->id_ctx->srv_opts->max_ipnetwork_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "IP network higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_ipnetwork_value); + + tevent_req_done(req); +} + +errno_t +enum_ipnetworks_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_nested_groups.c b/src/providers/ldap/sdap_async_nested_groups.c new file mode 100644 index 0000000..2e3b0c4 --- /dev/null +++ b/src/providers/ldap/sdap_async_nested_groups.c @@ -0,0 +1,2997 @@ +/* + SSSD + + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 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 <errno.h> +#include <string.h> +#include <tevent.h> +#include <talloc.h> +#include <ldb.h> +#include <dhash.h> +#include <stdint.h> +#include <time.h> + +#include "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_dn.h" + +#define sdap_nested_group_sysdb_search_users(domain, dn) \ + sdap_nested_group_sysdb_search((domain), (dn), true) + +#define sdap_nested_group_sysdb_search_groups(domain, dn) \ + sdap_nested_group_sysdb_search((domain), (dn), false) + +enum sdap_nested_group_dn_type { + SDAP_NESTED_GROUP_DN_USER, + SDAP_NESTED_GROUP_DN_GROUP, + SDAP_NESTED_GROUP_DN_UNKNOWN +}; + +struct sdap_nested_group_member { + enum sdap_nested_group_dn_type type; + const char *dn; + const char *user_filter; + const char *group_filter; +}; + +#ifndef EXTERNAL_MEMBERS_CHUNK +#define EXTERNAL_MEMBERS_CHUNK 16 +#endif /* EXTERNAL_MEMBERS_CHUNK */ + +struct sdap_external_missing_member { + const char **parent_group_dns; + size_t parent_dn_idx; +}; + +struct sdap_nested_group_ctx { + struct sss_domain_info *domain; + struct sdap_options *opts; + struct sdap_search_base **user_search_bases; + struct sdap_search_base **group_search_bases; + struct sdap_search_base **ignore_user_search_bases; + struct sdap_handle *sh; + hash_table_t *users; + hash_table_t *groups; + hash_table_t *missing_external; + bool try_deref; + int deref_threshold; + int max_nesting_level; +}; + +static struct tevent_req * +sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + int nesting_level, + struct sysdb_attrs *group); + +static errno_t sdap_nested_group_process_recv(struct tevent_req *req); + +static struct tevent_req * +sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *members, + int num_members, + int num_groups_max, + int nesting_level); + +static errno_t sdap_nested_group_single_recv(struct tevent_req *req); + +static struct tevent_req * +sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_user); + +static struct tevent_req * +sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_group); + +static struct tevent_req * +sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member); + +static errno_t +sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_entry, + enum sdap_nested_group_dn_type *_type); + +static struct tevent_req * +sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct ldb_message_element *members, + const char *group_dn, + int nesting_level); + +static errno_t sdap_nested_group_deref_recv(struct tevent_req *req); + +static errno_t +sdap_nested_group_extract_hash_table(TALLOC_CTX *mem_ctx, + hash_table_t *table, + unsigned long *_num_entries, + struct sysdb_attrs ***_entries) +{ + struct sysdb_attrs **entries = NULL; + struct sysdb_attrs *entry = NULL; + hash_value_t *values; + unsigned long num_entries; + unsigned int i; + bool hret; + errno_t ret; + + hret = hash_values(table, &num_entries, &values); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + if (num_entries > 0) { + entries = talloc_array(mem_ctx, struct sysdb_attrs *, num_entries); + if (entries == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_entries; i++) { + entry = talloc_get_type(values[i].ptr, struct sysdb_attrs); + entries[i] = talloc_steal(entries, entry); + } + } + + if (_num_entries != NULL) { + *_num_entries = num_entries; + } + + if (_entries != NULL) { + *_entries = entries; + } + + ret = EOK; + +done: + talloc_free(values); + + if (ret != EOK) { + talloc_free(entries); + } + + return ret; +} + +static errno_t sdap_nested_group_hash_insert(hash_table_t *table, + const char *entry_key, + void *entry_value, + bool overwrite, + const char *table_name) +{ + hash_key_t key; + hash_value_t value; + int hret; + + DEBUG(SSSDBG_TRACE_ALL, "Inserting [%s] into hash table [%s]\n", + entry_key, table_name); + + key.type = HASH_KEY_STRING; + key.c_str = discard_const(entry_key); /* hash_enter() will make a copy */ + + if (overwrite == false && hash_has_key(table, &key)) { + return EEXIST; + } + + value.type = HASH_VALUE_PTR; + value.ptr = entry_value; + + hret = hash_enter(table, &key, &value); + if (hret != HASH_SUCCESS) { + return EIO; + } + + talloc_steal(table, value.ptr); + + return EOK; +} + +static errno_t sdap_nested_group_hash_entry(hash_table_t *table, + struct sysdb_attrs *entry, + const char *table_name) +{ + const char *name = NULL; + errno_t ret; + + ret = sysdb_attrs_get_string(entry, SYSDB_DN_FOR_MEMBER_HASH_TABLE, &name); + if (ret != EOK) { + ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name); + if (ret != EOK) { + return ret; + } + } + + return sdap_nested_group_hash_insert(table, name, entry, false, table_name); +} + +static errno_t +sdap_nested_group_hash_user(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *user) +{ + return sdap_nested_group_hash_entry(group_ctx->users, user, "users"); +} + +static errno_t +sdap_nested_group_hash_group(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *group) +{ + struct sdap_attr_map *map = group_ctx->opts->group_map; + gid_t gid = 0; + errno_t ret; + bool posix_group = true; + bool use_id_mapping; + bool can_find_gid; + bool need_filter; + + ret = sdap_check_ad_group_type(group_ctx->domain, group_ctx->opts, + group, "", &need_filter); + if (ret != EOK) { + return ret; + } + + if (need_filter) { + posix_group = false; + gid = 0; + } + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + group_ctx->opts->idmap_ctx, + group_ctx->domain->name, + group_ctx->domain->domain_id); + + can_find_gid = posix_group && !use_id_mapping; + if (can_find_gid) { + ret = sysdb_attrs_get_uint32_t(group, map[SDAP_AT_GROUP_GID].sys_name, + &gid); + } + if (!can_find_gid || ret == ENOENT || (ret == EOK && gid == 0)) { + DEBUG(SSSDBG_TRACE_ALL, + "The group's gid was %s\n", ret == ENOENT ? "missing" : "zero"); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking group as non-POSIX and setting GID=0!\n"); + + if (ret == ENOENT || !posix_group) { + ret = sysdb_attrs_add_uint32(group, + map[SDAP_AT_GROUP_GID].sys_name, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add a GID to non-POSIX group!\n"); + return ret; + } + } + + ret = sysdb_attrs_add_bool(group, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark group as non-POSIX!\n"); + return ret; + } + } else if (ret != EOK) { + return ret; + } + + return sdap_nested_group_hash_entry(group_ctx->groups, group, "groups"); +} + +static errno_t sdap_nested_group_external_add(hash_table_t *table, + const char *ext_member, + const char *parent_group_dn) +{ + hash_key_t key; + hash_value_t value; + int hret; + int ret; + struct sdap_external_missing_member *ext_mem; + + key.type = HASH_KEY_STRING; + key.str = discard_const(ext_member); + + DEBUG(SSSDBG_TRACE_ALL, + "Inserting external member [%s] into external members hash table\n", + ext_member); + + hret = hash_lookup(table, &key, &value); + switch (hret) { + case HASH_ERROR_KEY_NOT_FOUND: + ext_mem = talloc_zero(table, struct sdap_external_missing_member); + if (ext_mem == NULL) { + return ENOMEM; + } + ext_mem->parent_group_dns = talloc_zero_array(ext_mem, + const char *, + EXTERNAL_MEMBERS_CHUNK); + if (ext_mem->parent_group_dns == NULL) { + talloc_free(ext_mem); + return ENOMEM; + } + + ret = sdap_nested_group_hash_insert(table, ext_member, ext_mem, + true, "missing external users"); + if (ret != EOK) { + return ret; + } + break; + + case HASH_SUCCESS: + ext_mem = talloc_get_type(value.ptr, + struct sdap_external_missing_member); + if (ext_mem->parent_dn_idx == \ + talloc_array_length(ext_mem->parent_group_dns)) { + ext_mem->parent_group_dns = talloc_realloc(ext_mem, + ext_mem->parent_group_dns, + const char *, + ext_mem->parent_dn_idx + \ + EXTERNAL_MEMBERS_CHUNK); + if (ext_mem->parent_group_dns == NULL) { + talloc_free(ext_mem); + return ENOMEM; + } + } + break; + default: + return EIO; + } + + ext_mem->parent_group_dns[ext_mem->parent_dn_idx] = \ + talloc_strdup(ext_mem->parent_group_dns, + parent_group_dn); + if (ext_mem->parent_group_dns[ext_mem->parent_dn_idx] == NULL) { + return ENOMEM; + } + ext_mem->parent_dn_idx++; + + return EOK; +} + +static errno_t sdap_nested_group_sysdb_search(struct sss_domain_info *domain, + const char *dn, + bool user) +{ + static const char *attrs[] = {SYSDB_CACHE_EXPIRE, + SYSDB_UIDNUM, + NULL}; + struct ldb_message **msgs = NULL; + size_t count; + time_t now = time(NULL); + uint64_t expire; + uid_t uid; + errno_t ret; + + if (user) { + ret = sysdb_search_users_by_orig_dn(NULL, domain, dn, attrs, + &count, &msgs); + } else { + ret = sysdb_search_groups_by_orig_dn(NULL, domain, dn, attrs, + &count, &msgs); + } + if (ret != EOK) { + goto done; + } + + if (count != 1) { + DEBUG(SSSDBG_OP_FAILURE, "More than one entry found?\n"); + ret = EFAULT; + goto done; + } + + /* we found an object with this origDN in the sysdb, + * check if it is valid */ + if (user) { + uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0); + if (uid == 0) { + DEBUG(SSSDBG_OP_FAILURE, "User with no UID?\n"); + ret = EINVAL; + goto done; + } + } + + expire = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_CACHE_EXPIRE, 0); + if (expire != 0 && expire <= now) { + /* needs refresh */ + ret = EAGAIN; + goto done; + } + + /* valid object */ + ret = EOK; + +done: + talloc_zfree(msgs); + return ret; +} + +static errno_t +sdap_nested_group_check_cache(struct sdap_options *opts, + struct sss_domain_info *domain, + const char *member_dn, + enum sdap_nested_group_dn_type *_type) +{ + struct sdap_domain *sdap_domain = NULL; + struct sss_domain_info *member_domain = NULL; + errno_t ret; + + /* determine correct domain of this member */ + sdap_domain = sdap_domain_get_by_dn(opts, member_dn); + member_domain = sdap_domain == NULL ? domain : sdap_domain->dom; + + /* search in users */ + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_PRE); + ret = sdap_nested_group_sysdb_search_users(member_domain, member_dn); + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_POST); + if (ret == EOK || ret == EAGAIN) { + /* user found */ + *_type = SDAP_NESTED_GROUP_DN_USER; + goto done; + } else if (ret != ENOENT) { + /* error */ + goto done; + } + + /* search in groups */ + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_PRE); + ret = sdap_nested_group_sysdb_search_groups(member_domain, member_dn); + PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_POST); + if (ret == EOK || ret == EAGAIN) { + /* group found */ + *_type = SDAP_NESTED_GROUP_DN_GROUP; + goto done; + } else if (ret != ENOENT) { + /* error */ + goto done; + } + + /* not found in the sysdb */ + ret = ENOENT; + +done: + return ret; +} + +static bool +sdap_nested_member_is_ent(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter, bool is_user) +{ + struct sdap_domain *sditer = NULL; + bool ret = false; + struct sdap_search_base **search_bases; + + DLIST_FOR_EACH(sditer, group_ctx->opts->sdom) { + search_bases = is_user ? sditer->user_search_bases : \ + sditer->group_search_bases; + + ret = sss_ldap_dn_in_search_bases(group_ctx, dn, search_bases, + filter); + if (ret == true) { + break; + } + } + + return ret; +} + +static inline bool +sdap_nested_member_is_user(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter) +{ + return sdap_nested_member_is_ent(group_ctx, dn, filter, true); +} + +static inline bool +sdap_nested_member_is_group(struct sdap_nested_group_ctx *group_ctx, + const char *dn, char **filter) +{ + return sdap_nested_member_is_ent(group_ctx, dn, filter, false); +} + +static errno_t +sdap_nested_group_split_members(TALLOC_CTX *mem_ctx, + struct sdap_nested_group_ctx *group_ctx, + int threshold, + int nesting_level, + struct ldb_message_element *members, + struct sdap_nested_group_member **_missing, + int *_num_missing, + int *_num_groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sdap_nested_group_member *missing = NULL; + enum sdap_nested_group_dn_type type; + char *dn = NULL; + char *user_filter = NULL; + char *group_filter = NULL; + int num_missing = 0; + int num_groups = 0; + hash_key_t key; + bool bret; + bool is_user; + bool is_group; + errno_t ret; + int i; + + if (members == NULL) { + *_missing = NULL; + *_num_missing = 0; + *_num_groups = 0; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + missing = talloc_zero_array(tmp_ctx, struct sdap_nested_group_member, + members->num_values); + if (missing == NULL) { + ret = ENOMEM; + goto done; + } + + /* create list of missing members + * skip dn if: + * - is present in user or group hash table + * - is present in sysdb and not expired + * - it is a group and we have reached the maximal nesting level + * - it is not under user nor group search bases + * + * if dn is in sysdb but expired + * - we know what object type it is + * + * if dn is not in hash table or sysdb + * - try to determine type of object by search base that match dn + */ + for (i = 0; i < members->num_values; i++) { + dn = (char*)members->values[i].data; + type = SDAP_NESTED_GROUP_DN_UNKNOWN; + + /* check hash tables */ + key.type = HASH_KEY_STRING; + key.str = dn; + + bret = hash_has_key(group_ctx->users, &key); + if (bret) { + continue; + } + + bret = hash_has_key(group_ctx->groups, &key); + if (bret) { + continue; + } + + /* check sysdb */ + PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_PRE); + ret = sdap_nested_group_check_cache(group_ctx->opts, group_ctx->domain, + dn, &type); + PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_POST); + if (ret == EOK) { + /* found and valid */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] found in cache, skipping\n", dn); + continue; + } else if (ret != EAGAIN && ret != ENOENT) { + /* error */ + goto done; + } + + /* try to determine type by dn */ + if (type == SDAP_NESTED_GROUP_DN_UNKNOWN) { + /* user */ + is_user = sdap_nested_member_is_user(group_ctx, dn, + &user_filter); + + is_group = sdap_nested_member_is_group(group_ctx, dn, + &group_filter); + + if (is_user && is_group) { + /* search bases overlap */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] is unknown object\n", dn); + type = SDAP_NESTED_GROUP_DN_UNKNOWN; + } else if (is_user) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is a user\n", dn); + type = SDAP_NESTED_GROUP_DN_USER; + } else if (is_group) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is a group\n", dn); + type = SDAP_NESTED_GROUP_DN_GROUP; + } else { + /* dn is outside search bases */ + DEBUG(SSSDBG_TRACE_ALL, "[%s] is out of scope of configured " + "search bases, skipping\n", dn); + continue; + } + } + + /* check nesting level */ + if (type == SDAP_NESTED_GROUP_DN_GROUP) { + if (nesting_level >= group_ctx->max_nesting_level) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", dn, nesting_level); + talloc_zfree(user_filter); + talloc_zfree(group_filter); + continue; + } + } + + missing[num_missing].dn = talloc_strdup(missing, dn); + if (missing[num_missing].dn == NULL) { + ret = ENOMEM; + goto done; + } + + missing[num_missing].type = type; + missing[num_missing].user_filter = talloc_steal(missing, user_filter); + missing[num_missing].group_filter = talloc_steal(missing, group_filter); + + num_missing++; + if (threshold > 0 && num_missing > threshold) { + if (_num_missing) { + *_num_missing = num_missing; + } + + ret = ERR_DEREF_THRESHOLD; + goto done; + } + + if (type != SDAP_NESTED_GROUP_DN_USER) { + num_groups++; + } + } + + missing = talloc_realloc(mem_ctx, missing, + struct sdap_nested_group_member, num_missing); + /* talloc_realloc behaves as talloc_free if 3rd parameter (count) is 0, + * so it's OK to return NULL then + */ + if (missing == NULL && num_missing > 0) { + ret = ENOMEM; + goto done; + } + + if (_missing) { + *_missing = talloc_steal(mem_ctx, missing); + } + + if (_num_missing) { + *_num_missing = num_missing; + } + + if (_num_groups) { + *_num_groups = num_groups; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sdap_nested_group_add_ext_members(struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs *group, + struct ldb_message_element *ext_members) +{ + errno_t ret; + const char *ext_member_attr; + const char *orig_dn; + + if (ext_members == NULL) { + return EOK; + } + + ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "A group with no originalDN!?!\n"); + return ret; + } + + for (size_t i = 0; i < ext_members->num_values; i++) { + ext_member_attr = (const char *) ext_members->values[i].data; + + ret = sdap_nested_group_external_add(group_ctx->missing_external, + ext_member_attr, + orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot add %s into external members [%d]: %s\n", + ext_member_attr, ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +} + +static struct ldb_message_element * +sdap_nested_group_ext_members(struct sdap_options *opts, + struct sysdb_attrs *group) +{ + errno_t ret; + struct ldb_message_element *ext_members = NULL; + + if (opts->ext_ctx == NULL) { + return NULL; + } + + ret = sysdb_attrs_get_el_ext(group, + opts->group_map[SDAP_AT_GROUP_EXT_MEMBER].sys_name, + false, &ext_members); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve external member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ext_members; +} + + +struct sdap_nested_group_state { + struct sdap_nested_group_ctx *group_ctx; +}; + +static void sdap_nested_group_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_nested_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group) +{ + struct sdap_nested_group_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + int i; + + PROBE(SDAP_NESTED_GROUP_SEND); + + req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + /* create main nested group context */ + state->group_ctx = talloc_zero(state, struct sdap_nested_group_ctx); + if (state->group_ctx == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->users); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + ret = sss_hash_create(state->group_ctx, 0, + &state->group_ctx->missing_external); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + state->group_ctx->try_deref = true; + state->group_ctx->deref_threshold = dp_opt_get_int(opts->basic, + SDAP_DEREF_THRESHOLD); + state->group_ctx->max_nesting_level = dp_opt_get_int(opts->basic, + SDAP_NESTING_LEVEL); + state->group_ctx->domain = sdom->dom; + state->group_ctx->opts = opts; + state->group_ctx->user_search_bases = sdom->user_search_bases; + state->group_ctx->group_search_bases = sdom->group_search_bases; + state->group_ctx->ignore_user_search_bases = sdom->ignore_user_search_bases; + state->group_ctx->sh = sh; + state->group_ctx->try_deref = sdap_has_deref_support(sh, opts); + + /* disable deref if threshold <= 0 */ + if (state->group_ctx->deref_threshold <= 0) { + state->group_ctx->try_deref = false; + } + + /* if any search base contains filter, disable dereference. */ + if (state->group_ctx->try_deref) { + for (i = 0; opts->sdom->user_search_bases[i] != NULL; i++) { + if (opts->sdom->user_search_bases[i]->filter != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "User search base contains filter, " + "dereference will be disabled\n"); + state->group_ctx->try_deref = false; + break; + } + } + } + + if (state->group_ctx->try_deref) { + for (i = 0; opts->sdom->group_search_bases[i] != NULL; i++) { + if (opts->sdom->group_search_bases[i]->filter != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Group search base contains filter, " + "dereference will be disabled\n"); + state->group_ctx->try_deref = false; + break; + } + } + } + + /* insert initial group into hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to insert group into hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + /* resolve group */ + subreq = sdap_nested_group_process_send(state, ev, state->group_ctx, + 0, group); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_nested_group_process_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + unsigned long *_num_users, + struct sysdb_attrs ***_users, + unsigned long *_num_groups, + struct sysdb_attrs ***_groups, + hash_table_t **_missing_external) +{ + struct sdap_nested_group_state *state = NULL; + struct sysdb_attrs **users = NULL; + struct sysdb_attrs **groups = NULL; + unsigned long num_users; + unsigned long num_groups; + errno_t ret; + + state = tevent_req_data(req, struct sdap_nested_group_state); + + PROBE(SDAP_NESTED_GROUP_RECV); + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->users, + &num_users, &users); + if (ret != EOK) { + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%lu users found in the hash table\n", + num_users); + + ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->groups, + &num_groups, &groups); + if (ret != EOK) { + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%lu groups found in the hash table\n", + num_groups); + + if (_num_users != NULL) { + *_num_users = num_users; + } + + if (_users != NULL) { + *_users = talloc_steal(mem_ctx, users); + } + + if (_num_groups!= NULL) { + *_num_groups = num_groups; + } + + if (_groups != NULL) { + *_groups = talloc_steal(mem_ctx, groups); + } + + if (_missing_external) { + *_missing_external = talloc_steal(mem_ctx, + state->group_ctx->missing_external); + } + + return EOK; +} + +struct sdap_nested_group_process_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *missing; + int num_missing_total; + int num_missing_groups; + struct ldb_message_element *ext_members; + struct ldb_message_element *members; + int nesting_level; + char *group_dn; + bool deref; + bool deref_shortcut; +}; + +static void sdap_nested_group_process_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + int nesting_level, + struct sysdb_attrs *group) +{ + struct sdap_nested_group_process_state *state = NULL; + struct sdap_attr_map *group_map = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *orig_dn = NULL; + errno_t ret; + int split_threshold; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_process_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->nesting_level = nesting_level; + group_map = state->group_ctx->opts->group_map; + + /* get original dn */ + ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve original dn " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + state->group_dn = talloc_strdup(state, orig_dn); + if (state->group_dn == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "About to process group [%s]\n", orig_dn); + PROBE(SDAP_NESTED_GROUP_PROCESS_SEND, state->group_dn); + + /* get member list, both direct and external */ + state->ext_members = sdap_nested_group_ext_members(state->group_ctx->opts, + group); + + ret = sysdb_attrs_get_el_ext(group, group_map[SDAP_AT_GROUP_MEMBER].sys_name, + false, &state->members); + if (ret == ENOENT && state->ext_members == NULL) { + ret = EOK; /* no members, direct or external */ + goto immediately; + } else if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve member list " + "[%d]: %s\n", ret, strerror(ret)); + goto immediately; + } + + split_threshold = state->group_ctx->try_deref ? \ + state->group_ctx->deref_threshold : \ + -1; + + /* get members that need to be refreshed */ + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); + ret = sdap_nested_group_split_members(state, state->group_ctx, + split_threshold, + state->nesting_level, + state->members, + &state->missing, + &state->num_missing_total, + &state->num_missing_groups); + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); + if (ret == ERR_DEREF_THRESHOLD) { + DEBUG(SSSDBG_TRACE_FUNC, + "More members were missing than the deref threshold\n"); + state->deref_shortcut = true; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + ret = sdap_nested_group_add_ext_members(state->group_ctx, + group, + state->ext_members); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split external member list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + if (state->num_missing_total == 0 + && hash_count(state->group_ctx->missing_external) == 0) { + ret = EOK; /* we're done */ + goto immediately; + } + + /* If there are only indirect members of the group, it's still safe to + * proceed and let the direct lookup code just fall through. + */ + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Looking up %d/%d members of group [%s]\n", + state->num_missing_total, + state->members ? state->members->num_values : 0, + orig_dn); + + /* process members */ + if (group_ctx->try_deref + && state->num_missing_total > group_ctx->deref_threshold) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Dereferencing members of group [%s]\n", + orig_dn); + state->deref = true; + subreq = sdap_nested_group_deref_send(state, ev, group_ctx, + state->members, orig_dn, + state->nesting_level); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " + "processed individually\n", orig_dn); + state->deref = false; + subreq = sdap_nested_group_single_send(state, ev, group_ctx, + state->missing, + state->num_missing_total, + state->num_missing_groups, + state->nesting_level); + } + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_process_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_process_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_process_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_process_state); + + if (state->deref) { + ret = sdap_nested_group_deref_recv(subreq); + talloc_zfree(subreq); + if (ret == ENOTSUP) { + /* dereference is not supported, try again without dereference */ + state->group_ctx->try_deref = false; + state->deref = false; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " + "processed individually\n", state->group_dn); + + if (state->deref_shortcut == true) { + /* If we previously short-cut dereference, we need to split the + * members again to get full list of missing member types + */ + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); + ret = sdap_nested_group_split_members(state, state->group_ctx, + -1, + state->nesting_level, + state->members, + &state->missing, + &state->num_missing_total, + &state->num_missing_groups); + PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " + "[%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + subreq = sdap_nested_group_single_send(state, + state->ev, + state->group_ctx, + state->missing, + state->num_missing_total, + state->num_missing_groups, + state->nesting_level); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_process_done, + req); + + ret = EAGAIN; + } + } else { + ret = sdap_nested_group_single_recv(subreq); + talloc_zfree(subreq); + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static errno_t sdap_nested_group_process_recv(struct tevent_req *req) +{ +#ifdef HAVE_SYSTEMTAP + struct sdap_nested_group_process_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_process_state); + + PROBE(SDAP_NESTED_GROUP_PROCESS_RECV, state->group_dn); +#endif + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_nested_group_recurse_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sysdb_attrs **groups; + int num_groups; + int index; + int nesting_level; +}; + +static errno_t sdap_nested_group_recurse_step(struct tevent_req *req); +static void sdap_nested_group_recurse_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_recurse_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sysdb_attrs **nested_groups, + int num_groups, + int nesting_level) +{ + struct sdap_nested_group_recurse_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_recurse_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->groups = nested_groups; + state->num_groups = num_groups; + state->index = 0; + state->nesting_level = nesting_level; + + /* process each group individually */ + ret = sdap_nested_group_recurse_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_nested_group_recurse_step(struct tevent_req *req) +{ + struct sdap_nested_group_recurse_state *state = NULL; + struct tevent_req *subreq = NULL; + + state = tevent_req_data(req, struct sdap_nested_group_recurse_state); + + if (state->index >= state->num_groups) { + /* we're done */ + return EOK; + } + + subreq = sdap_nested_group_process_send(state, state->ev, state->group_ctx, + state->nesting_level, + state->groups[state->index]); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_nested_group_recurse_done, req); + + state->index++; + + return EAGAIN; +} + +static void sdap_nested_group_recurse_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_nested_group_process_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = sdap_nested_group_recurse_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_nested_group_recurse_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_nested_group_single_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *members; + int nesting_level; + + struct sdap_nested_group_member *current_member; + int num_members; + int member_index; + + struct sysdb_attrs **nested_groups; + int num_groups; + bool ignore_unreadable_references; +}; + +static errno_t sdap_nested_group_single_step(struct tevent_req *req); +static void sdap_nested_group_single_step_done(struct tevent_req *subreq); +static void sdap_nested_group_single_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *members, + int num_members, + int num_groups_max, + int nesting_level) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_single_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->members = members; + state->nesting_level = nesting_level; + state->current_member = NULL; + state->num_members = num_members; + state->member_index = 0; + state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, + num_groups_max); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto immediately; + } + state->num_groups = 0; /* we will count exact number of the groups */ + state->ignore_unreadable_references = dp_opt_get_bool( + group_ctx->opts->basic, SDAP_IGNORE_UNREADABLE_REFERENCES); + + /* process each member individually */ + ret = sdap_nested_group_single_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t must_ignore(struct sdap_search_base **ignore_user_search_bases, + struct ldb_context *ldb_ctx, + const char *dn_str, + bool *_ignore) +{ + bool ignore; + struct ldb_dn *ldn; + struct sdap_search_base **base; + + if (ldb_ctx == NULL || dn_str == NULL) { + return EINVAL; + } + + if (ignore_user_search_bases == NULL) { + *_ignore = false; + return EOK; + } + + ldn = ldb_dn_new(NULL, ldb_ctx, dn_str); + if (ldn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate memory for the DN\n"); + return ENOMEM; + } + + ignore = false; + for (base = ignore_user_search_bases; *base != NULL; base++) { + if ((*base)->ldb_basedn != NULL) { + if (ldb_dn_compare_base((*base)->ldb_basedn, ldn) == 0) { + ignore = true; + DEBUG(SSSDBG_TRACE_INTERNAL, "Ignoring entry [%s]\n", dn_str); + break; + } + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Not checking ignore user search base %s \n", + (*base)->basedn); + } + } + *_ignore = ignore; + + talloc_free(ldn); + return EOK; +} + +static errno_t sdap_nested_group_single_step(struct tevent_req *req) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + bool ignore; + + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + do { + if (state->member_index >= state->num_members) { + /* we're done */ + return EOK; + } + + state->current_member = &state->members[state->member_index]; + state->member_index++; + + ret = must_ignore(state->group_ctx->ignore_user_search_bases, + sysdb_ctx_get_ldb(state->group_ctx->domain->sysdb), + state->current_member->dn, &ignore); + if (ret != EOK) { + return ret; + } + } while (ignore); + + switch (state->current_member->type) { + case SDAP_NESTED_GROUP_DN_USER: + subreq = sdap_nested_group_lookup_user_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + case SDAP_NESTED_GROUP_DN_GROUP: + subreq = sdap_nested_group_lookup_group_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + case SDAP_NESTED_GROUP_DN_UNKNOWN: + subreq = sdap_nested_group_lookup_unknown_send(state, state->ev, + state->group_ctx, + state->current_member); + break; + } + + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_nested_group_single_step_done, req); + + return EAGAIN; +} + +static errno_t +sdap_nested_group_single_step_process(struct tevent_req *subreq) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + enum sdap_nested_group_dn_type type = SDAP_NESTED_GROUP_DN_UNKNOWN; + const char *orig_dn = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + /* set correct type if possible */ + if (state->current_member->type == SDAP_NESTED_GROUP_DN_UNKNOWN) { + ret = sdap_nested_group_lookup_unknown_recv(state, subreq, + &entry, &type); + if (ret != EOK) { + goto done; + } + + if (entry != NULL) { + state->current_member->type = type; + } + } + + switch (state->current_member->type) { + case SDAP_NESTED_GROUP_DN_USER: + if (entry == NULL) { + /* type was not unknown, receive data */ + ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* user not found, continue */ + break; + } + } + + /* The original DN of the user object itself might differ from the one + * used in the member attribute, e.g. different case. To make sure if + * can be found in a hash table when iterating over group members the + * DN from the member attribute used for the search as saved as well. + */ + ret = sysdb_attrs_add_string(entry, + SYSDB_DN_FOR_MEMBER_HASH_TABLE, + state->current_member->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + /* save user in hash table */ + ret = sdap_nested_group_hash_user(state->group_ctx, entry); + if (ret == EEXIST) { + /* the user is already present, skip it */ + talloc_zfree(entry); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save user in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + break; + case SDAP_NESTED_GROUP_DN_GROUP: + if (entry == NULL) { + /* type was not unknown, receive data */ + ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* group not found, continue */ + break; + } + } else { + /* the type was unknown so we had to pull the group, + * but we don't want to process it if we have reached + * the nesting level */ + if (state->nesting_level >= state->group_ctx->max_nesting_level) { + ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The entry has no originalDN\n"); + orig_dn = "invalid"; + } + + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", orig_dn, state->nesting_level); + break; + } + } + + /* save group in hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, entry); + if (ret == EEXIST) { + /* the group is already present, skip it */ + talloc_zfree(entry); + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save group in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* remember the group for later processing */ + state->nested_groups[state->num_groups] = entry; + state->num_groups++; + + break; + case SDAP_NESTED_GROUP_DN_UNKNOWN: + if (state->ignore_unreadable_references) { + DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", + state->current_member->dn); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown entry type [%s]!\n", + state->current_member->dn); + ret = EINVAL; + goto done; + } + break; + } + + ret = EOK; + +done: + return ret; +} + +static void sdap_nested_group_single_step_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + /* process direct members */ + ret = sdap_nested_group_single_step_process(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + ret = sdap_nested_group_single_step(req); + if (ret == EOK) { + /* we have processed all direct members, + * now recurse and process nested groups */ + subreq = sdap_nested_group_recurse_send(state, state->ev, + state->group_ctx, + state->nested_groups, + state->num_groups, + state->nesting_level + 1); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_single_done, req); + } else if (ret != EAGAIN) { + /* error */ + goto done; + } + + /* we're not done yet */ + ret = EAGAIN; + +done: + if (ret == EOK) { + /* tevent_req_error() cannot cope with EOK */ + DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); + tevent_req_error(req, EINVAL); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void sdap_nested_group_single_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* all nested groups are completed */ + ret = sdap_nested_group_recurse_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing nested groups " + "[%d]: %s.\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + + return; +} + +static errno_t sdap_nested_group_single_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t sdap_nested_group_get_ipa_user(TALLOC_CTX *mem_ctx, + const char *user_dn, + struct sysdb_ctx *sysdb, + struct sysdb_attrs **_user) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *user; + char *name; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_get_rdn(tmp_ctx, sysdb, user_dn, &name, "uid", + "cn", "users", "cn", "accounts"); + if (ret != EOK) { + goto done; + } + + user = sysdb_new_attrs(tmp_ctx); + if (user == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_NAME, name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_ORIG_DN, user_dn); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(user, SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS); + if (ret != EOK) { + goto done; + } + + *_user = talloc_steal(mem_ctx, user); + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sdap_nested_group_lookup_user_state { + struct sysdb_attrs *user; +}; + +static void sdap_nested_group_lookup_user_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char **attrs = NULL; + const char *base_filter = NULL; + const char *filter = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_user_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_SEND); + + if (group_ctx->opts->schema_type == SDAP_SCHEMA_IPA_V1) { + /* if the schema is IPA, then just shortcut and guess the name */ + ret = sdap_nested_group_get_ipa_user(state, member->dn, + group_ctx->domain->sysdb, + &state->user); + if (ret == EOK) { + goto immediately; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Couldn't parse out user information " + "based on DN %s, falling back to an LDAP lookup\n", member->dn); + } + + /* only pull down username and originalDN */ + attrs = talloc_array(state, const char *, 3); + if (attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + + attrs[0] = "objectClass"; + attrs[1] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; + attrs[2] = NULL; + + /* create filter */ + base_filter = talloc_asprintf(state, "(objectclass=%s)", + group_ctx->opts->user_map[SDAP_OC_USER].name); + if (base_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* use search base filter if needed */ + filter = sdap_combine_filters(state, base_filter, member->user_filter); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* search */ + subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, + member->dn, LDAP_SCOPE_BASE, filter, attrs, + group_ctx->opts->user_map, + group_ctx->opts->user_map_cnt, + dp_opt_get_int(group_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_user_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_lookup_user_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **user = NULL; + size_t count = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &user); + talloc_zfree(subreq); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + goto done; + } + + if (count == 1) { + state->user = user[0]; + } else if (count == 0) { + /* group not found */ + state->user = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "BASE search returned more than one records\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_user) +{ + struct sdap_nested_group_lookup_user_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_user != NULL) { + *_user = talloc_steal(mem_ctx, state->user); + } + + return EOK; +} + +struct sdap_nested_group_lookup_group_state { + struct sysdb_attrs *group; +}; + +static void sdap_nested_group_lookup_group_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_attr_map *map = group_ctx->opts->group_map; + const char **attrs = NULL; + const char *base_filter = NULL; + const char *filter = NULL; + char *oc_list; + errno_t ret; + + PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_SEND); + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_group_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ret = build_attrs_from_map(state, group_ctx->opts->group_map, + SDAP_OPTS_GROUP, NULL, &attrs, NULL); + if (ret != EOK) { + goto immediately; + } + + /* create filter */ + oc_list = sdap_make_oc_list(state, map); + if (oc_list == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); + ret = ENOMEM; + goto immediately; + } + + base_filter = talloc_asprintf(attrs, "(&(%s)(%s=*))", oc_list, + map[SDAP_AT_GROUP_NAME].name); + if (base_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* use search base filter if needed */ + filter = sdap_combine_filters(state, base_filter, member->group_filter); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* search */ + subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, + member->dn, LDAP_SCOPE_BASE, filter, attrs, + map, SDAP_OPTS_GROUP, + dp_opt_get_int(group_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_group_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sdap_nested_group_lookup_group_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **group = NULL; + size_t count = 0; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &group); + talloc_zfree(subreq); + if (ret == ENOENT) { + count = 0; + } else if (ret != EOK) { + goto done; + } + + if (count == 1) { + state->group = group[0]; + } else if (count == 0) { + /* group not found */ + state->group = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "BASE search returned more than one records\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_group) +{ + struct sdap_nested_group_lookup_group_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_group != NULL) { + *_group = talloc_steal(mem_ctx, state->group); + } + + return EOK; +} + +struct sdap_nested_group_lookup_unknown_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct sdap_nested_group_member *member; + enum sdap_nested_group_dn_type type; + struct sysdb_attrs *entry; +}; + +static void +sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq); + +static void +sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct sdap_nested_group_member *member) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_unknown_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_SEND); + + state->ev = ev; + state->group_ctx = group_ctx; + state->member = member; + + /* try users first */ + subreq = sdap_nested_group_lookup_user_send(state, + state->ev, + state->group_ctx, + state->member); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, + sdap_nested_group_lookup_unknown_user_done, + req); + } + + return req; +} + +static void +sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (entry != NULL) { + /* found in users */ + state->entry = entry; + state->type = SDAP_NESTED_GROUP_DN_USER; + ret = EOK; + goto done; + } + + /* not found in users, try group */ + subreq = sdap_nested_group_lookup_group_send(state, + state->ev, + state->group_ctx, + state->member); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_unknown_group_done, + req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void +sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs *entry = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (entry == NULL) { + /* not found, end request */ + state->entry = NULL; + state->type = SDAP_NESTED_GROUP_DN_UNKNOWN; + } else { + /* found in groups */ + state->entry = entry; + state->type = SDAP_NESTED_GROUP_DN_GROUP; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs **_entry, + enum sdap_nested_group_dn_type *_type) +{ + struct sdap_nested_group_lookup_unknown_state *state = NULL; + state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); + + PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_entry != NULL) { + *_entry = talloc_steal(mem_ctx, state->entry); + } + + if (_type != NULL) { + *_type = state->type; + } + + + return EOK; +} + +struct sdap_nested_group_deref_state { + struct tevent_context *ev; + struct sdap_nested_group_ctx *group_ctx; + struct ldb_message_element *members; + int nesting_level; + + struct sysdb_attrs **nested_groups; + int num_groups; +}; + +static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq); +static void sdap_nested_group_deref_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_nested_group_ctx *group_ctx, + struct ldb_message_element *members, + const char *group_dn, + int nesting_level) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_attr_map_info *maps = NULL; + static const int num_maps = 2; + struct sdap_options *opts = group_ctx->opts; + const char **attrs = NULL; + size_t num_attrs = 0; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_deref_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + PROBE(SDAP_NESTED_GROUP_DEREF_SEND); + + state->ev = ev; + state->group_ctx = group_ctx; + state->members = members; + state->nesting_level = nesting_level; + state->num_groups = 0; /* we will count exact number of the groups */ + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps); + if (maps == NULL) { + ret = ENOMEM; + goto immediately; + } + + maps[0].map = opts->user_map; + maps[0].num_attrs = opts->user_map_cnt; + maps[1].map = opts->group_map; + maps[1].num_attrs = SDAP_OPTS_GROUP; + + /* pull down the whole group map, + * but only pull down username and originalDN for users */ + ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, + NULL, &attrs, &num_attrs); + if (ret != EOK) { + goto immediately; + } + + attrs = talloc_realloc(state, attrs, const char *, num_attrs + 2); + if (attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + + attrs[num_attrs] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; + attrs[num_attrs + 1] = NULL; + + /* send request */ + subreq = sdap_deref_search_send(state, ev, opts, group_ctx->sh, group_dn, + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + attrs, num_maps, maps, + dp_opt_get_int(opts->basic, + SDAP_SEARCH_TIMEOUT)); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_deref_direct_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static hash_table_t * +convert_ldb_element_to_set(const struct ldb_message_element *members) +{ + errno_t ret; + hash_table_t *set = NULL; + hash_key_t key; + hash_value_t value; + size_t j; + + ret = sss_hash_create(NULL, members->num_values, &set); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, strerror(ret)); + return NULL; + } + + key.type = HASH_KEY_CONST_STRING; + value.type = HASH_VALUE_UNDEF; + + for (j = 0; j < members->num_values; ++j) { + key.c_str = (const char*)members->values[j].data; + /* since hash table is used as a set, we don't care about value */ + ret = hash_enter(set, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add '%s'\n", key.c_str); + hash_destroy(set); + return NULL; + } + } + + return set; +} + +static bool set_has_key(hash_table_t *set, const char *key) +{ + hash_key_t hkey; + + hkey.type = HASH_KEY_CONST_STRING; + hkey.c_str = key; + + return hash_has_key(set, &hkey); +} + +static errno_t +sdap_nested_group_deref_direct_process(struct tevent_req *subreq) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + struct sdap_options *opts = NULL; + struct sdap_deref_attrs **entries = NULL; + struct ldb_message_element *members = NULL; + hash_table_t *members_set = NULL; /* will be used as a `set` */ + const char *orig_dn = NULL; + size_t num_entries = 0; + size_t i; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_deref_state); + + opts = state->group_ctx->opts; + members = state->members; + members_set = convert_ldb_element_to_set(state->members); + if (members_set == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sdap_deref_search_recv(subreq, state, &num_entries, &entries); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Received %zu dereference results, " + "about to process them\n", num_entries); + + /* + * We don't have any knowledge about possible number of groups when + * dereferencing. We expect that every member is a group and we will + * allocate enough space to hold it. We will shrink the memory later. + */ + state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, + num_entries); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto done; + } + + PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_PRE); + for (i = 0; i < num_entries; i++) { + ret = sysdb_attrs_get_string(entries[i]->attrs, + SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "The entry has no originalDN\n"); + goto done; + } + + /* Ensure that all members returned from the deref request are included + * in the member processing. Sometimes we will get more results back + * from deref/asq than we got from the initial lookup, as is the case + * with Active Directory and its range retrieval mechanism. + */ + if (!set_has_key(members_set, orig_dn)) { + /* Append newly found member to member list. + * Changes in state->members will propagate into sysdb_attrs of + * the group. */ + state->members->values = talloc_realloc(members, members->values, + struct ldb_val, + members->num_values + 1); + if (members->values == NULL) { + ret = ENOMEM; + goto done; + } + + members->values[members->num_values].data = + (uint8_t *)talloc_strdup(members->values, orig_dn); + if (members->values[members->num_values].data == NULL) { + ret = ENOMEM; + goto done; + } + + members->values[members->num_values].length = strlen(orig_dn); + members->num_values++; + } + + if (entries[i]->map == opts->user_map) { + /* we found a user */ + + /* skip the user if it is not amongst configured search bases */ + if (!sdap_nested_member_is_user(state->group_ctx, orig_dn, NULL)) { + continue; + } + + /* save user in hash table */ + ret = sdap_nested_group_hash_user(state->group_ctx, + entries[i]->attrs); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to save user in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + } else if (entries[i]->map == opts->group_map) { + /* we found a group */ + + /* skip the group if we have reached the nesting limit */ + if (state->nesting_level >= state->group_ctx->max_nesting_level) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " + "(level %d), skipping\n", orig_dn, state->nesting_level); + continue; + } + + /* skip the group if it is not amongst configured search bases */ + if (!sdap_nested_member_is_group(state->group_ctx, orig_dn, NULL)) { + continue; + } + + /* save group in hash table */ + ret = sdap_nested_group_hash_group(state->group_ctx, + entries[i]->attrs); + if (ret == EEXIST) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to save group in hash table " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* remember the group for later processing */ + state->nested_groups[state->num_groups] = entries[i]->attrs; + state->num_groups++; + + } else { + /* this should never happen, but if it does, do not loop forever */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Entry does not match any known map, skipping\n"); + continue; + } + } + PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_POST); + + /* adjust size of nested groups array */ + if (state->num_groups > 0) { + state->nested_groups = talloc_realloc(state, state->nested_groups, + struct sysdb_attrs *, + state->num_groups); + if (state->nested_groups == NULL) { + ret = ENOMEM; + goto done; + } + } else { + talloc_zfree(state->nested_groups); + } + + ret = EOK; + +done: + if (members_set != NULL) { + hash_destroy(members_set); + } + return ret; +} + +static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq) +{ + struct sdap_nested_group_deref_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_nested_group_deref_state); + + /* process direct members */ + ret = sdap_nested_group_deref_direct_process(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* we have processed all direct members, + * now recurse and process nested groups */ + subreq = sdap_nested_group_recurse_send(state, state->ev, + state->group_ctx, + state->nested_groups, + state->num_groups, + state->nesting_level + 1); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_nested_group_deref_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + /* tevent_req_error() cannot cope with EOK */ + DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); + tevent_req_error(req, EINVAL); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; + +} + +static void sdap_nested_group_deref_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + /* process nested groups */ + ret = sdap_nested_group_recurse_recv(subreq); + talloc_zfree(subreq); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sdap_nested_group_deref_recv(struct tevent_req *req) +{ + PROBE(SDAP_NESTED_GROUP_DEREF_RECV); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ext_member { + struct sdap_external_missing_member *missing_mem; + const char *ext_member_attr; + + enum sysdb_member_type member_type; + struct sss_domain_info *dom; + struct sysdb_attrs *attrs; +}; + +struct sdap_nested_group_lookup_external_state { + struct tevent_context *ev; + struct sdap_ext_member_ctx *ext_ctx; + struct sss_domain_info *group_dom; + hash_table_t *missing_external; + + hash_entry_t *entries; + unsigned long n_entries; + unsigned long eniter; + + struct sdap_ext_member *ext_members; + + ext_member_send_fn_t ext_member_resolve_send; + ext_member_recv_fn_t ext_member_resolve_recv; +}; + +static errno_t +sdap_nested_group_lookup_external_step(struct tevent_req *req); +static void +sdap_nested_group_lookup_external_done(struct tevent_req *subreq); +static errno_t +sdap_nested_group_lookup_external_link(struct tevent_req *req); +static errno_t +sdap_nested_group_lookup_external_link_member( + struct sdap_nested_group_lookup_external_state *state, + struct sdap_ext_member *member); +static errno_t +sdap_nested_group_memberof_dn_by_original_dn( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *group_dom, + const char *original_dn, + const char ***_parents); + +struct tevent_req * +sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *group_dom, + struct sdap_ext_member_ctx *ext_ctx, + hash_table_t *missing_external) +{ + struct sdap_nested_group_lookup_external_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_nested_group_lookup_external_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->group_dom = group_dom; + state->ext_ctx = ext_ctx; + state->missing_external = missing_external; + + if (state->ext_ctx->ext_member_resolve_send == NULL + || state->ext_ctx->ext_member_resolve_recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context\n"); + ret = EINVAL; + goto immediately; + } + + ret = hash_entries(state->missing_external, + &state->n_entries, &state->entries); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries returned %d\n", ret); + ret = EIO; + goto immediately; + } + state->eniter = 0; + + state->ext_members = talloc_zero_array(state, + struct sdap_ext_member, + state->n_entries); + if (state->ext_members == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_nested_group_lookup_external_step(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_nested_group_lookup_external_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct sdap_nested_group_lookup_external_state *state = NULL; + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + subreq = state->ext_ctx->ext_member_resolve_send(state, + state->ev, + state->entries[state->eniter].key.str, + state->ext_ctx->pvt); + if (subreq == NULL) { + return ENOMEM; + } + DEBUG(SSSDBG_TRACE_FUNC, "Refreshing member %lu/%lu\n", + state->eniter, state->n_entries); + tevent_req_set_callback(subreq, + sdap_nested_group_lookup_external_done, + req); + + return EAGAIN; +} + +static void +sdap_nested_group_lookup_external_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct sdap_nested_group_lookup_external_state *state = NULL; + enum sysdb_member_type member_type; + struct sysdb_attrs *member; + struct sss_domain_info *member_dom; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + ret = state->ext_ctx->ext_member_resolve_recv(state, subreq, + &member_type, + &member_dom, + &member); + talloc_free(subreq); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Refreshed member %lu\n", state->eniter); + state->ext_members[state->eniter].missing_mem = \ + state->entries[state->eniter].value.ptr; + state->ext_members[state->eniter].dom = member_dom; + + state->ext_members[state->eniter].ext_member_attr = \ + talloc_steal(state->ext_members, + state->entries[state->eniter].key.str); + state->ext_members[state->eniter].member_type = member_type; + state->ext_members[state->eniter].attrs = \ + talloc_steal(state->ext_members, member); + } + + state->eniter++; + if (state->eniter >= state->n_entries) { + DEBUG(SSSDBG_TRACE_FUNC, "All external members processed\n"); + ret = sdap_nested_group_lookup_external_link(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + tevent_req_done(req); + return; + } + + ret = sdap_nested_group_lookup_external_step(req); + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + return; +} + +static errno_t +sdap_nested_group_lookup_external_link(struct tevent_req *req) +{ + errno_t ret, tret; + bool in_transaction = false; + struct sdap_nested_group_lookup_external_state *state = NULL; + state = tevent_req_data(req, + struct sdap_nested_group_lookup_external_state); + + ret = sysdb_transaction_start(state->group_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + + for (size_t i = 0; i < state->eniter; i++) { + if (state->ext_members[i].attrs == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "The member %s could not be resolved\n", + state->ext_members[i].ext_member_attr); + continue; + } + + ret = sdap_nested_group_lookup_external_link_member(state, + &state->ext_members[i]); + if (ret != EOK) { + goto fail; + } + } + + ret = sysdb_transaction_commit(state->group_dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + + return EOK; + +fail: + if (in_transaction) { + tret = sysdb_transaction_cancel(state->group_dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return EFAULT; +} + +static errno_t +sdap_nested_group_lookup_external_link_member( + struct sdap_nested_group_lookup_external_state *state, + struct sdap_ext_member *member) +{ + const char *name; + int ret; + const char **parents = NULL; + size_t i; + TALLOC_CTX *tmp_ctx; + const char *orig_dn; + + tmp_ctx = talloc_new(state); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string(member->attrs, SYSDB_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No name for a user\n"); + goto done; + } + + /* This only works because the groups were saved in a previous + * transaction */ + for (i=0; i < member->missing_mem->parent_dn_idx; i++) { + orig_dn = member->missing_mem->parent_group_dns[i]; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Linking external members %s from domain %s to parents of %s\n", + name, member->dom->name, orig_dn); + ret = sdap_nested_group_memberof_dn_by_original_dn(tmp_ctx, + state->group_dom, + orig_dn, + &parents); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find parents of %s\n", orig_dn); + continue; + } + + /* We don't have to remove the members here, since all members attributes + * are always written anew + */ + ret = sysdb_update_members_dn(member->dom, name, member->member_type, + parents, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot link %s@%s to its parents\n", + name, member->dom->name); + goto done; + } + + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_nested_group_memberof_dn_by_original_dn( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *group_dom, + const char *original_dn, + const char ***_parents) +{ + errno_t ret; + const char *attrs[] = { SYSDB_NAME, + SYSDB_MEMBEROF, + NULL }; + struct ldb_message **msgs = NULL; + size_t count; + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *memberof; + const char **parents; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, group_dom, original_dn, + attrs, &count, &msgs); + if (ret != EOK) { + goto done; + } + + if (count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "More than one entry found by originalDN?\n"); + goto done; + } + + memberof = ldb_msg_find_element(msgs[0], SYSDB_MEMBEROF); + if (memberof == NULL || memberof->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The external group is not a member of any groups\n"); + ret = ENOENT; + goto done; + } + + parents = talloc_zero_array(tmp_ctx, + const char *, + memberof->num_values + 1); + if (parents == NULL) { + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < memberof->num_values; i++) { + parents[i] = talloc_strdup(parents, + (const char *) memberof->values[i].data); + if (parents[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_parents = talloc_steal(mem_ctx, parents); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_netgroups.c b/src/providers/ldap/sdap_async_netgroups.c new file mode 100644 index 0000000..db486d3 --- /dev/null +++ b/src/providers/ldap/sdap_async_netgroups.c @@ -0,0 +1,778 @@ +/* + SSSD + + Async LDAP Helper routines for netgroups + + Authors: + Sumit Bose <sbose@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 "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +bool is_dn(const char *str) +{ + int ret; + LDAPDN dn; + + ret = ldap_str2dn(str, &dn, LDAP_DN_FORMAT_LDAPV3); + ldap_dnfree(dn); + + return (ret == LDAP_SUCCESS ? true : false); +} + +static errno_t sdap_save_netgroup(TALLOC_CTX *memctx, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *attrs, + char **_timestamp, + time_t now) +{ + struct ldb_message_element *el; + struct sysdb_attrs *netgroup_attrs; + const char *name = NULL; + int ret; + char *timestamp = NULL; + char **missing = NULL; + + ret = sdap_get_netgroup_primary_name(opts, attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get netgroup name\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Processing netgroup %s\n", name); + + netgroup_attrs = sysdb_new_attrs(memctx); + if (!netgroup_attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN, + "original DN", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name, + &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original mod-Timestamp is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(netgroup_attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto fail; + } + timestamp = talloc_strdup(memctx, (const char*)el->values[0].data); + if (!timestamp) { + ret = ENOMEM; + goto fail; + } + } + + ret = sdap_attrs_add_list(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_TRIPLE].sys_name, + "netgroup triple", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sdap_attrs_add_list(attrs, + opts->netgroup_map[SDAP_AT_NETGROUP_MEMBER].sys_name, + "original members", + name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + ret = sdap_attrs_add_list(attrs, SYSDB_NETGROUP_MEMBER, + "members", name, netgroup_attrs); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for netgroup %s\n", name); + + ret = sdap_save_all_names(name, attrs, dom, SYSDB_MEMBER_NETGROUP, + netgroup_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save netgroup names\n"); + goto fail; + } + + /* Make sure that any attributes we requested from LDAP that we + * did not receive are also removed from the sysdb + */ + ret = list_missing_attrs(attrs, opts->netgroup_map, SDAP_OPTS_NETGROUP, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to list missing attributes\n"); + goto fail; + } + + /* We store memberNisNetgroup from LDAP as originalMemberNisNetgroup in + * sysdb. It may contain simple name or DN. That's the reason why we always + * translate/generate simple name and store it in SYSDB_NETGROUP_MEMBER + * (memberNisNetgroup) in sysdb which is internally used for searching + * netgropus. + * We need to ensure if originalMemberNisNetgroup is missing, + * memberNisNetgroup is missing too. + */ + if (string_in_list(SYSDB_ORIG_NETGROUP_MEMBER, missing, false)) { + ret = add_string_to_list(attrs, SYSDB_NETGROUP_MEMBER, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add string into list\n"); + goto fail; + } + } + + ret = sysdb_add_netgroup(dom, name, NULL, netgroup_attrs, missing, + dom->netgroup_timeout, now); + if (ret) goto fail; + + if (_timestamp) { + *_timestamp = timestamp; + } + + return EOK; + +fail: + DEBUG(SSSDBG_OP_FAILURE, "Failed to save netgroup %s\n", name); + return ret; +} + +errno_t update_dn_list(struct dn_item *dn_list, const size_t count, + struct ldb_message **res, bool *all_resolved) +{ + struct dn_item *dn_item; + size_t c; + const char *dn; + const char *cn; + bool not_resolved = false; + + *all_resolved = false; + + DLIST_FOR_EACH(dn_item, dn_list) { + if (dn_item->cn != NULL) { + continue; + } + + for(c = 0; c < count; c++) { + dn = ldb_msg_find_attr_as_string(res[c], SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing original DN.\n"); + return EINVAL; + } + if (strcmp(dn, dn_item->dn) == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found matching entry for [%s].\n", dn_item->dn); + cn = ldb_msg_find_attr_as_string(res[c], SYSDB_NAME, NULL); + if (cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); + return EINVAL; + } + dn_item->cn = talloc_strdup(dn_item, cn); + break; + } + } + + if (dn_item->cn == NULL) { + not_resolved = true; + } + } + + *all_resolved = !not_resolved; + + return EOK; +} + +struct netgr_translate_members_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + + struct sysdb_attrs **netgroups; + size_t count; + struct dn_item *dn_list; + struct dn_item *dn_item; + struct dn_item *dn_idx; +}; + +static errno_t netgr_translate_members_ldap_step(struct tevent_req *req); +static void netgr_translate_members_ldap_done(struct tevent_req *subreq); + +struct tevent_req *netgr_translate_members_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + const size_t count, + struct sysdb_attrs **netgroups) +{ + struct tevent_req *req; + struct netgr_translate_members_state *state; + size_t c; + size_t mc; + const char **member_list; + size_t sysdb_count; + int ret; + struct ldb_message **sysdb_res; + struct dn_item *dn_item; + char *dn_filter; + char *sysdb_filter; + struct ldb_dn *netgr_basedn; + bool all_resolved; + const char *cn_attr[] = { SYSDB_NAME, SYSDB_ORIG_DN, NULL }; + + req = tevent_req_create(memctx, &state, + struct netgr_translate_members_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->netgroups = netgroups; + state->count = count; + state->dn_list = NULL; + state->dn_item = NULL; + state->dn_idx = NULL; + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(netgroups[c], + SYSDB_ORIG_NETGROUP_MEMBER, state, + &member_list); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "Missing netgroup members.\n"); + continue; + } + + for (mc = 0; member_list[mc] != NULL; mc++) { + if (is_dn(member_list[mc])) { + dn_item = talloc_zero(state, struct dn_item); + if (dn_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Adding [%s] to DN list.\n", member_list[mc]); + dn_item->netgroup = netgroups[c]; + dn_item->dn = member_list[mc]; + DLIST_ADD(state->dn_list, dn_item); + } else { + ret = sysdb_attrs_add_string(netgroups[c], SYSDB_NETGROUP_MEMBER, + member_list[mc]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto fail; + } + } + } + } + + if (state->dn_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No DNs found among netgroup members.\n"); + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + } + + dn_filter = talloc_strdup(state, "(|"); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto fail; + } + + DLIST_FOR_EACH(dn_item, state->dn_list) { + dn_filter = talloc_asprintf_append(dn_filter, "(%s=%s)", + SYSDB_ORIG_DN, dn_item->dn); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto fail; + } + } + + dn_filter = talloc_asprintf_append(dn_filter, ")"); + if (dn_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto fail; + } + + sysdb_filter = talloc_asprintf(state, "(&(%s)%s)", SYSDB_NC, dn_filter); + if (sysdb_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto fail; + } + + netgr_basedn = sysdb_netgroup_base_dn(state, dom); + if (netgr_basedn == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_search_entry(state, sysdb, netgr_basedn, LDB_SCOPE_BASE, + sysdb_filter, cn_attr, &sysdb_count, &sysdb_res); + talloc_zfree(netgr_basedn); + talloc_zfree(sysdb_filter); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry failed.\n"); + goto fail; + } + + if (ret == EOK) { + ret = update_dn_list(state->dn_list, sysdb_count, sysdb_res, + &all_resolved); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "update_dn_list failed.\n"); + goto fail; + } + + if (all_resolved) { + DLIST_FOR_EACH(dn_item, state->dn_list) { + ret = sysdb_attrs_add_string(dn_item->netgroup, + SYSDB_NETGROUP_MEMBER, + dn_item->cn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto fail; + } + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + } + } + + state->dn_idx = state->dn_list; + ret = netgr_translate_members_ldap_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "netgr_translate_members_ldap_step failed.\n"); + goto fail; + } + + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +/* netgr_translate_members_ldap_step() returns + * EOK: if everthing is translated, the caller can call tevent_req_done + * EAGAIN: if there are still members waiting to be translated, the caller + * should return to the mainloop + * Exyz: every other return code indicates an error and tevent_req_error + * should be called + */ +static errno_t netgr_translate_members_ldap_step(struct tevent_req *req) +{ + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + const char **cn_attr; + char *filter = NULL; + struct tevent_req *subreq; + int ret; + + DLIST_FOR_EACH(state->dn_item, state->dn_idx) { + if (state->dn_item->cn == NULL) { + break; + } + } + if (state->dn_item == NULL) { + DLIST_FOR_EACH(state->dn_item, state->dn_list) { + ret = sysdb_attrs_add_string(state->dn_item->netgroup, + SYSDB_NETGROUP_MEMBER, + state->dn_item->cn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_attrs_add_string failed.\n"); + tevent_req_error(req, ret); + return ret; + } + } + + return EOK; + } + + if (!sss_ldap_dn_in_search_bases(state, state->dn_item->dn, + state->opts->sdom->netgroup_search_bases, + &filter)) { + /* not in search base, skip it */ + state->dn_idx = state->dn_item->next; + DLIST_REMOVE(state->dn_list, state->dn_item); + return netgr_translate_members_ldap_step(req); + } + + cn_attr = talloc_array(state, const char *, 3); + if (cn_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + cn_attr[0] = state->opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name; + cn_attr[1] = "objectclass"; + cn_attr[2] = NULL; + + DEBUG(SSSDBG_TRACE_ALL, "LDAP base search for [%s].\n", state->dn_item->dn); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->dn_item->dn, LDAP_SCOPE_BASE, filter, + cn_attr, state->opts->netgroup_map, + SDAP_OPTS_NETGROUP, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + talloc_steal(subreq, cn_attr); + + tevent_req_set_callback(subreq, netgr_translate_members_ldap_done, req); + return EAGAIN; +} + +static void netgr_translate_members_ldap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + int ret; + size_t count; + struct sysdb_attrs **netgroups; + const char *str; + + ret = sdap_get_generic_recv(subreq, state, &count, &netgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic request failed.\n"); + goto fail; + } + + switch (count) { + case 0: + DEBUG(SSSDBG_FATAL_FAILURE, + "sdap_get_generic_recv found no entry for [%s].\n", + state->dn_item->dn); + break; + case 1: + ret = sysdb_attrs_get_string(netgroups[0], SYSDB_NAME, &str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + break; + } + state->dn_item->cn = talloc_strdup(state->dn_item, str); + if (state->dn_item->cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected number of results [%zu] for base search.\n", + count); + } + + if (state->dn_item->cn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to resolve netgroup name for DN [%s], using DN.\n", + state->dn_item->dn); + state->dn_item->cn = talloc_strdup(state->dn_item, state->dn_item->dn); + } + + state->dn_idx = state->dn_item->next; + ret = netgr_translate_members_ldap_step(req); + if (ret != EOK && ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "netgr_translate_members_ldap_step failed.\n"); + goto fail; + } + + if (ret == EOK) { + tevent_req_done(req); + } + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static errno_t netgroup_translate_ldap_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***netgroups) +{ + struct netgr_translate_members_state *state = tevent_req_data(req, + struct netgr_translate_members_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *count = state->count; + *netgroups = talloc_steal(mem_ctx, state->netgroups); + + return EOK; +} + +/* ==Search-Netgroups-with-filter============================================ */ + +struct sdap_get_netgroups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + + char *higher_timestamp; + struct sysdb_attrs **netgroups; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_get_netgroups_next_base(struct tevent_req *req); +static void sdap_get_netgroups_process(struct tevent_req *subreq); +static void netgr_translate_members_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_netgroups_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_netgroups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->netgroups = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Netgroup lookup request without a netgroup search base\n"); + ret = EINVAL; + goto done; + } + + + ret = sdap_get_netgroups_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + return req; +} + +static errno_t sdap_get_netgroups_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_netgroups_state *state; + + state = tevent_req_data(req, struct sdap_get_netgroups_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for netgroups with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->netgroup_map, SDAP_OPTS_NETGROUP, + state->timeout, + false); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_netgroups_process, req); + + return EOK; +} + +static void sdap_get_netgroups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + int ret; + + ret = sdap_get_generic_recv(subreq, state, + &state->count, &state->netgroups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for netgroups, returned %zu results.\n", state->count); + + if (state->count == 0) { + /* No netgroups found in this search */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_netgroups_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + tevent_req_error(req, ENOENT); + return; + } + + subreq = netgr_translate_members_send(state, state->ev, state->opts, + state->sh, state->dom, state->sysdb, + state->count, state->netgroups); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, netgr_translate_members_done, req); + + return; + +} + +static void netgr_translate_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + int ret; + size_t c; + time_t now; + + ret = netgroup_translate_ldap_members_recv(subreq, state, &state->count, + &state->netgroups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + now = time(NULL); + for (c = 0; c < state->count; c++) { + ret = sdap_save_netgroup(state, + state->dom, + state->opts, + state->netgroups[c], + &state->higher_timestamp, + now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store netgroups.\n"); + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Netgroups - Done\n", state->count); + + tevent_req_done(req); +} + +int sdap_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_get_netgroups_state *state = tevent_req_data(req, + struct sdap_get_netgroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + if (reply_count) { + *reply_count = state->count; + } + + if (reply) { + *reply = talloc_steal(mem_ctx, state->netgroups); + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h new file mode 100644 index 0000000..90ed365 --- /dev/null +++ b/src/providers/ldap/sdap_async_private.h @@ -0,0 +1,196 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> + + 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/>. +*/ + +#ifndef _SDAP_ASYNC_PRIVATE_H_ +#define _SDAP_ASYNC_PRIVATE_H_ + +#include "config.h" +#include "util/sss_krb5.h" +#include "providers/ldap/sdap_async.h" + +struct dn_item { + const char *dn; + /* Parent netgroup containing this record */ + struct sysdb_attrs *netgroup; + char *cn; + struct dn_item *next; + struct dn_item *prev; +}; + +bool is_dn(const char *str); +errno_t update_dn_list(struct dn_item *dn_list, + const size_t count, + struct ldb_message **res, + bool *all_resolved); + +struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx); + +void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *pvt); + +int setup_ldap_connection_callbacks(struct sdap_handle *sh, + struct tevent_context *ev); +int remove_ldap_connection_callbacks(struct sdap_handle *sh); + +int get_fd_from_ldap(LDAP *ldap, int *fd); + +errno_t sdap_set_connected(struct sdap_handle *sh, struct tevent_context *ev); + +errno_t sdap_call_conn_cb(const char *uri,int fd, struct sdap_handle *sh); + +int sdap_op_get_msgid(struct sdap_op *op); + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, const char *stat_info, + sdap_op_callback_t *callback, void *data, + int timeout, struct sdap_op **_op); + +struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh); +int sdap_get_rootdse_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sysdb_attrs **rootdse); + +errno_t deref_string_to_val(const char *str, int *val); + +/* Extract server IP from sdap_handle and return it as string or NULL in case + * of an error */ +const char *sdap_get_server_peer_str(struct sdap_handle *sh); + +/* Same as sdap_get_server_peer_str() but always returns a strings */ +const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh); + +/* from sdap_child_helpers.c */ + +struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + int timeout); + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + krb5_error_code *kerr, + char **ccname, + time_t *expire_time_out); + +int sdap_save_users(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + struct sysdb_attrs *mapped_attrs, + char **_usn_value); + +int sdap_initgr_common_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + const char *name, + enum sysdb_member_type type, + char **sysdb_grouplist, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count); + +errno_t get_sysdb_grouplist(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist); + +errno_t get_sysdb_grouplist_dn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + char ***grouplist); + +/* from sdap_async_nested_groups.c */ +struct tevent_req *sdap_nested_group_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *sdom, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group); + +errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + unsigned long *_num_users, + struct sysdb_attrs ***_users, + unsigned long *_num_groups, + struct sysdb_attrs ***_groups, + hash_table_t **missing_external); + +struct tevent_req * +sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *group_dom, + struct sdap_ext_member_ctx *ext_ctx, + hash_table_t *missing_external); +errno_t +sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + +/* from sdap_async_initgroups.c */ +errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + char **sysdb_groupnames, + struct sysdb_attrs **ldap_groups, + int ldap_groups_count); + +/* from sdap_ad_groups.c */ +errno_t sdap_check_ad_group_type(struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *group_attrs, + const char *group_name, + bool *_need_filter); + +struct tevent_req *rfc2307bis_nested_groups_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct sdap_options *opts, struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, struct sdap_handle *sh, + struct sdap_search_base **search_bases, + struct sysdb_attrs **groups, size_t num_groups, + hash_table_t *group_hash, size_t nesting); +errno_t rfc2307bis_nested_groups_recv(struct tevent_req *req); + +errno_t sdap_nested_groups_store(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sdap_options *opts, + struct sysdb_attrs **groups, + unsigned long count); + +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups); +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req); +#endif /* _SDAP_ASYNC_PRIVATE_H_ */ diff --git a/src/providers/ldap/sdap_async_resolver_enum.c b/src/providers/ldap/sdap_async_resolver_enum.c new file mode 100644 index 0000000..8c92260 --- /dev/null +++ b/src/providers/ldap/sdap_async_resolver_enum.c @@ -0,0 +1,318 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_resolver_enum.h" +#include "providers/ldap/sdap_async_resolver_enum.h" + +static errno_t sdap_dom_resolver_enum_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb); +static bool sdap_dom_resolver_enum_connected(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_get_iphost(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_iphost_done(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_get_ipnetwork(struct tevent_req *subreq); +static void sdap_dom_resolver_enum_ipnetwork_done(struct tevent_req *subreq); + +struct sdap_dom_resolver_enum_state { + struct tevent_context *ev; + struct sdap_resolver_ctx *resolver_ctx; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + + struct sdap_id_conn_ctx *conn; + struct sdap_id_op *iphost_op; + struct sdap_id_op *ipnetwork_op; + + bool purge; +}; + +struct tevent_req * +sdap_dom_resolver_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_resolver_ctx *resolver_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn) +{ + struct tevent_req *req; + struct sdap_dom_resolver_enum_state *state; + int t; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct sdap_dom_resolver_enum_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->resolver_ctx = resolver_ctx; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->resolver_ctx->last_enum = tevent_timeval_current(); + + t = dp_opt_get_int(resolver_ctx->id_ctx->opts->basic, SDAP_PURGE_CACHE_TIMEOUT); + if ((state->resolver_ctx->last_purge.tv_sec + t) < state->resolver_ctx->last_enum.tv_sec) { + state->purge = true; + } + + state->iphost_op = sdap_id_op_create(state, conn->conn_cache); + if (state->iphost_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_create failed for iphosts\n"); + ret = EIO; + goto fail; + } + + ret = sdap_dom_resolver_enum_retry(req, state->iphost_op, + sdap_dom_resolver_enum_get_iphost); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_dom_enum_retry failed\n"); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_dom_resolver_enum_retry(struct tevent_req *req, + struct sdap_id_op *op, + tevent_req_fn tcb) +{ + struct sdap_dom_resolver_enum_state *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + subreq = sdap_id_op_connect_send(op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: %d\n", ret); + return ret; + } + + tevent_req_set_callback(subreq, tcb, req); + return EOK; +} + +static bool sdap_dom_resolver_enum_connected(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return false; + } + + return true; +} + +static void sdap_dom_resolver_enum_get_iphost(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + if (sdap_dom_resolver_enum_connected(subreq) == false) { + return; + } + + subreq = enum_iphosts_send(state, state->ev, + state->id_ctx, + state->iphost_op, + state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_dom_resolver_enum_iphost_done, req); +} + +static void sdap_dom_resolver_enum_iphost_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + errno_t ret; + int dp_error; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + ret = enum_iphosts_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->iphost_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_resolver_enum_retry(req, state->iphost_op, + sdap_dom_resolver_enum_get_iphost); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "IP hosts enumeration failed: %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->ipnetwork_op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->ipnetwork_op == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_id_op_create failed for IP networks\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sdap_dom_resolver_enum_retry(req, state->ipnetwork_op, + sdap_dom_resolver_enum_get_ipnetwork); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Continues to sdap_dom_resolver_enum_get_ipnetwork */ +} + +static void sdap_dom_resolver_enum_get_ipnetwork(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + if (sdap_dom_resolver_enum_connected(subreq) == false) { + return; + } + + subreq = enum_ipnetworks_send(state, state->ev, + state->id_ctx, + state->ipnetwork_op, + state->purge); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_dom_resolver_enum_ipnetwork_done, req); +} + +static void sdap_dom_resolver_enum_ipnetwork_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_dom_resolver_enum_state *state; + errno_t ret; + int dp_error; + + state = tevent_req_data(req, struct sdap_dom_resolver_enum_state); + + ret = enum_ipnetworks_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->ipnetwork_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_dom_resolver_enum_retry(req, state->ipnetwork_op, + sdap_dom_resolver_enum_get_ipnetwork); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } else if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, "Backend is offline, retrying later\n"); + tevent_req_done(req); + return; + } else if (ret != EOK && ret != ENOENT) { + /* Non-recoverable error */ + DEBUG(SSSDBG_OP_FAILURE, + "IP networks enumeration failed: %d: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->sdom->dom, SYSDB_HAS_ENUMERATED_RESOLVER, + true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not mark domain as having enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + if (state->purge) { + ret = ldap_resolver_cleanup(state->resolver_ctx); + if (ret != EOK) { + /* Not fatal, worst case we'll have stale entries that would be + * removed on a subsequent online lookup + */ + DEBUG(SSSDBG_MINOR_FAILURE, "Cleanup failed: [%d]: %s\n", + ret, sss_strerror(ret)); + } + + } + + tevent_req_done(req); +} + +errno_t sdap_dom_resolver_enum_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_resolver_enum.h b/src/providers/ldap/sdap_async_resolver_enum.h new file mode 100644 index 0000000..e096b74 --- /dev/null +++ b/src/providers/ldap/sdap_async_resolver_enum.h @@ -0,0 +1,36 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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/>. +*/ + +#ifndef _SDAP_ASYNC_RESOLVER_ENUM_H_ +#define _SDAP_ASYNC_RESOLVER_ENUM_H_ + +struct tevent_req * +sdap_dom_resolver_enum_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_resolver_ctx *resolver_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn); + +errno_t sdap_dom_resolver_enum_recv(struct tevent_req *req); + +#endif /* _SDAP_ASYNC_RESOLVER_ENUM_H_ */ diff --git a/src/providers/ldap/sdap_async_services.c b/src/providers/ldap/sdap_async_services.c new file mode 100644 index 0000000..5fa3bca --- /dev/null +++ b/src/providers/ldap/sdap_async_services.c @@ -0,0 +1,644 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_get_services_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + const char **attrs; + const char *base_filter; + char *filter; + int timeout; + bool enumeration; + + char *higher_usn; + struct sysdb_attrs **services; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t +sdap_get_services_next_base(struct tevent_req *req); +static void +sdap_get_services_process(struct tevent_req *subreq); +static errno_t +sdap_save_services(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **services, + size_t num_services, + char **_usn_value); +static errno_t +sdap_save_service(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now); + +struct tevent_req * +sdap_get_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + bool enumeration) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_get_services_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_services_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->higher_usn = NULL; + state->services = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->enumeration = enumeration; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Services lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_get_services_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static errno_t +sdap_get_services_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_get_services_state *state; + + state = tevent_req_data(req, struct sdap_get_services_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for services with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->service_map, SDAP_OPTS_SERVICES, + state->timeout, + state->enumeration); /* If we're enumerating, we need paging */ + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_get_services_process, req); + + return EOK; +} + +static void +sdap_get_services_process(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_services_state *state = + tevent_req_data(req, struct sdap_get_services_state); + int ret; + size_t count, i; + struct sysdb_attrs **services; + bool next_base = false; + + ret = sdap_get_generic_recv(subreq, state, + &count, &services); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for services, returned %zu results.\n", + count); + + if (state->enumeration || count == 0) { + /* No services found in this search or enumerating */ + next_base = true; + } + + /* Add this batch of sevices to the list */ + if (count > 0) { + state->services = + talloc_realloc(state, + state->services, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->services) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new services into the list + */ + for (i = 0; i < count; i++) { + state->services[state->count + i] = + talloc_steal(state->services, services[i]); + } + + state->count += count; + state->services[state->count] = NULL; + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_services_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + /* No more search bases + * Return ENOENT if no services were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = sdap_save_services(state, state->sysdb, + state->dom, state->opts, + state->services, state->count, + &state->higher_usn); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store services.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Saving %zu services - Done\n", state->count); + + tevent_req_done(req); +} + +static errno_t +sdap_save_services(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **services, + size_t num_services, + char **_usn_value) +{ + errno_t ret, sret; + time_t now; + size_t i; + bool in_transaction = false; + char *higher_usn = NULL; + char *usn_value; + TALLOC_CTX *tmp_ctx; + + if (num_services == 0) { + /* Nothing to do */ + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_services; i++) { + usn_value = NULL; + + ret = sdap_save_service(tmp_ctx, sysdb, opts, dom, + services[i], + &usn_value, now); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store service %zu. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Service [%zu/%zu] processed!\n", i, num_services); + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(mem_ctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction!\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_save_service(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + char **_usn_value, + time_t now) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *svc_attrs; + struct ldb_message_element *el; + char *usn_value = NULL; + const char *name = NULL; + const char **aliases; + const char **protocols; + const char **cased_protocols = NULL; + const char **store_protocols; + char **missing; + uint16_t port; + uint64_t cache_timeout; + + DEBUG(SSSDBG_TRACE_ALL, "Saving service\n"); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + svc_attrs = sysdb_new_attrs(tmp_ctx); + if (!svc_attrs) { + ret = ENOMEM; + goto done; + } + + /* Identify the primary name of this services */ + ret = sdap_get_primary_name(opts->service_map[SDAP_AT_SERVICE_NAME].name, + attrs, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not determine the primary name of the service\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Primary name: [%s]\n", name); + + + /* Handle any available aliases */ + ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name, + !dom->case_sensitive, + &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service aliases\n"); + goto done; + } + + /* Get the port number */ + ret = sysdb_attrs_get_uint16_t(attrs, SYSDB_SVC_PORT, &port); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service port: [%s]\n", + strerror(ret)); + goto done; + } + + /* Get the protocols this service offers on that port */ + ret = sysdb_attrs_get_string_array(attrs, SYSDB_SVC_PROTO, + tmp_ctx, &protocols); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify service protocols: [%s]\n", + strerror(ret)); + goto done; + } + + if (dom->case_sensitive == false) { + /* Don't perform the extra mallocs if not necessary */ + ret = sss_get_cased_name_list(tmp_ctx, protocols, + dom->case_sensitive, &cased_protocols); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get case_sensitive protocols names: [%s]\n", + strerror(ret)); + goto done; + } + } + + store_protocols = dom->case_sensitive ? protocols : cased_protocols; + + /* Get the USN value, if available */ + ret = sysdb_attrs_get_el(attrs, + opts->service_map[SDAP_AT_SERVICE_USN].sys_name, &el); + if (ret && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to retrieve USN value: [%s]\n", + strerror(ret)); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original USN value is not available for [%s].\n", + name); + } else { + ret = sysdb_attrs_add_string(svc_attrs, + opts->service_map[SDAP_AT_SERVICE_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add USN value: [%s]\n", + strerror(ret)); + goto done; + } + usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + /* Make sure to remove any extra attributes from the sysdb + * that have been removed from LDAP + */ + ret = list_missing_attrs(svc_attrs, opts->service_map, SDAP_OPTS_SERVICES, + attrs, &missing); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to identify removed attributes: [%s]\n", + strerror(ret)); + goto done; + } + + cache_timeout = dom->service_timeout; + + ret = sysdb_store_service(dom, name, port, aliases, store_protocols, + svc_attrs, missing, cache_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store service in the sysdb: [%s]\n", + strerror(ret)); + goto done; + } + + *_usn_value = talloc_steal(mem_ctx, usn_value); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_get_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **usn_value) +{ + struct sdap_get_services_state *state = + tevent_req_data(req, struct sdap_get_services_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + + +/* Enumeration routines */ + +struct enum_services_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + char *filter; + const char **attrs; +}; + +static void +enum_services_op_done(struct tevent_req *subreq); + +struct tevent_req * +enum_services_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_id_op *op, + bool purge) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct enum_services_state *state; + + req = tevent_req_create(memctx, &state, struct enum_services_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->op = op; + + if (id_ctx->srv_opts && id_ctx->srv_opts->max_service_value && !purge) { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s=*)(%s>=%s)(!(%s=%s)))", + id_ctx->opts->service_map[SDAP_OC_SERVICE].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_USN].name, + id_ctx->srv_opts->max_service_value, + id_ctx->opts->service_map[SDAP_AT_SERVICE_USN].name, + id_ctx->srv_opts->max_service_value); + } else { + state->filter = talloc_asprintf( + state, + "(&(objectclass=%s)(%s=*)(%s=*)(%s=*))", + id_ctx->opts->service_map[SDAP_OC_SERVICE].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name, + id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name); + } + if (!state->filter) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->service_map, + SDAP_OPTS_SERVICES, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + subreq = sdap_get_services_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->id_ctx->opts->sdom->service_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + true); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_services_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +enum_services_op_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct enum_services_state *state = + tevent_req_data(req, struct enum_services_state); + char *usn_value; + char *endptr = NULL; + unsigned usn_number; + int ret; + + ret = sdap_get_services_recv(state, subreq, &usn_value); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (usn_value) { + talloc_zfree(state->id_ctx->srv_opts->max_service_value); + state->id_ctx->srv_opts->max_service_value = + talloc_steal(state->id_ctx, usn_value); + errno = 0; + usn_number = strtoul(usn_value, &endptr, 10); + if (!errno && endptr && (*endptr == '\0') && (endptr != usn_value) + && (usn_number > state->id_ctx->srv_opts->last_usn)) { + state->id_ctx->srv_opts->last_usn = usn_number; + } + } + + DEBUG(SSSDBG_FUNC_DATA, "Services higher USN value: [%s]\n", + state->id_ctx->srv_opts->max_service_value); + + tevent_req_done(req); +} + +errno_t +enum_services_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_sudo.c b/src/providers/ldap/sdap_async_sudo.c new file mode 100644 index 0000000..28b65b6 --- /dev/null +++ b/src/providers/ldap/sdap_async_sudo.c @@ -0,0 +1,698 @@ +/* + SSSD + + Async LDAP Helper routines for sudo + + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2012 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 <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_load_sudoers_state { + struct sysdb_attrs **rules; + size_t num_rules; +}; + +static void sdap_sudo_load_sudoers_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_sudo_load_sudoers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *ldap_filter) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_sudo_load_sudoers_state *state; + struct sdap_search_base **sb; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_load_sudoers_state); + if (!req) { + return NULL; + } + + state->rules = NULL; + state->num_rules = 0; + + sb = opts->sdom->sudo_search_bases; + if (sb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "SUDOERS lookup request without a search base\n"); + ret = EINVAL; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo rules\n"); + + subreq = sdap_search_bases_send(state, ev, opts, sh, sb, + opts->sudorule_map, true, 0, + ldap_filter, NULL, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_load_sudoers_done, req); + + ret = EOK; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_sudo_load_sudoers_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_load_sudoers_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); + + ret = sdap_search_bases_recv(subreq, state, &state->num_rules, + &state->rules); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo rules\n", + state->num_rules); + + tevent_req_done(req); + + return; +} + +static int sdap_sudo_load_sudoers_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sdap_sudo_load_sudoers_state *state; + + state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *num_rules = state->num_rules; + *rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} + +static char *sdap_sudo_build_host_filter(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + char **hostnames, + char **ip_addr, + bool netgroups, + bool regexp) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *filter = NULL; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + filter = talloc_strdup(tmp_ctx, "(|"); + if (filter == NULL) { + goto done; + } + + /* sudoHost is not specified and it is a cn=defaults rule */ + filter = talloc_asprintf_append_buffer(filter, "(&(!(%s=*))(%s=defaults))", + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_NAME].name); + if (filter == NULL) { + goto done; + } + + /* ALL */ + filter = talloc_asprintf_append_buffer(filter, "(%s=ALL)", + map[SDAP_AT_SUDO_HOST].name); + if (filter == NULL) { + goto done; + } + + /* hostnames */ + if (hostnames != NULL) { + for (i = 0; hostnames[i] != NULL; i++) { + filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", + map[SDAP_AT_SUDO_HOST].name, + hostnames[i]); + if (filter == NULL) { + goto done; + } + } + } + + /* ip addresses and networks */ + if (ip_addr != NULL) { + for (i = 0; ip_addr[i] != NULL; i++) { + filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", + map[SDAP_AT_SUDO_HOST].name, + ip_addr[i]); + if (filter == NULL) { + goto done; + } + } + } + + /* sudoHost contains netgroup - will be filtered more by sudo */ + if (netgroups) { + filter = talloc_asprintf_append_buffer(filter, SDAP_SUDO_FILTER_NETGROUP, + map[SDAP_AT_SUDO_HOST].name, + "*"); + if (filter == NULL) { + goto done; + } + } + + /* sudoHost contains regexp - will be filtered more by sudo */ + /* from sudo match.c : + * #define has_meta(s) (strpbrk(s, "\\?*[]") != NULL) + */ + if (regexp) { + filter = talloc_asprintf_append_buffer(filter, + "(|(%s=*\\\\*)(%s=*?*)(%s=*\\2A*)" + "(%s=*[*]*))", + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name, + map[SDAP_AT_SUDO_HOST].name); + if (filter == NULL) { + goto done; + } + } + + filter = talloc_strdup_append_buffer(filter, ")"); + if (filter == NULL) { + goto done; + } + + talloc_steal(mem_ctx, filter); + +done: + talloc_free(tmp_ctx); + + return filter; +} + +static char *sdap_sudo_get_filter(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sdap_sudo_ctx *sudo_ctx, + const char *rule_filter) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *host_filter = NULL; + char *filter = NULL; + + if (!sudo_ctx->use_host_filter) { + return talloc_strdup(mem_ctx, rule_filter); + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + host_filter = sdap_sudo_build_host_filter(tmp_ctx, map, + sudo_ctx->hostnames, + sudo_ctx->ip_addr, + sudo_ctx->include_netgroups, + sudo_ctx->include_regexp); + if (host_filter == NULL) { + goto done; + } + + filter = sdap_combine_filters(tmp_ctx, rule_filter, host_filter); + if (filter == NULL) { + goto done; + } + + talloc_steal(mem_ctx, filter); + +done: + talloc_free(tmp_ctx); + return filter; +} + +struct sdap_sudo_refresh_state { + struct sdap_sudo_ctx *sudo_ctx; + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_id_op *sdap_op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *search_filter; + const char *delete_filter; + bool update_usn; + + int dp_error; + size_t num_rules; +}; + +static errno_t sdap_sudo_refresh_retry(struct tevent_req *req); +static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq); +static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq); +static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req); +static void sdap_sudo_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char *search_filter, + const char *delete_filter, + bool update_usn) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_refresh_state); + if (!req) { + return NULL; + } + + /* if we don't have a search filter, this request is meaningless */ + if (search_filter == NULL) { + ret = EINVAL; + goto immediately; + } + + state->sudo_ctx = sudo_ctx; + state->ev = id_ctx->be->ev; + state->opts = id_ctx->opts; + state->domain = id_ctx->be->domain; + state->sysdb = id_ctx->be->domain->sysdb; + state->dp_error = DP_ERR_FATAL; + state->update_usn = update_usn; + + state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + state->search_filter = talloc_strdup(state, search_filter); + if (state->search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->delete_filter = talloc_strdup(state, delete_filter); + if (delete_filter != NULL && state->delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_sudo_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static errno_t sdap_sudo_refresh_retry(struct tevent_req *req) +{ + struct sdap_sudo_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_connect_done, req); + + return EAGAIN; +} + +static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "SUDO LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO LDAP connection successful\n"); + + /* Renew host information if needed. */ + if (state->sudo_ctx->run_hostinfo) { + subreq = sdap_sudo_get_hostinfo_send(state, state->opts, + state->sudo_ctx->id_ctx->be); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_hostinfo_done, req); + state->sudo_ctx->run_hostinfo = false; + return; + } + + ret = sdap_sudo_refresh_sudoers(req); + if (ret != EAGAIN) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } +} + +static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq) +{ + struct sdap_sudo_ctx *sudo_ctx; + struct sdap_sudo_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + sudo_ctx = state->sudo_ctx; + + ret = sdap_sudo_get_hostinfo_recv(sudo_ctx, subreq, &sudo_ctx->hostnames, + &sudo_ctx->ip_addr); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve host information, " + "host filter will be disabled [%d]: %s\n", + ret, sss_strerror(ret)); + sudo_ctx->use_host_filter = false; + } else { + sudo_ctx->use_host_filter = true; + } + + ret = sdap_sudo_refresh_sudoers(req); + if (ret != EAGAIN) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } +} + +static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req) +{ + struct sdap_sudo_refresh_state *state; + struct tevent_req *subreq; + char *filter; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + /* We are connected. Host information may have changed during transition + * from offline to online state. At this point we can combine search + * and host filter. */ + filter = sdap_sudo_get_filter(state, state->opts->sudorule_map, + state->sudo_ctx, state->search_filter); + if (filter == NULL) { + return ENOMEM; + } + + subreq = sdap_sudo_load_sudoers_send(state, state->ev, + state->opts, + sdap_id_op_handle(state->sdap_op), + filter); + if (subreq == NULL) { + talloc_free(filter); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_sudo_refresh_done, req); + + return EAGAIN; +} + +static errno_t sdap_sudo_qualify_names(struct sss_domain_info *dom, + struct sysdb_attrs **rules, + size_t rules_count) +{ + errno_t ret; + bool qualify; + struct ldb_message_element *el; + char *domain; + char *name; + const char *orig_name; + struct ldb_message_element unique_el; + + for (size_t i = 0; i < rules_count; i++) { + ret = sysdb_attrs_get_el_ext(rules[i], SYSDB_SUDO_CACHE_AT_USER, + false, &el); + if (ret != EOK) { + continue; + } + + unique_el.values = talloc_zero_array(rules, struct ldb_val, el->num_values); + if (unique_el.values == NULL) { + return ENOMEM; + } + unique_el.num_values = 0; + + for (size_t ii = 0; ii < el->num_values; ii++) { + orig_name = (const char *) el->values[ii].data; + + qualify = is_user_or_group_name(orig_name); + if (qualify) { + struct ldb_val fqval; + struct ldb_val *dup; + + ret = sss_parse_name(rules, dom->names, orig_name, + &domain, &name); + if (ret != EOK) { + continue; + } + + if (domain == NULL) { + domain = talloc_strdup(rules, dom->name); + if (domain == NULL) { + talloc_zfree(name); + return ENOMEM; + } + } + + fqval.data = (uint8_t * ) sss_create_internal_fqname(rules, + name, + domain); + talloc_zfree(domain); + talloc_zfree(name); + if (fqval.data == NULL) { + return ENOMEM; + } + fqval.length = strlen((const char *) fqval.data); + + /* Prevent saving duplicates in case the sudo rule contains + * e.g. foo and foo@domain + */ + dup = ldb_msg_find_val(&unique_el, &fqval); + if (dup != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Discarding duplicate value %s\n", (const char *) fqval.data); + talloc_free(fqval.data); + continue; + } + unique_el.values[unique_el.num_values].data = talloc_steal(unique_el.values, fqval.data); + unique_el.values[unique_el.num_values].length = fqval.length; + unique_el.num_values++; + } else { + unique_el.values[unique_el.num_values] = ldb_val_dup(unique_el.values, + &el->values[ii]); + unique_el.num_values++; + } + } + + talloc_zfree(el->values); + el->values = unique_el.values; + el->num_values = unique_el.num_values; + } + + return EOK; +} + +static void sdap_sudo_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_sudo_refresh_state *state; + struct sysdb_attrs **rules = NULL; + size_t rules_count = 0; + char *usn = NULL; + int dp_error; + int ret; + errno_t sret; + bool in_transaction = false; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + ret = sdap_sudo_load_sudoers_recv(subreq, state, &rules_count, &rules); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_sudo_refresh_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Received %zu rules\n", rules_count); + + /* Save users and groups fully qualified */ + ret = sdap_sudo_qualify_names(state->domain, rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* start transaction */ + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + /* purge cache */ + ret = sysdb_sudo_purge(state->domain, state->delete_filter, + rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* store rules */ + ret = sysdb_sudo_store(state->domain, rules, rules_count); + if (ret != EOK) { + goto done; + } + + /* commit transaction */ + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + DEBUG(SSSDBG_TRACE_FUNC, "Sudoers is successfully stored in cache\n"); + + if (state->update_usn) { + /* remember new usn */ + ret = sysdb_get_highest_usn(state, rules, rules_count, &usn); + if (ret == EOK) { + sdap_sudo_set_usn(state->sudo_ctx->id_ctx->srv_opts, usn); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get highest USN [%d]: %s\n", + ret, sss_strerror(ret)); + } + } + + ret = EOK; + state->num_rules = rules_count; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + state->dp_error = dp_error; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +int sdap_sudo_refresh_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *dp_error, + size_t *num_rules) +{ + struct sdap_sudo_refresh_state *state; + + state = tevent_req_data(req, struct sdap_sudo_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + if (num_rules != NULL) { + *num_rules = state->num_rules; + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_sudo_hostinfo.c b/src/providers/ldap/sdap_async_sudo_hostinfo.c new file mode 100644 index 0000000..a3c3e10 --- /dev/null +++ b/src/providers/ldap/sdap_async_sudo_hostinfo.c @@ -0,0 +1,516 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2012 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 <errno.h> +#include <tevent.h> +#include <talloc.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <ifaddrs.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> + +#include "util/util.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/ldap/sdap_sudo.h" +#include "resolv/async_resolv.h" + +static int sdap_sudo_get_ip_addresses(TALLOC_CTX *mem_ctx, char ***_ip_addr); + +struct sdap_sudo_get_hostinfo_state { + char **hostnames; + char **ip_addr; +}; + +struct sdap_sudo_get_hostnames_state { + struct tevent_context *ev; + struct resolv_ctx *resolv_ctx; + enum host_database *host_db; + enum restrict_family family_order; + char **hostnames; +}; + +static void sdap_sudo_get_hostinfo_done(struct tevent_req *req); + +static struct tevent_req *sdap_sudo_get_hostnames_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx); + +static void sdap_sudo_get_hostnames_done(struct tevent_req *subreq); + +static int sdap_sudo_get_hostnames_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames); + + +struct tevent_req * sdap_sudo_get_hostinfo_send(TALLOC_CTX *mem_ctx, + struct sdap_options *opts, + struct be_ctx *be_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_get_hostinfo_state *state = NULL; + char *conf_hostnames = NULL; + char *conf_ip_addr = NULL; + int ret = EOK; + + /* create request */ + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_get_hostinfo_state); + if (req == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->hostnames = NULL; + state->ip_addr = NULL; + + /* load info from configuration */ + conf_hostnames = dp_opt_get_string(opts->basic, SDAP_SUDO_HOSTNAMES); + conf_ip_addr = dp_opt_get_string(opts->basic, SDAP_SUDO_IP); + + if (conf_hostnames != NULL) { + ret = split_on_separator(state, conf_hostnames, ' ', true, true, + &state->hostnames, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to parse hostnames [%d]: %s\n", ret, strerror(ret)); + goto done; + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "Hostnames set to: %s\n", conf_hostnames); + } + } + + if (conf_ip_addr != NULL) { + ret = split_on_separator(state, conf_ip_addr, ' ', true, true, + &state->ip_addr, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to parse IP addresses [%d]: %s\n", + ret, strerror(ret)); + goto done; + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "IP addresses set to: %s\n", + conf_ip_addr); + } + } + + /* if IP addresses are not specified, configure it automatically */ + if (state->ip_addr == NULL) { + ret = sdap_sudo_get_ip_addresses(state, &state->ip_addr); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to detect IP addresses [%d]: %s\n", + ret, strerror(ret)); + } + } + + /* if hostnames are not specified, configure it automatically */ + if (state->hostnames == NULL) { + subreq = sdap_sudo_get_hostnames_send(state, be_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_sudo_get_hostinfo_done, req); + ret = EAGAIN; + } + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + } + + return req; +} + +static void sdap_sudo_get_hostinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_get_hostinfo_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_get_hostinfo_state); + + ret = sdap_sudo_get_hostnames_recv(state, subreq, &state->hostnames); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve hostnames [%d]: %s\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_get_hostinfo_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames, char ***ip_addr) +{ + struct sdap_sudo_get_hostinfo_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_get_hostinfo_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hostnames = talloc_steal(mem_ctx, state->hostnames); + *ip_addr = talloc_steal(mem_ctx, state->ip_addr); + + return EOK; +} + +static int sdap_sudo_get_ip_addresses(TALLOC_CTX *mem_ctx, + char ***_ip_addr_list) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **ip_addr_list = NULL; + struct ifaddrs *ifaces = NULL; + struct ifaddrs *iface = NULL; + struct sockaddr_in ip4_addr; + struct sockaddr_in ip4_network; + struct sockaddr_in6 ip6_addr; + struct sockaddr_in6 ip6_network; + char ip_addr[INET6_ADDRSTRLEN + 1]; + char network_addr[INET6_ADDRSTRLEN + 1]; + in_addr_t ip4_netmask = 0; + uint32_t ip6_netmask = 0; + unsigned int netmask = 0; + void *sinx_addr = NULL; + void *sinx_network = NULL; + int addr_count = 0; + int ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + errno = 0; + ret = getifaddrs(&ifaces); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Could not read interfaces [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + for (iface = ifaces; iface != NULL; iface = iface->ifa_next) { + /* Some interfaces don't have an ifa_addr */ + if (!iface->ifa_addr) continue; + + netmask = 0; + switch (iface->ifa_addr->sa_family) { + case AF_INET: + memcpy(&ip4_addr, iface->ifa_addr, sizeof(struct sockaddr_in)); + memcpy(&ip4_network, iface->ifa_netmask, sizeof(struct sockaddr_in)); + + if (!check_ipv4_addr(&ip4_addr.sin_addr, + SSS_NO_LOOPBACK|SSS_NO_MULTICAST + |SSS_NO_BROADCAST)) { + continue; + } + + /* get network mask length */ + ip4_netmask = ntohl(ip4_network.sin_addr.s_addr); + while (ip4_netmask) { + netmask++; + ip4_netmask <<= 1; + } + + /* get network address */ + ip4_network.sin_addr.s_addr = ip4_addr.sin_addr.s_addr + & ip4_network.sin_addr.s_addr; + + sinx_addr = &ip4_addr.sin_addr; + sinx_network = &ip4_network.sin_addr; + break; + case AF_INET6: + memcpy(&ip6_addr, iface->ifa_addr, sizeof(struct sockaddr_in6)); + memcpy(&ip6_network, iface->ifa_netmask, sizeof(struct sockaddr_in6)); + + if (!check_ipv6_addr(&ip6_addr.sin6_addr, + SSS_NO_LOOPBACK|SSS_NO_MULTICAST)) { + continue; + } + + /* get network mask length */ + for (i = 0; i < 4; i++) { + ip6_netmask = ntohl(((uint32_t*)(&ip6_network.sin6_addr))[i]); + while (ip6_netmask) { + netmask++; + ip6_netmask <<= 1; + } + } + + /* get network address */ + for (i = 0; i < 4; i++) { + ((uint32_t*)(&ip6_network.sin6_addr))[i] = + ((uint32_t*)(&ip6_addr.sin6_addr))[i] + & ((uint32_t*)(&ip6_network.sin6_addr))[i]; + } + + sinx_addr = &ip6_addr.sin6_addr; + sinx_network = &ip6_network.sin6_addr; + break; + default: + /* skip other families */ + continue; + } + + /* ip address */ + errno = 0; + if (inet_ntop(iface->ifa_addr->sa_family, sinx_addr, + ip_addr, INET6_ADDRSTRLEN) == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "inet_ntop() failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* network */ + errno = 0; + if (inet_ntop(iface->ifa_addr->sa_family, sinx_network, + network_addr, INET6_ADDRSTRLEN) == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "inet_ntop() failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + addr_count += 2; + ip_addr_list = talloc_realloc(tmp_ctx, ip_addr_list, char*, + addr_count + 1); + if (ip_addr_list == NULL) { + ret = ENOMEM; + goto done; + } + + ip_addr_list[addr_count - 2] = talloc_strdup(ip_addr_list, ip_addr); + if (ip_addr_list[addr_count - 2] == NULL) { + ret = ENOMEM; + goto done; + } + + ip_addr_list[addr_count - 1] = talloc_asprintf(ip_addr_list, "%s/%d", + network_addr, netmask); + if (ip_addr_list[addr_count - 1] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Found IP address: %s in network %s/%d\n", + ip_addr, network_addr, netmask); + } + + if (ip_addr_list) { + ip_addr_list[addr_count] = NULL; + } + *_ip_addr_list = talloc_steal(mem_ctx, ip_addr_list); + +done: + freeifaddrs(ifaces); + talloc_free(tmp_ctx); + + return ret; +} + +/* + * SUDO allows only one hostname that is returned from gethostname() + * (and set to "localhost" if the returned value is empty) + * and then - if allowed - resolves its fqdn using gethostbyname() or + * getaddrinfo() if available. + */ +static struct tevent_req *sdap_sudo_get_hostnames_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_get_hostnames_state *state = NULL; + char *dot = NULL; + char hostname[HOST_NAME_MAX + 1]; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_sudo_get_hostnames_state); + if (req == NULL) { + return NULL; + } + + state->ev = be_ctx->ev; + state->hostnames = NULL; + + /* hostname, fqdn and NULL */ + state->hostnames = talloc_zero_array(state, char*, 3); + if (state->hostnames == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + /* get hostname */ + + errno = 0; + ret = gethostname(hostname, sizeof(hostname)); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve machine hostname " + "[%d]: %s\n", ret, strerror(ret)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + + state->hostnames[0] = talloc_strdup(state->hostnames, hostname); + if (state->hostnames[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + dot = strchr(hostname, '.'); + if (dot != NULL) { + /* already a fqdn, determine hostname and finish */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Found fqdn: %s\n", hostname); + + *dot = '\0'; + DEBUG(SSSDBG_TRACE_INTERNAL, "Found hostname: %s\n", hostname); + + state->hostnames[1] = talloc_strdup(state->hostnames, hostname); + if (state->hostnames[1] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found hostname: %s\n", hostname); + } + + state->resolv_ctx = be_ctx->be_res->resolv; + state->host_db = default_host_dbs; + + /* get fqdn */ + subreq = resolv_gethostbyname_send(state, state->ev, state->resolv_ctx, + hostname, be_ctx->be_res->family_order, + state->host_db); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_sudo_get_hostnames_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + } + + return req; +} + +static void sdap_sudo_get_hostnames_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_get_hostnames_state *state = NULL; + struct resolv_hostent *rhostent = NULL; + int resolv_status; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_get_hostnames_state); + + ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL, + &rhostent); + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Empty result, just quit */ + DEBUG(SSSDBG_TRACE_INTERNAL, "No hostent found\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve fqdn for this machine, error [%d]: %s, " + "resolver returned: [%d]: %s\n", ret, strerror(ret), + resolv_status, resolv_strerror(resolv_status)); + tevent_req_error(req, ret); + return; + } + + /* EOK */ + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found fqdn: %s\n", rhostent->name); + + if (state->hostnames == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "state->hostnames is NULL\n"); + ret = EINVAL; + goto done; + } + + state->hostnames[1] = talloc_strdup(state->hostnames, rhostent->name); + if (state->hostnames[1] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int sdap_sudo_get_hostnames_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames) +{ + struct sdap_sudo_get_hostnames_state *state = NULL; + + state = tevent_req_data(req, struct sdap_sudo_get_hostnames_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *hostnames = talloc_steal(mem_ctx, state->hostnames); + + return EOK; +} diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c new file mode 100644 index 0000000..728295d --- /dev/null +++ b/src/providers/ldap/sdap_async_users.c @@ -0,0 +1,1209 @@ +/* + SSSD + + Async LDAP Helper routines - retrieving users + + Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009 + Copyright (C) 2010, Ralf Haferkamp <rhafer@suse.de>, Novell Inc. + Copyright (C) Jan Zeleny <jzeleny@redhat.com> - 2011 + + 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 <ctype.h> + +#include "util/util.h" +#include "util/probes.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_users.h" + +#define REALM_SEPARATOR '@' + +static void make_realm_upper_case(const char *upn) +{ + char *c; + + c = strchr(upn, REALM_SEPARATOR); + if (c == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No realm delimiter found in upn [%s].\n", upn); + return; + } + + while(*(++c) != '\0') { + c[0] = toupper(*c); + } + + return; +} + +/* ==Save-User-Entry====================================================== */ + +static errno_t +sdap_get_idmap_primary_gid(struct sdap_options *opts, + struct sysdb_attrs *attrs, + char *sid_str, + char *dom_sid_str, + gid_t *_gid) +{ + errno_t ret; + TALLOC_CTX *tmpctx = NULL; + gid_t gid, primary_gid; + char *group_sid_str; + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_PRIMARY_GROUP].sys_name, + &primary_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "no primary group ID provided\n"); + ret = EINVAL; + goto done; + } + + /* The primary group ID is just the RID part of the objectSID + * of the group. Generate the GID by adding this to the domain + * SID value. + */ + + /* First, get the domain SID if we didn't do so above */ + if (!dom_sid_str) { + ret = sdap_idmap_get_dom_sid_from_object(tmpctx, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + } + + /* Add the RID to the end */ + group_sid_str = talloc_asprintf(tmpctx, "%s-%lu", dom_sid_str, + (unsigned long) primary_gid); + if (!group_sid_str) { + ret = ENOMEM; + goto done; + } + + /* Convert the SID into a UNIX group ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, group_sid_str, &gid); + if (ret != EOK) goto done; + + ret = EOK; + *_gid = gid; +done: + talloc_free(tmpctx); + return ret; +} + +static errno_t sdap_set_non_posix_flag(struct sysdb_attrs *attrs, + const char *pkey) +{ + errno_t ret; + + ret = sysdb_attrs_add_uint32(attrs, pkey, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add a zero ID to a non-POSIX object!\n"); + return ret; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_POSIX, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error: Failed to mark objects as non-POSIX!\n"); + return ret; + } + + return EOK; +} + +static int sdap_user_set_mpg(struct sysdb_attrs *user_attrs, + gid_t *_gid) +{ + errno_t ret; + + if (_gid == NULL) { + return EINVAL; + } + + if (*_gid == 0) { + /* The original entry had no GID number. This is OK, we just won't add + * the SYSDB_PRIMARY_GROUP_GIDNUM attribute + */ + return EOK; + } + + ret = sysdb_attrs_add_uint32(user_attrs, + SYSDB_PRIMARY_GROUP_GIDNUM, + (uint32_t) *_gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_uint32 failed.\n"); + return ret; + } + + /* We won't really store gidNumber=0, but the zero value tells + * the sysdb layer that no GID is set, which sysdb requires for + * MPG-enabled domains + */ + *_gid = 0; + return EOK; +} + +/* FIXME: support storing additional attributes */ +int sdap_save_user(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + struct sysdb_attrs *mapped_attrs, + char **_usn_value, + time_t now, + bool set_non_posix) +{ + struct ldb_message_element *el; + int ret; + const char *user_name = NULL; + const char *fullname = NULL; + const char *pwd; + const char *gecos; + const char *homedir; + const char *shell; + const char *orig_dn = NULL; + uid_t uid = 0; + gid_t gid = 0; + struct sysdb_attrs *user_attrs; + char *upn = NULL; + size_t i; + int cache_timeout; + char *usn_value = NULL; + char **missing = NULL; + TALLOC_CTX *tmpctx = NULL; + bool use_id_mapping; + char *sid_str; + char *dom_sid_str = NULL; + struct sss_domain_info *subdomain; + size_t c; + char *p1; + char *p2; + bool is_posix = true; + + DEBUG(SSSDBG_TRACE_FUNC, "Save user\n"); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + ret = ENOMEM; + goto done; + } + + user_attrs = sysdb_new_attrs(tmpctx); + if (user_attrs == NULL) { + ret = ENOMEM; + goto done; + } + + /* Always store SID string if available */ + ret = sdap_attrs_get_sid_str(tmpctx, opts->idmap_ctx, attrs, + opts->user_map[SDAP_AT_USER_OBJECTSID].sys_name, + &sid_str); + if (ret == EOK) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_SID_STR, sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not add SID string: [%s]\n", + sss_strerror(ret)); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "objectSID: not available for user\n"); + sid_str = NULL; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not identify objectSID: [%s]\n", + sss_strerror(ret)); + sid_str = NULL; + } + + /* Always store UUID if available */ + ret = sysdb_handle_original_uuid(opts->user_map[SDAP_AT_USER_UUID].def_name, + attrs, + opts->user_map[SDAP_AT_USER_UUID].sys_name, + user_attrs, SYSDB_UUID); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_TRACE_ALL : SSSDBG_MINOR_FAILURE, + "Failed to retrieve UUID [%d][%s].\n", ret, sss_strerror(ret)); + } + + /* If this object has a SID available, we will determine the correct + * domain by its SID. */ + if (sid_str != NULL) { + subdomain = find_domain_by_sid(get_domains_head(dom), sid_str); + if (subdomain) { + dom = subdomain; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "SID %s does not belong to any known " + "domain\n", sid_str); + } + } + + ret = sdap_get_user_primary_name(memctx, opts, attrs, dom, &user_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get user name\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Processing user %s\n", user_name); + + if (opts->schema_type == SDAP_SCHEMA_AD) { + ret = sysdb_attrs_get_string(attrs, + opts->user_map[SDAP_AT_USER_FULLNAME].sys_name, &fullname); + if (ret == EOK) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_FULLNAME, fullname); + if (ret != EOK) { + goto done; + } + } else if (ret != ENOENT) { + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_PWD].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) pwd = NULL; + else pwd = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_GECOS].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) gecos = NULL; + else gecos = (const char *)el->values[0].data; + + if (!gecos) { + /* Fall back to the user's full name */ + ret = sysdb_attrs_get_el( + attrs, + opts->user_map[SDAP_AT_USER_FULLNAME].sys_name, &el); + if (ret) goto done; + if (el->num_values > 0) gecos = (const char *)el->values[0].data; + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_HOME].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) homedir = NULL; + else homedir = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_SHELL].sys_name, &el); + if (ret) goto done; + if (el->num_values == 0) shell = NULL; + else shell = (const char *)el->values[0].data; + + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(opts->idmap_ctx, + dom->name, + sid_str); + + /* Retrieve or map the UID as appropriate */ + if (use_id_mapping) { + + if (sid_str == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "SID not available, cannot map a " \ + "unix ID to user [%s].\n", user_name); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Mapping user [%s] objectSID [%s] to unix ID\n", user_name, sid_str); + + /* Convert the SID into a UNIX user ID */ + ret = sdap_idmap_sid_to_unix(opts->idmap_ctx, sid_str, &uid); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Store the UID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sdap_replace_id(attrs, SYSDB_UIDNUM, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped UID\n"); + goto done; + } + } else { + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_UID].sys_name, + &uid); + if (ret == ENOENT && (dom->type == DOM_TYPE_APPLICATION || set_non_posix)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking object as non-POSIX and setting ID=0!\n"); + ret = sdap_set_non_posix_flag(user_attrs, + opts->user_map[SDAP_AT_USER_UID].sys_name); + if (ret != EOK) { + goto done; + } + is_posix = false; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot retrieve UID for [%s] in domain [%s].\n", + user_name, dom->name); + ret = ERR_NO_POSIX; + goto done; + } + } + + /* check that the uid is valid for this domain if the user is a POSIX one */ + if (is_posix == true && OUT_OF_ID_RANGE(uid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_OP_FAILURE, + "User [%s] filtered out! (uid out of range)\n", + user_name); + ret = EINVAL; + goto done; + } + + if (use_id_mapping) { + ret = sdap_get_idmap_primary_gid(opts, attrs, sid_str, dom_sid_str, + &gid); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot get the GID for [%s] in domain [%s].\n", + user_name, dom->name); + goto done; + } + + if (sss_domain_is_mpg(dom) == true) { + /* For subdomain users, only create the private group as + * the subdomain is an MPG domain. + * But we have to save the GID of the original primary group + * because otherwise this information might be lost because + * typically (UNIX and AD) the user is not listed in his primary + * group as a member. + */ + ret = sdap_user_set_mpg(user_attrs, &gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_user_set_mpg failed [%d]: %s\n", ret, + sss_strerror(ret)); + goto done; + } + } + + /* Store the GID in the ldap_attrs so it doesn't get + * treated as a missing attribute from LDAP and removed. + */ + ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, gid); + if (ret != EOK) goto done; + } else if (sss_domain_is_mpg(dom)) { + /* Likewise, if a domain is set to contain 'magic private groups', do + * not process the real GID, but save it in the cache as originalGID + * (if available) + */ + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name, + &gid); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "Missing GID, won't save the %s attribute\n", + SYSDB_PRIMARY_GROUP_GIDNUM); + + /* Store the UID as GID (since we're in a MPG domain so that it doesn't + * get treated as a missing attribute and removed + */ + ret = sdap_replace_id(attrs, SYSDB_GIDNUM, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the id-mapped UID\n"); + goto done; + } + gid = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve GID, won't save the %s attribute\n", + SYSDB_PRIMARY_GROUP_GIDNUM); + gid = 0; + } + + ret = sdap_user_set_mpg(user_attrs, &gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_user_set_mpg failed [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } else { + ret = sysdb_attrs_get_uint32_t(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name, + &gid); + if (ret == ENOENT && (dom->type == DOM_TYPE_APPLICATION || set_non_posix)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Marking object as non-POSIX and setting ID=0!\n"); + ret = sdap_set_non_posix_flag(attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name); + if (ret != EOK) { + goto done; + } + is_posix = false; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot retrieve GID for [%s] in domain [%s].\n", + user_name, dom->name); + ret = ERR_NO_POSIX; + goto done; + } + } + + /* check that the gid is valid for this domain */ + if (is_posix == true && IS_SUBDOMAIN(dom) == false + && sss_domain_is_mpg(dom) == false + && OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User [%s] filtered out! (primary gid out of range)\n", + user_name); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto done; + } + if (!el || el->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "originalDN is not available for [%s].\n", user_name); + } else { + orig_dn = (const char *) el->values[0].data; + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding originalDN [%s] to attributes " + "of [%s].\n", orig_dn, user_name); + + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_DN, orig_dn); + if (ret) { + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_MEMBEROF, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original memberOf is not available for [%s].\n", user_name); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Adding original memberOf attributes to [%s].\n", user_name); + for (i = 0; i < el->num_values; i++) { + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF, + (const char *) el->values[i].data); + if (ret) { + goto done; + } + } + } + + ret = sdap_attrs_add_string(attrs, + opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, + "original mod-Timestamp", + user_name, user_attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_USN].sys_name, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Original USN value is not available for [%s].\n", user_name); + } else { + ret = sysdb_attrs_add_string(user_attrs, + opts->user_map[SDAP_AT_USER_USN].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto done; + } + usn_value = talloc_strdup(tmpctx, (const char*)el->values[0].data); + if (!usn_value) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el); + if (ret) { + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "User principal is not available for [%s].\n", user_name); + } else { + for (c = 0; c < el->num_values; c++) { + upn = talloc_strdup(tmpctx, (const char*) el->values[c].data); + if (!upn) { + ret = ENOMEM; + goto done; + } + + /* Check for IPA Kerberos enterprise principal strings + * 'user\@my.realm@IPA.REALM' and use 'user@my.realm' */ + if ( (p1 = strchr(upn,'\\')) != NULL + && *(p1 + 1) == '@' + && (p2 = strchr(p1 + 2, '@')) != NULL) { + *p1 = '\0'; + *p2 = '\0'; + upn = talloc_asprintf(tmpctx, "%s%s", upn, p1 + 1); + if (upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (dp_opt_get_bool(opts->basic, SDAP_FORCE_UPPER_CASE_REALM)) { + make_realm_upper_case(upn); + } + DEBUG(SSSDBG_TRACE_FUNC, + "Adding user principal [%s] to attributes of [%s].\n", + upn, user_name); + ret = sysdb_attrs_add_string(user_attrs, SYSDB_UPN, upn); + if (ret) { + goto done; + } + } + } + + for (i = SDAP_FIRST_EXTRA_USER_AT; i < opts->user_map_cnt; i++) { + ret = sdap_attrs_add_list(attrs, opts->user_map[i].sys_name, + NULL, user_name, user_attrs); + if (ret) { + goto done; + } + } + + cache_timeout = dom->user_timeout; + + ret = sdap_save_all_names(user_name, attrs, dom, + SYSDB_MEMBER_USER, user_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save user names\n"); + goto done; + } + + /* Make sure that any attributes we requested from LDAP that we + * did not receive are also removed from the sysdb + */ + ret = list_missing_attrs(user_attrs, opts->user_map, opts->user_map_cnt, + attrs, &missing); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for user %s\n", user_name); + + ret = sysdb_store_user(dom, user_name, pwd, uid, gid, + gecos, homedir, shell, orig_dn, + user_attrs, missing, cache_timeout, now); + if (ret) goto done; + + if (mapped_attrs != NULL) { + ret = sysdb_set_user_attr(dom, user_name, mapped_attrs, SYSDB_MOD_ADD); + if (ret) return ret; + } + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, usn_value); + } + + talloc_steal(memctx, user_attrs); + ret = EOK; + +done: + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to save user [%s]\n", + user_name ? user_name : "Unknown"); + } + talloc_free(tmpctx); + return ret; +} + + +/* ==Generic-Function-to-save-multiple-users============================= */ + +int sdap_save_users(TALLOC_CTX *memctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + struct sysdb_attrs *mapped_attrs, + char **_usn_value) +{ + TALLOC_CTX *tmpctx; + char *higher_usn = NULL; + char *usn_value; + int ret; + errno_t sret; + int i; + time_t now; + bool in_transaction = false; + + if (num_users == 0) { + /* Nothing to do if there are no users */ + return EOK; + } + + tmpctx = talloc_new(memctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (mapped_attrs != NULL) { + ret = sysdb_remove_mapped_data(dom, mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_mapped_data failed, " + "some cached entries might contain invalid mapping data.\n"); + } + } + + now = time(NULL); + for (i = 0; i < num_users; i++) { + usn_value = NULL; + + ret = sdap_save_user(tmpctx, opts, dom, users[i], mapped_attrs, + &usn_value, now, false); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user %d. Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_ALL, "User %d processed!\n", i); + } + + if (usn_value) { + if (higher_usn) { + if ((strlen(usn_value) > strlen(higher_usn)) || + (strcmp(usn_value, higher_usn) > 0)) { + talloc_zfree(higher_usn); + higher_usn = usn_value; + } else { + talloc_zfree(usn_value); + } + } else { + higher_usn = usn_value; + } + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + + if (_usn_value) { + *_usn_value = talloc_steal(memctx, higher_usn); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + talloc_zfree(tmpctx); + return ret; +} + + +/* ==Search-Users-with-filter============================================= */ + +struct sdap_search_user_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + struct sss_domain_info *dom; + + const char **attrs; + const char *base_filter; + const char *filter; + int timeout; + enum sdap_entry_lookup_type lookup_type; + + char *higher_usn; + struct sysdb_attrs **users; + size_t count; + + size_t base_iter; + struct sdap_search_base **search_bases; +}; + +static errno_t sdap_search_user_next_base(struct tevent_req *req); +static void sdap_search_user_copy_batch(struct sdap_search_user_state *state, + struct sysdb_attrs **users, + size_t count); +static void sdap_search_user_process(struct tevent_req *subreq); + +struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_search_user_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_search_user_state); + if (req == NULL) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->attrs = attrs; + state->higher_usn = NULL; + state->users = NULL; + state->count = 0; + state->timeout = timeout; + state->base_filter = filter; + state->base_iter = 0; + state->search_bases = search_bases; + state->lookup_type = lookup_type; + + if (!state->search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sdap_search_user_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ev); + } + + return req; +} + +static errno_t sdap_search_user_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_search_user_state *state; + bool need_paging = false; + int sizelimit = 0; + + state = tevent_req_data(req, struct sdap_search_user_state); + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for users with base [%s]\n", + state->search_bases[state->base_iter]->basedn); + + switch (state->lookup_type) { + case SDAP_LOOKUP_SINGLE: + break; + /* Only requests that can return multiple entries should require + * the paging control + */ + case SDAP_LOOKUP_WILDCARD: + sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT); + need_paging = true; + break; + case SDAP_LOOKUP_ENUMERATE: + need_paging = true; + break; + } + + subreq = sdap_get_and_parse_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->user_map, state->opts->user_map_cnt, + 0, NULL, NULL, sizelimit, state->timeout, + need_paging); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, sdap_search_user_process, req); + + return EOK; +} + +static void sdap_search_user_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_search_user_state *state = tevent_req_data(req, + struct sdap_search_user_state); + int ret; + size_t count; + struct sysdb_attrs **users; + bool next_base = false; + + ret = sdap_get_and_parse_generic_recv(subreq, state, + &count, &users); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search for users, returned %zu results.\n", count); + + if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \ + state->lookup_type == SDAP_LOOKUP_ENUMERATE || \ + count == 0) { + /* No users found in this search or looking up multiple entries */ + next_base = true; + } + + /* Add this batch of users to the list */ + if (count > 0) { + state->users = + talloc_realloc(state, + state->users, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->users) { + tevent_req_error(req, ENOMEM); + return; + } + + sdap_search_user_copy_batch(state, users, count); + } + + if (next_base) { + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_search_user_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieved total %zu users\n", state->count); + + /* No more search bases + * Return ENOENT if no users were found + */ + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + tevent_req_done(req); +} + +static void sdap_search_user_copy_batch(struct sdap_search_user_state *state, + struct sysdb_attrs **users, + size_t count) +{ + size_t copied; + bool filter; + + /* Always copy all objects for wildcard lookups. */ + filter = state->lookup_type == SDAP_LOOKUP_SINGLE ? true : false; + + copied = sdap_steal_objects_in_dom(state->opts, + state->users, + state->count, + state->dom, + users, count, filter); + + state->count += copied; + state->users[state->count] = NULL; +} + +int sdap_search_user_recv(TALLOC_CTX *memctx, struct tevent_req *req, + char **higher_usn, struct sysdb_attrs ***users, + size_t *count) +{ + struct sdap_search_user_state *state = tevent_req_data(req, + struct sdap_search_user_state); + + if (higher_usn) { + *higher_usn = talloc_steal(memctx, state->higher_usn); + } + + if (users) { + *users = talloc_steal(memctx, state->users); + } + + if (count) { + *count = state->count; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* ==Search-And-Save-Users-with-filter============================================= */ +struct sdap_get_users_state { + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + const char *filter; + + char *higher_usn; + struct sysdb_attrs **users; + struct sysdb_attrs *mapped_attrs; + size_t count; +}; + +static void sdap_get_users_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout, + enum sdap_entry_lookup_type lookup_type, + struct sysdb_attrs *mapped_attrs) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_get_users_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_users_state); + if (!req) return NULL; + + state->sysdb = sysdb; + state->opts = opts; + state->dom = dom; + + state->filter = filter; + PROBE(SDAP_SEARCH_USER_SEND, state->filter); + + if (mapped_attrs == NULL) { + state->mapped_attrs = NULL; + } else { + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_copy(mapped_attrs, state->mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_copy failed.\n"); + goto done; + } + } + + subreq = sdap_search_user_send(state, ev, dom, opts, search_bases, + sh, attrs, filter, timeout, lookup_type); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sdap_get_users_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sdap_get_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_users_state *state = tevent_req_data(req, + struct sdap_get_users_state); + int ret; + + ret = sdap_search_user_recv(state, subreq, &state->higher_usn, + &state->users, &state->count); + if (ret) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve users [%d][%s].\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + PROBE(SDAP_SEARCH_USER_SAVE_BEGIN, state->filter); + + ret = sdap_save_users(state, state->sysdb, + state->dom, state->opts, + state->users, state->count, + state->mapped_attrs, + &state->higher_usn); + PROBE(SDAP_SEARCH_USER_SAVE_END, state->filter); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store users [%d][%s].\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Users - Done\n", state->count); + + tevent_req_done(req); +} + +int sdap_get_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **usn_value) +{ + struct sdap_get_users_state *state = tevent_req_data(req, + struct sdap_get_users_state); + + PROBE(SDAP_SEARCH_USER_RECV, state->filter); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (usn_value) { + *usn_value = talloc_steal(mem_ctx, state->higher_usn); + } + + return EOK; +} + +/* ==Fetch-Fallback-local-user============================================ */ + +errno_t sdap_fallback_local_user(TALLOC_CTX *memctx, + const char *name, uid_t uid, + struct sysdb_attrs ***reply) +{ + struct sysdb_attrs **ua; + struct sysdb_attrs *user; + struct passwd *pwd; + int ret; + + if (name) { + pwd = getpwnam(name); + } else { + pwd = getpwuid(uid); + } + + if (!pwd) { + return errno ? errno : ENOENT; + } + + ua = talloc_array(memctx, struct sysdb_attrs *, 2); + if (!ua) { + ret = ENOMEM; + goto done; + } + ua[1] = NULL; + + user = sysdb_new_attrs(ua); + if (!user) { + ret = ENOMEM; + goto done; + } + ua[0] = user; + + ret = sysdb_attrs_add_string(user, SYSDB_NAME, pwd->pw_name); + if (ret != EOK) { + goto done; + } + + if (pwd->pw_passwd) { + ret = sysdb_attrs_add_string(user, SYSDB_PWD, pwd->pw_passwd); + if (ret != EOK) { + goto done; + } + } + + ret = sysdb_attrs_add_long(user, SYSDB_UIDNUM, (long)pwd->pw_uid); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_long(user, SYSDB_GIDNUM, (long)pwd->pw_gid); + if (ret != EOK) { + goto done; + } + + if (pwd->pw_gecos && *pwd->pw_gecos) { + ret = sysdb_attrs_add_string(user, SYSDB_GECOS, pwd->pw_gecos); + if (ret != EOK) { + goto done; + } + } + + if (pwd->pw_dir && *pwd->pw_dir) { + ret = sysdb_attrs_add_string(user, SYSDB_HOMEDIR, pwd->pw_dir); + if (ret != EOK) { + goto done; + } + } + + if (pwd->pw_shell && *pwd->pw_shell) { + ret = sysdb_attrs_add_string(user, SYSDB_SHELL, pwd->pw_shell); + if (ret != EOK) { + goto done; + } + } + +done: + if (ret != EOK) { + talloc_free(ua); + } else { + *reply = ua; + } + + return ret; +} diff --git a/src/providers/ldap/sdap_autofs.c b/src/providers/ldap/sdap_autofs.c new file mode 100644 index 0000000..b951790 --- /dev/null +++ b/src/providers/ldap/sdap_autofs.c @@ -0,0 +1,491 @@ +/* + SSSD + + LDAP handler for autofs + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 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 <errno.h> +#include <tevent.h> + +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_autofs.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/backend.h" +#include "providers/data_provider.h" +#include "db/sysdb_autofs.h" +#include "util/util.h" + +static void +sdap_autofs_invalidate_maps(struct sdap_id_ctx *id_ctx, + const char *mapname) +{ + const char *master_map; + errno_t ret; + + master_map = dp_opt_get_string(id_ctx->opts->basic, + SDAP_AUTOFS_MAP_MASTER_NAME); + if (strcmp(master_map, mapname) == 0) { + DEBUG(SSSDBG_FUNC_DATA, "Refresh of automount master map triggered: " + "%s\n", mapname); + + ret = sysdb_invalidate_autofs_maps(id_ctx->be->domain); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not invalidate autofs maps, " + "backend might return stale entries\n"); + } + } +} + +struct sdap_autofs_enumerate_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + const char *map_name; + + int dp_error; +}; + +static errno_t +sdap_autofs_enumerate_retry(struct tevent_req *req); +static void +sdap_autofs_enumerate_connect_done(struct tevent_req *subreq); +static void +sdap_autofs_enumerate_done(struct tevent_req *req); + +static struct tevent_req * +sdap_autofs_enumerate_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *map_name) +{ + struct tevent_req *req; + struct sdap_autofs_enumerate_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_autofs_enumerate_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->dp_error = DP_ERR_FATAL; + state->map_name = map_name; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + ret = sdap_autofs_enumerate_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_autofs_enumerate_retry(struct tevent_req *req) +{ + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + struct tevent_req *subreq; + int ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_autofs_enumerate_connect_done, req); + return EOK; +} + +static void +sdap_autofs_enumerate_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_autofs_setautomntent_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->domain->sysdb, + sdap_id_op_handle(state->op), + state->op, + state->ctx->opts, + state->map_name); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_autofs_setautomntent_send failed\n"); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_autofs_enumerate_done, req); + +} + +static void +sdap_autofs_enumerate_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_autofs_setautomntent_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_autofs_enumerate_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT) { + ret = sysdb_delete_autofsmap(state->ctx->be->domain, state->map_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete autofs map %s [%d]: %s\n", + state->map_name, ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_autofs_enumerate_recv(struct tevent_req *req, int *dp_error_out) +{ + struct sdap_autofs_enumerate_state *state = + tevent_req_data(req, struct sdap_autofs_enumerate_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_enumerate_handler_state { + int dummy; +}; + +static void sdap_autofs_enumerate_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_enumerate_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_enumerate_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_enumerate_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s\n", data->mapname); + + sdap_autofs_invalidate_maps(id_ctx, data->mapname); + + subreq = sdap_autofs_enumerate_send(mem_ctx, params->ev, + id_ctx, data->mapname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s.\n", + data->mapname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_enumerate_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_enumerate_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_enumerate_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_enumerate_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_get_map_handler_state { + int dummy; +}; + +static void sdap_autofs_get_map_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_get_map_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_get_map_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_get_map_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s\n", data->mapname); + + sdap_autofs_invalidate_maps(id_ctx, data->mapname); + + subreq = sdap_autofs_get_map_send(mem_ctx, id_ctx, data->mapname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s.\n", + data->mapname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_map_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_get_map_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_get_map_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_get_map_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_autofs_get_entry_handler_state { + int dummy; +}; + +static void sdap_autofs_get_entry_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_autofs_get_entry_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_autofs_data *data, + struct dp_req_params *params) +{ + struct sdap_autofs_get_entry_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_autofs_get_entry_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, "Requested refresh for: %s:%s\n", + data->mapname, data->entryname); + + subreq = sdap_autofs_get_entry_send(mem_ctx, id_ctx, + data->mapname, data->entryname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request for %s:%s.\n", + data->mapname, data->entryname); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_autofs_get_entry_handler_done, req); + + ret = EAGAIN; + +immediately: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, params->ev); + } + + return req; +} + +static void sdap_autofs_get_entry_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_autofs_get_entry_recv(subreq, &dp_error); + talloc_zfree(subreq); + ret = dp_error_to_ret(ret, dp_error); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_autofs_get_entry_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + dp_no_output *_no_output) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t sdap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing autofs LDAP back end\n"); + + ret = ldap_get_autofs_options(id_ctx, sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->cdb, be_ctx->conf_path, id_ctx->opts); + if (ret != EOK) { + return ret; + } + + dp_set_method(dp_methods, DPM_AUTOFS_ENUMERATE, + sdap_autofs_enumerate_handler_send, sdap_autofs_enumerate_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_MAP, + sdap_autofs_get_map_handler_send, sdap_autofs_get_map_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_ENTRY, + sdap_autofs_get_entry_handler_send, sdap_autofs_get_entry_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + return EOK; +} diff --git a/src/providers/ldap/sdap_autofs.h b/src/providers/ldap/sdap_autofs.h new file mode 100644 index 0000000..2103937 --- /dev/null +++ b/src/providers/ldap/sdap_autofs.h @@ -0,0 +1,62 @@ +/* + SSSD + + LDAP handler for autofs + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef _SDAP_AUTOFS_H_ +#define _SDAP_AUTOFS_H_ + +errno_t sdap_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods); + +struct tevent_req * +sdap_autofs_setautomntent_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_id_op *op, + struct sdap_options *opts, + const char *mapname); + +errno_t +sdap_autofs_setautomntent_recv(struct tevent_req *req); + +struct tevent_req *sdap_autofs_get_map_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname); + +errno_t sdap_autofs_get_map_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_autofs_get_entry_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + const char *mapname, + const char *entryname); + +errno_t sdap_autofs_get_entry_recv(struct tevent_req *req, + int *dp_error); + +#endif /* _SDAP_AUTOFS_H_ */ + diff --git a/src/providers/ldap/sdap_certmap.c b/src/providers/ldap/sdap_certmap.c new file mode 100644 index 0000000..e32895c --- /dev/null +++ b/src/providers/ldap/sdap_certmap.c @@ -0,0 +1,150 @@ + +/* + SSSD + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2017 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 "util/util.h" +#include "lib/certmap/sss_certmap.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_certmap_ctx { + struct sss_certmap_ctx *certmap_ctx; +}; + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +struct sss_certmap_ctx *sdap_get_sss_certmap(struct sdap_certmap_ctx *ctx) +{ + return ctx == NULL ? NULL : ctx->certmap_ctx; +} + +errno_t sdap_setup_certmap(struct sdap_certmap_ctx *sdap_certmap_ctx, + struct certmap_info **certmap_list) +{ + int ret; + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + size_t c; + + if (sdap_certmap_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sdap_certmap_ctx.\n"); + return EINVAL; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(sdap_certmap_ctx, ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(sdap_certmap_ctx->certmap_ctx); + sdap_certmap_ctx->certmap_ctx = sss_certmap_ctx; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + } + + return ret; +} + +errno_t sdap_init_certmap(TALLOC_CTX *mem_ctx, struct sdap_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + + if (id_ctx->opts->sdap_certmap_ctx == NULL) { + id_ctx->opts->sdap_certmap_ctx = talloc_zero(mem_ctx, + struct sdap_certmap_ctx); + if (id_ctx->opts->sdap_certmap_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + } + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + ret = sdap_setup_certmap(id_ctx->opts->sdap_certmap_ctx, certmap_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_setup_certmap failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c new file mode 100644 index 0000000..3d42f4e --- /dev/null +++ b/src/providers/ldap/sdap_child_helpers.c @@ -0,0 +1,515 @@ +/* + SSSD + + LDAP Backend Module -- child helpers + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2009 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 <sys/types.h> +#include <sys/wait.h> +#include <pwd.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "util/child_common.h" + +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#else +#define LDAP_CHILD SSSD_LIBEXEC_PATH"/ldap_child" +#endif + +#ifndef LDAP_CHILD_USER +#define LDAP_CHILD_USER "nobody" +#endif + +struct sdap_child { + /* child info */ + pid_t pid; + struct child_io_fds *io; +}; + +static void sdap_close_fd(int *fd) +{ + int ret; + + if (*fd == -1) { + DEBUG(SSSDBG_TRACE_FUNC, "fd already closed\n"); + return; + } + + ret = close(*fd); + if (ret) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "Closing fd %d, return error %d (%s)\n", + *fd, ret, strerror(ret)); + } + + *fd = -1; +} + +static void child_callback(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + if (WEXITSTATUS(child_status) == CHILD_TIMEOUT_EXIT_CODE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDAP child was terminated due to timeout\n"); + + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + tevent_req_error(req, ETIMEDOUT); + } +} + +static errno_t sdap_fork_child(struct tevent_context *ev, + struct sdap_child *child, struct tevent_req *req) +{ + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + pid_t pid; + errno_t ret; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe(from) failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe(to) failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child(child, + pipefd_to_child, pipefd_from_child, + LDAP_CHILD, LDAP_CHILD_LOG_FILE); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec LDAP child\n"); + } else if (pid > 0) { /* parent */ + child->pid = pid; + child->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + child->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(child->io->read_from_child_fd); + sss_fd_nonblocking(child->io->write_to_child_fd); + + ret = child_handler_setup(ev, pid, child_callback, req, NULL); + if (ret != EOK) { + goto fail; + } + + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + return EOK; + +fail: + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + return ret; +} + +static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + + buf = talloc(mem_ctx, struct io_buffer); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + buf->size = 6 * sizeof(uint32_t); + if (realm_str) { + buf->size += strlen(realm_str); + } + if (princ_str) { + buf->size += strlen(princ_str); + } + if (keytab_name) { + buf->size += strlen(keytab_name); + } + + DEBUG(SSSDBG_TRACE_FUNC, "buffer size: %zu\n", buf->size); + + buf->data = talloc_size(buf, buf->size); + if (buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + + /* realm */ + if (realm_str) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(realm_str), &rp); + safealign_memcpy(&buf->data[rp], realm_str, strlen(realm_str), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* principal */ + if (princ_str) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(princ_str), &rp); + safealign_memcpy(&buf->data[rp], princ_str, strlen(princ_str), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* keytab */ + if (keytab_name) { + SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab_name), &rp); + safealign_memcpy(&buf->data[rp], keytab_name, strlen(keytab_name), &rp); + } else { + SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); + } + + /* lifetime */ + SAFEALIGN_SET_UINT32(&buf->data[rp], lifetime, &rp); + + /* UID and GID to drop privileges to, if needed. The ldap_child process runs as + * setuid if the back end runs unprivileged as it needs to access the keytab + */ + SAFEALIGN_SET_UINT32(&buf->data[rp], geteuid(), &rp); + SAFEALIGN_SET_UINT32(&buf->data[rp], getegid(), &rp); + + *io_buf = buf; + return EOK; +} + +static int parse_child_response(TALLOC_CTX *mem_ctx, + uint8_t *buf, ssize_t size, + int *result, krb5_error_code *kerr, + char **ccache, time_t *expire_time_out) +{ + size_t p = 0; + uint32_t len; + uint32_t res; + char *ccn; + time_t expire_time; + krb5_error_code krberr; + + /* operation result code */ + SAFEALIGN_COPY_UINT32_CHECK(&res, buf + p, size, &p); + + /* krb5 error code */ + safealign_memcpy(&krberr, buf+p, sizeof(krberr), &p); + + /* ccache name size */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + + if (len > size - p) return EINVAL; + + ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1)); + if (ccn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + safealign_memcpy(ccn, buf+p, sizeof(char) * len, &p); + ccn[len] = '\0'; + + if (p + sizeof(time_t) > size) { + talloc_free(ccn); + return EINVAL; + } + safealign_memcpy(&expire_time, buf+p, sizeof(time_t), &p); + + *result = res; + *ccache = ccn; + *expire_time_out = expire_time; + *kerr = krberr; + return EOK; +} + +/* ==The-public-async-interface============================================*/ + +struct sdap_get_tgt_state { + struct tevent_context *ev; + struct sdap_child *child; + ssize_t len; + uint8_t *buf; + + struct tevent_timer *kill_te; +}; + +static errno_t set_tgt_child_timeout(struct tevent_req *req, + struct tevent_context *ev, + int timeout); +static void sdap_get_tgt_step(struct tevent_req *subreq); +static void sdap_get_tgt_done(struct tevent_req *subreq); + +struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + int32_t lifetime, + int timeout) +{ + struct tevent_req *req, *subreq; + struct sdap_get_tgt_state *state; + struct io_buffer *buf; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_get_tgt_state); + if (!req) { + return NULL; + } + + state->ev = ev; + + state->child = talloc_zero(state, struct sdap_child); + if (!state->child) { + ret = ENOMEM; + goto fail; + } + + state->child->io = talloc(state, struct child_io_fds); + if (state->child->io == NULL) { + ret = ENOMEM; + goto fail; + } + state->child->io->read_from_child_fd = -1; + state->child->io->write_to_child_fd = -1; + talloc_set_destructor((TALLOC_CTX *) state->child->io, child_io_destructor); + + /* prepare the data to pass to child */ + ret = create_tgt_req_send_buffer(state, + realm_str, princ_str, keytab_name, lifetime, + &buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_tgt_req_send_buffer failed.\n"); + goto fail; + } + + ret = sdap_fork_child(state->ev, state->child, req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_fork_child failed.\n"); + goto fail; + } + + ret = set_tgt_child_timeout(req, ev, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "set_tgt_child_timeout failed.\n"); + goto fail; + } + + subreq = write_pipe_send(state, ev, buf->data, buf->size, + state->child->io->write_to_child_fd); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_get_tgt_step, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_get_tgt_step(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + sdap_close_fd(&state->child->io->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, + state->child->io->read_from_child_fd); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_tgt_done, req); +} + +static void sdap_get_tgt_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + ret = read_pipe_recv(subreq, state, &state->buf, &state->len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + sdap_close_fd(&state->child->io->read_from_child_fd); + + if (state->kill_te == NULL) { + tevent_req_done(req); + return; + } + + /* wait for child callback to terminate the request */ +} + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + krb5_error_code *kerr, + char **ccname, + time_t *expire_time_out) +{ + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + char *ccn; + time_t expire_time; + int res; + int ret; + krb5_error_code krberr; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = parse_child_response(mem_ctx, state->buf, state->len, + &res, &krberr, &ccn, &expire_time); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse child response: [%d][%s]\n", ret, strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Child responded: %d [%s], expired on [%"SPRItime"]\n", + res, ccn, expire_time); + *result = res; + *kerr = krberr; + *ccname = ccn; + *expire_time_out = expire_time; + return EOK; +} + +static void get_tgt_sigkill_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + DEBUG(SSSDBG_TRACE_ALL, + "timeout for sending SIGKILL to TGT child [%d] reached.\n", + state->child->pid); + + ret = kill(state->child->pid, SIGKILL); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "kill failed [%d][%s].\n", errno, strerror(errno)); + } + + tevent_req_error(req, ETIMEDOUT); +} + +static void get_tgt_timeout_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + int ret; + + DEBUG(SSSDBG_TRACE_ALL, + "timeout for sending SIGTERM to TGT child [%d] reached.\n", + state->child->pid); + + ret = kill(state->child->pid, SIGTERM); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGTERM failed [%d][%s].\n", ret, strerror(ret)); + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Setting %d seconds timeout for sending SIGKILL to TGT child\n", + SIGTERM_TO_SIGKILL_TIME); + + tv = tevent_timeval_current_ofs(SIGTERM_TO_SIGKILL_TIME, 0); + + state->kill_te = tevent_add_timer(ev, req, tv, get_tgt_sigkill_handler, req); + if (state->kill_te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + tevent_req_error(req, ECANCELED); + } +} + +static errno_t set_tgt_child_timeout(struct tevent_req *req, + struct tevent_context *ev, + int timeout) +{ + struct tevent_timer *te; + struct timeval tv; + + DEBUG(SSSDBG_TRACE_FUNC, + "Setting %d seconds timeout for TGT child\n", timeout); + + tv = tevent_timeval_current_ofs(timeout, 0); + + te = tevent_add_timer(ev, req, tv, get_tgt_timeout_handler, req); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + return ENOMEM; + } + + return EOK; +} diff --git a/src/providers/ldap/sdap_domain.c b/src/providers/ldap/sdap_domain.c new file mode 100644 index 0000000..95f2ef9 --- /dev/null +++ b/src/providers/ldap/sdap_domain.c @@ -0,0 +1,232 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2008-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 "providers/ldap/ldap_common.h" + +int +sdap_domain_destructor(void *mem) +{ + struct sdap_domain *dom = + talloc_get_type(mem, struct sdap_domain); + DLIST_REMOVE(*(dom->head), dom); + return 0; +} + +struct sdap_domain * +sdap_domain_get(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + struct sdap_domain *sditer = NULL; + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sditer->dom == dom) { + break; + } + } + + return sditer; +} + +struct sdap_domain * +sdap_domain_get_by_name(struct sdap_options *opts, + const char *dom_name) +{ + struct sdap_domain *sditer = NULL; + + if (dom_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing domain name.\n"); + return NULL; + } + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sditer->dom->name != NULL + && strcasecmp(sditer->dom->name, dom_name) == 0) { + break; + } + } + + return sditer; +} + +struct sdap_domain * +sdap_domain_get_by_dn(struct sdap_options *opts, + const char *dn) +{ + struct sdap_domain *sditer = NULL; + struct sdap_domain *sdmatch = NULL; + TALLOC_CTX *tmp_ctx = NULL; + int match_len; + int best_match_len = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + DLIST_FOR_EACH(sditer, opts->sdom) { + if (sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, sditer->search_bases, + NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->user_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->group_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->netgroup_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->sudo_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->service_search_bases, NULL, &match_len) + || sss_ldap_dn_in_search_bases_len(tmp_ctx, dn, + sditer->autofs_search_bases, NULL, &match_len)) { + if (best_match_len < match_len) { + /*this is a longer match*/ + best_match_len = match_len; + sdmatch = sditer; + } + } + } + talloc_free(tmp_ctx); + return sdmatch; +} + +errno_t +sdap_domain_add(struct sdap_options *opts, + struct sss_domain_info *dom, + struct sdap_domain **_sdom) +{ + struct sdap_domain *sdom; + errno_t ret; + + sdom = talloc_zero(opts, struct sdap_domain); + if (sdom == NULL) { + return ENOMEM; + } + sdom->dom = dom; + sdom->head = &opts->sdom; + + /* Convert the domain name into search base */ + ret = domain_to_basedn(sdom, sdom->dom->name, &sdom->basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot convert domain name [%s] to base DN [%d]: %s\n", + dom->name, ret, strerror(ret)); + goto done; + } + + talloc_set_destructor((TALLOC_CTX *)sdom, sdap_domain_destructor); + DLIST_ADD_END(opts->sdom, sdom, struct sdap_domain *); + + if (_sdom) *_sdom = sdom; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sdom); + } + + return ret; +} + +errno_t +sdap_domain_subdom_add(struct sdap_id_ctx *sdap_id_ctx, + struct sdap_domain *sdom_list, + struct sss_domain_info *parent) +{ + struct sss_domain_info *dom; + struct sdap_domain *sdom, *sditer; + errno_t ret; + + for (dom = get_next_domain(parent, SSS_GND_DESCEND|SSS_GND_INCLUDE_DISABLED); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, SSS_GND_INCLUDE_DISABLED)) { + + /* Always create sdap domain object for the forest root, even if it is + * disabled so that we can connect later to discover trusted domains + * in the forest. */ + if (sss_domain_get_state(dom) == DOM_DISABLED + && !sss_domain_is_forest_root(dom)) { + continue; + } + + DLIST_FOR_EACH(sditer, sdom_list) { + if (sditer->dom == dom) { + break; + } + } + + if (sditer == NULL) { + /* New sdap domain */ + DEBUG(SSSDBG_TRACE_FUNC, "subdomain %s is a new one, will " + "create a new sdap domain object\n", dom->name); + + ret = sdap_domain_add(sdap_id_ctx->opts, dom, &sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot add new sdap domain for domain %s [%d]: %s\n", + parent->name, ret, strerror(ret)); + return ret; + } + } else if (sditer->search_bases != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "subdomain %s has already initialized search bases\n", + dom->name); + continue; + } else { + sdom = sditer; + } + + /* Update search bases */ + talloc_zfree(sdom->search_bases); + sdom->search_bases = talloc_array(sdom, struct sdap_search_base *, 2); + if (sdom->search_bases == NULL) { + return ENOMEM; + } + sdom->search_bases[1] = NULL; + + ret = sdap_create_search_base(sdom, sysdb_ctx_get_ldb(dom->sysdb), + sdom->basedn, LDAP_SCOPE_SUBTREE, + NULL, &sdom->search_bases[0]); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create new sdap search base\n"); + return ret; + } + + sdom->user_search_bases = sdom->search_bases; + sdom->group_search_bases = sdom->search_bases; + sdom->netgroup_search_bases = sdom->search_bases; + sdom->sudo_search_bases = sdom->search_bases; + sdom->service_search_bases = sdom->search_bases; + sdom->autofs_search_bases = sdom->search_bases; + } + + return EOK; +} + +void +sdap_domain_remove(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + struct sdap_domain *sdom; + + sdom = sdap_domain_get(opts, dom); + if (sdom == NULL) return; + + DLIST_REMOVE(*(sdom->head), sdom); +} diff --git a/src/providers/ldap/sdap_dyndns.c b/src/providers/ldap/sdap_dyndns.c new file mode 100644 index 0000000..3535fb4 --- /dev/null +++ b/src/providers/ldap/sdap_dyndns.c @@ -0,0 +1,713 @@ +/* + SSSD + + sdap_dyndns.c: LDAP specific dynamic DNS update + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 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 "util/util.h" +#include "resolv/async_resolv.h" +#include "providers/backend.h" +#include "providers/be_dyndns.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_dyndns.h" +#include "providers/ldap/sdap_id_op.h" +#include "providers/ldap/ldap_common.h" + +static struct tevent_req * +sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_ctx, + const char *iface); +static errno_t +sdap_dyndns_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addresses); + +struct sdap_dyndns_update_state { + struct tevent_context *ev; + struct be_resolv_ctx *be_res; + struct dp_option *opts; + + const char *hostname; + const char *realm; + const char *servername; + int ttl; + + struct sss_iface_addr *addresses; + struct sss_iface_addr *dns_addrlist; + uint8_t remove_af; + + bool update_per_family; + bool update_ptr; + bool check_diff; + enum be_nsupdate_auth auth_type; + enum be_nsupdate_auth auth_ptr_type; + bool fallback_mode; + char *update_msg; +}; + +static void sdap_dyndns_update_addrs_done(struct tevent_req *subreq); +static void sdap_dyndns_dns_addrs_done(struct tevent_req *subreq); +static errno_t sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, + bool *_do_update); +static errno_t sdap_dyndns_update_step(struct tevent_req *req); +static errno_t sdap_dyndns_update_ptr_step(struct tevent_req *req); +static void sdap_dyndns_update_done(struct tevent_req *subreq); +static void sdap_dyndns_update_ptr_done(struct tevent_req *subreq); + +static bool should_retry(int nsupdate_ret, int child_status) +{ + if ((WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) + || nsupdate_ret == ERR_DYNDNS_TIMEOUT) { + return true; + } + + return false; +} + +struct tevent_req * +sdap_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct dp_option *opts, + struct sdap_id_ctx *sdap_ctx, + enum be_nsupdate_auth auth_type, + enum be_nsupdate_auth auth_ptr_type, + const char *ifname, + const char *hostname, + const char *realm, + const int ttl, + bool check_diff) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_dyndns_update_state *state; + const char *conf_servername; + + req = tevent_req_create(mem_ctx, &state, struct sdap_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->check_diff = check_diff; + state->update_per_family = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PER_FAMILY); + state->update_ptr = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PTR); + state->hostname = hostname; + state->realm = realm; + state->servername = NULL; + state->fallback_mode = false; + state->ttl = ttl; + state->be_res = be_ctx->be_res; + state->ev = ev; + state->opts = opts; + state->auth_type = auth_type; + state->auth_ptr_type = auth_ptr_type; + + /* fallback servername is overridden by user option */ + conf_servername = dp_opt_get_string(opts, DP_OPT_DYNDNS_SERVER); + if (conf_servername != NULL) { + state->servername = conf_servername; + } + + if (ifname) { + /* Unless one family is restricted, just replace all + * address families during the update + */ + switch (state->be_res->family_order) { + case IPV4_ONLY: + state->remove_af |= DYNDNS_REMOVE_A; + break; + case IPV6_ONLY: + state->remove_af |= DYNDNS_REMOVE_AAAA; + break; + case IPV4_FIRST: + case IPV6_FIRST: + state->remove_af |= (DYNDNS_REMOVE_A | + DYNDNS_REMOVE_AAAA); + break; + } + } else { + /* If the interface isn't specified, we ONLY want to have the address + * that's connected to the LDAP server stored, so we need to check + * (and later remove) both address families. + */ + state->remove_af = (DYNDNS_REMOVE_A | DYNDNS_REMOVE_AAAA); + } + + subreq = sdap_dyndns_get_addrs_send(state, state->ev, sdap_ctx, ifname); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, sdap_dyndns_update_addrs_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +sdap_dyndns_update_addrs_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = sdap_dyndns_get_addrs_recv(subreq, state, &state->addresses); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + tevent_req_error(req, ret); + return; + } + + if (state->check_diff || state->update_ptr) { + /* Check if we need the update at all. In case we are updating the PTR + * records as well, we need to know the old addresses to be able to + * reliably delete the PTR records */ + subreq = nsupdate_get_addrs_send(state, state->ev, + state->be_res, state->hostname); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Can't initiate address check\n"); + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, sdap_dyndns_dns_addrs_done, req); + return; + } + + /* Perform update */ + ret = sdap_dyndns_update_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + /* Execution will resume in sdap_dyndns_update_done */ +} + +static void +sdap_dyndns_dns_addrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + errno_t ret; + bool do_update; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = nsupdate_get_addrs_recv(subreq, state, &state->dns_addrlist, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not receive list of current addresses [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->check_diff) { + ret = sdap_dyndns_addrs_diff(state, &do_update); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check the diff between DNS " + "and current addresses [%d]: %s\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (do_update == false) { + DEBUG(SSSDBG_TRACE_FUNC, + "No DNS update needed, addresses did not change\n"); + tevent_req_done(req); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Detected IP addresses change, will perform an update\n"); + } + + /* Either we needed the addresses for updating PTR records only or + * the addresses have changed (or both) */ + ret = sdap_dyndns_update_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not start the update [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + } + return; +} + +static errno_t +sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, bool *_do_update) +{ + errno_t ret; + int i; + char **str_dnslist = NULL, **str_local_list = NULL; + char **dns_only = NULL, **local_only = NULL; + bool do_update = false; + + ret = sss_iface_addr_list_as_str_list(state, + state->dns_addrlist, &str_dnslist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting DNS IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = sss_iface_addr_list_as_str_list(state, + state->addresses, &str_local_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting local IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* Compare the lists */ + ret = diff_string_lists(state, str_dnslist, str_local_list, + &dns_only, &local_only, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "diff_string_lists failed: [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (dns_only) { + for (i=0; dns_only[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Address in DNS only: %s\n", dns_only[i]); + do_update = true; + } + } + + if (local_only) { + for (i=0; local_only[i]; i++) { + DEBUG(SSSDBG_TRACE_LIBS, + "Address on localhost only: %s\n", local_only[i]); + do_update = true; + } + } + + *_do_update = do_update; + return EOK; +} + +static errno_t +sdap_dyndns_update_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_fwd_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &state->update_msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + return ret; + } + + /* Fork a child process to perform the DNS update */ + subreq = be_nsupdate_send(state, state->ev, state->auth_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + if (subreq == NULL) { + return EIO; + } + + tevent_req_set_callback(subreq, sdap_dyndns_update_done, req); + return EOK; +} + +static void +sdap_dyndns_update_done(struct tevent_req *subreq) +{ + errno_t ret; + int child_status; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = be_nsupdate_recv(subreq, &child_status); + talloc_zfree(subreq); + if (ret != EOK) { + /* If the update didn't succeed, we can retry using the server name */ + if (state->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, + "nsupdate failed, retrying.\n"); + ret = sdap_dyndns_update_step(req); + if (ret == EOK) { + return; + } + } + } + + if (state->update_ptr == false) { + DEBUG(SSSDBG_TRACE_FUNC, "No PTR update requested, done\n"); + tevent_req_done(req); + return; + } + + talloc_free(state->update_msg); + + ret = sdap_dyndns_update_ptr_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + /* Execution will resume in sdap_dyndns_update_ptr_done */ +} + +static errno_t +sdap_dyndns_update_ptr_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode == true) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_ptr_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &state->update_msg); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses for DNS update\n"); + return ret; + } + + /* Fork a child process to perform the DNS update */ + subreq = be_nsupdate_send(state, state->ev, state->auth_ptr_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + if (subreq == NULL) { + return EIO; + } + + tevent_req_set_callback(subreq, sdap_dyndns_update_ptr_done, req); + return EOK; +} + +static void +sdap_dyndns_update_ptr_done(struct tevent_req *subreq) +{ + errno_t ret; + int child_status; + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + ret = be_nsupdate_recv(subreq, &child_status); + talloc_zfree(subreq); + if (ret != EOK) { + /* If the update didn't succeed, we can retry using the server name */ + if (state->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, "nsupdate failed, retrying\n"); + ret = sdap_dyndns_update_ptr_step(req); + if (ret == EOK) { + return; + } + } + + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_dyndns_update_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* A request to get addresses to update with */ +struct sdap_dyndns_get_addrs_state { + struct sdap_id_op* sdap_op; + struct sss_iface_addr *addresses; +}; + +static void sdap_dyndns_get_addrs_done(struct tevent_req *subreq); +static errno_t sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state, + struct sdap_handle *sh); + +static errno_t get_ifaces_addrs(TALLOC_CTX *mem_ctx, + const char *iface, + struct sss_iface_addr **_result) +{ + struct sss_iface_addr *result_addrs = NULL; + struct sss_iface_addr *intf_addrs; + TALLOC_CTX *tmp_ctx; + char **list_of_intfs; + int num_of_intfs; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, iface, ',', true, true, &list_of_intfs, + &num_of_intfs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Parsing names of interfaces failed - %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; i < num_of_intfs; i++) { + ret = sss_iface_addr_list_get(tmp_ctx, list_of_intfs[i], &intf_addrs); + if (ret == EOK) { + if (result_addrs != NULL) { + /* If there is already an existing list, head of this existing + * list will be considered as parent talloc context for the + * new list. + */ + talloc_steal(result_addrs, intf_addrs); + } + sss_iface_addr_concatenate(&result_addrs, intf_addrs); + } else if (ret == ENOENT) { + /* non-critical failure */ + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot get interface %s or there are no addresses " + "bind to it.\n", list_of_intfs[i]); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get list of addresses from interface %s - %d:[%s]\n", + list_of_intfs[i], ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + *_result = talloc_steal(mem_ctx, result_addrs); + +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct tevent_req * +sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_ctx, + const char *iface) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_dyndns_get_addrs_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_dyndns_get_addrs_state); + if (req == NULL) { + return NULL; + } + + if (iface) { + ret = get_ifaces_addrs(state, iface, &state->addresses); + if (ret != EOK || state->addresses == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "get_ifaces_addrs() failed: %d:[%s]\n", + ret, sss_strerror(ret)); + } + /* We're done. Just fake an async request completion */ + goto done; + } + + /* Detect DYNDNS address from LDAP connection */ + state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn->conn_cache); + if (!state->sdap_op) { + ret = ENOMEM; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, sdap_dyndns_get_addrs_done, req); + + ret = EAGAIN; +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + /* EAGAIN - resolution in progress */ + return req; +} + +static void +sdap_dyndns_get_addrs_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + struct tevent_req *req; + struct sdap_dyndns_get_addrs_state *state; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No LDAP server is available, " + "dynamic DNS update is skipped in offline mode.\n"); + ret = ERR_DYNDNS_OFFLINE; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + ret = sdap_dyndns_add_ldap_conn(state, sdap_id_op_handle(state->sdap_op)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get addresses from LDAP connection\n"); + tevent_req_error(req, ret); + return; + } + + /* Got the address! Done! */ + tevent_req_done(req); +} + +static errno_t +sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state, + struct sdap_handle *sh) +{ + int ret; + int fd; + struct sockaddr_storage ss = {0}; + socklen_t ss_len = sizeof(ss); + + if (sh == NULL) { + return EINVAL; + } + + /* Get the file descriptor for the primary LDAP connection */ + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret != EOK) { + return ret; + } + + errno = 0; + ret = getsockname(fd, (struct sockaddr *) &ss, &ss_len); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get socket name\n"); + return ret; + } + + if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Connection to LDAP is neither IPv4 nor IPv6\n"); + return EIO; + } + + ret = sss_get_dualstack_addresses(state, (struct sockaddr *) &ss, + &state->addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_get_dualstack_addresses failed: %d:[%s]\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t +sdap_dyndns_get_addrs_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sss_iface_addr **_addresses) +{ + struct sdap_dyndns_get_addrs_state *state; + + state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_addresses = talloc_steal(mem_ctx, state->addresses); + return EOK; +} diff --git a/src/providers/ldap/sdap_dyndns.h b/src/providers/ldap/sdap_dyndns.h new file mode 100644 index 0000000..5fb3667 --- /dev/null +++ b/src/providers/ldap/sdap_dyndns.h @@ -0,0 +1,49 @@ +/* + SSSD + + sdap_dyndns.h: LDAP specific dynamic DNS update + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 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/>. +*/ + +#ifndef SDAP_DYNDNS_H_ +#define SDAP_DYNDNS_H_ + +#include "util/util.h" +#include "providers/backend.h" +#include "providers/be_dyndns.h" +#include "providers/ldap/ldap_common.h" + +struct tevent_req * +sdap_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct dp_option *opts, + struct sdap_id_ctx *sdap_ctx, + enum be_nsupdate_auth auth_type, + enum be_nsupdate_auth auth_ptr_type, + const char *ifname, + const char *hostname, + const char *realm, + const int ttl, + bool check_diff); + +errno_t sdap_dyndns_update_recv(struct tevent_req *req); + +#endif /* SDAP_DYNDNS_H_ */ diff --git a/src/providers/ldap/sdap_fd_events.c b/src/providers/ldap/sdap_fd_events.c new file mode 100644 index 0000000..42b2efe --- /dev/null +++ b/src/providers/ldap/sdap_fd_events.c @@ -0,0 +1,357 @@ +/* + SSSD + + Helper routines for file descriptor events + + Authors: + Sumit Bose <sbose@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 "util/util.h" +#include "util/sss_sockets.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async_private.h" + +struct sdap_fd_events { +#ifdef HAVE_LDAP_CONNCB + struct ldap_conncb *conncb; +#else + struct tevent_fd *fde; +#endif +}; + +int get_fd_from_ldap(LDAP *ldap, int *fd) +{ + int ret; + + ret = ldap_get_option(ldap, LDAP_OPT_DESC, fd); + if (ret != LDAP_OPT_SUCCESS || *fd < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get fd from ldap!!\n"); + *fd = -1; + return EIO; + } + + return EOK; +} + +int remove_ldap_connection_callbacks(struct sdap_handle *sh) +{ + /* sdap_fd_events might be NULL here if the back end was marked offline + * before a connection was established. + */ + if (sh->sdap_fd_events) { +#ifdef HAVE_LDAP_CONNCB + talloc_zfree(sh->sdap_fd_events->conncb); +#else + talloc_zfree(sh->sdap_fd_events->fde); +#endif + } + return EOK; +} + +#ifdef HAVE_LDAP_CONNCB + +static int remove_connection_callback(TALLOC_CTX *mem_ctx) +{ + int lret; + struct ldap_conncb *conncb = talloc_get_type(mem_ctx, struct ldap_conncb); + + struct ldap_cb_data *cb_data = talloc_get_type(conncb->lc_arg, + struct ldap_cb_data); + + lret = ldap_get_option(cb_data->sh->ldap, LDAP_OPT_CONNECT_CB, conncb); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to remove connection callback.\n"); + } else { + DEBUG(SSSDBG_TRACE_ALL, "Successfully removed connection callback.\n"); + } + return EOK; +} + +static int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, + LDAPURLDesc *srv, + struct sockaddr *addr, + struct ldap_conncb *ctx) +{ + int ret; + ber_socket_t ber_fd; + uint64_t old_chain_id; + struct timeval *tv = NULL; + struct fd_event_item *fd_event_item; + struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg, + struct ldap_cb_data); + + if (cb_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_ldap_connect_callback_add called without " + "callback data.\n"); + return EINVAL; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return EINVAL; + } + + /* (ld == NULL) means call flow is sdap_sys_connect_done() -> + * sdap_call_conn_cb() and this is "regular" socket that was already setup + * in sssd_async_socket_init_send(). + * Otherwise this is socket open by libldap during referral chasing and it + * requires setting up. + */ + if (ld != NULL) { + ret = ldap_get_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if ((ret == LDAP_OPT_SUCCESS) && (tv != NULL)) { + ret = set_fd_common_opts(ber_fd, tv->tv_sec); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "set_fd_common_opts() failed\n"); + } + free(tv); + tv = NULL; + } else if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldap_get_option(LDAP_OPT_NETWORK_TIMEOUT) failed\n"); + } + } + + char *uri = ldap_url_desc2str(srv); + DEBUG(SSSDBG_TRACE_ALL, "New connection to [%s] with fd [%d]\n", + uri, ber_fd); + free(uri); + + fd_event_item = talloc_zero(cb_data, struct fd_event_item); + if (fd_event_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + /* This is a global event which is shared between multiple requests. However + * it is usually created from an input request chain therefore we need to set + * the chain id to zero explicitly. */ + old_chain_id = sss_chain_id_set(0); + fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd, + TEVENT_FD_READ, sdap_ldap_result, + cb_data->sh); + sss_chain_id_set(old_chain_id); + if (fd_event_item->fde == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); + talloc_free(fd_event_item); + return ENOMEM; + } + fd_event_item->fd = ber_fd; + + DLIST_ADD(cb_data->fd_list, fd_event_item); + + return LDAP_SUCCESS; +} + +static void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb, + struct ldap_conncb *ctx) +{ + int ret; + ber_socket_t ber_fd; + struct fd_event_item *fd_event_item; + struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg, + struct ldap_cb_data); + + if (sb == NULL || cb_data == NULL) { + return; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return; + } + DEBUG(SSSDBG_TRACE_ALL, "Closing LDAP connection with fd [%d].\n", ber_fd); + + DLIST_FOR_EACH(fd_event_item, cb_data->fd_list) { + if (fd_event_item->fd == ber_fd) { + break; + } + } + if (fd_event_item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No event for fd [%d] found.\n", ber_fd); + return; + } + + DLIST_REMOVE(cb_data->fd_list, fd_event_item); + talloc_zfree(fd_event_item); + + return; +} + +#else + +static int sdap_install_ldap_callbacks(struct sdap_handle *sh, + struct tevent_context *ev) +{ + uint64_t old_chain_id; + int fd; + int ret; + + if (sh->sdap_fd_events) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_install_ldap_callbacks is called with already " + "initialized sdap_fd_events.\n"); + return EINVAL; + } + + sh->sdap_fd_events = talloc_zero(sh, struct sdap_fd_events); + if (!sh->sdap_fd_events) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret) return ret; + + /* This is a global event which is shared between multiple requests. However + * it is usually created from an input request chain therefore we need to set + * the chain id to zero explicitly. */ + old_chain_id = sss_chain_id_set(0); + sh->sdap_fd_events->fde = tevent_add_fd(ev, sh->sdap_fd_events, fd, + TEVENT_FD_READ, sdap_ldap_result, + sh); + sss_chain_id_set(old_chain_id); + if (!sh->sdap_fd_events->fde) { + talloc_zfree(sh->sdap_fd_events); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Trace: sh[%p], connected[%d], ops[%p], fde[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->sdap_fd_events->fde, + sh->ldap); + + return EOK; +} + +#endif + + +errno_t setup_ldap_connection_callbacks(struct sdap_handle *sh, + struct tevent_context *ev) +{ +#ifdef HAVE_LDAP_CONNCB + int ret; + struct ldap_cb_data *cb_data; + + sh->sdap_fd_events = talloc_zero(sh, struct sdap_fd_events); + if (sh->sdap_fd_events == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + sh->sdap_fd_events->conncb = talloc_zero(sh->sdap_fd_events, + struct ldap_conncb); + if (sh->sdap_fd_events->conncb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + + cb_data = talloc_zero(sh->sdap_fd_events->conncb, struct ldap_cb_data); + if (cb_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto fail; + } + cb_data->sh = sh; + cb_data->ev = ev; + + sh->sdap_fd_events->conncb->lc_add = sdap_ldap_connect_callback_add; + sh->sdap_fd_events->conncb->lc_del = sdap_ldap_connect_callback_del; + sh->sdap_fd_events->conncb->lc_arg = cb_data; + + ret = ldap_set_option(sh->ldap, LDAP_OPT_CONNECT_CB, + sh->sdap_fd_events->conncb); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set connection callback\n"); + ret = EFAULT; + goto fail; + } + + talloc_set_destructor((TALLOC_CTX *) sh->sdap_fd_events->conncb, + remove_connection_callback); + + return EOK; + +fail: + talloc_zfree(sh->sdap_fd_events); + return ret; +#else + DEBUG(SSSDBG_TRACE_ALL, "LDAP connection callbacks are not supported.\n"); + return EOK; +#endif +} + +errno_t sdap_set_connected(struct sdap_handle *sh, struct tevent_context *ev) +{ + int ret = EOK; + + sh->connected = true; + +#ifndef HAVE_LDAP_CONNCB + ret = sdap_install_ldap_callbacks(sh, ev); +#endif + + return ret; +} + +errno_t sdap_call_conn_cb(const char *uri,int fd, struct sdap_handle *sh) +{ +#ifdef HAVE_LDAP_CONNCB + int ret; + Sockbuf *sb; + LDAPURLDesc *lud; + + sb = ber_sockbuf_alloc(); + if (sb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_alloc failed.\n"); + return ENOMEM; + } + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_SET_FD, &fd); + if (ret != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "ber_sockbuf_ctrl failed.\n"); + return EFAULT; + } + + ret = ldap_url_parse(uri, &lud); + if (ret != 0) { + ber_sockbuf_free(sb); + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_url_parse failed to validate [%s] on fd [%d].\n", + uri, fd); + return EFAULT; + } + + ret = sdap_ldap_connect_callback_add(NULL, sb, lud, NULL, + sh->sdap_fd_events->conncb); + + ldap_free_urldesc(lud); + ber_sockbuf_free(sb); + return ret; +#else + DEBUG(SSSDBG_TRACE_ALL, "LDAP connection callbacks are not supported.\n"); + return EOK; +#endif +} diff --git a/src/providers/ldap/sdap_hostid.c b/src/providers/ldap/sdap_hostid.c new file mode 100644 index 0000000..ae8caad --- /dev/null +++ b/src/providers/ldap/sdap_hostid.c @@ -0,0 +1,324 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "db/sysdb_ssh.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_hostid.h" + +struct hosts_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_id_op *op; + struct sss_domain_info *domain; + const char *name; + const char *alias; + + size_t count; + struct sysdb_attrs **hosts; + int dp_error; +}; + +static errno_t +hosts_get_retry(struct tevent_req *req); +static void +hosts_get_connect_done(struct tevent_req *subreq); +static void +hosts_get_done(struct tevent_req *subreq); + +struct tevent_req * +hosts_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + const char *name, + const char *alias) +{ + struct tevent_req *req; + struct hosts_get_state *state; + errno_t ret; + + req = tevent_req_create(memctx, &state, struct hosts_get_state); + if (!req) return NULL; + + state->ev = ev; + state->id_ctx = id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, id_ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = id_ctx->be->domain; + state->name = name; + state->alias = alias; + + ret = hosts_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +hosts_get_retry(struct tevent_req *req) +{ + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + struct tevent_req *subreq; + errno_t ret = EOK; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + return ret; + } + + tevent_req_set_callback(subreq, hosts_get_connect_done, req); + return EOK; +} + +static void +hosts_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_host_info_send(state, state->ev, + sdap_id_op_handle(state->op), + state->id_ctx->opts, state->name, + state->id_ctx->opts->host_map, + state->id_ctx->opts->sdom->host_search_bases); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, hosts_get_done, req); +} + +static void +hosts_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + struct sysdb_attrs *attrs; + time_t now = time(NULL); + + ret = sdap_host_info_recv(subreq, state, + &state->count, &state->hosts); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = hosts_get_retry(req); + if (ret != EOK) { + goto done; + } + return; + } + + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (state->count == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "No host with name [%s] found.\n", state->name); + + ret = sysdb_delete_ssh_host(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = EINVAL; + goto done; + } + + if (state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one host with name [%s].\n", state->name); + ret = EINVAL; + goto done; + } + + attrs = sysdb_new_attrs(state); + if (!attrs) { + ret = ENOMEM; + goto done; + } + + /* we are interested only in the host keys */ + ret = sysdb_attrs_copy_values(state->hosts[0], attrs, SYSDB_SSH_PUBKEY); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_ssh_host(state->domain, state->name, state->alias, + state->domain->ssh_host_timeout, now, attrs); + if (ret != EOK) { + goto done; + } + + dp_error = DP_ERR_OK; + +done: + state->dp_error = dp_error; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +hosts_get_recv(struct tevent_req *req, + int *dp_error_out) +{ + struct hosts_get_state *state = tevent_req_data(req, + struct hosts_get_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_hostid_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_hostid_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_hostid_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_hostid_data *data, + struct dp_req_params *params) +{ + struct sdap_hostid_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_hostid_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + subreq = hosts_get_send(state, params->ev, id_ctx, + data->name, data->alias); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request\n"); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_hostid_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_hostid_handler_done(struct tevent_req *subreq) +{ + struct sdap_hostid_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_hostid_handler_state); + + ret = hosts_get_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_hostid_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_hostid_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_hostid_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +errno_t sdap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + (void)be_ctx; + + dp_set_method(dp_methods, DPM_HOSTID_HANDLER, + sdap_hostid_handler_send, sdap_hostid_handler_recv, id_ctx, + struct sdap_id_ctx, struct dp_hostid_data, struct dp_reply_std); + + return EOK; +} diff --git a/src/providers/ldap/sdap_hostid.h b/src/providers/ldap/sdap_hostid.h new file mode 100644 index 0000000..6234f9f --- /dev/null +++ b/src/providers/ldap/sdap_hostid.h @@ -0,0 +1,40 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef _SDAP_HOSTID_H_ +#define _SDAP_HOSTID_H_ + +errno_t sdap_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_method *dp_methods); + +struct tevent_req * +sdap_hostid_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct dp_hostid_data *data, + struct dp_req_params *params); + +errno_t +sdap_hostid_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +#endif /* _SDAP_HOSTID_H_ */ diff --git a/src/providers/ldap/sdap_id_op.c b/src/providers/ldap/sdap_id_op.c new file mode 100644 index 0000000..857a635 --- /dev/null +++ b/src/providers/ldap/sdap_id_op.c @@ -0,0 +1,1054 @@ +/* + SSSD + + LDAP ID backend operation retry logic and connection cache + + Authors: + Eugene Indenbom <eindenbom@gmail.com> + + Copyright (C) 2008-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 "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_id_op.h" +#include "util/sss_chain_id.h" + +/* LDAP async connection cache */ +struct sdap_id_conn_cache { + struct sdap_id_conn_ctx *id_conn; + + /* list of all open connections */ + struct sdap_id_conn_data *connections; + /* cached (current) connection */ + struct sdap_id_conn_data *cached_connection; +}; + +/* LDAP async operation tracker: + * - keeps track of connection usage + * - keeps track of operation retries */ +struct sdap_id_op { + /* ID backend context */ + struct sdap_id_conn_cache *conn_cache; + /* double linked list pointers */ + struct sdap_id_op *prev, *next; + /* current connection */ + struct sdap_id_conn_data *conn_data; + /* number of reconnects for this operation */ + int reconnect_retry_count; + /* connection request + * It is required as we need to know which requests to notify + * when shared connection request to sdap_handle completes. + * This member is cleared when sdap_id_op_connect_state + * associated with request is destroyed */ + struct tevent_req *connect_req; + + /* chain id of the request that created this op */ + uint64_t chain_id; +}; + +/* LDAP connection cache connection attempt/established connection data */ +struct sdap_id_conn_data { + /* LDAP connection cache */ + struct sdap_id_conn_cache *conn_cache; + /* double linked list pointers */ + struct sdap_id_conn_data *prev, *next; + /* sdap handle */ + struct sdap_handle *sh; + /* connection request */ + struct tevent_req *connect_req; + /* timer for connection expiration */ + struct tevent_timer *expire_timer; + /* timer for idle connection expiration */ + struct tevent_timer *idle_timer; + /* number of running connection notifies */ + int notify_lock; + /* list of operations using connect */ + struct sdap_id_op *ops; + /* A flag which is signalizing that this + * connection will be disconnected and should + * not be used any more */ + bool disconnecting; +}; + +static void sdap_id_conn_cache_be_offline_cb(void *pvt); +static void sdap_id_conn_cache_fo_reconnect_cb(void *pvt); + +static void sdap_id_release_conn_data(struct sdap_id_conn_data *conn_data); +static int sdap_id_conn_data_destroy(struct sdap_id_conn_data *conn_data); +static bool sdap_is_connection_expired(struct sdap_id_conn_data *conn_data, int timeout); +static bool sdap_can_reuse_connection(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_expire_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static int sdap_id_conn_data_set_expire_timer(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static int sdap_id_conn_data_start_idle_timer(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_not_idle(struct sdap_id_conn_data *conn_data); +static void sdap_id_conn_data_idle(struct sdap_id_conn_data *conn_data); + +static void sdap_id_op_hook_conn_data(struct sdap_id_op *op, struct sdap_id_conn_data *conn_data); +static int sdap_id_op_destroy(void *pvt); +static bool sdap_id_op_can_reconnect(struct sdap_id_op *op); + +static void sdap_id_op_connect_req_complete(struct sdap_id_op *op, int dp_error, int ret); +static int sdap_id_op_connect_state_destroy(void *pvt); +static int sdap_id_op_connect_step(struct tevent_req *req); +static void sdap_id_op_connect_done(struct tevent_req *subreq); + +/* Create a connection cache */ +int sdap_id_conn_cache_create(TALLOC_CTX *memctx, + struct sdap_id_conn_ctx *id_conn, + struct sdap_id_conn_cache** conn_cache_out) +{ + int ret; + struct sdap_id_conn_cache *conn_cache = talloc_zero(memctx, struct sdap_id_conn_cache); + if (!conn_cache) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_zero(struct sdap_id_conn_cache) failed.\n"); + ret = ENOMEM; + goto fail; + } + + conn_cache->id_conn = id_conn; + + ret = be_add_offline_cb(conn_cache, id_conn->id_ctx->be, + sdap_id_conn_cache_be_offline_cb, conn_cache, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_offline_cb failed.\n"); + goto fail; + } + + ret = be_add_reconnect_cb(conn_cache, id_conn->id_ctx->be, + sdap_id_conn_cache_fo_reconnect_cb, conn_cache, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "be_add_reconnect_cb failed.\n"); + goto fail; + } + + *conn_cache_out = conn_cache; + return EOK; + +fail: + talloc_zfree(conn_cache); + return ret; +} + +/* Callback on BE going offline */ +static void sdap_id_conn_cache_be_offline_cb(void *pvt) +{ + struct sdap_id_conn_cache *conn_cache = talloc_get_type(pvt, struct sdap_id_conn_cache); + struct sdap_id_conn_data *cached_connection = conn_cache->cached_connection; + + /* Release any cached connection on going offline */ + if (cached_connection != NULL) { + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(cached_connection); + } +} + +/* Callback for attempt to reconnect to primary server */ +static void sdap_id_conn_cache_fo_reconnect_cb(void *pvt) +{ + struct sdap_id_conn_cache *conn_cache = talloc_get_type(pvt, struct sdap_id_conn_cache); + struct sdap_id_conn_data *cached_connection = conn_cache->cached_connection; + + /* Release any cached connection on going offline */ + if (cached_connection != NULL) { + cached_connection->disconnecting = true; + } +} + +/* Release sdap_id_conn_data and destroy it if no longer needed */ +static void sdap_id_release_conn_data(struct sdap_id_conn_data *conn_data) +{ + ber_socket_t fd = -1; + Sockbuf *sb; + int ret; + struct sdap_id_conn_cache *conn_cache; + if (!conn_data || conn_data->ops || conn_data->notify_lock) { + /* connection is in use */ + return; + } + + conn_cache = conn_data->conn_cache; + if (conn_data == conn_cache->cached_connection) { + return; + } + + if (conn_data->sh && conn_data->sh->ldap) { + ret = ldap_get_option(conn_data->sh->ldap, LDAP_OPT_SOCKBUF, &sb); + if (ret == LDAP_OPT_SUCCESS) { + if (ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &fd) != 1) { + fd = -1; + } + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Releasing unused connection with fd [%d]\n", fd); + + DLIST_REMOVE(conn_cache->connections, conn_data); + talloc_zfree(conn_data); +} + +/* Destructor for struct sdap_id_conn_data */ +static int sdap_id_conn_data_destroy(struct sdap_id_conn_data *conn_data) +{ + struct sdap_id_op *op; + + /* we clean out list of ops to make sure that order of destruction does not matter */ + while ((op = conn_data->ops) != NULL) { + op->conn_data = NULL; + DLIST_REMOVE(conn_data->ops, op); + } + + return 0; +} + +/* Check whether connection will expire after timeout seconds */ +static bool sdap_is_connection_expired(struct sdap_id_conn_data *conn_data, int timeout) +{ + time_t expire_time; + if (!conn_data || !conn_data->sh || !conn_data->sh->connected) { + return true; + } + + expire_time = conn_data->sh->expire_time; + if ((expire_time != 0) && (expire_time < time( NULL ) + timeout) ) { + return true; + } + + return false; +} + +/* Check whether connection can be reused for next LDAP ID operation */ +static bool sdap_can_reuse_connection(struct sdap_id_conn_data *conn_data) +{ + int timeout; + + if (!conn_data || !conn_data->sh || + !conn_data->sh->connected || conn_data->disconnecting) { + return false; + } + + timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + return !sdap_is_connection_expired(conn_data, timeout); +} + +/* Set expiration timer for connection if needed */ +static int sdap_id_conn_data_set_expire_timer(struct sdap_id_conn_data *conn_data) +{ + int timeout; + struct timeval tv; + + talloc_zfree(conn_data->expire_timer); + + memset(&tv, 0, sizeof(tv)); + + tv.tv_sec = conn_data->sh->expire_time; + if (tv.tv_sec <= 0) { + return EOK; + } + + timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + if (timeout > 0) { + tv.tv_sec -= timeout; + } + + if (tv.tv_sec <= time(NULL)) { + DEBUG(SSSDBG_TRACE_ALL, + "Not starting expire timer because connection is already expired\n"); + return EOK; + } + + conn_data->expire_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_expire_handler, + conn_data); + if (!conn_data->expire_timer) { + return ENOMEM; + } + + return EOK; +} + +/* Handler for connection expiration timer */ +static void sdap_id_conn_data_expire_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sdap_id_conn_data *conn_data = talloc_get_type(pvt, + struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + + if (conn_cache->cached_connection == conn_data) { + DEBUG(SSSDBG_TRACE_ALL, + "Connection is about to expire, releasing it\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + } +} + +/* We could simply cancel the idle timer at the beginning of every operation + * then reschedule it at the end of every operation. However, to reduce the + * overhead associated with canceling and rescheduling the timer, we instead + * update conn_data->sh->idle_time at the beginning and end of each operation, + * then have the timer handler check idle_time and reschedule the timer as + * needed. + * + * Note that sdap_id_conn_data_not_idle() and/or sdap_id_conn_data_idle() may be + * called before sdap_id_conn_data_start_idle_timer() is called for a particular + * connection. + */ + +/* Start idle timer for connection if needed */ +static int sdap_id_conn_data_start_idle_timer(struct sdap_id_conn_data *conn_data) +{ + time_t now; + int idle_timeout; + struct timeval tv; + + now = time(NULL); + conn_data->sh->idle_time = now; + + talloc_zfree(conn_data->idle_timer); + + idle_timeout = dp_opt_get_int(conn_data->conn_cache->id_conn->id_ctx->opts->basic, + SDAP_IDLE_TIMEOUT); + conn_data->sh->idle_timeout = idle_timeout; + DEBUG(SSSDBG_CONF_SETTINGS, "idle timeout is %d\n", idle_timeout); + if (idle_timeout <= 0) { + return EOK; + } + + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = now + idle_timeout; + DEBUG(SSSDBG_TRACE_ALL, + "Scheduling connection idle timer to run at %"SPRItime"\n", tv.tv_sec); + + conn_data->idle_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_idle_handler, + conn_data); + if (!conn_data->idle_timer) { + return ENOMEM; + } + + return EOK; +} + +/* Handler for idle connection expiration timer */ +static void sdap_id_conn_data_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sdap_id_conn_data *conn_data = talloc_get_type(pvt, + struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + + time_t now; + time_t idle_time; + int idle_timeout; + struct timeval tv; + + if (conn_cache->cached_connection != conn_data) { + DEBUG(SSSDBG_TRACE_ALL, "Abandoning idle timer for released connection\n"); + return; + } + + now = time(NULL); + idle_time = conn_data->sh->idle_time; + idle_timeout = conn_data->sh->idle_timeout; + + if (idle_time != 0 && idle_time + idle_timeout <= now) { + DEBUG(SSSDBG_TRACE_ALL, + "Connection has reached idle timeout, releasing it\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + return; + } + + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = (idle_time == 0 ? now : idle_time) + idle_timeout; + DEBUG(SSSDBG_TRACE_ALL, + "Rescheduling connection idle timer to run at %"SPRItime"\n", tv.tv_sec); + + conn_data->idle_timer = + tevent_add_timer(conn_data->conn_cache->id_conn->id_ctx->be->ev, + conn_data, tv, + sdap_id_conn_data_idle_handler, + conn_data); + if (!conn_data->idle_timer) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_idle_handler() failed to reschedule connection idle timer"); + } +} + +/* Mark connection as not idle */ +static void sdap_id_conn_data_not_idle(struct sdap_id_conn_data *conn_data) +{ + if (conn_data && conn_data->sh) { + DEBUG(SSSDBG_TRACE_ALL, "Marking connection as not idle\n"); + conn_data->sh->idle_time = 0; + } +} + +/* Mark connection as idle */ +static void sdap_id_conn_data_idle(struct sdap_id_conn_data *conn_data) +{ + if (conn_data && conn_data->sh) { + DEBUG(SSSDBG_TRACE_ALL, "Marking connection as idle\n"); + conn_data->sh->idle_time = time(NULL); + } +} + +/* Create an operation object */ +struct sdap_id_op *sdap_id_op_create(TALLOC_CTX *memctx, struct sdap_id_conn_cache *conn_cache) +{ + struct sdap_id_op *op = talloc_zero(memctx, struct sdap_id_op); + if (!op) { + return NULL; + } + + op->conn_cache = conn_cache; + + /* Remember the current chain id so we can use it when connection is + * established. This is required since the connection might be done + * by other request that was called before. */ + op->chain_id = sss_chain_id_get(); + + talloc_set_destructor((void*)op, sdap_id_op_destroy); + return op; +} + +/* Attach/detach connection to sdap_id_op */ +static void sdap_id_op_hook_conn_data(struct sdap_id_op *op, struct sdap_id_conn_data *conn_data) +{ + struct sdap_id_conn_data *current; + + if (!op) { + DEBUG(SSSDBG_FATAL_FAILURE, "NULL op passed!!!\n"); + return; + } + + current = op->conn_data; + if (conn_data == current) { + return; + } + + if (current) { + DLIST_REMOVE(current->ops, op); + } + + op->conn_data = conn_data; + + if (conn_data) { + sdap_id_conn_data_not_idle(conn_data); + DLIST_ADD_END(conn_data->ops, op, struct sdap_id_op*); + } + + if (current && !current->ops) { + if (current == current->conn_cache->cached_connection) { + sdap_id_conn_data_idle(current); + } else { + sdap_id_release_conn_data(current); + } + } +} + +/* Destructor for sdap_id_op */ +static int sdap_id_op_destroy(void *pvt) +{ + struct sdap_id_op *op = talloc_get_type(pvt, struct sdap_id_op); + + if (op->conn_data) { + DEBUG(SSSDBG_TRACE_ALL, "releasing operation connection\n"); + sdap_id_op_hook_conn_data(op, NULL); + } + + return 0; +} + +/* Check whether retry with reconnect can be performed for the operation */ +static bool sdap_id_op_can_reconnect(struct sdap_id_op *op) +{ + /* we allow 2 retries for failover server configured: + * - one for connection broken during request execution + * - one for the following (probably failed) reconnect attempt */ + int max_retries; + int count; + + count = be_fo_get_server_count(op->conn_cache->id_conn->id_ctx->be, + op->conn_cache->id_conn->service->name); + max_retries = 2 * count -1; + if (max_retries < 1) { + max_retries = 1; + } + + return op->reconnect_retry_count < max_retries; +} + +/* state of connect request */ +struct sdap_id_op_connect_state { + struct sdap_id_conn_ctx *id_conn; + struct tevent_context *ev; + struct sdap_id_op *op; + int dp_error; + int result; +}; + +/* Destructor for operation connection request */ +static int sdap_id_op_connect_state_destroy(void *pvt) +{ + struct sdap_id_op_connect_state *state = talloc_get_type(pvt, + struct sdap_id_op_connect_state); + if (state->op != NULL) { + /* clear destroyed connection request */ + state->op->connect_req = NULL; + } + + return 0; +} + +/* Begin to connect to LDAP server */ +struct tevent_req *sdap_id_op_connect_send(struct sdap_id_op *op, + TALLOC_CTX *memctx, + int *ret_out) +{ + struct tevent_req *req = NULL; + struct sdap_id_op_connect_state *state; + int ret = EOK; + + if (!memctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: no memory context passed.\n"); + ret = EINVAL; + goto done; + } + + if (op->connect_req) { + /* Connection already in progress, invalid operation */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: connection request is already running or completed and leaked.\n"); + ret = EINVAL; + goto done; + } + + req = tevent_req_create(memctx, &state, struct sdap_id_op_connect_state); + if (!req) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((void*)state, sdap_id_op_connect_state_destroy); + + state->id_conn = op->conn_cache->id_conn; + state->ev = state->id_conn->id_ctx->be->ev; + state->op = op; + op->connect_req = req; + + if (op->conn_data) { + /* If the operation is already connected, + * reuse existing connection regardless of its status */ + DEBUG(SSSDBG_TRACE_ALL, "reusing operation connection\n"); + ret = EOK; + goto done; + } + + ret = sdap_id_op_connect_step(req); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + talloc_zfree(req); + } else if (op->conn_data && !op->conn_data->connect_req) { + /* Connection is already established */ + tevent_req_done(req); + tevent_req_post(req, state->ev); + } + + if (ret_out) { + *ret_out = ret; + } + + return req; +} + +/* Begin a connection retry to LDAP server */ +static int sdap_id_op_connect_step(struct tevent_req *req) +{ + struct sdap_id_op_connect_state *state = + tevent_req_data(req, struct sdap_id_op_connect_state); + struct sdap_id_op *op = state->op; + struct sdap_id_conn_cache *conn_cache = op->conn_cache; + + int ret = EOK; + struct sdap_id_conn_data *conn_data; + struct tevent_req *subreq = NULL; + + /* Try to reuse context cached connection */ + conn_data = conn_cache->cached_connection; + if (conn_data) { + if (conn_data->connect_req) { + DEBUG(SSSDBG_TRACE_ALL, "waiting for connection to complete\n"); + sdap_id_op_hook_conn_data(op, conn_data); + goto done; + } + + if (sdap_can_reuse_connection(conn_data)) { + DEBUG(SSSDBG_TRACE_ALL, "reusing cached connection\n"); + sdap_id_op_hook_conn_data(op, conn_data); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "releasing expired cached connection\n"); + conn_cache->cached_connection = NULL; + sdap_id_release_conn_data(conn_data); + } + + DEBUG(SSSDBG_TRACE_ALL, "beginning to connect\n"); + + conn_data = talloc_zero(conn_cache, struct sdap_id_conn_data); + if (!conn_data) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor(conn_data, sdap_id_conn_data_destroy); + + conn_data->conn_cache = conn_cache; + subreq = sdap_cli_connect_send(conn_data, state->ev, + state->id_conn->id_ctx->opts, + state->id_conn->id_ctx->be, + state->id_conn->service, false, + CON_TLS_DFL, false); + + if (!subreq) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_id_op_connect_done, conn_data); + conn_data->connect_req = subreq; + + DLIST_ADD(conn_cache->connections, conn_data); + conn_cache->cached_connection = conn_data; + + sdap_id_op_hook_conn_data(op, conn_data); + +done: + if (ret != EOK && conn_data) { + sdap_id_release_conn_data(conn_data); + } + + if (ret != EOK) { + talloc_zfree(subreq); + } + + return ret; +} + +static void sdap_id_op_connect_reinit_done(struct tevent_req *req); + +/* Subrequest callback for connection completion */ +static void sdap_id_op_connect_done(struct tevent_req *subreq) +{ + struct sdap_id_conn_data *conn_data = + tevent_req_callback_data(subreq, struct sdap_id_conn_data); + struct sdap_id_conn_cache *conn_cache = conn_data->conn_cache; + struct sdap_server_opts *srv_opts = NULL; + struct sdap_server_opts *current_srv_opts = NULL; + bool can_retry = false; + bool is_offline = false; + struct tevent_req *reinit_req = NULL; + bool reinit = false; + int ret; + int ret_nonfatal; + + ret = sdap_cli_connect_recv(subreq, conn_data, &can_retry, + &conn_data->sh, &srv_opts); + conn_data->connect_req = NULL; + talloc_zfree(subreq); + + conn_data->notify_lock++; + + if (ret == ENOTSUP) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Authentication mechanism not Supported by server\n"); + } + + if (ret == EOK && (!conn_data->sh || !conn_data->sh->connected)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "sdap_cli_connect_recv returned bogus connection\n"); + ret = EFAULT; + } + + if (ret != EOK && !can_retry) { + if (conn_cache->id_conn->ignore_mark_offline) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to connect to server, but ignore mark offline " + "is enabled.\n"); + } else { + /* be is going offline as there is no more servers to try */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect, going offline (%d [%s])\n", + ret, strerror(ret)); + is_offline = true; + be_mark_offline(conn_cache->id_conn->id_ctx->be); + } + } + + if (ret == EOK) { + current_srv_opts = conn_cache->id_conn->id_ctx->srv_opts; + if (current_srv_opts) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Old USN: %lu, New USN: %lu\n", current_srv_opts->last_usn, srv_opts->last_usn); + + if (strcmp(srv_opts->server_id, current_srv_opts->server_id) == 0 + && srv_opts->supports_usn + && current_srv_opts->last_usn > srv_opts->last_usn) { + DEBUG(SSSDBG_FUNC_DATA, "Server was probably re-initialized\n"); + + current_srv_opts->max_user_value = 0; + current_srv_opts->max_group_value = 0; + current_srv_opts->max_service_value = 0; + current_srv_opts->max_sudo_value = 0; + current_srv_opts->max_iphost_value = 0; + current_srv_opts->max_ipnetwork_value = 0; + current_srv_opts->last_usn = srv_opts->last_usn; + + reinit = true; + } + } + ret_nonfatal = sdap_id_conn_data_set_expire_timer(conn_data); + if (ret_nonfatal != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_set_expire_timer() failed [%d]: %s", + ret_nonfatal, sss_strerror(ret_nonfatal)); + } + ret_nonfatal = sdap_id_conn_data_start_idle_timer(conn_data); + if (ret_nonfatal != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_id_conn_data_start_idle_timer() failed [%d]: %s", + ret_nonfatal, sss_strerror(ret_nonfatal)); + } + sdap_steal_server_opts(conn_cache->id_conn->id_ctx, &srv_opts); + } + + if (can_retry) { + switch (ret) { + case EOK: + case ENOTSUP: + case EACCES: + case EIO: + case EFAULT: + case ETIMEDOUT: + case ERR_AUTH_FAILED: + break; + + default: + /* do not attempt to retry on errors like ENOMEM */ + DEBUG(SSSDBG_TRACE_FUNC, + "Marking the backend \"%s\" offline [%d]: %s\n", + conn_cache->id_conn->id_ctx->be->domain->name, + ret, sss_strerror(ret)); + can_retry = false; + is_offline = true; + be_mark_offline(conn_cache->id_conn->id_ctx->be); + break; + } + } + + int notify_count = 0; + + /* Notify about connection */ + for(;;) { + struct sdap_id_op *op; + + if (ret == EOK && !conn_data->sh->connected) { + DEBUG(SSSDBG_TRACE_ALL, + "connection was broken after %d notifies\n", notify_count); + } + + DLIST_FOR_EACH(op, conn_data->ops) { + if (op->connect_req) { + break; + } + } + + if (!op) { + break; + } + + /* another operation to notify */ + notify_count++; + + if (ret != EOK || !conn_data->sh->connected) { + /* failed to connect or connection got broken during notify */ + bool retry = false; + + /* drop connection from cache now */ + if (conn_cache->cached_connection == conn_data) { + conn_cache->cached_connection = NULL; + } + + if (can_retry) { + /* determining whether retry is possible */ + if (be_is_offline(conn_cache->id_conn->id_ctx->be)) { + /* be is offline, no retry possible */ + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "skipping automatic retry on op #%d as be is offline\n", notify_count); + ret = EIO; + } + + can_retry = false; + is_offline = true; + } else { + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "attempting automatic retry on op #%d\n", notify_count); + retry = true; + } else if (sdap_id_op_can_reconnect(op)) { + DEBUG(SSSDBG_TRACE_ALL, + "attempting failover retry on op #%d\n", notify_count); + op->reconnect_retry_count++; + retry = true; + } + } + } + + if (retry && op->connect_req) { + int retry_ret = sdap_id_op_connect_step(op->connect_req); + if (retry_ret != EOK) { + can_retry = false; + sdap_id_op_connect_req_complete(op, DP_ERR_FATAL, retry_ret); + } + + continue; + } + } + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "notify connected to op #%d\n", notify_count); + sdap_id_op_connect_req_complete(op, DP_ERR_OK, ret); + } else if (is_offline) { + DEBUG(SSSDBG_TRACE_ALL, "notify offline to op #%d\n", notify_count); + sdap_id_op_connect_req_complete(op, DP_ERR_OFFLINE, EAGAIN); + } else { + DEBUG(SSSDBG_TRACE_ALL, + "notify error to op #%d: %d [%s]\n", notify_count, ret, strerror(ret)); + sdap_id_op_connect_req_complete(op, DP_ERR_FATAL, ret); + } + } + + /* all operations notified */ + if (conn_data->notify_lock > 0) { + conn_data->notify_lock--; + } + + if ((ret == EOK) + && conn_data->sh->connected + && !be_is_offline(conn_cache->id_conn->id_ctx->be)) { + DEBUG(SSSDBG_TRACE_ALL, + "caching successful connection after %d notifies\n", notify_count); + conn_cache->cached_connection = conn_data; + + /* Run any post-connection routines */ + be_run_unconditional_online_cb(conn_cache->id_conn->id_ctx->be); + be_run_online_cb(conn_cache->id_conn->id_ctx->be); + + } else { + if (conn_cache->cached_connection == conn_data) { + conn_cache->cached_connection = NULL; + } + + sdap_id_release_conn_data(conn_data); + } + + if (reinit) { + DEBUG(SSSDBG_TRACE_FUNC, "Server reinitialization detected. " + "Cleaning cache.\n"); + reinit_req = sdap_reinit_cleanup_send(conn_cache->id_conn->id_ctx->be, + conn_cache->id_conn->id_ctx->be, + conn_cache->id_conn->id_ctx); + if (reinit_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up.\n"); + return; + } + + tevent_req_set_callback(reinit_req, sdap_id_op_connect_reinit_done, + NULL); + } +} + +static void sdap_id_op_connect_reinit_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sdap_reinit_cleanup_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up [%d]: %s\n", ret, strerror(ret)); + /* not fatal */ + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Reinitialization clean up completed\n"); +} + +/* Mark operation connection request as complete */ +static void sdap_id_op_connect_req_complete(struct sdap_id_op *op, int dp_error, int ret) +{ + struct tevent_req *req = op->connect_req; + struct sdap_id_op_connect_state *state; + uint64_t old_chain_id; + + if (!req) { + return; + } + + op->connect_req = NULL; + + state = tevent_req_data(req, struct sdap_id_op_connect_state); + state->dp_error = dp_error; + state->result = ret; + + /* Set the chain id to the one associated with this request. */ + old_chain_id = sss_chain_id_set(op->chain_id); + if (ret == EOK) { + tevent_req_done(req); + } else { + sdap_id_op_hook_conn_data(op, NULL); + tevent_req_error(req, ret); + } + sss_chain_id_set(old_chain_id); +} + +/* Get the result of an asynchronous connect operation on sdap_id_op + * + * In dp_error data provider error code is returned: + * DP_ERR_OK - connection established + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed + */ +int sdap_id_op_connect_recv(struct tevent_req *req, int *dp_error) +{ + struct sdap_id_op_connect_state *state = tevent_req_data(req, + struct sdap_id_op_connect_state); + + *dp_error = state->dp_error; + return state->result; +} + +/* Report completion of LDAP operation and release associated connection. + * Returns operation result (possible updated) passed in ret parameter. + * + * In dp_error data provider error code is returned: + * DP_ERR_OK (operation result = EOK) - operation completed + * DP_ERR_OK (operation result != EOK) - operation can be retried + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed */ +int sdap_id_op_done(struct sdap_id_op *op, int retval, int *dp_err_out) +{ + bool communication_error; + struct sdap_id_conn_data *current_conn = op->conn_data; + switch (retval) { + case EIO: + case ETIMEDOUT: + /* this currently the only possible communication error after connection is established */ + communication_error = true; + break; + + default: + communication_error = false; + break; + } + + if (communication_error && current_conn != 0 + && current_conn == op->conn_cache->cached_connection) { + /* do not reuse failed connection */ + op->conn_cache->cached_connection = NULL; + + DEBUG(SSSDBG_FUNC_DATA, + "communication error on cached connection, moving to next server\n"); + be_fo_try_next_server(op->conn_cache->id_conn->id_ctx->be, + op->conn_cache->id_conn->service->name); + } + + int dp_err; + if (retval == EOK) { + dp_err = DP_ERR_OK; + } else if (be_is_offline(op->conn_cache->id_conn->id_ctx->be)) { + /* if backend is already offline, just report offline, do not duplicate errors */ + dp_err = DP_ERR_OFFLINE; + retval = EAGAIN; + DEBUG(SSSDBG_TRACE_ALL, "falling back to offline data...\n"); + } else if (communication_error) { + /* communication error, can try to reconnect */ + + if (!sdap_id_op_can_reconnect(op)) { + dp_err = DP_ERR_FATAL; + DEBUG(SSSDBG_TRACE_ALL, + "too many communication failures, giving up...\n"); + } else { + dp_err = DP_ERR_OK; + retval = EAGAIN; + } + } else { + dp_err = DP_ERR_FATAL; + } + + if (dp_err == DP_ERR_OK && retval != EOK) { + /* reconnect retry */ + op->reconnect_retry_count++; + DEBUG(SSSDBG_TRACE_ALL, + "advising for connection retry #%i\n", op->reconnect_retry_count); + } else { + /* end of request */ + op->reconnect_retry_count = 0; + } + + if (current_conn) { + DEBUG(SSSDBG_TRACE_ALL, "releasing operation connection\n"); + sdap_id_op_hook_conn_data(op, NULL); + } + + *dp_err_out = dp_err; + return retval; +} + +/* Get SDAP handle associated with operation by sdap_id_op_connect */ +struct sdap_handle *sdap_id_op_handle(struct sdap_id_op *op) +{ + return op && op->conn_data ? op->conn_data->sh : NULL; +} diff --git a/src/providers/ldap/sdap_id_op.h b/src/providers/ldap/sdap_id_op.h new file mode 100644 index 0000000..f7f230a --- /dev/null +++ b/src/providers/ldap/sdap_id_op.h @@ -0,0 +1,76 @@ +/* + SSSD + + LDAP ID backend operation retry logic and connection cache + + Authors: + Eugene Indenbom <eindenbom@gmail.com> + + Copyright (C) 2008-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/>. +*/ + +#ifndef _SDAP_ID_OP_H_ +#define _SDAP_ID_OP_H_ + +struct sdap_id_ctx; +struct sdap_id_conn_ctx; + +/* LDAP async connection cache */ +struct sdap_id_conn_cache; + +/* LDAP async operation tracker: + * - keeps track of connection usage + * - keeps track of operation retries */ +struct sdap_id_op; + +/* Create a connection cache */ +int sdap_id_conn_cache_create(TALLOC_CTX *memctx, + struct sdap_id_conn_ctx *id_conn, + struct sdap_id_conn_cache** conn_cache_out); + +/* Create an operation object */ +struct sdap_id_op *sdap_id_op_create(TALLOC_CTX *memctx, struct sdap_id_conn_cache *cache); + +/* Begin to connect to LDAP server. */ +struct tevent_req *sdap_id_op_connect_send(struct sdap_id_op *op, + TALLOC_CTX *memctx, + int *ret_out); + +/* Get the result of an asynchronous connect operation on sdap_id_op + * + * In dp_error data provider error code is returned: + * DP_ERR_OK - connection established + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed + */ +int sdap_id_op_connect_recv(struct tevent_req *req, int *dp_error); + +/* Report completion of LDAP operation and release associated connection. + * Returns operation result (possible updated) passed in ret parameter. + * + * In dp_error data provider error code is returned: + * DP_ERR_OK (operation result = EOK) - operation completed + * DP_ERR_OK (operation result != EOK) - operation can be retried + * DP_ERR_OFFLINE - backend is offline, operation result is set EAGAIN + * DP_ERR_FATAL - operation failed */ +int sdap_id_op_done(struct sdap_id_op*, int ret, int *dp_error); + +/* Get SDAP handle associated with operation by sdap_id_op_connect */ +struct sdap_handle *sdap_id_op_handle(struct sdap_id_op *op); +/* Get root DSE entry of connected LDAP server */ +const struct sysdb_attrs *sdap_id_op_rootDSE(struct sdap_id_op *op); + +#endif /* _SDAP_ID_OP_H_ */ diff --git a/src/providers/ldap/sdap_idmap.c b/src/providers/ldap/sdap_idmap.c new file mode 100644 index 0000000..3795ed6 --- /dev/null +++ b/src/providers/ldap/sdap_idmap.c @@ -0,0 +1,629 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 "util/util.h" +#include "util/dlinklist.h" +#include "providers/ldap/sdap_idmap.h" +#include "util/util_sss_idmap.h" + +static errno_t +sdap_idmap_get_configured_external_range(struct sdap_idmap_ctx *idmap_ctx, + struct sss_idmap_range *range) +{ + int int_id; + struct sdap_id_ctx *id_ctx; + uint32_t min; + uint32_t max; + + if (idmap_ctx == NULL) { + return EINVAL; + } + + id_ctx = idmap_ctx->id_ctx; + + int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MIN_ID); + if (int_id < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ldap_min_id must be greater than 0.\n"); + return EINVAL; + } + min = int_id; + + int_id = dp_opt_get_int(id_ctx->opts->basic, SDAP_MAX_ID); + if (int_id < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "ldap_max_id must be greater than 0.\n"); + return EINVAL; + } + max = int_id; + + if ((min == 0 && max != 0) || (min != 0 && max == 0)) { + DEBUG(SSSDBG_CONF_SETTINGS, "Both ldap_min_id and ldap_max_id " \ + "either must be 0 (not set) " \ + "or positive integers.\n"); + return EINVAL; + } + + if (min == 0 && max == 0) { + /* ldap_min_id and ldap_max_id not set, using min_id and max_id */ + min = id_ctx->be->domain->id_min; + max = id_ctx->be->domain->id_max; + if (max == 0) { + max = UINT32_MAX; + } + } + + range->min = min; + range->max =max; + + return EOK; +} + +static errno_t +sdap_idmap_add_configured_external_range(struct sdap_idmap_ctx *idmap_ctx) +{ + int ret; + struct sss_idmap_range range; + struct sdap_id_ctx *id_ctx; + enum idmap_error_code err; + + ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_get_configured_external_range failed.\n"); + return ret; + } + + id_ctx = idmap_ctx->id_ctx; + + err = sss_idmap_add_auto_domain_ex(idmap_ctx->map, + id_ctx->be->domain->name, + id_ctx->be->domain->domain_id, &range, + NULL, 0, true, NULL, NULL); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s] to the map: [%d]\n", + id_ctx->be->domain->name, err); + return EIO; + } + + return EOK; +} + +errno_t sdap_idmap_find_new_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str) +{ + int ret; + + ret = sdap_idmap_add_domain(idmap_ctx, + dom_name, dom_sid_str, + -1); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain [%s]\n", dom_name); + return ret; + } + + return EOK; +} + +errno_t +sdap_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum idmap_error_code err; + size_t i; + struct ldb_result *res; + const char *dom_name; + const char *sid_str; + id_t slice_num; + id_t idmap_lower; + id_t idmap_upper; + id_t rangesize; + bool autorid_mode; + int extra_slice_init; + struct sdap_idmap_ctx *idmap_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + idmap_ctx = talloc_zero(tmp_ctx, struct sdap_idmap_ctx); + if (!idmap_ctx) { + ret = ENOMEM; + goto done; + } + idmap_ctx->id_ctx = id_ctx; + idmap_ctx->find_new_domain = sdap_idmap_find_new_domain; + + idmap_lower = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_LOWER); + idmap_upper = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_UPPER); + rangesize = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_RANGESIZE); + autorid_mode = dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_AUTORID_COMPAT); + extra_slice_init = dp_opt_get_int(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_EXTRA_SLICE_INIT); + + /* Validate that the values make sense */ + if (rangesize <= 0 + || idmap_upper <= idmap_lower + || (idmap_upper-idmap_lower) < rangesize) + { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid settings for range selection: " + "[%"SPRIid"][%"SPRIid"][%"SPRIid"]\n", + idmap_lower, idmap_upper, rangesize); + ret = EINVAL; + goto done; + } + + if (((idmap_upper - idmap_lower) % rangesize) != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Range size does not divide evenly. Uppermost range will " + "not be used\n"); + } + + /* Initialize the map */ + err = sss_idmap_init(sss_idmap_talloc, idmap_ctx, + sss_idmap_talloc_free, + &idmap_ctx->map); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize the ID map: [%s]\n", + idmap_error_string(err)); + if (err == IDMAP_OUT_OF_MEMORY) { + ret = ENOMEM; + } else { + ret = EINVAL; + } + goto done; + } + + err = sss_idmap_ctx_set_autorid(idmap_ctx->map, autorid_mode); + err |= sss_idmap_ctx_set_lower(idmap_ctx->map, idmap_lower); + err |= sss_idmap_ctx_set_upper(idmap_ctx->map, idmap_upper); + err |= sss_idmap_ctx_set_rangesize(idmap_ctx->map, rangesize); + err |= sss_idmap_ctx_set_extra_slice_init(idmap_ctx->map, extra_slice_init); + if (err != IDMAP_SUCCESS) { + /* This should never happen */ + DEBUG(SSSDBG_CRIT_FAILURE, "sss_idmap_ctx corrupted\n"); + ret = EIO; + goto done; + } + + + /* Setup range for externally managed IDs, i.e. IDs are read from the + * ldap_user_uid_number and ldap_group_gid_number attributes. */ + if (!dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) { + ret = sdap_idmap_add_configured_external_range(idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_add_configured_external_range failed.\n"); + goto done; + } + } + + /* Read in any existing mappings from the cache */ + ret = sysdb_idmap_get_mappings(tmp_ctx, id_ctx->be->domain, &res); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not read ID mappings from the cache: [%s]\n", + strerror(ret)); + goto done; + } + + if (ret == EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Initializing [%d] domains for ID-mapping\n", res->count); + + for (i = 0; i < res->count; i++) { + dom_name = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (!dom_name) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + sid_str = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_IDMAP_SID_ATTR, + NULL); + if (!sid_str) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + slice_num = ldb_msg_find_attr_as_int(res->msgs[i], + SYSDB_IDMAP_SLICE_ATTR, + -1); + if (slice_num == -1) { + /* This should never happen */ + ret = EINVAL; + goto done; + } + + ret = sdap_idmap_add_domain(idmap_ctx, dom_name, + sid_str, slice_num); + if (ret != EOK) { + if (ret == EINVAL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%"SPRIid"] " + "to ID map: [%s] " + "Unexpected ID map configuration. Check ID map related " + "parameters in sssd.conf and remove the sssd cache if " + "some of these parameters were changed recently.\n", + dom_name, sid_str, slice_num, strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%"SPRIid"] " + "to ID map: [%s]\n", + dom_name, sid_str, slice_num, strerror(ret)); + } + + goto done; + } + } + } else { + /* This is the first time we're setting up id-mapping + * Store the default domain as slice 0 + */ + dom_name = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN); + if (!dom_name) { + /* If it's not explicitly specified, use the SSSD domain name */ + dom_name = idmap_ctx->id_ctx->be->domain->name; + ret = dp_opt_set_string(idmap_ctx->id_ctx->opts->basic, + SDAP_IDMAP_DEFAULT_DOMAIN, + dom_name); + if (ret != EOK) goto done; + } + + sid_str = dp_opt_get_string(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_DEFAULT_DOMAIN_SID); + if (sid_str) { + struct sss_domain_info *domain = idmap_ctx->id_ctx->be->domain; + domain->domain_id = talloc_strdup(domain, sid_str); + if (domain->domain_id == NULL) { + ret = ENOMEM; + goto done; + } + + /* Set the default domain as slice 0 */ + ret = sdap_idmap_add_domain(idmap_ctx, dom_name, + sid_str, 0); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s][%s][%u] to ID map: [%s]\n", + dom_name, sid_str, 0, strerror(ret)); + goto done; + } + } else { + if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_IDMAP_AUTORID_COMPAT)) { + /* In autorid compatibility mode, we MUST have a slice 0 */ + DEBUG(SSSDBG_CRIT_FAILURE, + "WARNING: Autorid compatibility mode selected, " + "but %s is not set. UID/GID values may differ " + "between clients.\n", + idmap_ctx->id_ctx->opts->basic[SDAP_IDMAP_DEFAULT_DOMAIN_SID].opt_name); + } + /* Otherwise, we'll just fall back to hash values as they are seen */ + } + } + + *_idmap_ctx = talloc_steal(mem_ctx, idmap_ctx); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sdap_idmap_add_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid, + id_t slice) +{ + errno_t ret; + struct sss_idmap_range range; + enum idmap_error_code err; + id_t idmap_upper; + bool external_mapping = true; + + ret = sss_idmap_ctx_get_upper(idmap_ctx->map, &idmap_upper); + if (ret != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get upper bound of available ID range.\n"); + ret = EIO; + goto done; + } + + if (dp_opt_get_bool(idmap_ctx->id_ctx->opts->basic, SDAP_ID_MAPPING)) { + external_mapping = false; + ret = sss_idmap_calculate_range(idmap_ctx->map, dom_sid, &slice, &range); + if (ret != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to calculate range for domain [%s]: [%d]\n", dom_name, + ret); + ret = EIO; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Adding domain [%s] as slice [%"SPRIid"]\n", dom_sid, slice); + + if (range.max > idmap_upper) { + /* This should never happen */ + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: Range maximum exceeds the global maximum: " + "%u > %"SPRIid"\n", range.max, idmap_upper); + ret = EINVAL; + goto done; + } + } else { + ret = sdap_idmap_get_configured_external_range(idmap_ctx, &range); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_idmap_get_configured_external_range failed.\n"); + return ret; + } + } + + /* Add this domain to the map */ + err = sss_idmap_add_auto_domain_ex(idmap_ctx->map, dom_name, dom_sid, + &range, NULL, 0, external_mapping, + NULL, NULL); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add domain [%s] to the map: [%d]\n", + dom_name, err); + ret = EIO; + goto done; + } + + /* If algorithmic mapping is used add this domain to the SYSDB cache so it + * will survive reboot */ + if (!external_mapping) { + ret = sysdb_idmap_store_mapping(idmap_ctx->id_ctx->be->domain, + dom_name, dom_sid, + slice); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_idmap_store_mapping failed.\n"); + goto done; + } + } + +done: + return ret; +} + +errno_t +sdap_idmap_get_dom_sid_from_object(TALLOC_CTX *mem_ctx, + const char *object_sid, + char **dom_sid_str) +{ + const char *p; + long long a; + size_t c; + char *endptr; + + if (object_sid == NULL + || strncmp(object_sid, DOM_SID_PREFIX, DOM_SID_PREFIX_LEN) != 0) { + return EINVAL; + } + + p = object_sid + DOM_SID_PREFIX_LEN; + c = 0; + + do { + errno = 0; + a = strtoull(p, &endptr, 10); + if (errno != 0 || a > UINT32_MAX) { + return EINVAL; + } + + if (*endptr == '-') { + p = endptr + 1; + } else { + return EINVAL; + } + c++; + } while(c < 3); + + /* If we made it here, we are now one character past + * the last hyphen in the object-sid. + * Copy the dom-sid substring. + */ + *dom_sid_str = talloc_strndup(mem_ctx, object_sid, + (endptr-object_sid)); + if (!*dom_sid_str) return ENOMEM; + + return EOK; +} + +errno_t +sdap_idmap_sid_to_unix(struct sdap_idmap_ctx *idmap_ctx, + const char *sid_str, + id_t *id) +{ + errno_t ret; + enum idmap_error_code err; + char *dom_sid_str = NULL; + + /* Convert the SID into a UNIX ID */ + err = sss_idmap_sid_to_unix(idmap_ctx->map, + sid_str, + (uint32_t *)id); + switch (err) { + case IDMAP_SUCCESS: + break; + case IDMAP_NO_DOMAIN: + /* This is the first time we've seen this domain + * Create a new domain for it. We'll use the dom-sid + * as the domain name for now, since we don't have + * any way to get the real name. + */ + ret = sdap_idmap_get_dom_sid_from_object(NULL, sid_str, + &dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", sid_str); + goto done; + } + + ret = idmap_ctx->find_new_domain(idmap_ctx, dom_sid_str, dom_sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain for sid [%s]\n", sid_str); + goto done; + } + + /* Now try converting to a UNIX ID again */ + err = sss_idmap_sid_to_unix(idmap_ctx->map, + sid_str, + (uint32_t *)id); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert objectSID [%s] to a UNIX ID\n", + sid_str); + ret = EIO; + goto done; + } + break; + case IDMAP_BUILTIN_SID: + DEBUG(SSSDBG_TRACE_FUNC, + "Object SID [%s] is a built-in one.\n", sid_str); + /* ENOTSUP indicates built-in SID */ + ret = ENOTSUP; + goto done; + break; + case IDMAP_NO_RANGE: + DEBUG(SSSDBG_IMPORTANT_INFO, + "Object SID [%s] has a RID that is larger than the " + "ldap_idmap_range_size. See the \"ID MAPPING\" section of " + "sssd-ad(5) for an explanation of how to resolve this issue.\n", + sid_str); + /* Fall through intentionally */ + SSS_ATTRIBUTE_FALLTHROUGH; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not convert objectSID [%s] to a UNIX ID\n", + sid_str); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + talloc_free(dom_sid_str); + return ret; +} + +bool sdap_idmap_domain_has_algorithmic_mapping(struct sdap_idmap_ctx *ctx, + const char *dom_name, + const char *dom_sid) +{ + enum idmap_error_code err; + bool has_algorithmic_mapping; + char *new_dom_sid; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + + if (dp_opt_get_bool(ctx->id_ctx->opts->basic, SDAP_ID_MAPPING) + && dp_target_enabled(ctx->id_ctx->be->provider, "ldap", DPT_ID)) { + return true; + } + + err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid, + &has_algorithmic_mapping); + switch (err){ + case IDMAP_SUCCESS: + return has_algorithmic_mapping; + case IDMAP_SID_INVALID: /* FALLTHROUGH */ + case IDMAP_SID_UNKNOWN: /* FALLTHROUGH */ + case IDMAP_NO_DOMAIN: /* FALLTHROUGH */ + /* continue with idmap_domain_by_name */ + break; + default: + return false; + } + + err = sss_idmap_domain_by_name_has_algorithmic_mapping(ctx->map, + dom_name, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + return has_algorithmic_mapping; + } else if (err != IDMAP_NAME_UNKNOWN && err != IDMAP_NO_DOMAIN) { + return false; + } + + /* If there is no SID, e.g. IPA without enabled trust support, we cannot + * have algorithmic mapping */ + if (dom_sid == NULL) { + return false; + } + + /* This is the first time we've seen this domain + * Create a new domain for it. We'll use the dom-sid + * as the domain name for now, since we don't have + * any way to get the real name. + */ + + if (is_domain_sid(dom_sid)) { + new_dom_sid = discard_const(dom_sid); + } else { + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return false; + } + + ret = sdap_idmap_get_dom_sid_from_object(tmp_ctx, dom_sid, + &new_dom_sid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not parse domain SID from [%s]\n", dom_sid); + talloc_free(tmp_ctx); + return false; + } + } + + ret = ctx->find_new_domain(ctx, dom_name, new_dom_sid); + talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add new domain for sid [%s]\n", dom_sid); + return false; + } + + err = sss_idmap_domain_has_algorithmic_mapping(ctx->map, dom_sid, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + return has_algorithmic_mapping; + } + + return false; +} diff --git a/src/providers/ldap/sdap_idmap.h b/src/providers/ldap/sdap_idmap.h new file mode 100644 index 0000000..07499dc --- /dev/null +++ b/src/providers/ldap/sdap_idmap.h @@ -0,0 +1,63 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef SDAP_IDMAP_H_ +#define SDAP_IDMAP_H_ + +#include "src/providers/ldap/sdap.h" +#include "src/providers/ldap/ldap_common.h" + +typedef errno_t (find_new_domain_fn_t)(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str); +struct sdap_idmap_ctx { + struct sss_idmap_ctx *map; + + struct sdap_id_ctx *id_ctx; + find_new_domain_fn_t *find_new_domain; +}; + +errno_t sdap_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx); + +errno_t +sdap_idmap_add_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid, + id_t slice); + +errno_t +sdap_idmap_get_dom_sid_from_object(TALLOC_CTX *mem_ctx, + const char *object_sid, + char **dom_sid_str); + +errno_t +sdap_idmap_sid_to_unix(struct sdap_idmap_ctx *idmap_ctx, + const char *sid_str, + id_t *id); + +bool sdap_idmap_domain_has_algorithmic_mapping(struct sdap_idmap_ctx *ctx, + const char *name, + const char *dom_sid); + +#endif /* SDAP_IDMAP_H_ */ diff --git a/src/providers/ldap/sdap_iphost.c b/src/providers/ldap/sdap_iphost.c new file mode 100644 index 0000000..79c707b --- /dev/null +++ b/src/providers/ldap/sdap_iphost.c @@ -0,0 +1,375 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb_iphosts.h" + +struct sdap_ip_host_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + uint32_t filter_type; + const char *filter_value; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +sdap_ip_host_get_retry(struct tevent_req *req); + +static struct tevent_req * +sdap_iphost_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + uint32_t filter_type, + const char *filter_value, + bool noexist_delete) +{ + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + const char *attr_name; + char *clean_filter_value; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_ip_host_get_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NAME].name; + break; + case BE_FILTER_ADDR: + attr_name = id_ctx->opts->iphost_map[SDAP_AT_IPHOST_NUMBER].name; + break; + default: + ret = EINVAL; + goto fail; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_filter_value); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(objectclass=%s)(%s=%s))", + id_ctx->opts->iphost_map[SDAP_OC_IPHOST].name, + attr_name, clean_filter_value); + talloc_zfree(clean_filter_value); + if (state->filter == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->iphost_map, + SDAP_OPTS_IPHOST, NULL, &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + ret = sdap_ip_host_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ip_host_get_connect_done(struct tevent_req *subreq); + +static errno_t +sdap_ip_host_get_retry(struct tevent_req *req) +{ + struct sdap_ip_host_get_state *state; + struct tevent_req *subreq; + errno_t ret = EOK; + + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_ip_host_get_connect_done, req); + + return EOK; +} + +static void +sdap_ip_host_get_done(struct tevent_req *subreq); + +static void +sdap_ip_host_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_iphost_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->iphost_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ip_host_get_done, req); +} + +static void +sdap_ip_host_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_ip_host_get_state *state; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + ret = sdap_get_iphost_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_ip_host_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch (state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_host_delete(state->domain, state->filter_value, + NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_ADDR: + ret = sysdb_host_delete(state->domain, NULL, + state->filter_value); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_ip_host_get_recv(struct tevent_req *req, + int *dp_error_out, + int *sdap_ret) +{ + struct sdap_ip_host_get_state *state; + + state = tevent_req_data(req, struct sdap_ip_host_get_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret != NULL) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ip_host_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_ip_host_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_iphost_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct sdap_ip_host_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ip_host_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (resolver_data->filter_type == BE_FILTER_ENUM) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_iphost_get_send(state, params->ev, + resolver_ctx->id_ctx, + resolver_ctx->id_ctx->opts->sdom, + resolver_ctx->id_ctx->conn, + resolver_data->filter_type, + resolver_data->filter_value, + true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_ip_host_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_ip_host_handler_done(struct tevent_req *subreq) +{ + struct sdap_ip_host_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ip_host_handler_state); + + ret = sdap_ip_host_get_recv(subreq, &dp_error, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_iphost_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_ip_host_handler_state *state; + + state = tevent_req_data(req, struct sdap_ip_host_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_ipnetwork.c b/src/providers/ldap/sdap_ipnetwork.c new file mode 100644 index 0000000..b78f50b --- /dev/null +++ b/src/providers/ldap/sdap_ipnetwork.c @@ -0,0 +1,378 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "db/sysdb_ipnetworks.h" + +struct sdap_ipnetwork_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *id_ctx; + struct sdap_domain *sdom; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sdap_id_conn_ctx *conn; + + uint32_t filter_type; + const char *filter_value; + + char *filter; + const char **attrs; + + int dp_error; + int sdap_ret; + bool noexist_delete; +}; + +static errno_t +sdap_ipnetwork_get_retry(struct tevent_req *req); + +static struct tevent_req * +sdap_ipnetwork_get_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *id_ctx, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx *conn, + uint32_t filter_type, + const char *filter_value, + bool noexist_delete) +{ + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + const char *attr_name; + char *clean_filter_value; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_ipnetwork_get_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->id_ctx = id_ctx; + state->sdom = sdom; + state->conn = conn; + state->dp_error = DP_ERR_FATAL; + state->domain = sdom->dom; + state->sysdb = sdom->dom->sysdb; + state->filter_value = filter_value; + state->filter_type = filter_type; + state->noexist_delete = noexist_delete; + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NAME].name; + break; + case BE_FILTER_ADDR: + attr_name = id_ctx->opts->ipnetwork_map[SDAP_AT_IPNETWORK_NUMBER].name; + break; + default: + ret = EINVAL; + goto fail; + } + + ret = sss_filter_sanitize(state, filter_value, &clean_filter_value); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(objectclass=%s)(%s=%s))", + id_ctx->opts->ipnetwork_map[SDAP_OC_IPNETWORK].name, + attr_name, clean_filter_value); + talloc_zfree(clean_filter_value); + if (state->filter == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to build the base filter\n"); + ret = ENOMEM; + goto fail; + } + + ret = build_attrs_from_map(state, id_ctx->opts->ipnetwork_map, + SDAP_OPTS_IPNETWORK, NULL, &state->attrs, NULL); + if (ret != EOK) { + goto fail; + } + + ret = sdap_ipnetwork_get_retry(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ipnetwork_get_connect_done(struct tevent_req *subreq); + +static errno_t +sdap_ipnetwork_get_retry(struct tevent_req *req) +{ + struct sdap_ipnetwork_get_state *state; + struct tevent_req *subreq; + errno_t ret = EOK; + + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ret; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_get_connect_done, req); + + return EOK; +} + +static void +sdap_ipnetwork_get_done(struct tevent_req *subreq); + +static void +sdap_ipnetwork_get_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_ipnetwork_send(state, state->ev, + state->domain, state->sysdb, + state->id_ctx->opts, + state->sdom->ipnetwork_search_bases, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + dp_opt_get_int(state->id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_get_done, req); +} + +static void +sdap_ipnetwork_get_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_ipnetwork_get_state *state; + int dp_error = DP_ERR_FATAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + ret = sdap_get_ipnetwork_recv(NULL, subreq, NULL); + talloc_zfree(subreq); + + /* Check whether we need to try again with another + * failover server. */ + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = sdap_ipnetwork_get_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Return to the mainloop to retry */ + return; + } + state->sdap_ret = ret; + + /* An error occurred. */ + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT && state->noexist_delete == true) { + /* Ensure that this entry is removed from the sysdb */ + switch (state->filter_type) { + case BE_FILTER_NAME: + ret = sysdb_ipnetwork_delete(state->domain, + state->filter_value, + NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + case BE_FILTER_ADDR: + ret = sysdb_ipnetwork_delete(state->domain, NULL, + state->filter_value); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +static errno_t +sdap_ipnetwork_get_recv(struct tevent_req *req, + int *dp_error_out, + int *sdap_ret) +{ + struct sdap_ipnetwork_get_state *state; + + state = tevent_req_data(req, struct sdap_ipnetwork_get_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + if (sdap_ret != NULL) { + *sdap_ret = state->sdap_ret; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_ipnetwork_handler_state { + struct dp_reply_std reply; +}; + +static void +sdap_ipnetwork_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ipnetwork_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct sdap_ipnetwork_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ipnetwork_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (resolver_data->filter_type == BE_FILTER_ENUM) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_ipnetwork_get_send(state, params->ev, + resolver_ctx->id_ctx, + resolver_ctx->id_ctx->opts->sdom, + resolver_ctx->id_ctx->conn, + resolver_data->filter_type, + resolver_data->filter_value, + true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_ipnetwork_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void +sdap_ipnetwork_handler_done(struct tevent_req *subreq) +{ + struct sdap_ipnetwork_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_ipnetwork_handler_state); + + ret = sdap_ipnetwork_get_recv(subreq, &dp_error, NULL); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t +sdap_ipnetwork_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_ipnetwork_handler_state *state; + + state = tevent_req_data(req, struct sdap_ipnetwork_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_online_check.c b/src/providers/ldap/sdap_online_check.c new file mode 100644 index 0000000..9c104e5 --- /dev/null +++ b/src/providers/ldap/sdap_online_check.c @@ -0,0 +1,244 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <errno.h> +#include <talloc.h> +#include <tevent.h> +#include "util/util.h" +#include "providers/backend.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_online_check_state { + struct sdap_id_ctx *id_ctx; + struct be_ctx *be_ctx; +}; + +static void sdap_online_check_connect_done(struct tevent_req *subreq); +static void sdap_online_check_reinit_done(struct tevent_req *subreq); + +static struct tevent_req *sdap_online_check_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx) +{ + struct sdap_online_check_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_ctx *be_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_online_check_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->id_ctx = id_ctx; + state->be_ctx = be_ctx = id_ctx->be; + + subreq = sdap_cli_connect_send(state, be_ctx->ev, id_ctx->opts, be_ctx, + id_ctx->conn->service, false, + CON_TLS_DFL, false); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + } else { + tevent_req_set_callback(subreq, sdap_online_check_connect_done, req); + } + + return req; +} + +static void sdap_online_check_connect_done(struct tevent_req *subreq) +{ + struct sdap_online_check_state *state; + struct sdap_server_opts *srv_opts; + struct sdap_id_ctx *id_ctx; + struct tevent_req *req; + bool can_retry; + bool reinit = false; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_online_check_state); + + id_ctx = state->id_ctx; + + ret = sdap_cli_connect_recv(subreq, state, &can_retry, NULL, &srv_opts); + talloc_zfree(subreq); + if (ret != EOK) { + if (can_retry == false) { + ret = ERR_OFFLINE; + } + + goto done; + } else { + if (id_ctx->srv_opts == NULL) { + srv_opts->max_user_value = 0; + srv_opts->max_group_value = 0; + srv_opts->max_service_value = 0; + srv_opts->max_sudo_value = 0; + srv_opts->max_iphost_value = 0; + srv_opts->max_ipnetwork_value = 0; + } else if (strcmp(srv_opts->server_id, id_ctx->srv_opts->server_id) == 0 + && srv_opts->supports_usn + && id_ctx->srv_opts->last_usn > srv_opts->last_usn) { + id_ctx->srv_opts->max_user_value = 0; + id_ctx->srv_opts->max_group_value = 0; + id_ctx->srv_opts->max_service_value = 0; + id_ctx->srv_opts->max_sudo_value = 0; + id_ctx->srv_opts->max_iphost_value = 0; + id_ctx->srv_opts->max_ipnetwork_value = 0; + id_ctx->srv_opts->last_usn = srv_opts->last_usn; + + reinit = true; + } + + sdap_steal_server_opts(id_ctx, &srv_opts); + } + + if (reinit) { + DEBUG(SSSDBG_TRACE_FUNC, "Server reinitialization detected. " + "Cleaning cache.\n"); + subreq = sdap_reinit_cleanup_send(state, state->be_ctx, id_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up.\n"); + /* not fatal */ + goto done; + } + + tevent_req_set_callback(subreq, sdap_online_check_reinit_done, req); + return; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void sdap_online_check_reinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_reinit_cleanup_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform reinitialization " + "clean up [%d]: %s\n", ret, strerror(ret)); + /* not fatal */ + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Reinitialization clean up completed\n"); + } + + tevent_req_done(req); +} + +static errno_t sdap_online_check_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sdap_online_check_handler_state { + struct dp_reply_std reply; +}; + +static void sdap_online_check_handler_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_online_check_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + void *data, + struct dp_req_params *params) +{ + struct sdap_online_check_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_online_check_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + subreq = sdap_online_check_send(state, id_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_online_check_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_online_check_handler_done(struct tevent_req *subreq) +{ + struct sdap_online_check_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_online_check_handler_state); + + ret = sdap_online_check_recv(subreq); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + tevent_req_done(req); +} + +errno_t sdap_online_check_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_online_check_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_online_check_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ldap/sdap_ops.c b/src/providers/ldap/sdap_ops.c new file mode 100644 index 0000000..2125b21 --- /dev/null +++ b/src/providers/ldap/sdap_ops.c @@ -0,0 +1,553 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_search_bases_ex_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *filter; + const char **attrs; + struct sdap_attr_map *map; + int map_num_attrs; + int timeout; + bool allow_paging; + bool return_first_reply; + const char *base_dn; + + size_t base_iter; + struct sdap_search_base *cur_base; + struct sdap_search_base **bases; + + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static errno_t sdap_search_bases_ex_next_base(struct tevent_req *req); +static void sdap_search_bases_ex_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_search_bases_ex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + bool return_first_reply, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + struct tevent_req *req; + struct sdap_search_bases_ex_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_search_bases_ex_state); + if (req == NULL) { + return NULL; + } + + if (bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No search base specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->bases = bases; + state->map = map; + state->filter = filter; + state->attrs = attrs; + state->allow_paging = allow_paging; + state->return_first_reply = return_first_reply; + state->base_dn = base_dn; + + state->timeout = timeout == 0 + ? dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT) + : timeout; + + if (state->map != NULL) { + for (state->map_num_attrs = 0; + state->map[state->map_num_attrs].opt_name != NULL; + state->map_num_attrs++) { + /* no op */; + } + } else { + state->map_num_attrs = 0; + } + + if (state->attrs == NULL && state->map != NULL) { + ret = build_attrs_from_map(state, state->map, state->map_num_attrs, + NULL, &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build attrs from map " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + } + + state->base_iter = 0; + ret = sdap_search_bases_ex_next_base(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_search_bases_ex_next_base(struct tevent_req *req) +{ + struct sdap_search_bases_ex_state *state; + struct tevent_req *subreq; + const char *base_dn; + char *filter; + + state = tevent_req_data(req, struct sdap_search_bases_ex_state); + state->cur_base = state->bases[state->base_iter]; + if (state->cur_base == NULL) { + return EOK; + } + + /* Combine lookup and search base filters. */ + filter = sdap_combine_filters(state, state->filter, + state->cur_base->filter); + if (filter == NULL) { + return ENOMEM; + } + + base_dn = state->base_dn != NULL ? state->base_dn : state->cur_base->basedn; + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing LDAP lookup with base [%s]\n", base_dn); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base_dn, state->cur_base->scope, filter, + state->attrs, state->map, + state->map_num_attrs, state->timeout, + state->allow_paging); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_search_bases_ex_done, req); + + state->base_iter++; + return EAGAIN; +} + +static void sdap_search_bases_ex_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_search_bases_ex_state *state; + struct sysdb_attrs **attrs; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_search_bases_ex_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Receiving data from base [%s]\n", + state->cur_base->basedn); + + ret = sdap_get_generic_recv(subreq, state, &count, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Add rules to result. */ + if (count > 0) { + if (state->return_first_reply == false) { + /* Merge with previous reply. */ + state->reply = talloc_realloc(state, state->reply, + struct sysdb_attrs *, + state->reply_count + count); + if (state->reply == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i = 0; i < count; i++) { + state->reply[state->reply_count + i] = talloc_steal(state->reply, + attrs[i]); + } + + state->reply_count += count; + } else { + /* Return the first successful search result. */ + state->reply_count = count; + state->reply = attrs; + tevent_req_done(req); + return; + } + } + + /* Try next search base. */ + ret = sdap_search_bases_ex_next_base(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static int sdap_search_bases_ex_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct sdap_search_bases_ex_state *state = + tevent_req_data(req, struct sdap_search_bases_ex_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +struct tevent_req * +sdap_search_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + return sdap_search_bases_ex_send(mem_ctx, ev, opts, sh, bases, map, + allow_paging, false, timeout, + filter, attrs, base_dn); +} + +int sdap_search_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply) +{ + return sdap_search_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct tevent_req * +sdap_search_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn) +{ + return sdap_search_bases_ex_send(mem_ctx, ev, opts, sh, bases, map, + allow_paging, true, timeout, + filter, attrs, base_dn); +} + +int sdap_search_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply) +{ + return sdap_search_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct sdap_deref_bases_ex_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + const char *filter; + const char **attrs; + const char *deref_attr; + struct sdap_attr_map_info *maps; + size_t num_maps; + unsigned int flags; + bool return_first_reply; + int timeout; + + size_t base_iter; + struct sdap_search_base *cur_base; + struct sdap_search_base **bases; + + size_t reply_count; + struct sdap_deref_attrs **reply; +}; + +static errno_t sdap_deref_bases_ex_next_base(struct tevent_req *req); +static void sdap_deref_bases_ex_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_deref_bases_ex_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + bool return_first_reply, + int timeout) +{ + struct tevent_req *req; + struct sdap_deref_bases_ex_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_deref_bases_ex_state); + if (req == NULL) { + return NULL; + } + + if (bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No search base specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + if (maps == NULL || attrs == NULL || deref_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No attributes or map specified!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->bases = bases; + state->maps = maps; + state->filter = filter; + state->attrs = attrs; + state->deref_attr = deref_attr; + state->return_first_reply = return_first_reply; + state->flags = flags; + + state->timeout = timeout == 0 + ? dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT) + : timeout; + + for (state->num_maps = 0; maps[state->num_maps].map != NULL; + state->num_maps++) { + /* no op */; + } + + state->base_iter = 0; + ret = sdap_deref_bases_ex_next_base(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_deref_bases_ex_next_base(struct tevent_req *req) +{ + struct sdap_deref_bases_ex_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_deref_bases_ex_state); + state->cur_base = state->bases[state->base_iter]; + if (state->cur_base == NULL) { + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing LDAP deref lookup with base [%s]\n", + state->cur_base->basedn); + + subreq = sdap_deref_search_with_filter_send(state, state->ev, state->opts, + state->sh, state->cur_base->basedn, state->filter, + state->deref_attr, state->attrs, state->num_maps, state->maps, + state->timeout, state->flags); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_deref_bases_ex_done, req); + + state->base_iter++; + return EAGAIN; +} + +static void sdap_deref_bases_ex_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_deref_bases_ex_state *state; + struct sdap_deref_attrs **attrs; + size_t count; + size_t i; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_deref_bases_ex_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Receiving data from base [%s]\n", + state->cur_base->basedn); + + ret = sdap_deref_search_with_filter_recv(subreq, state, &count, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Add rules to result. */ + if (count > 0) { + if (state->return_first_reply == false) { + /* Merge with previous reply. */ + state->reply = talloc_realloc(state, state->reply, + struct sdap_deref_attrs *, + state->reply_count + count); + if (state->reply == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (i = 0; i < count; i++) { + state->reply[state->reply_count + i] = talloc_steal(state->reply, + attrs[i]); + } + + state->reply_count += count; + } else { + /* Return the first successful search result. */ + state->reply_count = count; + state->reply = attrs; + tevent_req_done(req); + return; + } + } + + /* Try next search base. */ + ret = sdap_deref_bases_ex_next_base(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static int sdap_deref_bases_ex_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sdap_deref_attrs ***reply) +{ + struct sdap_deref_bases_ex_state *state = + tevent_req_data(req, struct sdap_deref_bases_ex_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *reply_count = state->reply_count; + *reply = talloc_steal(mem_ctx, state->reply); + + return EOK; +} + +struct tevent_req * +sdap_deref_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout) +{ + return sdap_deref_bases_ex_send(mem_ctx, ev, opts, sh, bases, maps, + filter, attrs, deref_attr, flags, + false, timeout); +} + +int sdap_deref_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply) +{ + return sdap_deref_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} + +struct tevent_req * +sdap_deref_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout) +{ + return sdap_deref_bases_ex_send(mem_ctx, ev, opts, sh, bases, maps, + filter, attrs, deref_attr, flags, + true, timeout); +} + +int sdap_deref_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply) +{ + return sdap_deref_bases_ex_recv(req, mem_ctx, _reply_count, _reply); +} diff --git a/src/providers/ldap/sdap_ops.h b/src/providers/ldap/sdap_ops.h new file mode 100644 index 0000000..648a2b6 --- /dev/null +++ b/src/providers/ldap/sdap_ops.h @@ -0,0 +1,99 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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/>. +*/ + +#ifndef _SDAP_OPS_H_ +#define _SDAP_OPS_H_ + +#include <talloc.h> +#include <tevent.h> +#include "providers/ldap/ldap_common.h" + +struct tevent_req *sdap_search_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn); + +int sdap_search_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req * +sdap_search_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map *map, + bool allow_paging, + int timeout, + const char *filter, + const char **attrs, + const char *base_dn); + +int sdap_search_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sysdb_attrs ***_reply); + +struct tevent_req * +sdap_deref_bases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout); + +int sdap_deref_bases_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply); + +struct tevent_req * +sdap_deref_bases_return_first_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sdap_search_base **bases, + struct sdap_attr_map_info *maps, + const char *filter, + const char **attrs, + const char *deref_attr, + unsigned int flags, + int timeout); + +int sdap_deref_bases_return_first_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_reply_count, + struct sdap_deref_attrs ***_reply); + +#endif /* _SDAP_OPS_H_ */ diff --git a/src/providers/ldap/sdap_range.c b/src/providers/ldap/sdap_range.c new file mode 100644 index 0000000..44c3350 --- /dev/null +++ b/src/providers/ldap/sdap_range.c @@ -0,0 +1,142 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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 "providers/ldap/sdap_range.h" +#include "util/util.h" +#include "util/strtonum.h" + +#define SDAP_RANGE_STRING "range=" + +errno_t sdap_parse_range(TALLOC_CTX *mem_ctx, + const char *attr_desc, + char **base_attr, + uint32_t *range_offset, + bool disable_range_retrieval) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + char *endptr; + char *end_range; + char *base; + size_t rangestringlen = sizeof(SDAP_RANGE_STRING) - 1; + + *range_offset = 0; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* The base_attr is the portion before the semicolon (if it exists) */ + endptr = strchr(attr_desc, ';'); + if (endptr == NULL) { + /* Not a ranged attribute. Just copy the attribute desc */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = EOK; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "No sub-attributes for [%s]\n", attr_desc); + goto done; + } + + /* This is a complex attribute. First get the base attribute name */ + base = talloc_strndup(tmp_ctx, attr_desc, + endptr - attr_desc); + if (!base) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "Base attribute of [%s] is [%s]\n", + attr_desc, base); + + /* Next, determine if this is a ranged attribute */ + if (strncmp(endptr+1, SDAP_RANGE_STRING, rangestringlen) != 0) { + /* This is some other sub-attribute. We'll just return the whole + * thing in case it's dealt with elsewhere. + */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = EOK; + } + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] contains sub-attribute other than a range, returning whole\n", + attr_desc); + goto done; + } else if (disable_range_retrieval) { + /* This is range sub-attribute, but we want to ignore it. + */ + *base_attr = talloc_strdup(mem_ctx, attr_desc); + if (!*base_attr) { + ret = ENOMEM; + } else { + ret = ECANCELED; + } + goto done; + } + + /* Get the end of the range */ + end_range = strchr(endptr + rangestringlen +1, '-'); + if (!end_range) { + ret = EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find hyphen in [%s]\n", + endptr + rangestringlen +1); + goto done; + } + end_range++; /* advance past the hyphen */ + + if (*end_range == '*') { + /* this was the last iteration of range retrievals */ + *base_attr = talloc_steal(mem_ctx, base); + *range_offset = 0; + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] contained the last set of values for this attribute\n", + attr_desc); + ret = EOK; + goto done; + } + + *range_offset = strtouint32(end_range, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (end_range == endptr)) { + *range_offset = 0; + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "[%s] did not parse as an unsigned integer: [%s]\n", + end_range, strerror(ret)); + goto done; + } + (*range_offset)++; + + *base_attr = talloc_steal(mem_ctx, base); + DEBUG(SSSDBG_TRACE_LIBS, + "Parsed range values: [%s][%d]\n", + base, *range_offset); + + ret = EAGAIN; +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ldap/sdap_range.h b/src/providers/ldap/sdap_range.h new file mode 100644 index 0000000..f11b3be --- /dev/null +++ b/src/providers/ldap/sdap_range.h @@ -0,0 +1,34 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef SDAP_RANGE_H_ +#define SDAP_RANGE_H_ + +#include "src/util/util.h" + +errno_t sdap_parse_range(TALLOC_CTX *mem_ctx, + const char *attr_desc, + char **base_attr, + uint32_t *range_offset, + bool disable_range_retrieval); + +#endif /* SDAP_RANGE_H_ */ diff --git a/src/providers/ldap/sdap_refresh.c b/src/providers/ldap/sdap_refresh.c new file mode 100644 index 0000000..402db53 --- /dev/null +++ b/src/providers/ldap/sdap_refresh.c @@ -0,0 +1,238 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 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 <talloc.h> +#include <tevent.h> + +#include "providers/ldap/sdap.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct dp_id_data *account_req; + struct sdap_id_ctx *id_ctx; + struct sss_domain_info *domain; + struct sdap_domain *sdom; + char **names; + size_t index; +}; + +static errno_t sdap_refresh_step(struct tevent_req *req); +static void sdap_refresh_done(struct tevent_req *subreq); + +static struct tevent_req *sdap_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + int entry_type, + char **names, + void *pvt) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (names == NULL) { + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = domain; + state->id_ctx = talloc_get_type(pvt, struct sdap_id_ctx); + state->names = names; + state->index = 0; + + state->sdom = sdap_domain_get(state->id_ctx->opts, domain); + if (state->sdom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto immediately; + } + + state->account_req = be_refresh_acct_req(state, entry_type, + BE_FILTER_NAME, domain); + if (state->account_req == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = sdap_refresh_step(req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Nothing to refresh\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_refresh_step() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t sdap_refresh_step(struct tevent_req *req) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + state = tevent_req_data(req, struct sdap_refresh_state); + + if (state->names == NULL) { + ret = EOK; + goto done; + } + + state->account_req->filter_value = state->names[state->index]; + if (state->account_req->filter_value == NULL) { + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing refresh of %s %s\n", + be_req2str(state->account_req->entry_type), + state->account_req->filter_value); + + subreq = sdap_handle_acct_req_send(state, state->be_ctx, + state->account_req, state->id_ctx, + state->sdom, state->id_ctx->conn, true); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sdap_refresh_done, req); + + state->index++; + ret = EAGAIN; + +done: + return ret; +} + +static void sdap_refresh_done(struct tevent_req *subreq) +{ + struct sdap_refresh_state *state = NULL; + struct tevent_req *req = NULL; + const char *err_msg = NULL; + errno_t dp_error; + int sdap_ret; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_refresh_state); + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &err_msg, &sdap_ret); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh %s [dp_error: %d, " + "sdap_ret: %d, errno: %d]: %s\n", + be_req2str(state->account_req->entry_type), + dp_error, sdap_ret, ret, err_msg); + goto done; + } + + if (state->account_req->entry_type == BE_REQ_INITGROUPS) { + ret = sysdb_set_initgr_expire_timestamp(state->domain, + state->account_req->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + state->account_req->filter_value); + } + } + + ret = sdap_refresh_step(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sdap_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +REFRESH_SEND_RECV_FNS(sdap_refresh_initgroups, sdap_refresh, BE_REQ_INITGROUPS); +REFRESH_SEND_RECV_FNS(sdap_refresh_users, sdap_refresh, BE_REQ_USER); +REFRESH_SEND_RECV_FNS(sdap_refresh_groups, sdap_refresh, BE_REQ_GROUP); +REFRESH_SEND_RECV_FNS(sdap_refresh_netgroups, sdap_refresh, BE_REQ_NETGROUP); + +errno_t sdap_refresh_init(struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx) +{ + errno_t ret; + struct be_refresh_cb sdap_refresh_callbacks[] = { + { .send_fn = sdap_refresh_initgroups_send, + .recv_fn = sdap_refresh_initgroups_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_users_send, + .recv_fn = sdap_refresh_users_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_groups_send, + .recv_fn = sdap_refresh_groups_recv, + .pvt = id_ctx, + }, + { .send_fn = sdap_refresh_netgroups_send, + .recv_fn = sdap_refresh_netgroups_recv, + .pvt = id_ctx, + }, + }; + + ret = be_refresh_ctx_init_with_callbacks(be_ctx, + SYSDB_NAME, + sdap_refresh_callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize background refresh\n"); + return ret; + } + + return ret; +} diff --git a/src/providers/ldap/sdap_reinit.c b/src/providers/ldap/sdap_reinit.c new file mode 100644 index 0000000..1764ecd --- /dev/null +++ b/src/providers/ldap/sdap_reinit.c @@ -0,0 +1,335 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2012 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 <talloc.h> +#include <tevent.h> +#include <string.h> +#include <ldb.h> + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_enum.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" + +struct sdap_reinit_cleanup_state { + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; +}; + +static errno_t sdap_reinit_clear_usn(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain); +static void sdap_reinit_cleanup_done(struct tevent_req *subreq); +static errno_t sdap_reinit_delete_records(struct sss_domain_info *domain); + +struct tevent_req* sdap_reinit_cleanup_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_reinit_cleanup_state *state; + int ret; + + /* + * 1. remove entryUSN attribute from all entries + * 2. run enumeration + * 3. remove records that doesn't have entryUSN attribute updated + * + * We don't need to do this for sudo rules, they will be refreshed + * automatically during next smart/full refresh, or when an expired rule + * is deleted. + */ + + req = tevent_req_create(mem_ctx, &state, struct sdap_reinit_cleanup_state); + if (req == NULL) { + return NULL; + } + + state->sysdb = be_ctx->domain->sysdb; + state->domain = be_ctx->domain; + + if (!be_ctx->domain->enumerate) { + /* enumeration is disabled, this whole process is meaningless */ + ret = EOK; + goto immediately; + } + + ret = sdap_reinit_clear_usn(state->sysdb, state->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to clear USN attributes [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + subreq = sdap_dom_enum_send(id_ctx, be_ctx->ev, id_ctx, + id_ctx->opts->sdom, id_ctx->conn); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue enumeration request\n"); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_reinit_cleanup_done, req); + + return req; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, be_ctx->ev); + + return req; +} + +static void sdap_delete_msgs_usn(struct sysdb_ctx *sysdb, + struct ldb_message **msgs, + size_t msgs_num) +{ + struct ldb_message_element el = { 0, SYSDB_USN, 0, NULL }; + struct sysdb_attrs usn_el = { 1, &el }; + errno_t ret; + int i; + + for (i = 0; i < msgs_num; i++) { + ret = sysdb_set_entry_attr(sysdb, msgs[i]->dn, &usn_el, SYSDB_MOD_DEL); + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to clean USN on entry: [%s]\n", + ldb_dn_get_linearized(msgs[i]->dn)); + } + } +} + +static errno_t sdap_reinit_clear_usn(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + struct ldb_message **msgs = NULL; + size_t msgs_num = 0; + const char *attrs[] = { "dn", NULL }; + int sret; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + /* reset users' usn */ + ret = sysdb_search_users(tmp_ctx, domain, + "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* reset groups' usn */ + ret = sysdb_search_groups(tmp_ctx, domain, "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* reset services' usn */ + ret = sysdb_search_services(tmp_ctx, domain, "", attrs, &msgs_num, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot search services [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + sdap_delete_msgs_usn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + ret = sysdb_transaction_commit(sysdb); + if (ret == EOK) { + in_transaction = false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not commit transaction\n"); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +static void sdap_reinit_cleanup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_reinit_cleanup_state *state = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_reinit_cleanup_state); + + ret = sdap_dom_enum_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Domain enumeration failed [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* Ok, we've completed an enumeration. Save this to the + * sysdb so we can postpone starting up the enumeration + * process on the next SSSD service restart (to avoid + * slowing down system boot-up + */ + ret = sysdb_set_enumerated(state->domain, SYSDB_HAS_ENUMERATED_ID, true); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not mark domain as having " + "enumerated.\n"); + /* This error is non-fatal, so continue */ + } + + ret = sdap_reinit_delete_records(state->domain); + if (ret != EOK) { + goto fail; + } + + tevent_req_done(req); + return; + +fail: + tevent_req_error(req, ret); +} + +static void sdap_delete_msgs_dn(struct sysdb_ctx *sysdb, + struct ldb_message **msgs, + size_t msgs_num) +{ + errno_t ret; + int i; + + for (i = 0; i < msgs_num; i++) { + ret = sysdb_delete_entry(sysdb, msgs[i]->dn, true); + if (ret) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to delete entry: [%s]\n", + ldb_dn_get_linearized(msgs[i]->dn)); + } + } +} + +static errno_t sdap_reinit_delete_records(struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + struct ldb_message **msgs = NULL; + size_t msgs_num = 0; + const char *attrs[] = { "dn", NULL }; + int sret; + errno_t ret; + struct sysdb_ctx *sysdb = domain->sysdb; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + goto done; + } + in_transaction = true; + + /* purge untouched users */ + ret = sysdb_search_users(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* purge untouched groups */ + ret = sysdb_search_groups(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + goto done; + } + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + /* purge untouched services */ + ret = sysdb_search_services(tmp_ctx, domain, "(!("SYSDB_USN"=*))", + attrs, &msgs_num, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot search services [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + sdap_delete_msgs_dn(sysdb, msgs, msgs_num); + talloc_zfree(msgs); + msgs_num = 0; + + ret = sysdb_transaction_commit(sysdb); + if (ret == EOK) { + in_transaction = false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not commit transaction\n"); + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +errno_t sdap_reinit_cleanup_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ldap/sdap_sudo.c b/src/providers/ldap/sdap_sudo.c new file mode 100644 index 0000000..8cea919 --- /dev/null +++ b/src/providers/ldap/sdap_sudo.c @@ -0,0 +1,231 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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 <errno.h> +#include <string.h> +#include <tevent.h> + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_sudo.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_handler_state { + uint32_t type; + struct dp_reply_std reply; + struct sdap_sudo_ctx *sudo_ctx; +}; + +static void sdap_sudo_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +sdap_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + struct dp_sudo_data *data, + struct dp_req_params *params) +{ + struct sdap_sudo_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = data->type; + state->sudo_ctx = sudo_ctx; + + switch (data->type) { + case BE_REQ_SUDO_FULL: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + subreq = sdap_sudo_full_refresh_send(state, sudo_ctx); + break; + case BE_REQ_SUDO_RULES: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a refresh of specific sudo rules\n"); + subreq = sdap_sudo_rules_refresh_send(state, sudo_ctx, data->rules); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", data->type); + ret = EINVAL; + goto immediately; + } + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request: %d\n", data->type); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void sdap_sudo_handler_done(struct tevent_req *subreq) +{ + struct sdap_sudo_handler_state *state; + struct tevent_req *req; + int dp_error; + bool deleted; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_handler_state); + + switch (state->type) { + case BE_REQ_SUDO_FULL: + ret = sdap_sudo_full_refresh_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* Reschedule the periodic task since the refresh was just finished + * per user request. */ + if (ret == EOK && dp_error == DP_ERR_OK) { + be_ptask_postpone(state->sudo_ctx->full_refresh); + } + break; + case BE_REQ_SUDO_RULES: + ret = sdap_sudo_rules_refresh_recv(subreq, &dp_error, &deleted); + talloc_zfree(subreq); + if (ret == EOK && deleted == true) { + ret = ENOENT; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", state->type); + dp_error = DP_ERR_FATAL; + ret = ERR_INTERNAL; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +static errno_t +sdap_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct sdap_sudo_handler_state *state = NULL; + + state = tevent_req_data(req, struct sdap_sudo_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +static void sdap_sudo_online_cb(void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + if (sudo_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "BUG: sudo_ctx is NULL\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "We are back online. SUDO host information will " + "be renewed on next refresh.\n"); + sudo_ctx->run_hostinfo = true; +} + +errno_t sdap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_attr_map *native_map, + struct dp_method *dp_methods) +{ + struct sdap_sudo_ctx *sudo_ctx; + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing sudo LDAP back end\n"); + + sudo_ctx = talloc_zero(mem_ctx, struct sdap_sudo_ctx); + if (sudo_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc() failed\n"); + return ENOMEM; + } + + sudo_ctx->id_ctx = id_ctx; + + ret = ldap_get_sudo_options(be_ctx->cdb, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->conf_path, id_ctx->opts, native_map, + &sudo_ctx->use_host_filter, + &sudo_ctx->include_regexp, + &sudo_ctx->include_netgroups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get SUDO options [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (sudo_ctx->use_host_filter) { + ret = be_add_online_cb(sudo_ctx, be_ctx, sdap_sudo_online_cb, + sudo_ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to install online callback " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Obtain hostinfo with the first refresh. */ + sudo_ctx->run_hostinfo = true; + } + + ret = sdap_sudo_ptask_setup(be_ctx, sudo_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to setup periodical refresh of " + "sudo rules [%d]: %s\n", ret, sss_strerror(ret)); + /* periodical updates will not work, but specific-rule update + * is no affected by this, therefore we don't have to fail here */ + } + + dp_set_method(dp_methods, DPM_SUDO_HANDLER, + sdap_sudo_handler_send, sdap_sudo_handler_recv, sudo_ctx, + struct sdap_sudo_ctx, struct dp_sudo_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sudo_ctx); + } + + return ret; +} diff --git a/src/providers/ldap/sdap_sudo.h b/src/providers/ldap/sdap_sudo.h new file mode 100644 index 0000000..85eeccf --- /dev/null +++ b/src/providers/ldap/sdap_sudo.h @@ -0,0 +1,106 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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/>. +*/ + +#ifndef _SDAP_SUDO_H_ +#define _SDAP_SUDO_H_ + +#include "providers/backend.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_sudo_ctx { + struct sdap_id_ctx *id_ctx; + struct be_ptask *full_refresh; + struct be_ptask *smart_refresh; + + char **hostnames; + char **ip_addr; + bool include_netgroups; + bool include_regexp; + bool use_host_filter; + + bool full_refresh_done; + + bool run_hostinfo; +}; + +/* Common functions from ldap_sudo.c */ + +errno_t sdap_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_attr_map *native_map, + struct dp_method *dp_methods); + +/* sdap async interface */ +struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char *ldap_filter, + const char *sysdb_filter, + bool update_usn); + +int sdap_sudo_refresh_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *dp_error, + size_t *num_rules); + +struct tevent_req *sdap_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx); + +int sdap_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx); + +int sdap_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error); + +struct tevent_req *sdap_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char **rules); + +int sdap_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted); + +errno_t +sdap_sudo_ptask_setup(struct be_ctx *be_ctx, struct sdap_sudo_ctx *sudo_ctx); + +/* host info */ +struct tevent_req * sdap_sudo_get_hostinfo_send(TALLOC_CTX *mem_ctx, + struct sdap_options *opts, + struct be_ctx *be_ctx); + +int sdap_sudo_get_hostinfo_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char ***hostnames, char ***ip_addr); + +/* (&(objectClass=sudoRole)(|(cn=defaults)(sudoUser=ALL)%s)) */ +#define SDAP_SUDO_FILTER_USER "(&(objectClass=%s)(|(%s=%s)(%s=ALL)%s))" +#define SDAP_SUDO_FILTER_CLASS "(%s=%s)" +#define SDAP_SUDO_FILTER_DEFAULTS "(&(objectClass=%s)(%s=%s))" +#define SDAP_SUDO_DEFAULTS "defaults" + +#define SDAP_SUDO_FILTER_USERNAME "(%s=%s)" +#define SDAP_SUDO_FILTER_UID "(%s=#%u)" +#define SDAP_SUDO_FILTER_GROUP "(%s=%%%s)" +#define SDAP_SUDO_FILTER_NETGROUP "(%s=+%s)" + +#endif /* _SDAP_SUDO_H_ */ diff --git a/src/providers/ldap/sdap_sudo_refresh.c b/src/providers/ldap/sdap_sudo_refresh.c new file mode 100644 index 0000000..a484c6a --- /dev/null +++ b/src/providers/ldap/sdap_sudo_refresh.c @@ -0,0 +1,485 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct sdap_sudo_full_refresh_state { + struct sdap_sudo_ctx *sudo_ctx; + struct sdap_id_ctx *id_ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + int dp_error; +}; + +static void sdap_sudo_full_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_sudo_full_refresh_state *state = NULL; + char *search_filter = NULL; + char *delete_filter = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_full_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->sudo_ctx = sudo_ctx; + state->id_ctx = id_ctx; + state->sysdb = id_ctx->be->domain->sysdb; + state->domain = id_ctx->be->domain; + + /* Download all rules from LDAP */ + search_filter = talloc_asprintf(state, SDAP_SUDO_FILTER_CLASS, + id_ctx->opts->sudorule_map[SDAP_AT_SUDO_OC].name, + id_ctx->opts->sudorule_map[SDAP_OC_SUDORULE].name); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Remove all rules from cache */ + delete_filter = talloc_asprintf(state, "(%s=%s)", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + + subreq = sdap_sudo_refresh_send(state, sudo_ctx, search_filter, + delete_filter, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_full_refresh_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static void sdap_sudo_full_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_full_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_full_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + /* save the time in the sysdb */ + ret = sysdb_sudo_set_last_full_refresh(state->domain, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to save time of " + "a successful full refresh\n"); + /* this is only a minor error that does not affect the functionality, + * therefore there is no need to report it with tevent_req_error() + * which would cause problems in the consumers */ + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful full refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* We just finished full request, we can postpone smart refresh. */ + be_ptask_postpone(state->sudo_ctx->smart_refresh); + + tevent_req_done(req); +} + +int sdap_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_sudo_full_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_full_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_sudo_smart_refresh_state { + struct sdap_id_ctx *id_ctx; + struct sysdb_ctx *sysdb; + int dp_error; +}; + +static void sdap_sudo_smart_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_attr_map *map = id_ctx->opts->sudorule_map; + struct sdap_server_opts *srv_opts = id_ctx->srv_opts; + struct sdap_sudo_smart_refresh_state *state = NULL; + char *search_filter = NULL; + const char *usn; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_smart_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (be_ptask_running(sudo_ctx->full_refresh)) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping smart refresh because " + "there is ongoing full refresh.\n"); + state->dp_error = DP_ERR_OK; + ret = EOK; + goto immediately; + } + + state->id_ctx = id_ctx; + state->sysdb = id_ctx->be->domain->sysdb; + + /* Download all rules from LDAP that are newer than usn */ + if (srv_opts == NULL || srv_opts->max_sudo_value == NULL + || strcmp(srv_opts->max_sudo_value, "0") == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "USN value is unknown, assuming zero and " + "omitting it from the filter.\n"); + usn = "0"; + search_filter = talloc_asprintf(state, "(%s=%s)", + map[SDAP_AT_SUDO_OC].name, + map[SDAP_OC_SUDORULE].name); + } else { + usn = srv_opts->max_sudo_value; + search_filter = talloc_asprintf(state, "(&(%s=%s)(%s>=%s))", + map[SDAP_AT_SUDO_OC].name, + map[SDAP_OC_SUDORULE].name, + map[SDAP_AT_SUDO_USN].name, usn); + } + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Do not remove any rules that are already in the sysdb + * sysdb_filter = NULL; */ + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a smart refresh of sudo rules " + "(USN >= %s)\n", usn); + + subreq = sdap_sudo_refresh_send(state, sudo_ctx, search_filter, NULL, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_smart_refresh_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, id_ctx->be->ev); + + return req; +} + +static void sdap_sudo_smart_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_smart_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_smart_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful smart refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct sdap_sudo_smart_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_smart_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct sdap_sudo_rules_refresh_state { + struct sdap_id_ctx *id_ctx; + size_t num_rules; + int dp_error; + bool deleted; +}; + +static void sdap_sudo_rules_refresh_done(struct tevent_req *subreq); + +struct tevent_req *sdap_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct sdap_sudo_ctx *sudo_ctx, + const char **rules) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_sudo_rules_refresh_state *state = NULL; + struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx; + struct sdap_options *opts = id_ctx->opts; + TALLOC_CTX *tmp_ctx = NULL; + char *search_filter = NULL; + char *delete_filter = NULL; + char *safe_rule = NULL; + int ret; + int i; + + if (rules == NULL) { + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_rules_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + search_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + delete_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + + /* Download only selected rules from LDAP */ + /* Remove all selected rules from cache */ + for (i = 0; rules[i] != NULL; i++) { + ret = sss_filter_sanitize(tmp_ctx, rules[i], &safe_rule); + if (ret != EOK) { + ret = ENOMEM; + goto immediately; + } + + search_filter = talloc_asprintf_append_buffer(search_filter, "(%s=%s)", + opts->sudorule_map[SDAP_AT_SUDO_NAME].name, + safe_rule); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf_append_buffer(delete_filter, "(%s=%s)", + SYSDB_SUDO_CACHE_AT_CN, + safe_rule); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + state->id_ctx = sudo_ctx->id_ctx; + state->num_rules = i; + + search_filter = talloc_asprintf(tmp_ctx, "(&"SDAP_SUDO_FILTER_CLASS"(|%s))", + opts->sudorule_map[SDAP_AT_SUDO_OC].name, + opts->sudorule_map[SDAP_OC_SUDORULE].name, + search_filter); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(|%s))", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC, + delete_filter); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_sudo_refresh_send(req, sudo_ctx, search_filter, + delete_filter, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_sudo_rules_refresh_done, req); + + ret = EOK; +immediately: + talloc_free(tmp_ctx); + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + } + + return req; +} + +static void sdap_sudo_rules_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct sdap_sudo_rules_refresh_state *state = NULL; + size_t downloaded_rules_num; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_sudo_rules_refresh_state); + + ret = sdap_sudo_refresh_recv(state, subreq, &state->dp_error, + &downloaded_rules_num); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + state->deleted = downloaded_rules_num != state->num_rules ? true : false; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted) +{ + struct sdap_sudo_rules_refresh_state *state = NULL; + state = tevent_req_data(req, struct sdap_sudo_rules_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + *deleted = state->deleted; + + return EOK; +} + +static struct tevent_req * +sdap_sudo_ptask_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + + return sdap_sudo_full_refresh_send(mem_ctx, sudo_ctx); +} + +static errno_t +sdap_sudo_ptask_full_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return sdap_sudo_full_refresh_recv(req, &dp_error); +} + +static struct tevent_req * +sdap_sudo_ptask_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct sdap_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct sdap_sudo_ctx); + + return sdap_sudo_smart_refresh_send(mem_ctx, sudo_ctx); +} + +static errno_t +sdap_sudo_ptask_smart_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return sdap_sudo_smart_refresh_recv(req, &dp_error); +} + +errno_t +sdap_sudo_ptask_setup(struct be_ctx *be_ctx, struct sdap_sudo_ctx *sudo_ctx) +{ + return sdap_sudo_ptask_setup_generic(be_ctx, sudo_ctx->id_ctx->opts->basic, + sdap_sudo_ptask_full_refresh_send, + sdap_sudo_ptask_full_refresh_recv, + sdap_sudo_ptask_smart_refresh_send, + sdap_sudo_ptask_smart_refresh_recv, + sudo_ctx, + &sudo_ctx->full_refresh, + &sudo_ctx->smart_refresh); +} diff --git a/src/providers/ldap/sdap_sudo_shared.c b/src/providers/ldap/sdap_sudo_shared.c new file mode 100644 index 0000000..2ddfbd7 --- /dev/null +++ b/src/providers/ldap/sdap_sudo_shared.c @@ -0,0 +1,227 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <errno.h> +#include <time.h> +#include <talloc.h> + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ldap/sdap.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +errno_t +sdap_sudo_ptask_setup_generic(struct be_ctx *be_ctx, + struct dp_option *opts, + be_ptask_send_t full_send_fn, + be_ptask_recv_t full_recv_fn, + be_ptask_send_t smart_send_fn, + be_ptask_recv_t smart_recv_fn, + void *pvt, + struct be_ptask **_full_refresh, + struct be_ptask **_smart_refresh) +{ + time_t smart; + time_t full; + time_t delay; + time_t last_refresh; + time_t offset; + errno_t ret; + + smart = dp_opt_get_int(opts, SDAP_SUDO_SMART_REFRESH_INTERVAL); + full = dp_opt_get_int(opts, SDAP_SUDO_FULL_REFRESH_INTERVAL); + offset = dp_opt_get_int(opts, SDAP_SUDO_RANDOM_OFFSET); + + if (smart == 0 && full == 0) { + /* We don't allow both types to be disabled. At least smart refresh + * needs to be enabled. In this case smart refresh will catch up new + * and modified rules and deleted rules are caught when expired. */ + smart = opts[SDAP_SUDO_SMART_REFRESH_INTERVAL].def_val.number; + + DEBUG(SSSDBG_CONF_SETTINGS, "At least smart refresh needs to be " + "enabled. Setting smart refresh interval to default value " + "(%"SPRItime") seconds.\n", smart); + } else if (full > 0 && full <= smart) { + /* In this case it does not make any sense to run smart refresh. */ + smart = 0; + + DEBUG(SSSDBG_CONF_SETTINGS, "Smart refresh interval has to be lower " + "than full refresh interval. Periodical smart refresh will be " + "disabled.\n"); + } + + ret = sysdb_sudo_get_last_full_refresh(be_ctx->domain, &last_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to obtain time of last full " + "refresh. Assuming none was performed so far.\n"); + last_refresh = 0; + } + + if (last_refresh == 0) { + /* If this is the first startup, we need to kick off an refresh + * immediately, to close a window where clients requesting sudo + * information won't get an immediate reply with no entries */ + delay = 0; + } else { + /* At least one update has previously run, so clients will get cached + * data. We will delay the refresh so we don't slow down the startup + * process if this is happening during system boot. */ + delay = 10; + } + + /* Full refresh. + * + * Disable when offline and run immediately when SSSD goes back online. + * Since we have periodical online check we don't have to run this task + * when offline. */ + if (full > 0) { + ret = be_ptask_create(be_ctx, be_ctx, full, delay, 0, offset, full, 0, + full_send_fn, full_recv_fn, pvt, + "SUDO Full Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + _full_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup full refresh ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + /* Smart refresh. + * + * Disable when offline and reschedule normally when SSSD goes back online. + * Since we have periodical online check we don't have to run this task + * when offline. */ + if (smart > 0) { + ret = be_ptask_create(be_ctx, be_ctx, smart, delay + smart, smart, + offset, smart, 0, + smart_send_fn, smart_recv_fn, pvt, + "SUDO Smart Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + _smart_refresh); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup smart refresh ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +} + +static char * +sdap_sudo_new_usn(TALLOC_CTX *mem_ctx, + unsigned long usn, + const char *leftover) +{ + const char *str = leftover == NULL ? "" : leftover; + char *newusn; + + /* Current largest USN is unknown so we keep "0" to indicate it. */ + if (usn == 0) { + return talloc_strdup(mem_ctx, "0"); + } + + /* We increment USN number so that we can later use simplified filter + * (just usn >= last+1 instead of usn >= last && usn != last). + */ + usn++; + + /* Convert back to string appending non-converted values since it + * is an indicator that modifyTimestamp is used instead of entryUSN. + * modifyTimestamp contains also timezone specification, usually Z. + * We can't really handle any errors here so we just use what we got. */ + newusn = talloc_asprintf(mem_ctx, "%lu%s", usn, str); + if (newusn == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to change USN value (OOM)!\n"); + return NULL; + } + + return newusn; +} + +void +sdap_sudo_set_usn(struct sdap_server_opts *srv_opts, + const char *usn) +{ + unsigned long usn_number; + char *newusn; + char *timezone = NULL; + errno_t ret; + + if (srv_opts == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Bug: srv_opts is NULL\n"); + return; + } + + if (usn == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Bug: usn is NULL\n"); + return; + } + + /* If usn == 0 it means that no new rules were found. We will use last known + * USN number as the new highest value. However, we need to get the timezone + * information in case this is a modify timestamp attribute instead of usn. + */ + if (!srv_opts->supports_usn && strcmp("0", usn) == 0) { + usn_number = 0; + + /* The value may not be defined yet. */ + if (srv_opts->max_sudo_value == NULL) { + timezone = NULL; + } else { + errno = 0; + strtoul(srv_opts->max_sudo_value, &timezone, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert USN %s [%d]: %s\n", + srv_opts->max_sudo_value, ret, sss_strerror(ret)); + return; + } + } + } else { + errno = 0; + usn_number = strtoul(usn, &timezone, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert USN %s [%d]: %s\n", + usn, ret, sss_strerror(ret)); + return; + } + } + + if (usn_number > srv_opts->last_usn) { + srv_opts->last_usn = usn_number; + } + + newusn = sdap_sudo_new_usn(srv_opts, srv_opts->last_usn, timezone); + if (newusn == NULL) { + return; + } + + talloc_zfree(srv_opts->max_sudo_value); + srv_opts->max_sudo_value = newusn; + + DEBUG(SSSDBG_FUNC_DATA, "SUDO higher USN value: [%s]\n", + srv_opts->max_sudo_value); +} diff --git a/src/providers/ldap/sdap_sudo_shared.h b/src/providers/ldap/sdap_sudo_shared.h new file mode 100644 index 0000000..846f3f8 --- /dev/null +++ b/src/providers/ldap/sdap_sudo_shared.h @@ -0,0 +1,42 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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/>. +*/ + +#ifndef _SDAP_SUDO_SHARED_H_ +#define _SDAP_SUDO_SHARED_H_ + +#include "providers/backend.h" +#include "providers/be_ptask.h" + +errno_t +sdap_sudo_ptask_setup_generic(struct be_ctx *be_ctx, + struct dp_option *opts, + be_ptask_send_t full_send_fn, + be_ptask_recv_t full_recv_fn, + be_ptask_send_t smart_send_fn, + be_ptask_recv_t smart_recv_fn, + void *pvt, + struct be_ptask **_full_refresh, + struct be_ptask **_smart_refresh); + +void +sdap_sudo_set_usn(struct sdap_server_opts *srv_opts, + const char *usn); + +#endif /* _SDAP_SUDO_SHARED_H_ */ diff --git a/src/providers/ldap/sdap_users.h b/src/providers/ldap/sdap_users.h new file mode 100644 index 0000000..74284cd --- /dev/null +++ b/src/providers/ldap/sdap_users.h @@ -0,0 +1,42 @@ +/* + SSSD + + Async LDAP Helper routines + + Copyright (C) Simo Sorce <ssorce@redhat.com> + + 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/>. +*/ + +#ifndef _SDAP_USERS_H_ +#define _SDAP_USERS_H_ + +#include "config.h" + +/* shared non-async user functions */ + +errno_t sdap_fallback_local_user(TALLOC_CTX *memctx, + const char *name, uid_t uid, + struct sysdb_attrs ***reply); + +int sdap_save_user(TALLOC_CTX *memctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + struct sysdb_attrs *mapped_attrs, + char **_usn_value, + time_t now, + bool set_non_posix); + +#endif /* _SDAP_USERS_H_ */ diff --git a/src/providers/ldap/sdap_utils.c b/src/providers/ldap/sdap_utils.c new file mode 100644 index 0000000..6d54310 --- /dev/null +++ b/src/providers/ldap/sdap_utils.c @@ -0,0 +1,235 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2013 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 <ctype.h> +#include "util/util.h" +#include "providers/ldap/sdap_async.h" + +errno_t +sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs, + const char *attr_name, + const char *attr_desc, + bool multivalued, + const char *name, + struct sysdb_attrs *attrs) +{ + errno_t ret; + struct ldb_message_element *el; + const char *objname = name ?: "object"; + const char *desc = attr_desc ?: attr_name; + unsigned int num_values, i; + char *printable; + + ret = sysdb_attrs_get_el(ldap_attrs, attr_name, &el); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get %s from the " + "list of the LDAP attributes [%d]: %s\n", + attr_name, ret, strerror(ret)); + return ret; + } + + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, "%s is not available " + "for [%s].\n", desc, objname); + } else { + num_values = multivalued ? el->num_values : 1; + for (i = 0; i < num_values; i++) { + printable = ldb_binary_encode(ldap_attrs, el->values[i]); + if (printable == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "ldb_binary_encode failed..\n"); + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Adding %s [%s] to attributes " + "of [%s].\n", desc, printable, objname); + + talloc_zfree(printable); + + ret = sysdb_attrs_add_mem(attrs, attr_name, el->values[i].data, + el->values[i].length); + if (ret) { + return ret; + } + } + } + + return EOK; +} + +errno_t +sdap_save_all_names(const char *name, + struct sysdb_attrs *ldap_attrs, + struct sss_domain_info *dom, + enum sysdb_member_type entry_type, + struct sysdb_attrs *attrs) +{ + const char **aliases = NULL; + const char *sysdb_alias; + errno_t ret; + TALLOC_CTX *tmp_ctx; + int i; + bool lowercase = !dom->case_sensitive; + bool store_as_fqdn; + + switch (entry_type) { + case SYSDB_MEMBER_USER: + case SYSDB_MEMBER_GROUP: + store_as_fqdn = true; + break; + default: + store_as_fqdn = false; + break; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_aliases(tmp_ctx, ldap_attrs, name, + lowercase, &aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get the alias list\n"); + goto done; + } + + for (i = 0; aliases[i]; i++) { + if (store_as_fqdn) { + sysdb_alias = sss_create_internal_fqname(tmp_ctx, aliases[i], + dom->name); + } else { + sysdb_alias = aliases[i]; + } + + if (sysdb_alias == NULL) { + ret = ENOMEM; + goto done; + } + + if (lowercase) { + ret = sysdb_attrs_add_lc_name_alias(attrs, sysdb_alias); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add lower-cased version " + "of alias [%s] into the " + "attribute list\n", aliases[i]); + goto done; + } + } else { + ret = sysdb_attrs_add_string(attrs, SYSDB_NAME_ALIAS, sysdb_alias); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add alias [%s] into the " + "attribute list\n", aliases[i]); + goto done; + } + } + + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t deref_string_to_val(const char *str, int *val) +{ + if (strcasecmp(str, "never") == 0) { + *val = LDAP_DEREF_NEVER; + } else if (strcasecmp(str, "searching") == 0) { + *val = LDAP_DEREF_SEARCHING; + } else if (strcasecmp(str, "finding") == 0) { + *val = LDAP_DEREF_FINDING; + } else if (strcasecmp(str, "always") == 0) { + *val = LDAP_DEREF_ALWAYS; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal deref option [%s].\n", str); + return EINVAL; + } + + return EOK; +} + +static char * +sdap_combine_filters_ex(TALLOC_CTX *mem_ctx, + char operator, + const char *base_filter, + const char *extra_filter) +{ + char *filter = NULL; + + if (extra_filter == NULL || extra_filter[0] == '\0') { + return talloc_strdup(mem_ctx, base_filter); + } else if (base_filter == NULL || base_filter[0] == '\0') { + return talloc_strdup(mem_ctx, extra_filter); + } + + if (extra_filter[0] == '(') { + filter = talloc_asprintf(mem_ctx, "(%c%s%s)", + operator, base_filter, extra_filter); + } else { + filter = talloc_asprintf(mem_ctx, "(%c%s(%s))", + operator, base_filter, extra_filter); + } + + return filter; /* NULL or not */ +} + +char *sdap_or_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter) +{ + return sdap_combine_filters_ex(mem_ctx, '|', base_filter, extra_filter); +} + +char *sdap_combine_filters(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *extra_filter) +{ + return sdap_combine_filters_ex(mem_ctx, '&', base_filter, extra_filter); +} + +char *get_enterprise_principal_string_filter(TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *princ, + struct dp_option *sdap_basic_opts) +{ + const char *realm; + char *p; + + if (attr_name == NULL || princ == NULL || sdap_basic_opts == NULL) { + return NULL; + } + + realm = dp_opt_get_cstring(sdap_basic_opts, SDAP_KRB5_REALM); + if (realm == NULL) { + return NULL; + } + + p = strchr(princ, '@'); + if (p == NULL) { + return NULL; + } + + return talloc_asprintf(mem_ctx, "(%s=%.*s\\\\@%s@%s)", attr_name, + (int) (p - princ), + princ, + p + 1, realm); +} |