summaryrefslogtreecommitdiffstats
path: root/src/providers/ldap
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ldap')
-rw-r--r--src/providers/ldap/ldap_access.c128
-rw-r--r--src/providers/ldap/ldap_auth.c1641
-rw-r--r--src/providers/ldap/ldap_auth.h49
-rw-r--r--src/providers/ldap/ldap_child.c788
-rw-r--r--src/providers/ldap/ldap_common.c891
-rw-r--r--src/providers/ldap/ldap_common.h486
-rw-r--r--src/providers/ldap/ldap_id.c1955
-rw-r--r--src/providers/ldap/ldap_id_cleanup.c520
-rw-r--r--src/providers/ldap/ldap_id_enum.c212
-rw-r--r--src/providers/ldap/ldap_id_netgroup.c247
-rw-r--r--src/providers/ldap/ldap_id_services.c308
-rw-r--r--src/providers/ldap/ldap_id_subid.c255
-rw-r--r--src/providers/ldap/ldap_init.c529
-rw-r--r--src/providers/ldap/ldap_options.c827
-rw-r--r--src/providers/ldap/ldap_opts.c425
-rw-r--r--src/providers/ldap/ldap_opts.h69
-rw-r--r--src/providers/ldap/ldap_resolver_cleanup.c230
-rw-r--r--src/providers/ldap/ldap_resolver_enum.c299
-rw-r--r--src/providers/ldap/ldap_resolver_enum.h44
-rw-r--r--src/providers/ldap/sdap.c2129
-rw-r--r--src/providers/ldap/sdap.h746
-rw-r--r--src/providers/ldap/sdap_access.c2402
-rw-r--r--src/providers/ldap/sdap_access.h114
-rw-r--r--src/providers/ldap/sdap_ad_groups.c69
-rw-r--r--src/providers/ldap/sdap_async.c3169
-rw-r--r--src/providers/ldap/sdap_async.h455
-rw-r--r--src/providers/ldap/sdap_async_ad.h59
-rw-r--r--src/providers/ldap/sdap_async_autofs.c1479
-rw-r--r--src/providers/ldap/sdap_async_connection.c2386
-rw-r--r--src/providers/ldap/sdap_async_enum.c773
-rw-r--r--src/providers/ldap/sdap_async_enum.h49
-rw-r--r--src/providers/ldap/sdap_async_groups.c2471
-rw-r--r--src/providers/ldap/sdap_async_hosts.c209
-rw-r--r--src/providers/ldap/sdap_async_initgroups.c3647
-rw-r--r--src/providers/ldap/sdap_async_initgroups_ad.c1742
-rw-r--r--src/providers/ldap/sdap_async_iphost.c640
-rw-r--r--src/providers/ldap/sdap_async_ipnetwork.c625
-rw-r--r--src/providers/ldap/sdap_async_nested_groups.c2997
-rw-r--r--src/providers/ldap/sdap_async_netgroups.c778
-rw-r--r--src/providers/ldap/sdap_async_private.h196
-rw-r--r--src/providers/ldap/sdap_async_resolver_enum.c318
-rw-r--r--src/providers/ldap/sdap_async_resolver_enum.h36
-rw-r--r--src/providers/ldap/sdap_async_services.c644
-rw-r--r--src/providers/ldap/sdap_async_sudo.c698
-rw-r--r--src/providers/ldap/sdap_async_sudo_hostinfo.c516
-rw-r--r--src/providers/ldap/sdap_async_users.c1209
-rw-r--r--src/providers/ldap/sdap_autofs.c491
-rw-r--r--src/providers/ldap/sdap_autofs.h62
-rw-r--r--src/providers/ldap/sdap_certmap.c150
-rw-r--r--src/providers/ldap/sdap_child_helpers.c515
-rw-r--r--src/providers/ldap/sdap_domain.c232
-rw-r--r--src/providers/ldap/sdap_dyndns.c713
-rw-r--r--src/providers/ldap/sdap_dyndns.h49
-rw-r--r--src/providers/ldap/sdap_fd_events.c357
-rw-r--r--src/providers/ldap/sdap_hostid.c324
-rw-r--r--src/providers/ldap/sdap_hostid.h40
-rw-r--r--src/providers/ldap/sdap_id_op.c1054
-rw-r--r--src/providers/ldap/sdap_id_op.h76
-rw-r--r--src/providers/ldap/sdap_idmap.c629
-rw-r--r--src/providers/ldap/sdap_idmap.h63
-rw-r--r--src/providers/ldap/sdap_iphost.c375
-rw-r--r--src/providers/ldap/sdap_ipnetwork.c378
-rw-r--r--src/providers/ldap/sdap_online_check.c244
-rw-r--r--src/providers/ldap/sdap_ops.c553
-rw-r--r--src/providers/ldap/sdap_ops.h99
-rw-r--r--src/providers/ldap/sdap_range.c142
-rw-r--r--src/providers/ldap/sdap_range.h34
-rw-r--r--src/providers/ldap/sdap_refresh.c238
-rw-r--r--src/providers/ldap/sdap_reinit.c335
-rw-r--r--src/providers/ldap/sdap_sudo.c231
-rw-r--r--src/providers/ldap/sdap_sudo.h106
-rw-r--r--src/providers/ldap/sdap_sudo_refresh.c485
-rw-r--r--src/providers/ldap/sdap_sudo_shared.c227
-rw-r--r--src/providers/ldap/sdap_sudo_shared.h42
-rw-r--r--src/providers/ldap/sdap_users.h42
-rw-r--r--src/providers/ldap/sdap_utils.c235
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, &param);
+ 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);
+}