diff options
Diffstat (limited to 'src/providers/ldap')
-rw-r--r-- | src/providers/ldap/ldap_auth.c | 1055 | ||||
-rw-r--r-- | src/providers/ldap/ldap_child.c | 429 | ||||
-rw-r--r-- | src/providers/ldap/ldap_common.c | 589 | ||||
-rw-r--r-- | src/providers/ldap/ldap_common.h | 115 | ||||
-rw-r--r-- | src/providers/ldap/ldap_id.c | 795 | ||||
-rw-r--r-- | src/providers/ldap/ldap_id_cleanup.c | 555 | ||||
-rw-r--r-- | src/providers/ldap/ldap_id_enum.c | 608 | ||||
-rw-r--r-- | src/providers/ldap/ldap_init.c | 179 | ||||
-rw-r--r-- | src/providers/ldap/sdap.c | 388 | ||||
-rw-r--r-- | src/providers/ldap/sdap.h | 258 | ||||
-rw-r--r-- | src/providers/ldap/sdap_async.c | 1018 | ||||
-rw-r--r-- | src/providers/ldap/sdap_async.h | 126 | ||||
-rw-r--r-- | src/providers/ldap/sdap_async_accounts.c | 2065 | ||||
-rw-r--r-- | src/providers/ldap/sdap_async_connection.c | 1141 | ||||
-rw-r--r-- | src/providers/ldap/sdap_async_private.h | 68 | ||||
-rw-r--r-- | src/providers/ldap/sdap_child_helpers.c | 462 |
16 files changed, 9851 insertions, 0 deletions
diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c new file mode 100644 index 000000000..cfe8adb97 --- /dev/null +++ b/src/providers/ldap/ldap_auth.c @@ -0,0 +1,1055 @@ +/* + SSSD + + LDAP Backend Module + + Authors: + Sumit Bose <sbose@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/>. +*/ + +#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 + +#define _XOPEN_SOURCE 500 /* for strptime() */ +#include <time.h> +#undef _XOPEN_SOURCE +#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" + +enum pwexpire { + PWEXPIRE_NONE = 0, + PWEXPIRE_LDAP_PASSWORD_POLICY, + PWEXPIRE_KERBEROS, + PWEXPIRE_SHADOW +}; + +static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now, + enum sdap_result *result) +{ + char *end; + struct tm tm = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + time_t expire_time; + + *result = SDAP_AUTH_FAILED; + + end = strptime(expire_date, "%Y%m%d%H%M%SZ", &tm); + if (end == NULL) { + DEBUG(1, ("Kerberos expire date [%s] invalid.\n", expire_date)); + return EINVAL; + } + if (*end != '\0') { + DEBUG(1, ("Kerberos expire date [%s] contains extra characters.\n", + expire_date)); + return EINVAL; + } + + expire_time = mktime(&tm); + if (expire_time == -1) { + DEBUG(1, ("mktime failed to convert [%s].\n", expire_date)); + return EINVAL; + } + + tzset(); + expire_time -= timezone; + DEBUG(9, ("Time info: tzname[0] [%s] tzname[1] [%s] timezone [%d] " + "daylight [%d] now [%d] expire_time [%d].\n", tzname[0], + tzname[1], timezone, daylight, now, expire_time)); + + if (difftime(now, expire_time) > 0.0) { + DEBUG(4, ("Kerberos password expired.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + } else { + *result = SDAP_AUTH_SUCCESS; + } + + return EOK; +} + +static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now, + enum sdap_result *result) +{ + long today; + long password_age; + + if (spwd->sp_lstchg <= 0) { + DEBUG(4, ("Last change day is not set, new password needed.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + return EOK; + } + + today = (long) (now / (60 * 60 *24)); + password_age = today - spwd->sp_lstchg; + if (password_age < 0) { + DEBUG(2, ("The last password change time is in the future!.\n")); + *result = SDAP_AUTH_SUCCESS; + 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(4, ("Account expired.\n")); + *result = SDAP_ACCT_EXPIRED; + return EOK; + } + + if (spwd->sp_max != -1 && password_age > spwd->sp_max) { + DEBUG(4, ("Password expired.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + return EOK; + } + +/* TODO: evaluate spwd->min and spwd->warn */ + + *result = SDAP_AUTH_SUCCESS; + return EOK; +} + +static errno_t string_to_shadowpw_days(const char *s, long *d) +{ + long l; + char *endptr; + + if (s == NULL || *s == '\0') { + *d = -1; + return EOK; + } + + errno = 0; + l = strtol(s, &endptr, 10); + if (errno != 0) { + DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + if (*endptr != '\0') { + DEBUG(1, ("Input string [%s] is invalid.\n", s)); + return EINVAL; + } + + if (*d < -1) { + DEBUG(1, ("Input string contains not allowed negative value [%d].\n", + *d)); + return EINVAL; + } + + *d = l; + + return EOK; +} + +static errno_t find_password_expiration_attributes(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + struct dp_option *opts, + enum pwexpire *type, void **data) +{ + const char *mark; + const char *val; + struct spwd *spwd; + const char *pwd_policy; + int ret; + + *type = PWEXPIRE_NONE; + *data = NULL; + + pwd_policy = dp_opt_get_string(opts, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(1, ("Missing password policy.\n")); + return EINVAL; + } + + mark = ldb_msg_find_attr_as_string(msg, SYSDB_PWD_ATTRIBUTE, NULL); + if (mark != NULL) { + DEBUG(9, ("Found pwdAttribute, " + "assuming LDAP password policies are active.\n")); + + *type = PWEXPIRE_LDAP_PASSWORD_POLICY; + return EOK; + } + + if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) == 0) { + DEBUG(9, ("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(9, ("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(1, ("talloc_strdup failed.\n")); + return ENOMEM; + } + *type = PWEXPIRE_KERBEROS; + + return EOK; + } + } else { + DEBUG(1, ("No Kerberos password expiration attributes found, " + "but MIT Kerberos password policy was requested.\n")); + return EINVAL; + } + } 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(9, ("Found shadow password expiration attributes.\n")) + spwd = talloc_zero(mem_ctx, struct spwd); + if (spwd == NULL) { + DEBUG(1, ("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; + *type = PWEXPIRE_SHADOW; + + return EOK; + } else { + DEBUG(1, ("No shadow password attributes found, " + "but shadow password policy was requested.\n")); + return EINVAL; + } + } + + DEBUG(9, ("No password expiration attributes found.\n")); + return EOK; + +shadow_fail: + talloc_free(spwd); + return ret; +} + +/* ==Get-User-DN========================================================== */ + +struct get_user_dn_state { + struct tevent_context *ev; + struct sdap_auth_ctx *ctx; + struct sdap_handle *sh; + + const char **attrs; + const char *name; + + char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; +}; + +static void get_user_dn_done(void *pvt, int err, struct ldb_result *res); + +struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_auth_ctx *ctx, + struct sdap_handle *sh, + const char *username) +{ + struct tevent_req *req; + struct get_user_dn_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct get_user_dn_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sh = sh; + state->name = username; + + state->attrs = talloc_array(state, const char *, 11); + if (!state->attrs) { + talloc_zfree(req); + return NULL; + } + state->attrs[0] = SYSDB_ORIG_DN; + state->attrs[1] = SYSDB_SHADOWPW_LASTCHANGE; + state->attrs[2] = SYSDB_SHADOWPW_MIN; + state->attrs[3] = SYSDB_SHADOWPW_MAX; + state->attrs[4] = SYSDB_SHADOWPW_WARNING; + state->attrs[5] = SYSDB_SHADOWPW_INACTIVE; + state->attrs[6] = SYSDB_SHADOWPW_EXPIRE; + state->attrs[7] = SYSDB_KRBPW_LASTCHANGE; + state->attrs[8] = SYSDB_KRBPW_EXPIRATION; + state->attrs[9] = SYSDB_PWD_ATTRIBUTE; + state->attrs[10] = NULL; + + /* this sysdb call uses a sysdn operation, which means it will be + * schedule only after we return, no timer hack needed */ + ret = sysdb_get_user_attr(state, state->ctx->be->sysdb, + state->ctx->be->domain, state->name, + state->attrs, get_user_dn_done, req); + if (ret) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void get_user_dn_done(void *pvt, int err, struct ldb_result *res) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + const char *dn; + int ret; + + if (err != LDB_SUCCESS) { + tevent_req_error(req, EIO); + return; + } + + switch (res->count) { + case 0: + /* FIXME: not in cache, needs a true search */ + tevent_req_error(req, ENOENT); + break; + + case 1: + dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL); + if (!dn) { + /* TODO: try to search ldap server ? */ + + /* FIXME: remove once we store originalDN on every call + * NOTE: this is wrong, works only with some DITs */ + dn = talloc_asprintf(state, "%s=%s,%s", + state->ctx->opts->user_map[SDAP_AT_USER_NAME].name, + state->name, + dp_opt_get_string(state->ctx->opts->basic, + SDAP_USER_SEARCH_BASE)); + if (!dn) { + tevent_req_error(req, ENOMEM); + break; + } + } + + state->dn = talloc_strdup(state, dn); + if (!state->dn) { + tevent_req_error(req, ENOMEM); + break; + } + + ret = find_password_expiration_attributes(state, res->msgs[0], + state->ctx->opts->basic, + &state->pw_expire_type, + &state->pw_expire_data); + if (ret != EOK) { + DEBUG(1, ("find_password_expiration_attributes failed.\n")); + tevent_req_error(req, ENOMEM); + break; + } + + tevent_req_done(req); + break; + + default: + DEBUG(1, ("A user search by name (%s) returned > 1 results!\n", + state->name)); + tevent_req_error(req, EFAULT); + break; + } +} + +static int get_user_dn_recv(struct tevent_req *req, + TALLOC_CTX *memctx, char **dn, + enum pwexpire *pw_expire_type, + void **pw_expire_data) +{ + struct get_user_dn_state *state = tevent_req_data(req, + struct get_user_dn_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dn = talloc_steal(memctx, state->dn); + if (!*dn) return ENOMEM; + + /* state->pw_expire_data may be NULL */ + *pw_expire_data = talloc_steal(memctx, state->pw_expire_data); + + *pw_expire_type = state->pw_expire_type; + + return EOK; +} + +/* ==Authenticate-User==================================================== */ + +struct auth_state { + struct tevent_context *ev; + struct sdap_auth_ctx *ctx; + const char *username; + struct dp_opt_blob password; + + struct sdap_handle *sh; + + enum sdap_result result; + char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; + + struct fo_server *srv; +}; + +static void auth_resolve_done(struct tevent_req *subreq); +static void auth_connect_done(struct tevent_req *subreq); +static void auth_get_user_dn_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 dp_opt_blob password) +{ + struct tevent_req *req, *subreq; + struct auth_state *state; + + req = tevent_req_create(memctx, &state, struct auth_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->username = username; + state->password = password; + state->srv = NULL; + + subreq = be_resolve_server_send(state, ev, ctx->be, ctx->service->name); + if (!subreq) goto fail; + + tevent_req_set_callback(subreq, auth_resolve_done, req); + + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void auth_resolve_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 = be_resolve_server_recv(subreq, &state->srv); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_connect_send(state, state->ev, state->ctx->opts, + state->ctx->service->uri, true); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, auth_connect_done, req); +} + +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_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret) { + if (state->srv) { + /* mark this server as bad if connection failed */ + fo_set_port_status(state->srv, PORT_NOT_WORKING); + } + + tevent_req_error(req, ret); + return; + } else if (state->srv) { + fo_set_port_status(state->srv, PORT_WORKING); + } + + subreq = get_user_dn_send(state, state->ev, + state->ctx, state->sh, + state->username); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, auth_get_user_dn_done, req); +} + +static void auth_get_user_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); + int ret; + + ret = get_user_dn_recv(subreq, state, &state->dn, &state->pw_expire_type, + &state->pw_expire_data); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_auth_send(state, state->ev, state->sh, + NULL, NULL, state->dn, + "password", state->password); + 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; + + ret = sdap_auth_recv(subreq, &state->result); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int auth_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh, + enum sdap_result *result, char **dn, + enum pwexpire *pw_expire_type, void **pw_expire_data) +{ + struct auth_state *state = tevent_req_data(req, struct auth_state); + enum tevent_req_state tstate; + uint64_t err; + + if (tevent_req_is_error(req, &tstate, &err)) { + switch (tstate) { + case TEVENT_REQ_USER_ERROR: + if (err == ETIMEDOUT) *result = SDAP_UNAVAIL; + else *result = SDAP_ERROR; + return err; + default: + *result = SDAP_ERROR; + return EIO; + } + } + + 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; + + *result = state->result; + return EOK; +} + +/* ==Perform-Password-Change===================== */ + +struct sdap_pam_chpass_state { + struct be_req *breq; + struct pam_data *pd; + const char *username; + char *dn; + char *password; + char *new_password; + struct sdap_handle *sh; +}; + +static void sdap_auth4chpass_done(struct tevent_req *req); +static void sdap_pam_chpass_done(struct tevent_req *req); +static void sdap_pam_auth_reply(struct be_req *breq, int dp_err, int result); + +void sdap_pam_chpass_handler(struct be_req *breq) +{ + struct sdap_pam_chpass_state *state; + struct sdap_auth_ctx *ctx; + struct tevent_req *subreq; + struct pam_data *pd; + struct dp_opt_blob authtok; + int dp_err = DP_ERR_FATAL; + + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_CHPASS].pvt_bet_data, + struct sdap_auth_ctx); + pd = talloc_get_type(breq->req_data, struct pam_data); + + if (be_is_offline(ctx->be)) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + dp_err = DP_ERR_OFFLINE; + goto done; + } + + DEBUG(2, ("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(2, ("chpass target was called by wrong pam command.\n")); + goto done; + } + + state = talloc_zero(breq, struct sdap_pam_chpass_state); + if (!state) goto done; + + state->breq = breq; + state->pd = pd; + state->username = pd->user; + state->password = talloc_strndup(state, + (char *)pd->authtok, pd->authtok_size); + if (!state->password) goto done; + talloc_set_destructor((TALLOC_CTX *)state->password, + password_destructor); + + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + state->new_password = talloc_strndup(state, + (char *)pd->newauthtok, + pd->newauthtok_size); + if (!state->new_password) goto done; + talloc_set_destructor((TALLOC_CTX *)state->new_password, + password_destructor); + } + + authtok.data = (uint8_t *)state->password; + authtok.length = strlen(state->password); + subreq = auth_send(breq, breq->be_ctx->ev, + ctx, state->username, authtok); + if (!subreq) goto done; + + tevent_req_set_callback(subreq, sdap_auth4chpass_done, state); + return; + +done: + sdap_pam_auth_reply(breq, dp_err, pd->pam_status); +} + +static void sdap_auth4chpass_done(struct tevent_req *req) +{ + struct sdap_pam_chpass_state *state = + tevent_req_callback_data(req, struct sdap_pam_chpass_state); + struct tevent_req *subreq; + enum sdap_result result; + enum pwexpire pw_expire_type; + void *pw_expire_data; + int dp_err = DP_ERR_FATAL; + int ret; + + ret = auth_recv(req, state, &state->sh, + &result, &state->dn, + &pw_expire_type, &pw_expire_data); + talloc_zfree(req); + if (ret) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (result == SDAP_AUTH_SUCCESS && + state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(9, ("Initial authentication for change password operation " + "successful.\n")); + state->pd->pam_status = PAM_SUCCESS; + goto done; + } + + if (result == SDAP_AUTH_SUCCESS) { + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_shadow failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_kerberos failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (result == SDAP_AUTH_PW_EXPIRED) { + DEBUG(1, ("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(1, ("Unknow pasword expiration type.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (result) { + case SDAP_AUTH_SUCCESS: + case SDAP_AUTH_PW_EXPIRED: + DEBUG(7, ("user [%s] successfully authenticated.\n", state->dn)); + if (pw_expire_type == PWEXPIRE_SHADOW) { +/* TODO: implement async ldap modify request */ + DEBUG(1, ("Changing shadow password attributes not implemented.\n")); + state->pd->pam_status = PAM_MODULE_UNKNOWN; + goto done; + } else { + subreq = sdap_exop_modify_passwd_send(state, + state->breq->be_ctx->ev, + state->sh, + state->dn, + state->password, + state->new_password); + + if (!subreq) { + DEBUG(2, ("Failed to change password for %s\n", state->username)); + goto done; + } + + tevent_req_set_callback(subreq, sdap_pam_chpass_done, state); + return; + } + break; + case SDAP_AUTH_FAILED: + state->pd->pam_status = PAM_AUTH_ERR; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + } + +done: + sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status); +} + +static void sdap_pam_chpass_done(struct tevent_req *req) +{ + struct sdap_pam_chpass_state *state = + tevent_req_callback_data(req, struct sdap_pam_chpass_state); + enum sdap_result result; + int dp_err = DP_ERR_FATAL; + int ret; + char *user_error_message = NULL; + size_t msg_len; + uint8_t *msg; + + ret = sdap_exop_modify_passwd_recv(req, state, &result, &user_error_message); + talloc_zfree(req); + if (ret) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + switch (result) { + case SDAP_SUCCESS: + state->pd->pam_status = PAM_SUCCESS; + dp_err = DP_ERR_OK; + break; + default: + state->pd->pam_status = PAM_AUTHTOK_ERR; + if (user_error_message != NULL) { + ret = pack_user_info_chpass_error(state->pd, user_error_message, + &msg_len, &msg); + if (ret != EOK) { + DEBUG(1, ("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(1, ("pam_add_response failed.\n")); + } + } + } + } + +done: + sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status); +} +/* ==Perform-User-Authentication-and-Password-Caching===================== */ + +struct sdap_pam_auth_state { + struct be_req *breq; + struct pam_data *pd; + const char *username; + struct dp_opt_blob password; +}; + +static void sdap_pam_auth_done(struct tevent_req *req); +static void sdap_password_cache_done(struct tevent_req *req); + +void sdap_pam_auth_handler(struct be_req *breq) +{ + struct sdap_pam_auth_state *state; + struct sdap_auth_ctx *ctx; + struct tevent_req *subreq; + struct pam_data *pd; + int dp_err = DP_ERR_FATAL; + + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_AUTH].pvt_bet_data, + struct sdap_auth_ctx); + pd = talloc_get_type(breq->req_data, struct pam_data); + + if (be_is_offline(ctx->be)) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + dp_err = DP_ERR_OFFLINE; + goto done; + } + + pd->pam_status = PAM_SYSTEM_ERR; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK_PRELIM: + + state = talloc_zero(breq, struct sdap_pam_auth_state); + if (!state) goto done; + + state->breq = breq; + state->pd = pd; + state->username = pd->user; + state->password.data = pd->authtok; + state->password.length = pd->authtok_size; + + subreq = auth_send(breq, breq->be_ctx->ev, ctx, + state->username, state->password); + if (!subreq) goto done; + + tevent_req_set_callback(subreq, sdap_pam_auth_done, state); + return; + + case SSS_PAM_CHAUTHTOK: + break; + + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + dp_err = DP_ERR_OK; + break; + default: + pd->pam_status = PAM_MODULE_UNKNOWN; + dp_err = DP_ERR_OK; + } + +done: + sdap_pam_auth_reply(breq, dp_err, pd->pam_status); +} + +static void sdap_pam_auth_done(struct tevent_req *req) +{ + struct sdap_pam_auth_state *state = + tevent_req_callback_data(req, struct sdap_pam_auth_state); + struct tevent_req *subreq; + enum sdap_result result; + enum pwexpire pw_expire_type; + void *pw_expire_data; + int dp_err = DP_ERR_OK; + int ret; + + ret = auth_recv(req, state, NULL, + &result, NULL, + &pw_expire_type, &pw_expire_data); + talloc_zfree(req); + if (ret) { + state->pd->pam_status = PAM_SYSTEM_ERR; + dp_err = DP_ERR_FATAL; + goto done; + } + + if (result == SDAP_AUTH_SUCCESS) { + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_shadow failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_kerberos failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + case PWEXPIRE_NONE: + break; + default: + DEBUG(1, ("Unknow pasword expiration type.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + + switch (result) { + case SDAP_AUTH_SUCCESS: + state->pd->pam_status = PAM_SUCCESS; + break; + case SDAP_AUTH_FAILED: + state->pd->pam_status = PAM_PERM_DENIED; + break; + case SDAP_UNAVAIL: + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + break; + case SDAP_ACCT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + break; + case SDAP_AUTH_PW_EXPIRED: + state->pd->pam_status = PAM_AUTHTOK_EXPIRED; + break; + default: + state->pd->pam_status = PAM_SYSTEM_ERR; + dp_err = DP_ERR_FATAL; + } + + if (result == SDAP_UNAVAIL) { + be_mark_offline(state->breq->be_ctx); + dp_err = DP_ERR_OFFLINE; + goto done; + } + + if (result == SDAP_AUTH_SUCCESS && + state->breq->be_ctx->domain->cache_credentials) { + + char *password = talloc_strndup(state, (char *) + state->password.data, + state->password.length); + /* password caching failures are not fatal errors */ + if (!password) { + DEBUG(2, ("Failed to cache password for %s\n", state->username)); + goto done; + } + talloc_set_destructor((TALLOC_CTX *)password, password_destructor); + + subreq = sysdb_cache_password_send(state, + state->breq->be_ctx->ev, + state->breq->be_ctx->sysdb, + NULL, + state->breq->be_ctx->domain, + state->username, password); + + /* password caching failures are not fatal errors */ + if (!subreq) { + DEBUG(2, ("Failed to cache password for %s\n", state->username)); + goto done; + } + + tevent_req_set_callback(subreq, sdap_password_cache_done, state); + return; + } + +done: + sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status); +} + +static void sdap_password_cache_done(struct tevent_req *subreq) +{ + struct sdap_pam_auth_state *state = tevent_req_callback_data(subreq, + struct sdap_pam_auth_state); + int ret; + + ret = sysdb_cache_password_recv(subreq); + talloc_zfree(subreq); + if (ret) { + /* password caching failures are not fatal errors */ + DEBUG(2, ("Failed to cache password for %s\n", state->username)); + } else { + DEBUG(4, ("Password successfully cached for %s\n", state->username)); + } + + sdap_pam_auth_reply(state->breq, DP_ERR_OK, state->pd->pam_status); +} + +static void sdap_pam_auth_reply(struct be_req *req, int dp_err, int result) +{ + req->fn(req, dp_err, result, NULL); +} + diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c new file mode 100644 index 000000000..0d34be2ca --- /dev/null +++ b/src/providers/ldap/ldap_child.c @@ -0,0 +1,429 @@ +/* + 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 <popt.h> + +#include <security/pam_modules.h> + +#include "util/util.h" +#include "util/sss_krb5.h" +#include "providers/child_common.h" +#include "providers/dp_backend.h" + +static krb5_context krb5_error_ctx; + +struct input_buffer { + const char *realm_str; + const char *princ_str; + const char *keytab_name; +}; + +static errno_t unpack_buffer(uint8_t *buf, size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + + DEBUG(7, ("total buffer size: %d\n", size)); + + /* realm_str size and length */ + COPY_UINT32_CHECK(&len, buf + p, p, size); + + DEBUG(7, ("realm_str size: %d\n", len)); + if (len) { + if ((p + len ) > size) return EINVAL; + ibuf->realm_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(7, ("got realm_str: %s\n", ibuf->realm_str)); + if (ibuf->realm_str == NULL) return ENOMEM; + p += len; + } + + /* princ_str size and length */ + COPY_UINT32_CHECK(&len, buf + p, p, size); + + DEBUG(7, ("princ_str size: %d\n", len)); + if (len) { + if ((p + len ) > size) return EINVAL; + ibuf->princ_str = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(7, ("got princ_str: %s\n", ibuf->princ_str)); + if (ibuf->princ_str == NULL) return ENOMEM; + p += len; + } + + /* keytab_name size and length */ + COPY_UINT32_CHECK(&len, buf + p, p, size); + + DEBUG(7, ("keytab_name size: %d\n", len)); + if (len) { + if ((p + len ) > size) return EINVAL; + ibuf->keytab_name = talloc_strndup(ibuf, (char *)(buf + p), len); + DEBUG(7, ("got keytab_name: %s\n", ibuf->keytab_name)); + if (ibuf->keytab_name == NULL) return ENOMEM; + p += len; + } + + return EOK; +} + +static int pack_buffer(struct response *r, int result, const char *msg) +{ + int len; + int p = 0; + + len = strlen(msg); + r->size = 2 * sizeof(uint32_t) + len; + + /* result */ + COPY_UINT32_VALUE(&r->buf[p], result, p); + + /* message size */ + COPY_UINT32_VALUE(&r->buf[p], len, p); + + /* message itself */ + COPY_MEM(&r->buf[p], msg, p, len); + + return EOK; +} + +static int ldap_child_get_tgt_sync(TALLOC_CTX *memctx, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + const char **ccname_out) +{ + char *ccname; + char *realm_name = NULL; + char *full_princ = NULL; + krb5_context context = NULL; + krb5_keytab keytab = NULL; + krb5_ccache ccache = NULL; + krb5_principal kprinc; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + krb5_error_code krberr; + int ret; + + krberr = krb5_init_context(&context); + if (krberr) { + DEBUG(2, ("Failed to init kerberos context\n")); + return EFAULT; + } + + if (!realm_str) { + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + DEBUG(2, ("Failed to get default realm name: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + } else { + realm_name = talloc_strdup(memctx, realm_str); + if (!realm_name) { + ret = ENOMEM; + goto done; + } + } + + if (princ_str) { + if (!strchr(princ_str, '@')) { + full_princ = talloc_asprintf(memctx, "%s@%s", + princ_str, realm_name); + } else { + full_princ = talloc_strdup(memctx, princ_str); + } + } else { + char hostname[512]; + + ret = gethostname(hostname, 511); + if (ret == -1) { + ret = errno; + goto done; + } + hostname[511] = '\0'; + + full_princ = talloc_asprintf(memctx, "host/%s@%s", + hostname, realm_name); + } + if (!full_princ) { + ret = ENOMEM; + goto done; + } + DEBUG(4, ("Principal name is: [%s]\n", full_princ)); + + krberr = krb5_parse_name(context, full_princ, &kprinc); + if (krberr) { + DEBUG(2, ("Unable to build principal: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + if (keytab_name) { + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + } else { + krberr = krb5_kt_default(context, &keytab); + } + if (krberr) { + DEBUG(2, ("Failed to read keytab file: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name); + if (!ccname) { + ret = ENOMEM; + goto done; + } + + krberr = krb5_cc_resolve(context, ccname, &ccache); + if (krberr) { + DEBUG(2, ("Failed to set cache name: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + memset(&my_creds, 0, sizeof(my_creds)); + memset(&options, 0, sizeof(options)); + + 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); + /* set a very short lifetime, we don't keep the ticket around */ + krb5_get_init_creds_opt_set_tkt_life(&options, 300); + + krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, + keytab, 0, NULL, &options); + + if (krberr) { + DEBUG(2, ("Failed to init credentials: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + krberr = krb5_cc_initialize(context, ccache, kprinc); + if (krberr) { + DEBUG(2, ("Failed to init ccache: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr) { + DEBUG(2, ("Failed to store creds: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + ret = EOK; + *ccname_out = ccname; + +done: + if (keytab) krb5_kt_close(context, keytab); + if (context) krb5_free_context(context); + return ret; +} + +static int prepare_response(TALLOC_CTX *mem_ctx, + const char *ccname, + krb5_error_code kerr, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + const char *krb5_msg = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (!r) return ENOMEM; + + r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE); + if (r->buf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + return ENOMEM; + } + r->max_size = MAX_CHILD_MSG_SIZE; + r->size = 0; + + if (kerr == 0) { + ret = pack_buffer(r, EOK, ccname); + } else { + krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); + if (krb5_msg == NULL) { + DEBUG(1, ("sss_krb5_get_error_message failed.\n")); + return ENOMEM; + } + + ret = pack_buffer(r, EFAULT, krb5_msg); + sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); + } + + if (ret != EOK) { + DEBUG(1, ("pack_buffer failed\n")); + return ret; + } + + *rsp = r; + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int kerr; + int opt; + int debug_fd = -1; + poptContext pc; + TALLOC_CTX *main_ctx; + uint8_t *buf = NULL; + ssize_t len = 0; + const char *ccname = NULL; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + size_t written; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, + _("Debug level"), NULL}, + {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0, + _("Add debug timestamps"), NULL}, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + POPT_TABLEEND + }; + + 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); + + DEBUG(7, ("ldap_child started.\n")); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + _exit(-1); + } + + debug_prg_name = talloc_asprintf(main_ctx, "[sssd[ldap_child[%d]]]", getpid()); + + if (debug_fd != -1) { + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + DEBUG(1, ("set_debug_file_from_fd failed.\n")); + } + } + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + goto fail; + } + + while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) { + if (ret == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno))); + goto fail; + } else if (ret > 0) { + len += ret; + if (len > IN_BUF_SIZE) { + DEBUG(1, ("read too much, this should never happen.\n")); + goto fail; + } + continue; + } else { + DEBUG(1, ("unexpected return code of read [%d].\n", ret)); + goto fail; + } + } + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(1, ("unpack_buffer failed.[%d][%s].\n", ret, strerror(ret))); + goto fail; + } + + kerr = ldap_child_get_tgt_sync(main_ctx, + ibuf->realm_str, ibuf->princ_str, + ibuf->keytab_name, &ccname); + if (kerr != EOK) { + DEBUG(1, ("ldap_child_get_tgt_sync failed.\n")); + /* Do not return, must report failure */ + } + + ret = prepare_response(main_ctx, ccname, kerr, &resp); + if (ret != EOK) { + DEBUG(1, ("prepare_response failed. [%d][%s].\n", ret, strerror(ret))); + return ENOMEM; + } + + written = 0; + while (written < resp->size) { + ret = write(STDOUT_FILENO, resp->buf + written, resp->size - written); + if (ret == -1) { + if (errno == EAGAIN || errno == EINTR) { + continue; + } + ret = errno; + DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret))); + return ret; + } + written += ret; + } + + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(0); + +fail: + 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 000000000..bd4294f83 --- /dev/null +++ b/src/providers/ldap/ldap_common.c @@ -0,0 +1,589 @@ +/* + SSSD + + LDAP Provider Common Functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2008-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 "providers/ldap/ldap_common.h" +#include "providers/fail_over.h" + +#include "util/sss_krb5.h" + +/* a fd the child process would log into */ +int ldap_child_debug_fd = -1; + +struct dp_option default_basic_opts[] = { + { "ldap_uri", DP_OPT_STRING, { "ldap://localhost" }, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, { "dc=example,dc=com" }, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, 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_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_schema", DP_OPT_STRING, { "rfc2307" }, 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_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER }, + { "entry_cache_timoeut", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", 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_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_realm", 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 } +}; + +struct sdap_attr_map generic_attr_map[] = { + { "ldap_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", NULL, SYSDB_USN, NULL } +}; + +struct sdap_attr_map gen_ipa_attr_map[] = { + { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL } +}; + +struct sdap_attr_map gen_ad_attr_map[] = { + { "ldap_entry_usn", "uSNChanged", SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", "highestCommittedUSN", SYSDB_HIGH_USN, NULL } +}; + +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_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, 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 } +}; + +struct sdap_attr_map rfc2307_group_map[] = { + { "ldap_group_object_class", "posixGroup", 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_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL } +}; + +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 }, + /* FIXME: this is 389ds specific */ + { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, 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 } +}; + +struct sdap_attr_map rfc2307bis_group_map[] = { + { "ldap_group_object_class", "posixGroup", 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 }, + /* FIXME: this is 389ds specific */ + { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL } +}; + +int ldap_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + 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_options *opts; + char *schema; + const char *pwd_policy; + int ret; + + opts = talloc_zero(memctx, struct sdap_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + default_basic_opts, + SDAP_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + /* set user/group search bases if they are not */ + if (NULL == dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE)) { + ret = dp_opt_set_string(opts->basic, SDAP_USER_SEARCH_BASE, + dp_opt_get_string(opts->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + opts->basic[SDAP_USER_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE))); + } + + if (NULL == dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE)) { + ret = dp_opt_set_string(opts->basic, SDAP_GROUP_SEARCH_BASE, + dp_opt_get_string(opts->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + DEBUG(6, ("Option %s set to %s\n", + opts->basic[SDAP_GROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE))); + } + + pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY); + if (pwd_policy == NULL) { + DEBUG(1, ("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(1, ("Unsupported password policy [%s].\n", pwd_policy)); + ret = EINVAL; + goto done; + } + + +#ifndef HAVE_LDAP_CONNCB + bool ldap_referrals; + + ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS); + if (ldap_referrals) { + DEBUG(1, ("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); + } +#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; + } 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; + } 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; + } else + if (strcasecmp(schema, "AD") == 0) { + opts->schema_type = SDAP_SCHEMA_AD; + default_attr_map = gen_ad_attr_map; + default_user_map = rfc2307bis_user_map; + default_group_map = rfc2307bis_group_map; + } else { + DEBUG(0, ("Unrecognized schema type: %s\n", schema)); + 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_get_map(opts, cdb, conf_path, + default_group_map, + SDAP_OPTS_GROUP, + &opts->group_map); + if (ret != EOK) { + goto done; + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +void sdap_handler_done(struct be_req *req, int dp_err, + int error, const char *errstr) +{ + return req->fn(req, dp_err, error, errstr); +} + +bool sdap_connected(struct sdap_id_ctx *ctx) +{ + if (ctx->gsh) { + return ctx->gsh->connected; + } + + return false; +} + +void sdap_mark_offline(struct sdap_id_ctx *ctx) +{ + if (ctx->gsh) { + /* make sure we mark the connection as gone when we go offline so that + * we do not try to reuse a bad connection by mistale later */ + talloc_zfree(ctx->gsh); + } + + be_mark_offline(ctx->be); +} + +bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx) +{ + int ret; + bool result = false; + const char *mech; + const char *realm; + char *ccname = NULL; + krb5_context context = NULL; + krb5_ccache ccache = NULL; + krb5_error_code krberr; + TALLOC_CTX *tmp_ctx = NULL; + krb5_creds mcred; + krb5_creds cred; + char *server_name = NULL; + char *client_princ_str = NULL; + char *full_princ = NULL; + krb5_principal client_principal = NULL; + krb5_principal server_principal = NULL; + char hostname[512]; + int l_errno; + + + mech = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_MECH); + if (mech == NULL || strcasecmp(mech, "GSSAPI") != 0) { + return false; + } + + realm = dp_opt_get_string(ctx->opts->basic, SDAP_KRB5_REALM); + if (realm == NULL) { + DEBUG(3, ("Kerberos realm not available.\n")); + return false; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + return false; + } + + ccname = talloc_asprintf(tmp_ctx, "FILE:%s/ccache_%s", DB_PATH, realm); + if (ccname == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + goto done; + } + + krberr = krb5_init_context(&context); + if (krberr) { + DEBUG(1, ("Failed to init kerberos context\n")); + goto done; + } + + krberr = krb5_cc_resolve(context, ccname, &ccache); + if (krberr != 0) { + DEBUG(1, ("krb5_cc_resolve failed.\n")); + goto done; + } + + server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm); + if (server_name == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + goto done; + } + + krberr = krb5_parse_name(context, server_name, &server_principal); + if (krberr != 0) { + DEBUG(1, ("krb5_parse_name failed.\n")); + goto done; + } + + client_princ_str = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_AUTHID); + if (client_princ_str) { + if (!strchr(client_princ_str, '@')) { + full_princ = talloc_asprintf(tmp_ctx, "%s@%s", client_princ_str, + realm); + } else { + full_princ = talloc_strdup(tmp_ctx, client_princ_str); + } + } else { + ret = gethostname(hostname, sizeof(hostname)-1); + if (ret == -1) { + l_errno = errno; + DEBUG(1, ("gethostname failed [%d][%s].\n", l_errno, + strerror(l_errno))); + goto done; + } + hostname[sizeof(hostname)-1] = '\0'; + + full_princ = talloc_asprintf(tmp_ctx, "host/%s@%s", hostname, realm); + } + if (!full_princ) { + DEBUG(1, ("Client principal not available.\n")); + goto done; + } + DEBUG(7, ("Client principal name is: [%s]\n", full_princ)); + krberr = krb5_parse_name(context, full_princ, &client_principal); + if (krberr != 0) { + DEBUG(1, ("krb5_parse_name failed.\n")); + goto done; + } + + memset(&mcred, 0, sizeof(mcred)); + memset(&cred, 0, sizeof(mcred)); + mcred.client = client_principal; + mcred.server = server_principal; + + krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcred, &cred); + if (krberr != 0) { + DEBUG(1, ("krb5_cc_retrieve_cred failed.\n")); + goto done; + } + + DEBUG(7, ("TGT end time [%d].\n", cred.times.endtime)); + + if (cred.times.endtime <= time(NULL)) { + DEBUG(3, ("TGT is expired.\n")); + result = true; + } + krb5_free_cred_contents(context, &cred); + +done: + if (client_principal != NULL) { + krb5_free_principal(context, client_principal); + } + if (server_principal != NULL) { + krb5_free_principal(context, server_principal); + } + if (ccache != NULL) { + if (result) { + krb5_cc_destroy(context, ccache); + } else { + krb5_cc_close(context, ccache); + } + } + if (context != NULL) krb5_free_context(context); + talloc_free(tmp_ctx); + return result; +} + +int sdap_id_setup_tasks(struct sdap_id_ctx *ctx) +{ + struct timeval tv; + int ret = EOK; + + /* set up enumeration task */ + if (ctx->be->domain->enumerate) { + /* run the first one in a couple of seconds so that we have time to + * finish initializations first*/ + tv = tevent_timeval_current_ofs(10, 0); + ret = ldap_id_enumerate_set_timer(ctx, tv); + } else { + /* the enumeration task, runs the cleanup process by itself, + * but if enumeration is not runnig we need to schedule it */ + + /* run the first one in a couple of seconds so that we have time to + * finish initializations first*/ + tv = tevent_timeval_current_ofs(10, 0); + ret = ldap_id_cleanup_set_timer(ctx, tv); + } + + return ret; +} + +static void sdap_uri_callback(void *private_data, struct fo_server *server) +{ + struct sdap_service *service; + const char *tmp; + char *new_uri; + + service = talloc_get_type(private_data, struct sdap_service); + if (!service) return; + + tmp = (const char *)fo_get_server_user_data(server); + if (tmp && ldap_is_ldap_url(tmp)) { + new_uri = talloc_strdup(service, tmp); + } else { + new_uri = talloc_asprintf(service, "ldap://%s", + fo_get_server_name(server)); + } + if (!new_uri) { + DEBUG(2, ("Failed to copy URI ...\n")); + return; + } + + /* free old one and replace with new one */ + talloc_zfree(service->uri); + service->uri = new_uri; +} + +int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *urls, + struct sdap_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct sdap_service *service; + LDAPURLDesc *lud; + char **list = NULL; + int ret; + int i; + + tmp_ctx = talloc_new(memctx); + 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); + if (ret != EOK) { + DEBUG(1, ("Failed to create failover service!\n")); + goto done; + } + + service->name = talloc_strdup(service, service_name); + if (!service->name) { + ret = ENOMEM; + goto done; + } + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, urls, ',', true, &list, NULL); + if (ret != EOK) { + DEBUG(1, ("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++) { + ret = ldap_url_parse(list[i], &lud); + if (ret != LDAP_SUCCESS) { + DEBUG(0, ("Failed to parse ldap URI (%s)!\n", list[i])); + ret = EINVAL; + goto done; + } + + DEBUG(6, ("Added URI %s\n", list[i])); + + talloc_steal(service, list[i]); + + ret = be_fo_add_server(ctx, service->name, + lud->lud_host, lud->lud_port, list[i]); + if (ret) { + goto done; + } + ldap_free_urldesc(lud); + } + + ret = be_fo_service_add_callback(memctx, ctx, service->name, + sdap_uri_callback, service); + if (ret != EOK) { + DEBUG(1, ("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; +} + diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h new file mode 100644 index 000000000..ff1ffb725 --- /dev/null +++ b/src/providers/ldap/ldap_common.h @@ -0,0 +1,115 @@ +/* + 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 "providers/dp_backend.h" +#include "providers/ldap/sdap.h" +#include "providers/fail_over.h" + +#define PWD_POL_OPT_NONE "none" +#define PWD_POL_OPT_SHADOW "shadow" +#define PWD_POL_OPT_MIT "mit_kerberos" + +/* a fd the child process would log into */ +extern int ldap_child_debug_fd; + +struct sdap_id_ctx { + struct be_ctx *be; + struct sdap_options *opts; + struct fo_service *fo_service; + struct sdap_service *service; + + /* what rootDSE returns */ + struct sysdb_attrs *rootDSE; + + /* global sdap handler */ + struct sdap_handle *gsh; + + /* enumeration loop timer */ + struct timeval last_enum; + /* cleanup loop timer */ + struct timeval last_purge; + + char *max_user_timestamp; + char *max_group_timestamp; +}; + +struct sdap_auth_ctx { + struct be_ctx *be; + struct sdap_options *opts; + struct fo_service *fo_service; + struct sdap_service *service; +}; + +/* id */ +void sdap_account_info_handler(struct be_req *breq); +int sdap_id_setup_tasks(struct sdap_id_ctx *ctx); + +/* auth */ +void sdap_pam_auth_handler(struct be_req *breq); + +/* chpass */ +void sdap_pam_chpass_handler(struct be_req *breq); + + + +void sdap_handler_done(struct be_req *req, int dp_err, + int error, const char *errstr); + +int sdap_service_init(TALLOC_CTX *mmectx, struct be_ctx *ctx, + const char *service_name, const char *urls, + struct sdap_service **service); + +/* options parser */ +int ldap_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts); + +int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv); +int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv); + +bool sdap_connected(struct sdap_id_ctx *ctx); +void sdap_mark_offline(struct sdap_id_ctx *ctx); +bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx); + +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type); +int users_get_recv(struct tevent_req *req); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type); +int groups_get_recv(struct tevent_req *req); + +/* setup child logging */ +int setup_child(struct sdap_id_ctx *ctx); + +#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 000000000..4bbc07a68 --- /dev/null +++ b/src/providers/ldap/ldap_id.c @@ -0,0 +1,795 @@ +/* + 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 "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +/* =Users-Related-Functions-(by-name,by-uid)============================== */ + +struct users_get_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int filter_type; + + char *filter; + const char **attrs; +}; + +static void users_get_connect_done(struct tevent_req *subreq); +static void users_get_done(struct tevent_req *subreq); +static void users_get_delete(struct tevent_req *subreq); + +struct tevent_req *users_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type) +{ + struct tevent_req *req, *subreq; + struct users_get_state *state; + const char *attr_name; + int ret; + + req = tevent_req_create(memctx, &state, struct users_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = state->ctx->be->domain; + state->name = name; + state->filter_type = filter_type; + + switch (filter_type) { + case BE_FILTER_NAME: + attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name; + break; + case BE_FILTER_IDNUM: + attr_name = ctx->opts->user_map[SDAP_AT_USER_UID].name; + break; + default: + ret = EINVAL; + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + attr_name, name, + ctx->opts->user_map[SDAP_OC_USER].name); + if (!state->filter) { + DEBUG(2, ("Failed to build filter\n")); + ret = ENOMEM; + goto fail; + } + + /* TODO: handle attrs_type */ + ret = build_attrs_from_map(state, ctx->opts->user_map, + SDAP_OPTS_USER, &state->attrs); + if (ret != EOK) goto fail; + + if (!sdap_connected(ctx)) { + + if (ctx->gsh) talloc_zfree(ctx->gsh); + + /* FIXME: add option to decide if tls should be used + * or SASL/GSSAPI, etc ... */ + subreq = sdap_cli_connect_send(state, ev, ctx->opts, + ctx->be, ctx->service, + &ctx->rootDSE); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, users_get_connect_done, req); + + return req; + } + + subreq = sdap_get_users_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, users_get_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +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 ret; + + ret = sdap_cli_connect_recv(subreq, state->ctx, + &state->ctx->gsh, &state->ctx->rootDSE); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOTSUP) { + DEBUG(0, ("Authentication mechanism not Supported by server")); + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_users_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + 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; + int ret; + + ret = sdap_get_users_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT) { + if (strchr(state->name, '*')) { + /* it was an enumeration */ + tevent_req_error(req, ret); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + subreq = sysdb_delete_user_send(state, state->ev, + state->sysdb, NULL, + state->domain, state->name, 0); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_delete, req); + return; + + case BE_FILTER_IDNUM: + errno = 0; + uid = (uid_t)strtol(state->name, &endptr, 0); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, errno); + return; + } + + subreq = sysdb_delete_user_send(state, state->ev, + state->sysdb, NULL, + state->domain, NULL, uid); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, users_get_delete, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + tevent_req_done(req); +} + +static void users_get_delete(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 ret; + + ret = sysdb_delete_user_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User (%s) delete returned %d (%s)\n", + state->name, ret, strerror(ret))); + } + + tevent_req_done(req); +} + +int users_get_recv(struct tevent_req *req) +{ + 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 sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int filter_type; + + char *filter; + const char **attrs; +}; + +static void groups_get_connect_done(struct tevent_req *subreq); +static void groups_get_done(struct tevent_req *subreq); +static void groups_get_delete(struct tevent_req *subreq); + +struct tevent_req *groups_get_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name, + int filter_type, + int attrs_type) +{ + struct tevent_req *req, *subreq; + struct groups_get_state *state; + const char *attr_name; + int ret; + + req = tevent_req_create(memctx, &state, struct groups_get_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = state->ctx->be->domain; + state->name = name; + state->filter_type = filter_type; + + switch(filter_type) { + case BE_FILTER_NAME: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + break; + case BE_FILTER_IDNUM: + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_GID].name; + break; + default: + ret = EINVAL; + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + attr_name, name, + ctx->opts->group_map[SDAP_OC_GROUP].name); + if (!state->filter) { + DEBUG(2, ("Failed to build filter\n")); + ret = ENOMEM; + goto fail; + } + + /* TODO: handle attrs_type */ + ret = build_attrs_from_map(state, ctx->opts->group_map, + SDAP_OPTS_GROUP, &state->attrs); + if (ret != EOK) goto fail; + + if (!sdap_connected(ctx)) { + + if (ctx->gsh) talloc_zfree(ctx->gsh); + + /* FIXME: add option to decide if tls should be used + * or SASL/GSSAPI, etc ... */ + subreq = sdap_cli_connect_send(state, ev, ctx->opts, + ctx->be, ctx->service, + &ctx->rootDSE); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, groups_get_connect_done, req); + + return req; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, groups_get_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +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 ret; + + ret = sdap_cli_connect_recv(subreq, state->ctx, + &state->ctx->gsh, &state->ctx->rootDSE); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOTSUP) { + DEBUG(0, ("Authentication mechanism not Supported by server")); + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->domain, state->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + 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); + char *endptr; + gid_t gid; + int ret; + + ret = sdap_get_groups_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + + if (ret == ENOENT) { + if (strchr(state->name, '*')) { + /* it was an enumeration */ + tevent_req_error(req, ret); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + subreq = sysdb_delete_group_send(state, state->ev, + state->sysdb, NULL, + state->domain, state->name, 0); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_delete, req); + return; + + case BE_FILTER_IDNUM: + errno = 0; + gid = (gid_t)strtol(state->name, &endptr, 0); + if (errno || *endptr || (state->name == endptr)) { + tevent_req_error(req, errno); + return; + } + + subreq = sysdb_delete_group_send(state, state->ev, + state->sysdb, NULL, + state->domain, NULL, gid); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, groups_get_delete, req); + return; + + default: + tevent_req_error(req, EINVAL); + return; + } + } + + tevent_req_done(req); +} + +static void groups_get_delete(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 ret; + + ret = sysdb_delete_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Group (%s) delete returned %d (%s)\n", + state->name, ret, strerror(ret))); + } + + tevent_req_done(req); +} + +int groups_get_recv(struct tevent_req *req) +{ + 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; + const char *name; + const char **attrs; +}; + +static void groups_by_user_connect_done(struct tevent_req *subreq); +static void groups_by_user_done(struct tevent_req *subreq); + +static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + const char *name) +{ + struct tevent_req *req, *subreq; + 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->name = name; + + ret = build_attrs_from_map(state, ctx->opts->group_map, + SDAP_OPTS_GROUP, &state->attrs); + if (ret != EOK) goto fail; + + if (!sdap_connected(ctx)) { + + if (ctx->gsh) talloc_zfree(ctx->gsh); + + /* FIXME: add option to decide if tls should be used + * or SASL/GSSAPI, etc ... */ + subreq = sdap_cli_connect_send(state, ev, ctx->opts, + ctx->be, ctx->service, + &ctx->rootDSE); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, groups_by_user_connect_done, req); + + return req; + } + + subreq = sdap_get_initgr_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, state->ctx->gsh, + state->name, state->attrs); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, groups_by_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +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 ret; + + ret = sdap_cli_connect_recv(subreq, state->ctx, + &state->ctx->gsh, &state->ctx->rootDSE); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOTSUP) { + DEBUG(0, ("Authentication mechanism not Supported by server")); + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_initgr_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, state->ctx->gsh, + state->name, state->attrs); + 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); + int ret; + + ret = sdap_get_initgr_recv(subreq); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int groups_by_user_recv(struct tevent_req *req) +{ + 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 void sdap_account_info_users_done(struct tevent_req *req); +static void sdap_account_info_groups_done(struct tevent_req *req); +static void sdap_account_info_initgr_done(struct tevent_req *req); + +void sdap_account_info_handler(struct be_req *breq) +{ + struct sdap_id_ctx *ctx; + struct be_acct_req *ar; + struct tevent_req *req; + const char *err = "Unknown Error"; + int ret = EOK; + + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct sdap_id_ctx); + + if (be_is_offline(ctx->be)) { + return sdap_handler_done(breq, DP_ERR_OFFLINE, EAGAIN, "Offline"); + } + + ar = talloc_get_type(breq->req_data, struct be_acct_req); + + switch (ar->entry_type & 0xFFF) { + case BE_REQ_USER: /* user */ + + /* skip enumerations on demand */ + if (strcmp(ar->filter_value, "*") == 0) { + return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success"); + } + + req = users_get_send(breq, breq->be_ctx->ev, ctx, + ar->filter_value, + ar->filter_type, + ar->attr_type); + if (!req) { + return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory"); + } + + tevent_req_set_callback(req, sdap_account_info_users_done, breq); + + break; + + case BE_REQ_GROUP: /* group */ + + if (strcmp(ar->filter_value, "*") == 0) { + return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success"); + } + + /* skip enumerations on demand */ + req = groups_get_send(breq, breq->be_ctx->ev, ctx, + ar->filter_value, + ar->filter_type, + ar->attr_type); + if (!req) { + return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory"); + } + + tevent_req_set_callback(req, sdap_account_info_groups_done, breq); + + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (ar->filter_type != BE_FILTER_NAME) { + ret = EINVAL; + err = "Invalid filter type"; + break; + } + if (ar->attr_type != BE_ATTR_CORE) { + ret = EINVAL; + err = "Invalid attr type"; + break; + } + if (strchr(ar->filter_value, '*')) { + ret = EINVAL; + err = "Invalid filter value"; + break; + } + req = groups_by_user_send(breq, breq->be_ctx->ev, ctx, + ar->filter_value); + if (!req) ret = ENOMEM; + /* tevent_req_set_callback(req, groups_by_user_done, breq); */ + + tevent_req_set_callback(req, sdap_account_info_initgr_done, breq); + + break; + + default: /*fail*/ + ret = EINVAL; + err = "Invalid request type"; + } + + if (ret != EOK) return sdap_handler_done(breq, DP_ERR_FATAL, ret, err); +} + +static void sdap_account_info_users_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = users_get_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Enum Users Failed"; + + if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) { + dp_err = DP_ERR_OFFLINE; + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + if (sdap_check_gssapi_reconnect(ctx)) { + talloc_zfree(ctx->gsh); + sdap_account_info_handler(breq); + return; + } + sdap_mark_offline(ctx); + } + } + + sdap_handler_done(breq, dp_err, ret, error); +} + +static void sdap_account_info_groups_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = groups_get_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Enum Groups Failed"; + + if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) { + dp_err = DP_ERR_OFFLINE; + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + if (sdap_check_gssapi_reconnect(ctx)) { + talloc_zfree(ctx->gsh); + sdap_account_info_handler(breq); + return; + } + sdap_mark_offline(ctx); + } + } + + return sdap_handler_done(breq, dp_err, ret, error); +} + +static void sdap_account_info_initgr_done(struct tevent_req *req) +{ + struct be_req *breq = tevent_req_callback_data(req, struct be_req); + struct sdap_id_ctx *ctx; + int dp_err = DP_ERR_OK; + const char *error = NULL; + int ret; + + ret = groups_by_user_recv(req); + talloc_zfree(req); + + if (ret) { + dp_err = DP_ERR_FATAL; + error = "Init Groups Failed"; + + if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) { + dp_err = DP_ERR_OFFLINE; + ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, + struct sdap_id_ctx); + if (sdap_check_gssapi_reconnect(ctx)) { + talloc_zfree(ctx->gsh); + sdap_account_info_handler(breq); + return; + } + sdap_mark_offline(ctx); + } + } + + return sdap_handler_done(breq, dp_err, ret, error); +} + diff --git a/src/providers/ldap/ldap_id_cleanup.c b/src/providers/ldap/ldap_id_cleanup.c new file mode 100644 index 000000000..f3fb4443c --- /dev/null +++ b/src/providers/ldap/ldap_id_cleanup.c @@ -0,0 +1,555 @@ +/* + 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 "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +/* ==Cleanup-Task========================================================= */ + +struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_reschedule(struct tevent_req *req); + +static void ldap_id_cleanup_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static void ldap_id_cleanup_timer(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, void *pvt) +{ + struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx); + struct tevent_timer *timeout; + struct tevent_req *req; + int delay; + + if (be_is_offline(ctx->be)) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_cleanup_set_timer(ctx, tv); + return; + } + + req = ldap_id_cleanup_send(ctx, ev, ctx); + if (!req) { + DEBUG(1, ("Failed to schedule cleanup, retrying later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_cleanup_set_timer(ctx, tv); + return; + } + tevent_req_set_callback(req, ldap_id_cleanup_reschedule, ctx); + + /* if cleanup takes so long, either we try to cleanup too + * frequently, or something went seriously wrong */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + timeout = tevent_add_timer(ctx->be->ev, req, tv, + ldap_id_cleanup_timeout, req); + return; +} + +static void ldap_id_cleanup_timeout(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_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + int delay; + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + DEBUG(1, ("Cleanup timed out! Timeout too small? (%ds)!\n", delay)); + + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); + + talloc_zfree(req); +} + +static void ldap_id_cleanup_reschedule(struct tevent_req *req) +{ + struct sdap_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + enum tevent_req_state tstate; + uint64_t err; + struct timeval tv; + int delay; + + if (tevent_req_is_error(req, &tstate, &err)) { + /* On error schedule starting from now, not the last run */ + tv = tevent_timeval_current(); + } else { + tv = ctx->last_purge; + } + talloc_zfree(req); + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + tv = tevent_timeval_add(&tv, delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); +} + + + +int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv) +{ + struct tevent_timer *cleanup_task; + + DEBUG(6, ("Scheduling next cleanup at %ld.%ld\n", + (long)tv.tv_sec, (long)tv.tv_usec)); + + cleanup_task = tevent_add_timer(ctx->be->ev, ctx, + tv, ldap_id_cleanup_timer, ctx); + if (!cleanup_task) { + DEBUG(0, ("FATAL: failed to setup cleanup task!\n")); + return EFAULT; + } + + return EOK; +} + + + +struct global_cleanup_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; +}; + +static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_users_done(struct tevent_req *subreq); +static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); +static void ldap_id_cleanup_groups_done(struct tevent_req *subreq); + +struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct global_cleanup_state *state; + struct tevent_req *req, *subreq; + + req = tevent_req_create(memctx, &state, struct global_cleanup_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + subreq = cleanup_users_send(state, ev, ctx); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req); + + ctx->last_purge = tevent_timeval_current(); + + return req; +} + +static void ldap_id_cleanup_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_cleanup_state *state = tevent_req_data(req, + struct global_cleanup_state); + enum tevent_req_state tstate; + uint64_t err = 0; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + subreq = cleanup_groups_send(state, state->ev, state->ctx); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req); + + return; + +fail: + if (err) { + DEBUG(9, ("User cleanup failed with: (%d)[%s]\n", + (int)err, strerror(err))); + + if (sdap_check_gssapi_reconnect(state->ctx)) { + talloc_zfree(state->ctx->gsh); + subreq = cleanup_users_send(state, state->ev, state->ctx); + if (subreq != NULL) { + tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req); + return; + } + } + sdap_mark_offline(state->ctx); + } + + DEBUG(1, ("Failed to cleanup users, retrying later!\n")); + tevent_req_done(req); +} + +static void ldap_id_cleanup_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_cleanup_state *state = tevent_req_data(req, + struct global_cleanup_state); + enum tevent_req_state tstate; + uint64_t err; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + tevent_req_done(req); + return; + +fail: + /* check if credentials are expired otherwise go offline on failures */ + if (sdap_check_gssapi_reconnect(state->ctx)) { + talloc_zfree(state->ctx->gsh); + subreq = cleanup_groups_send(state, state->ev, state->ctx); + if (subreq != NULL) { + tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req); + return; + } + } + sdap_mark_offline(state->ctx); + DEBUG(1, ("Failed to cleanup groups (%d [%s]), retrying later!\n", + (int)err, strerror(err))); + tevent_req_done(req); +} + + +/* ==User-Cleanup-Process================================================= */ + +struct cleanup_users_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + struct sysdb_handle *handle; + + struct ldb_message **msgs; + size_t count; + int cur; +}; + +static void cleanup_users_process(struct tevent_req *subreq); +static void cleanup_users_update(struct tevent_req *req); +static void cleanup_users_up_done(struct tevent_req *subreq); + +static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct tevent_req *req, *subreq; + struct cleanup_users_state *state; + static const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + + req = tevent_req_create(memctx, &state, struct cleanup_users_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = ctx->be->domain; + state->msgs = NULL; + state->count = 0; + state->cur = 0; + + subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, (long)now); + if (!subfilter) { + DEBUG(2, ("Failed to build filter\n")); + talloc_zfree(req); + return NULL; + } + + subreq = sysdb_search_users_send(state, state->ev, + state->sysdb, NULL, + state->domain, subfilter, attrs); + if (!subreq) { + DEBUG(2, ("Failed to send entry search\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, cleanup_users_process, req); + + return req; +} + +static void cleanup_users_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + int ret; + + ret = sysdb_search_users_recv(subreq, state, &state->count, &state->msgs); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOENT) { + tevent_req_done(req); + return; + } + tevent_req_error(req, ret); + return; + } + + DEBUG(4, ("Found %d expired user entries!\n", state->count)); + + if (state->count == 0) { + tevent_req_done(req); + } + + cleanup_users_update(req); +} + +static void cleanup_users_update(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + const char *str; + + str = ldb_msg_find_attr_as_string(state->msgs[state->cur], + SYSDB_NAME, NULL); + if (!str) { + DEBUG(2, ("Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(state->msgs[state->cur]->dn))); + tevent_req_error(req, EFAULT); + return; + } + + subreq = users_get_send(state, state->ev, state->ctx, + str, BE_FILTER_NAME, BE_ATTR_CORE); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cleanup_users_up_done, req); +} + +static void cleanup_users_up_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_users_state *state = tevent_req_data(req, + struct cleanup_users_state); + int ret; + + ret = users_get_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User check returned: %d(%s)\n", + ret, strerror(ret))); + } + + /* if the entry doesn't need to be purged, remove it from the list */ + if (ret != ENOENT) { + talloc_zfree(state->msgs[state->cur]); + } + + state->cur++; + if (state->cur < state->count) { + cleanup_users_update(req); + return; + } + + tevent_req_done(req); +} + +/* ==Group-Cleanup-Process================================================ */ + +struct cleanup_groups_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + struct sysdb_handle *handle; + + struct ldb_message **msgs; + size_t count; + int cur; +}; + +static void cleanup_groups_process(struct tevent_req *subreq); +static void cleanup_groups_update(struct tevent_req *req); +static void cleanup_groups_up_done(struct tevent_req *subreq); + +static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct tevent_req *req, *subreq; + struct cleanup_groups_state *state; + static const char *attrs[] = { SYSDB_NAME, NULL }; + time_t now = time(NULL); + char *subfilter; + + req = tevent_req_create(memctx, &state, struct cleanup_groups_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->ctx = ctx; + state->sysdb = ctx->be->sysdb; + state->domain = ctx->be->domain; + state->msgs = NULL; + state->count = 0; + state->cur = 0; + + subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))", + SYSDB_CACHE_EXPIRE, + SYSDB_CACHE_EXPIRE, (long)now); + if (!subfilter) { + DEBUG(2, ("Failed to build filter\n")); + talloc_zfree(req); + return NULL; + } + + subreq = sysdb_search_groups_send(state, state->ev, + state->sysdb, NULL, + state->domain, subfilter, attrs); + if (!subreq) { + DEBUG(2, ("Failed to send entry search\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, cleanup_groups_process, req); + + return req; +} + +static void cleanup_groups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + int ret; + + ret = sysdb_search_groups_recv(subreq, state, &state->count, &state->msgs); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOENT) { + tevent_req_done(req); + return; + } + tevent_req_error(req, ret); + return; + } + + DEBUG(4, ("Found %d expired group entries!\n", state->count)); + + if (state->count == 0) { + tevent_req_done(req); + } + + cleanup_groups_update(req); +} + +static void cleanup_groups_update(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + const char *str; + + str = ldb_msg_find_attr_as_string(state->msgs[state->cur], + SYSDB_NAME, NULL); + if (!str) { + DEBUG(2, ("Entry %s has no Name Attribute ?!?\n", + ldb_dn_get_linearized(state->msgs[state->cur]->dn))); + tevent_req_error(req, EFAULT); + return; + } + + subreq = groups_get_send(state, state->ev, state->ctx, + str, BE_FILTER_NAME, BE_ATTR_CORE); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cleanup_groups_up_done, req); +} + +static void cleanup_groups_up_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct cleanup_groups_state *state = tevent_req_data(req, + struct cleanup_groups_state); + int ret; + + ret = groups_get_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("User check returned: %d(%s)\n", + ret, strerror(ret))); + } + + state->cur++; + if (state->cur < state->count) { + cleanup_groups_update(req); + return; + } + + tevent_req_done(req); +} + diff --git a/src/providers/ldap/ldap_id_enum.c b/src/providers/ldap/ldap_id_enum.c new file mode 100644 index 000000000..bc06e8bdc --- /dev/null +++ b/src/providers/ldap/ldap_id_enum.c @@ -0,0 +1,608 @@ +/* + 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 <errno.h> +#include <time.h> +#include <sys/time.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +extern struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx); + +/* ==Enumeration-Task===================================================== */ + +static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev, + struct sdap_id_ctx *ctx); + +static void ldap_id_enumerate_reschedule(struct tevent_req *req); + +static void ldap_id_enumerate_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static void ldap_id_enumerate_timer(struct tevent_context *ev, + struct tevent_timer *tt, + struct timeval tv, void *pvt) +{ + struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx); + struct tevent_timer *timeout; + struct tevent_req *req; + int delay; + + if (be_is_offline(ctx->be)) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); + return; + } + + req = ldap_id_enumerate_send(ev, ctx); + if (!req) { + DEBUG(1, ("Failed to schedule enumeration, retrying later!\n")); + /* schedule starting from now, not the last run */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); + return; + } + tevent_req_set_callback(req, ldap_id_enumerate_reschedule, ctx); + + /* if enumeration takes so long, either we try to enumerate too + * frequently, or something went seriously wrong */ + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_current_ofs(delay, 0); + timeout = tevent_add_timer(ctx->be->ev, req, tv, + ldap_id_enumerate_timeout, req); + return; +} + +static void ldap_id_enumerate_timeout(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_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + int delay; + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n", delay)); + + tv = tevent_timeval_current_ofs(delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); + + talloc_zfree(req); +} + +static void ldap_id_enumerate_reschedule(struct tevent_req *req) +{ + struct sdap_id_ctx *ctx = tevent_req_callback_data(req, + struct sdap_id_ctx); + enum tevent_req_state tstate; + uint64_t err; + struct timeval tv; + int delay; + + if (tevent_req_is_error(req, &tstate, &err)) { + /* On error schedule starting from now, not the last run */ + tv = tevent_timeval_current(); + } else { + tv = ctx->last_enum; + } + talloc_zfree(req); + + delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT); + tv = tevent_timeval_add(&tv, delay, 0); + ldap_id_enumerate_set_timer(ctx, tv); +} + + + +int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv) +{ + struct tevent_timer *enum_task; + + DEBUG(6, ("Scheduling next enumeration at %ld.%ld\n", + (long)tv.tv_sec, (long)tv.tv_usec)); + + enum_task = tevent_add_timer(ctx->be->ev, ctx, + tv, ldap_id_enumerate_timer, ctx); + if (!enum_task) { + DEBUG(0, ("FATAL: failed to setup enumeration task!\n")); + return EFAULT; + } + + return EOK; +} + + +struct global_enum_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + + bool purge; +}; + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + bool purge); +static void ldap_id_enum_users_done(struct tevent_req *subreq); +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + bool purge); +static void ldap_id_enum_groups_done(struct tevent_req *subreq); +static void ldap_id_enum_cleanup_done(struct tevent_req *subreq); + +static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev, + struct sdap_id_ctx *ctx) +{ + struct global_enum_state *state; + struct tevent_req *req, *subreq; + int t; + + req = tevent_req_create(ctx, &state, struct global_enum_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + ctx->last_enum = tevent_timeval_current(); + + t = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT); + if ((ctx->last_purge.tv_sec + t) < ctx->last_enum.tv_sec) { + state->purge = true; + } else { + state->purge = false; + } + + subreq = enum_users_send(state, ev, ctx, state->purge); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ldap_id_enum_users_done, req); + + return req; +} + +static void ldap_id_enum_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_enum_state *state = tevent_req_data(req, + struct global_enum_state); + enum tevent_req_state tstate; + uint64_t err = 0; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + subreq = enum_groups_send(state, state->ev, state->ctx, state->purge); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req); + + return; + +fail: + if (err) { + DEBUG(9, ("User enumeration failed with: (%d)[%s]\n", + (int)err, strerror(err))); + + if (sdap_check_gssapi_reconnect(state->ctx)) { + talloc_zfree(state->ctx->gsh); + subreq = enum_users_send(state, state->ev, state->ctx, state->purge); + if (subreq != NULL) { + tevent_req_set_callback(subreq, ldap_id_enum_users_done, req); + return; + } + } + sdap_mark_offline(state->ctx); + } + + DEBUG(1, ("Failed to enumerate users, retrying later!\n")); + tevent_req_done(req); +} + +static void ldap_id_enum_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct global_enum_state *state = tevent_req_data(req, + struct global_enum_state); + enum tevent_req_state tstate; + uint64_t err = 0; + + if (tevent_req_is_error(subreq, &tstate, &err)) { + if (tstate != TEVENT_REQ_USER_ERROR) { + err = EIO; + } + if (err != ENOENT) { + goto fail; + } + } + talloc_zfree(subreq); + + if (state->purge) { + + subreq = ldap_id_cleanup_send(state, state->ev, state->ctx); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ldap_id_enum_cleanup_done, req); + + return; + } + + tevent_req_done(req); + return; + +fail: + /* check if credentials are expired otherwise go offline on failures */ + if (sdap_check_gssapi_reconnect(state->ctx)) { + talloc_zfree(state->ctx->gsh); + subreq = enum_groups_send(state, state->ev, state->ctx, state->purge); + if (subreq != NULL) { + tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req); + return; + } + } + sdap_mark_offline(state->ctx); + DEBUG(1, ("Failed to enumerate groups (%d [%s]), retrying later!\n", + (int)err, strerror(err))); + tevent_req_done(req); +} + +static void ldap_id_enum_cleanup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + talloc_zfree(subreq); + tevent_req_done(req); +} + + +/* ==User-Enumeration===================================================== */ + +struct enum_users_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + + char *filter; + const char **attrs; +}; + +static void enum_users_connect_done(struct tevent_req *subreq); +static void enum_users_op_done(struct tevent_req *subreq); + +static struct tevent_req *enum_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_users_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct enum_users_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + if (ctx->max_user_timestamp && !purge) { + + state->filter = talloc_asprintf(state, + "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))", + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_OC_USER].name, + ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name, + ctx->max_user_timestamp, + ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name, + ctx->max_user_timestamp); + } else { + state->filter = talloc_asprintf(state, + "(&(%s=*)(objectclass=%s))", + ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ctx->opts->user_map[SDAP_OC_USER].name); + } + if (!state->filter) { + DEBUG(2, ("Failed to build filter\n")); + ret = ENOMEM; + goto fail; + } + + /* TODO: handle attrs_type */ + ret = build_attrs_from_map(state, ctx->opts->user_map, + SDAP_OPTS_USER, &state->attrs); + if (ret != EOK) goto fail; + + if (!sdap_connected(ctx)) { + + if (ctx->gsh) talloc_zfree(ctx->gsh); + + /* FIXME: add option to decide if tls should be used + * or SASL/GSSAPI, etc ... */ + subreq = sdap_cli_connect_send(state, ev, ctx->opts, + ctx->be, ctx->service, + &ctx->rootDSE); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, enum_users_connect_done, req); + + return req; + } + + subreq = sdap_get_users_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, + state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_users_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_users_connect_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); + int ret; + + ret = sdap_cli_connect_recv(subreq, state->ctx, + &state->ctx->gsh, &state->ctx->rootDSE); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOTSUP) { + DEBUG(0, ("Authentication mechanism not Supported by server")); + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_users_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, enum_users_op_done, req); +} + +static void enum_users_op_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 *timestamp; + int ret; + + ret = sdap_get_users_recv(subreq, state, ×tamp); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (timestamp) { + talloc_zfree(state->ctx->max_user_timestamp); + state->ctx->max_user_timestamp = talloc_steal(state->ctx, timestamp); + } + + DEBUG(4, ("Users higher timestamp: [%s]\n", + state->ctx->max_user_timestamp)); + + tevent_req_done(req); +} + +/* =Group-Enumeration===================================================== */ + +struct enum_groups_state { + struct tevent_context *ev; + struct sdap_id_ctx *ctx; + + char *filter; + const char **attrs; +}; + +static void enum_groups_connect_done(struct tevent_req *subreq); +static void enum_groups_op_done(struct tevent_req *subreq); + +static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *ctx, + bool purge) +{ + struct tevent_req *req, *subreq; + struct enum_groups_state *state; + const char *attr_name; + int ret; + + req = tevent_req_create(memctx, &state, struct enum_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ctx; + + attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name; + + if (ctx->max_group_timestamp && !purge) { + + state->filter = talloc_asprintf(state, + "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))", + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name, + ctx->opts->group_map[SDAP_OC_GROUP].name, + ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name, + ctx->max_group_timestamp, + ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name, + ctx->max_group_timestamp); + } else { + state->filter = talloc_asprintf(state, + "(&(%s=*)(objectclass=%s))", + ctx->opts->group_map[SDAP_AT_GROUP_NAME].name, + ctx->opts->group_map[SDAP_OC_GROUP].name); + } + if (!state->filter) { + DEBUG(2, ("Failed to build filter\n")); + ret = ENOMEM; + goto fail; + } + + /* TODO: handle attrs_type */ + ret = build_attrs_from_map(state, ctx->opts->group_map, + SDAP_OPTS_GROUP, &state->attrs); + if (ret != EOK) goto fail; + + if (!sdap_connected(ctx)) { + + if (ctx->gsh) talloc_zfree(ctx->gsh); + + /* FIXME: add option to decide if tls should be used + * or SASL/GSSAPI, etc ... */ + subreq = sdap_cli_connect_send(state, ev, ctx->opts, + ctx->be, ctx->service, + &ctx->rootDSE); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, enum_groups_connect_done, req); + + return req; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, enum_groups_op_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void enum_groups_connect_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); + int ret; + + ret = sdap_cli_connect_recv(subreq, state->ctx, + &state->ctx->gsh, &state->ctx->rootDSE); + talloc_zfree(subreq); + if (ret) { + if (ret == ENOTSUP) { + DEBUG(0, ("Authentication mechanism not Supported by server")); + } + tevent_req_error(req, ret); + return; + } + + subreq = sdap_get_groups_send(state, state->ev, + state->ctx->be->domain, + state->ctx->be->sysdb, + state->ctx->opts, state->ctx->gsh, + state->attrs, state->filter); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, enum_groups_op_done, req); +} + +static void enum_groups_op_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 *timestamp; + int ret; + + ret = sdap_get_groups_recv(subreq, state, ×tamp); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (timestamp) { + talloc_zfree(state->ctx->max_group_timestamp); + state->ctx->max_group_timestamp = talloc_steal(state->ctx, timestamp); + } + + DEBUG(4, ("Groups higher timestamp: [%s]\n", + state->ctx->max_group_timestamp)); + + tevent_req_done(req); +} + diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c new file mode 100644 index 000000000..b1f053fbc --- /dev/null +++ b/src/providers/ldap/ldap_init.c @@ -0,0 +1,179 @@ +/* + 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 "providers/child_common.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" + +static void sdap_shutdown(struct be_req *req); + +/* Id Handler */ +struct bet_ops sdap_id_ops = { + .handler = sdap_account_info_handler, + .finalize = sdap_shutdown +}; + +/* Auth Handler */ +struct bet_ops sdap_auth_ops = { + .handler = sdap_pam_auth_handler, + .finalize = sdap_shutdown +}; + +/* Chpass Handler */ +struct bet_ops sdap_chpass_ops = { + .handler = sdap_pam_chpass_handler, + .finalize = sdap_shutdown +}; + +int sssm_ldap_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + struct sdap_id_ctx *ctx; + const char *urls; + int ret; + + ctx = talloc_zero(bectx, struct sdap_id_ctx); + if (!ctx) return ENOMEM; + + ctx->be = bectx; + + ret = ldap_get_options(ctx, bectx->cdb, + bectx->conf_path, &ctx->opts); + if (ret != EOK) { + goto done; + } + + urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI); + if (!urls) { + DEBUG(0, ("Missing ldap_uri\n")); + ret = EINVAL; + goto done; + } + + ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service); + if (ret != EOK) { + DEBUG(1, ("Failed to initialize failover service!\n")); + goto done; + } + + ret = setup_tls_config(ctx->opts->basic); + if (ret != EOK) { + DEBUG(1, ("setup_tls_config failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = sdap_id_setup_tasks(ctx); + if (ret != EOK) { + goto done; + } + + ret = setup_child(ctx); + if (ret != EOK) { + DEBUG(1, ("setup_child failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + *ops = &sdap_id_ops; + *pvt_data = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sssm_ldap_auth_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + struct sdap_auth_ctx *ctx; + const char *urls; + int ret; + + ctx = talloc(bectx, struct sdap_auth_ctx); + if (!ctx) return ENOMEM; + + ctx->be = bectx; + + ret = ldap_get_options(ctx, bectx->cdb, + bectx->conf_path, &ctx->opts); + if (ret != EOK) { + goto done; + } + + urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI); + if (!urls) { + DEBUG(0, ("Missing ldap_uri\n")); + ret = EINVAL; + goto done; + } + + ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service); + if (ret != EOK) { + DEBUG(1, ("Failed to initialize failover service!\n")); + goto done; + } + + ret = setup_tls_config(ctx->opts->basic); + if (ret != EOK) { + DEBUG(1, ("setup_tls_config failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + *ops = &sdap_auth_ops; + *pvt_data = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sssm_ldap_chpass_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + int ret; + + ret = sssm_ldap_auth_init(bectx, ops, pvt_data); + + *ops = &sdap_chpass_ops; + + return ret; +} + +static void sdap_shutdown(struct be_req *req) +{ + /* TODO: Clean up any internal data */ + sdap_handler_done(req, DP_ERR_OK, EOK, NULL); +} + diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c new file mode 100644 index 000000000..39c67cc92 --- /dev/null +++ b/src/providers/ldap/sdap.c @@ -0,0 +1,388 @@ +/* + 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/>. +*/ + +#define LDAP_DEPRECATED 1 +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/ldap/sdap.h" + +/* =Retrieve-Options====================================================== */ + +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; + int i, ret; + + map = talloc_array(memctx, struct sdap_attr_map, num_entries); + 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, + &map[i].name); + if ((ret != EOK) || (map[i].def_name && !map[i].name)) { + DEBUG(0, ("Failed to retrieve value for %s\n", map[i].opt_name)); + if (ret != EOK) { + talloc_zfree(map); + return EINVAL; + } + } + + DEBUG(5, ("Option %s has value %s\n", map[i].opt_name, map[i].name)); + } + + *_map = map; + return EOK; +} + +/* =Parse-msg============================================================= */ + +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, char **_dn) +{ + struct sysdb_attrs *attrs; + BerElement *ber = NULL; + struct berval **vals; + struct ldb_val v; + char *str; + int lerrno; + int a, i, ret; + const char *name; + bool store; + + lerrno = 0; + ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + + attrs = sysdb_new_attrs(memctx); + if (!attrs) return ENOMEM; + + str = ldap_get_dn(sh->ldap, sm->msg); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(1, ("ldap_get_dn failed: %d(%s)\n", + lerrno, ldap_err2string(lerrno))); + ret = EIO; + goto fail; + } + + DEBUG(9, ("OriginalDN: [%s].\n", str)); + ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, str); + if (ret) goto fail; + if (_dn) { + *_dn = talloc_strdup(memctx, str); + if (!*_dn) { + ret = ENOMEM; + ldap_memfree(str); + goto fail; + } + } + ldap_memfree(str); + + if (map) { + vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass"); + if (!vals) { + DEBUG(1, ("Unknown entry type, no objectClasses found!\n")); + ret = EINVAL; + goto fail; + } + + for (i = 0; vals[i]; i++) { + /* the objectclass is always the first name in the map */ + if (strncasecmp(map[0].name, + vals[i]->bv_val, vals[i]->bv_len) == 0) { + /* ok it's an entry of the right type */ + break; + } + } + if (!vals[i]) { + DEBUG(1, ("objectClass not matching: %s\n", + map[0].name)); + ldap_value_free_len(vals); + ret = EINVAL; + goto fail; + } + 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(1, ("Entry has no attributes [%d(%s)]!?\n", + lerrno, ldap_err2string(lerrno))); + if (map) { + ret = EINVAL; + goto fail; + } + } + while (str) { + if (map) { + for (a = 1; a < attrs_num; 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(str, map[a].name) == 0) break; + } + /* interesting attr */ + if (a < attrs_num) { + store = true; + name = map[a].sys_name; + } else { + store = false; + } + } else { + name = str; + 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); + DEBUG(1, ("LDAP Library error: %d(%s)", + lerrno, ldap_err2string(lerrno))); + ret = EIO; + goto fail; + } + if (!vals[0]) { + DEBUG(1, ("Missing value after ldap_get_values() ??\n")); + ret = EINVAL; + goto fail; + } + for (i = 0; vals[i]; i++) { + v.data = (uint8_t *)vals[i]->bv_val; + v.length = vals[i]->bv_len; + + ret = sysdb_attrs_add_val(attrs, name, &v); + if (ret) goto fail; + } + ldap_value_free_len(vals); + } + + ldap_memfree(str); + str = ldap_next_attribute(sh->ldap, sm->msg, ber); + } + ber_free(ber, 0); + + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + if (lerrno) { + DEBUG(1, ("LDAP Library error: %d(%s)", + lerrno, ldap_err2string(lerrno))); + ret = EIO; + goto fail; + } + + *_attrs = attrs; + return EOK; + +fail: + if (ber) ber_free(ber, 0); + talloc_free(attrs); + return ret; +} + +/* This function converts an ldap message into a sysdb_attrs structure. + * It converts only known user attributes, the rest are ignored. + * If the entry is not that of an user an error is returned. + * The original DN is stored as an attribute named originalDN */ + +int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sysdb_attrs **_attrs, char **_dn) +{ + + return sdap_parse_entry(memctx, sh, sm, opts->user_map, + SDAP_OPTS_USER, _attrs, _dn); +} + +/* This function converts an ldap message into a sysdb_attrs structure. + * It converts only known group attributes, the rest are ignored. + * If the entry is not that of an user an error is returned. + * The original DN is stored as an attribute named originalDN */ + +int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sysdb_attrs **_attrs, char **_dn) +{ + + return sdap_parse_entry(memctx, sh, sm, opts->group_map, + SDAP_OPTS_GROUP, _attrs, _dn); +} + +/* =Get-DN-from-message=================================================== */ + +int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh, + struct sdap_msg *sm, char **_dn) +{ + char *str; + int lerrno; + + lerrno = 0; + ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + + str = ldap_get_dn(sh->ldap, sm->msg); + if (!str) { + ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); + DEBUG(1, ("ldap_get_dn failed: %d(%s)\n", + lerrno, ldap_err2string(lerrno))); + return EIO; + } + + *_dn = talloc_strdup(memctx, str); + ldap_memfree(str); + if (!*_dn) return ENOMEM; + + return EOK; +} + +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(1, ("Unknown value for tls_reqcert.\n")); + 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(1, ("ldap_set_option failed: %s\n", 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(1, ("ldap_set_option failed: %s\n", 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(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret))); + return EIO; + } + } + + return EOK; +} + + +bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse, + const char *sasl_mech) +{ + struct ldb_message_element *el = NULL; + struct ldb_val *val; + int i; + + if (!sasl_mech) return false; + + for (i = 0; i < rootdse->num; i++) { + if (strcasecmp(rootdse->a[i].name, "supportedSASLMechanisms")) { + continue; + } + el = &rootdse->a[i]; + break; + } + + if (!el) { + /* no supported SASL Mechanism at all ? */ + return false; + } + + for (i = 0; i < el->num_values; i++) { + val = &el->values[i]; + if (strncasecmp(sasl_mech, (const char *)val->data, val->length)) { + continue; + } + return true; + } + + return false; +} + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, const char ***_attrs) +{ + char **attrs; + int i, j; + + attrs = talloc_array(memctx, char *, size + 1); + if (!attrs) return ENOMEM; + + /* first attribute is "objectclass" not the specifc 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) { + attrs[j] = map[i].name; + j++; + } + } + attrs[j] = NULL; + + *_attrs = (const char **)attrs; + + return EOK; +} + diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h new file mode 100644 index 000000000..16dbb7843 --- /dev/null +++ b/src/providers/ldap/sdap.h @@ -0,0 +1,258 @@ +/* + 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 "providers/dp_backend.h" +#include <ldap.h> +#include "util/sss_ldap.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 sdap_op { + struct sdap_op *prev, *next; + struct sdap_handle *sh; + + int msgid; + bool done; + + sdap_op_callback_t *callback; + void *data; + + struct tevent_context *ev; + struct sdap_msg *list; + struct sdap_msg *last; +}; + +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 sdap_handle { + LDAP *ldap; + bool connected; + +#ifdef HAVE_LDAP_CONNCB + struct ldap_conncb *conncb; +#else + struct tevent_fd *fde; +#endif + + struct sdap_op *ops; +}; + +struct sdap_service { + char *name; + char *uri; +}; + +#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_KRBPW_LASTCHANGE "krbLastPwdChange" +#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration" + +#define SYSDB_PWD_ATTRIBUTE "pwdAttribute" + +enum sdap_result { + SDAP_SUCCESS, + SDAP_NOT_FOUND, + SDAP_UNAVAIL, + SDAP_RETRY, + SDAP_ERROR, + SDAP_AUTH_SUCCESS, + SDAP_AUTH_FAILED, + SDAP_AUTH_PW_EXPIRED, + SDAP_ACCT_EXPIRED +}; + +enum sdap_basic_opt { + SDAP_URI = 0, + 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_GROUP_SEARCH_BASE, + SDAP_GROUP_SEARCH_SCOPE, + SDAP_GROUP_SEARCH_FILTER, + SDAP_SCHEMA, + SDAP_OFFLINE_TIMEOUT, + SDAP_FORCE_UPPER_CASE_REALM, + SDAP_ENUM_REFRESH_TIMEOUT, + SDAP_CACHE_PURGE_TIMEOUT, + SDAP_ENTRY_CACHE_TIMEOUT, + SDAP_TLS_CACERT, + SDAP_TLS_CACERTDIR, + SDAP_ID_TLS, + SDAP_SASL_MECH, + SDAP_SASL_AUTHID, + SDAP_KRB5_KEYTAB, + SDAP_KRB5_KINIT, + SDAP_KRB5_REALM, + SDAP_PWD_POLICY, + SDAP_REFERRALS, + + 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_MODSTAMP, + 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_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_AT_GROUP_NAME, + SDAP_AT_GROUP_PWD, + SDAP_AT_GROUP_GID, + SDAP_AT_GROUP_MEMBER, + SDAP_AT_GROUP_UUID, + SDAP_AT_GROUP_MODSTAMP, + + SDAP_OPTS_GROUP /* attrs counter */ +}; + +struct sdap_attr_map { + const char *opt_name; + const char *def_name; + const char *sys_name; + char *name; +}; + +struct sdap_options { + struct dp_option *basic; + struct sdap_attr_map *gen_map; + struct sdap_attr_map *user_map; + struct sdap_attr_map *group_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; + + struct ldb_dn *users_base; + struct ldb_dn *groups_base; +}; + +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, char **_dn); + +int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sysdb_attrs **_attrs, char **_dn); + +int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts, + struct sdap_handle *sh, struct sdap_msg *sm, + struct sysdb_attrs **_attrs, char **_dn); + +int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh, + struct sdap_msg *sm, char **_dn); + +errno_t setup_tls_config(struct dp_option *basic_opts); + +bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse, + const char *sasl_mech); + +int build_attrs_from_map(TALLOC_CTX *memctx, + struct sdap_attr_map *map, + size_t size, const char ***_attrs); +#endif /* _SDAP_H_ */ diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c new file mode 100644 index 000000000..959c08a65 --- /dev/null +++ b/src/providers/ldap/sdap_async.c @@ -0,0 +1,1018 @@ +/* + 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 "providers/ldap/sdap_async_private.h" + +#define REALM_SEPARATOR '@' +#define REPLY_REALLOC_INCREMENT 10 + +void make_realm_upper_case(const char *upn) +{ + char *c; + + c = strchr(upn, REALM_SEPARATOR); + if (c == NULL) { + DEBUG(9, ("No realm delimiter found in upn [%s].\n", upn)); + return; + } + + while(*(++c) != '\0') { + c[0] = toupper(*c); + } + + return; +} + +/* ==LDAP-Memory-Handling================================================= */ + +static int lmsg_destructor(void *mem) +{ + ldap_msgfree((LDAPMessage *)mem); + return 0; +} + +static int sdap_msg_attach(TALLOC_CTX *memctx, LDAPMessage *msg) +{ + void *h; + + if (!msg) return EINVAL; + + h = sss_mem_attach(memctx, msg, lmsg_destructor); + if (!h) return ENOMEM; + + return EOK; +} + +/* ==sdap-hanlde-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); + + sdap_handle_release(sh); + + return 0; +} + +static void sdap_handle_release(struct sdap_handle *sh) +{ + DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->ldap)); + + if (sh->connected) { + struct sdap_op *op; + +#ifdef HAVE_LDAP_CONNCB + /* remove all related fd events from the event loop */ + talloc_zfree(sh->conncb->lc_arg); +#else + talloc_zfree(sh->fde); +#endif + + while (sh->ops) { + op = sh->ops; + op->callback(op, NULL, EIO, op->data); + /* 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); + } +#ifdef HAVE_LDAP_CONNCB + talloc_zfree(sh->conncb); +#endif + sh->connected = false; + sh->ldap = NULL; + sh->ops = NULL; + } +} + +/* ==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); + +static 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 void sdap_process_result(struct tevent_context *ev, void *pvt) +{ + struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle); + struct timeval no_timeout = {0, 0}; + struct tevent_timer *te; + LDAPMessage *msg; + int ret; + + DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->ldap)); + + if (!sh->connected || !sh->ldap) { + DEBUG(2, ("ERROR: LDAP connection is not connected!\n")); + 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(8, ("Trace: ldap_result found nothing!\n")); + return; + } + + if (ret == -1) { + DEBUG(4, ("ldap_result gave -1, something bad happend!\n")); + 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 avilable */ + memset(&no_timeout, 0, sizeof(struct timeval)); + + te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh); + if (!te) { + DEBUG(1, ("Failed to add critical timer to fetch next result!\n")); + } + + /* now process this message */ + sdap_process_message(ev, sh, msg); +} + +/* process a messgae calling the right operation callback. + * msg is completely taken care of (including freeeing it) + * NOTE: this function may even end up freeing the sdap_handle + * so sdap_hanbdle 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 msgid; + int msgtype; + int ret; + + msgid = ldap_msgid(msg); + if (msgid == -1) { + DEBUG(2, ("can't fire callback, message id invalid!\n")); + ldap_msgfree(msg); + return; + } + + msgtype = ldap_msgtype(msg); + + for (op = sh->ops; op; op = op->next) { + if (op->msgid == msgid) break; + } + + if (op == NULL) { + DEBUG(2, ("Unmatched msgid, discarding message (type: %0x)\n", + msgtype)); + ldap_msgfree(msg); + return; + } + + /* shouldn't happen */ + if (op->done) { + DEBUG(2, ("Operation [%p] already handled (type: %0x)\n", op, msgtype)); + ldap_msgfree(msg); + return; + } + + switch (msgtype) { + case LDAP_RES_SEARCH_ENTRY: + /* go and process entry */ + break; + + case LDAP_RES_SEARCH_REFERENCE: + /* more ops to come with this msgid */ + /* just ignore */ + ldap_msgfree(msg); + return; + + 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: + /* unkwon msg type ?? */ + DEBUG(1, ("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 = sdap_msg_attach(reply, msg); + 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 */ + op->callback(op, reply, ret, op->data); + } +} + +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(1, ("Failed to add critical timer for next reply!\n")); + op->callback(op, NULL, EFAULT, op->data); + } + } +} + +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); + + op->callback(op, op->list, EOK, op->data); +} + +#ifdef HAVE_LDAP_CONNCB +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; + struct fd_event_item *fd_event_item; + struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg, + struct ldap_cb_data); + + ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd); + if (ret == -1) { + DEBUG(1, ("ber_sockbuf_ctrl failed.\n")); + return EINVAL; + } + DEBUG(9, ("New LDAP connection to [%s] with fd [%d].\n", + ldap_url_desc2str(srv), ber_fd)); + + fd_event_item = talloc_zero(cb_data, struct fd_event_item); + if (fd_event_item == NULL) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd, + TEVENT_FD_READ, sdap_ldap_result, + cb_data->sh); + if (fd_event_item->fde == NULL) { + DEBUG(1, ("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; +} + +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(1, ("ber_sockbuf_ctrl failed.\n")); + return; + } + DEBUG(9, ("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(1, ("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 get_fd_from_ldap(LDAP *ldap, int *fd) +{ + int ret; + + ret = ldap_get_option(ldap, LDAP_OPT_DESC, fd); + if (ret != LDAP_OPT_SUCCESS) { + DEBUG(1, ("Failed to get fd from ldap!!\n")); + *fd = -1; + return EIO; + } + + return EOK; +} + +int sdap_install_ldap_callbacks(struct sdap_handle *sh, + struct tevent_context *ev) +{ + int fd; + int ret; + + ret = get_fd_from_ldap(sh->ldap, &fd); + if (ret) return ret; + + sh->fde = tevent_add_fd(ev, sh, fd, TEVENT_FD_READ, sdap_ldap_result, sh); + if (!sh->fde) return ENOMEM; + + DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], fde[%p], ldap[%p]\n", + sh, (int)sh->connected, sh->ops, sh->fde, sh->ldap)); + + return EOK; +} + +#endif + + +/* ==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) 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 */ + 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(2, ("Timeout happened after op was finished !?\n")); + return; + } + + /* signal the caller that we have a timeout */ + op->callback(op, NULL, ETIMEDOUT, op->data); +} + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, + 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->sh = sh; + op->msgid = msgid; + op->callback = callback; + op->data = data; + op->ev = ev; + + /* 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====================================================== */ + +struct sdap_exop_modify_passwd_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + int result; + 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, + char *password, + char *new_password) +{ + 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[2]; + + 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(7, ("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(1, ("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(1, ("ber_flatten failed.\n")); + talloc_zfree(req); + return NULL; + } + + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &request_controls[0]); + if (ret != LDAP_SUCCESS) { + DEBUG(1, ("sss_ldap_control_create failed.\n")); + goto fail; + } + request_controls[1] = NULL; + + DEBUG(4, ("Executing extended operation\n")); + + ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD, + bv, request_controls, NULL, &msgid); + ber_bvfree(bv); + ldap_control_free(request_controls[0]); + if (ret == -1 || msgid == -1) { + DEBUG(1, ("ldap_extended_operation failed\n")); + goto fail; + } + DEBUG(8, ("ldap_extended_operation sent, msgid = %d\n", msgid)); + + /* FIXME: get timeouts from configuration, for now 5 secs. */ + ret = sdap_op_add(state, ev, state->sh, msgid, + sdap_exop_modify_passwd_done, req, 5, &state->op); + if (ret) { + DEBUG(1, ("Failed to set up operation!\n")); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, EIO); + 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; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &state->result, NULL, &errmsg, NULL, + &response_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid)); + ret = EIO; + goto done; + } + + if (response_controls == NULL) { + DEBUG(5, ("Server returned no controls.\n")); + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(9, ("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(1, ("ldap_parse_passwordpolicy_control failed.\n")); + ret = EIO; + goto done; + } + + DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error))); + } + } + } + + if (state->result != LDAP_SUCCESS) { + state->user_error_message = talloc_strdup(state, errmsg); + if (state->user_error_message == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + } + } + + DEBUG(3, ("ldap_extended_operation result: %s(%d), %s\n", + ldap_err2string(state->result), state->result, errmsg)); + + ret = LDAP_SUCCESS; +done: + ldap_controls_free(response_controls); + ldap_memfree(errmsg); + + if (ret == LDAP_SUCCESS) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +int sdap_exop_modify_passwd_recv(struct tevent_req *req, + TALLOC_CTX * mem_ctx, + enum sdap_result *result, + char **user_error_message) +{ + struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, + struct sdap_exop_modify_passwd_state); + + *result = SDAP_ERROR; + *user_error_message = talloc_steal(mem_ctx, state->user_error_message); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (state->result == LDAP_SUCCESS) { + *result = SDAP_SUCCESS; + } + + 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; + + DEBUG(9, ("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=*)", NULL, NULL, 0); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_rootdse_done, req); + + return req; +} + +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(2, ("No RootDSE for server ?!\n")); + tevent_req_error(req, ENOENT); + return; + } + + if (num_results > 1) { + DEBUG(2, ("Multiple replies when searching for RootDSE ??\n")); + tevent_req_error(req, EIO); + return; + } + + state->rootdse = talloc_steal(state, results[0]); + talloc_zfree(results); + + DEBUG(9, ("Got rootdse\n")); + + tevent_req_done(req); +} + +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; +} + +/* ==Generic Search============================================ */ + +struct sdap_get_generic_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; + struct sdap_attr_map *map; + int map_num_attrs; + + struct sdap_op *op; + + size_t reply_max; + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static errno_t add_to_reply(struct sdap_get_generic_state *state, + struct sysdb_attrs *msg); + +static void sdap_get_generic_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +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) +{ + struct tevent_req *req = NULL; + struct sdap_get_generic_state *state = NULL; + int lret; + int ret; + int msgid; + + req = tevent_req_create(memctx, &state, struct sdap_get_generic_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->map = map; + state->map_num_attrs = map_num_attrs; + state->op = NULL; + state->reply_max = 0; + state->reply_count = 0; + state->reply = NULL; + + DEBUG(6, ("calling ldap_search_ext with [%s][%s].\n", state->filter, + state->search_base)); + if (debug_level >= 7) { + int i; + + if (state->attrs) { + for (i = 0; state->attrs[i]; i++) { + DEBUG(7, ("Requesting attrs: [%s]\n", state->attrs[i])); + } + } + } + + lret = ldap_search_ext(state->sh->ldap, state->search_base, + state->scope, state->filter, + discard_const(state->attrs), + false, NULL, NULL, NULL, 0, &msgid); + if (lret != LDAP_SUCCESS) { + DEBUG(3, ("ldap_search_ext failed: %s\n", ldap_err2string(lret))); + ret = EIO; + goto fail; + } + DEBUG(8, ("ldap_search_ext called, msgid = %d\n", msgid)); + + ret = sdap_op_add(state, state->ev, state->sh, msgid, + sdap_get_generic_done, req, + dp_opt_get_int(state->opts->basic, + SDAP_SEARCH_TIMEOUT), + &state->op); + if (ret != EOK) { + DEBUG(1, ("Failed to set up operation!\n")); + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + + +static void sdap_get_generic_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_get_generic_state *state = tevent_req_data(req, + struct sdap_get_generic_state); + struct sysdb_attrs *attrs; + char *errmsg; + int result; + int ret; + + if (error) { + tevent_req_error(req, error); + return; + } + + switch (ldap_msgtype(reply->msg)) { + case LDAP_RES_SEARCH_REFERENCE: + /* ignore references for now */ + talloc_free(reply); + + /* 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 = sdap_parse_entry(state, state->sh, reply, + state->map, state->map_num_attrs, + &attrs, NULL); + if (ret != EOK) { + DEBUG(1, ("sdap_parse_generic_entry failed.\n")); + tevent_req_error(req, ENOMEM); + return; + } + + ret = add_to_reply(state, attrs); + if (ret != EOK) { + DEBUG(1, ("add_to_reply 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, NULL, NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid)); + tevent_req_error(req, EIO); + return; + } + + DEBUG(6, ("Search result: %s(%d), %s\n", + ldap_err2string(result), result, errmsg)); + + tevent_req_done(req); + return; + + default: + /* what is going on here !? */ + tevent_req_error(req, EIO); + return; + } +} + +static errno_t add_to_reply(struct sdap_get_generic_state *state, + struct sysdb_attrs *msg) +{ + if (state->reply == NULL || state->reply_max == state->reply_count) { + state->reply_max += REPLY_REALLOC_INCREMENT; + state->reply = talloc_realloc(state, state->reply, + struct sysdb_attrs *, + state->reply_max); + if (state->reply == NULL) { + DEBUG(1, ("talloc_realloc failed.\n")); + return ENOMEM; + } + } + + state->reply[state->reply_count++] = talloc_steal(state->reply, msg); + + return EOK; +} + +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; +} + diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h new file mode 100644 index 000000000..3c52d236b --- /dev/null +++ b/src/providers/ldap/sdap_async.h @@ -0,0 +1,126 @@ +/* + 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 <talloc.h> +#include <tevent.h> +#include "providers/dp_backend.h" +#include "providers/ldap/sdap.h" +#include "providers/fail_over.h" + +struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_options *opts, + const char *uri, + bool use_start_tls); +int sdap_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **sh); + +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_handle *sh, + const char **attrs, + const char *wildcard); +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 sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *wildcard); +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp); + +struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *keytab, + const char *principal, + const char *realm); +int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result); + +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, + const char *authtok_type, + struct dp_opt_blob authtok); +int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result); + +struct tevent_req *sdap_get_initgr_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, + const char *name, + const char **grp_attrs); +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, + char *password, + char *new_password); +int sdap_exop_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + enum sdap_result *result, + char **user_error_msg); + +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, + struct sysdb_attrs **rootdse); +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **gsh, + struct sysdb_attrs **rootdse); + +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 sdap_get_generic_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, size_t *reply_count, + struct sysdb_attrs ***reply_list); + +#endif /* _SDAP_ASYNC_H_ */ diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c new file mode 100644 index 000000000..abebe2883 --- /dev/null +++ b/src/providers/ldap/sdap_async_accounts.c @@ -0,0 +1,2065 @@ +/* + 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 "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" + +/* ==Save-User-Entry====================================================== */ + +struct sdap_save_user_state { + struct tevent_context *ev; + struct sysdb_handle *handle; + struct sdap_options *opts; + + struct sss_domain_info *dom; + + const char *name; + struct sysdb_attrs *attrs; + char *timestamp; +}; + +static void sdap_save_user_done(struct tevent_req *subreq); + + /* FIXME: support storing additional attributes */ + +static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_handle *handle, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + bool is_initgr) +{ + struct tevent_req *req, *subreq; + struct sdap_save_user_state *state; + struct ldb_message_element *el; + int ret; + const char *pwd; + const char *gecos; + const char *homedir; + const char *shell; + long int l; + uid_t uid; + gid_t gid; + struct sysdb_attrs *user_attrs; + char *upn = NULL; + int i; + char *val = NULL; + int cache_timeout; + + DEBUG(9, ("Save user\n")); + + req = tevent_req_create(memctx, &state, struct sdap_save_user_state); + if (!req) return NULL; + + state->ev = ev; + state->handle = handle; + state->dom = dom; + state->opts = opts; + state->attrs = attrs; + state->timestamp = NULL; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_NAME].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + state->name = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_PWD].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) pwd = NULL; + else pwd = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_GECOS].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) gecos = NULL; + else gecos = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_HOME].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) homedir = NULL; + else homedir = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_SHELL].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) shell = NULL; + else shell = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_UID].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) { + DEBUG(1, ("no uid provided for [%s] in domain [%s].\n", + state->name, dom->name)); + ret = EINVAL; + goto fail; + } + errno = 0; + l = strtol((const char *)el->values[0].data, NULL, 0); + if (errno) { + ret = EINVAL; + goto fail; + } + uid = l; + + /* check that the uid is valid for this domain */ + if (OUT_OF_ID_RANGE(uid, dom->id_min, dom->id_max)) { + DEBUG(2, ("User [%s] filtered out! (id out of range)\n", + state->name)); + ret = EINVAL; + goto fail; + } + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_GID].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) { + DEBUG(1, ("no gid provided for [%s] in domain [%s].\n", + state->name, dom->name)); + ret = EINVAL; + goto fail; + } + errno = 0; + l = strtol((const char *)el->values[0].data, NULL, 0); + if (errno) { + ret = EINVAL; + goto fail; + } + gid = l; + + /* check that the gid is valid for this domain */ + if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(2, ("User [%s] filtered out! (id out of range)\n", + state->name)); + ret = EINVAL; + goto fail; + } + + user_attrs = sysdb_new_attrs(state); + if (user_attrs == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(state->attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("Original DN is not available for [%s].\n", state->name)); + } else { + DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n", + el->values[0].data, state->name)); + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_DN, + (const char *) el->values[0].data); + if (ret) { + goto fail; + } + } + + ret = sysdb_attrs_get_el(state->attrs, SYSDB_MEMBEROF, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("Original memberOf is not available for [%s].\n", + state->name)); + } else { + DEBUG(7, ("Adding original memberOf attributes to [%s].\n", + state->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 fail; + } + } + } + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n", + state->name)); + } else { + ret = sysdb_attrs_add_string(user_attrs, + opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto fail; + } + state->timestamp = talloc_strdup(state, + (const char*)el->values[0].data); + if (!state->timestamp) { + ret = ENOMEM; + goto fail; + } + } + + ret = sysdb_attrs_get_el(state->attrs, + opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("User principle is not available for [%s].\n", state->name)); + } else { + upn = talloc_strdup(user_attrs, (const char*) el->values[0].data); + if (!upn) { + ret = ENOMEM; + goto fail; + } + if (dp_opt_get_bool(opts->basic, SDAP_FORCE_UPPER_CASE_REALM)) { + make_realm_upper_case(upn); + } + DEBUG(7, ("Adding user principle [%s] to attributes of [%s].\n", + upn, state->name)); + ret = sysdb_attrs_add_string(user_attrs, SYSDB_UPN, upn); + if (ret) { + goto fail; + } + } + + for (i = SDAP_FIRST_EXTRA_USER_AT; i < SDAP_OPTS_USER; i++) { + ret = sysdb_attrs_get_el(state->attrs, opts->user_map[i].sys_name, &el); + if (ret) { + goto fail; + } + if (el->num_values > 0) { + DEBUG(9, ("Adding [%s]=[%s] to user attributes.\n", + opts->user_map[i].sys_name, + (const char*) el->values[0].data)); + val = talloc_strdup(user_attrs, (const char*) el->values[0].data); + if (val == NULL) { + ret = ENOMEM; + goto fail; + } + ret = sysdb_attrs_add_string(user_attrs, + opts->user_map[i].sys_name, val); + if (ret) { + goto fail; + } + } + } + + cache_timeout = dp_opt_get_int(opts->basic, SDAP_ENTRY_CACHE_TIMEOUT); + + if (is_initgr) { + ret = sysdb_attrs_add_time_t(user_attrs, SYSDB_INITGR_EXPIRE, + (cache_timeout ? + (time(NULL) + cache_timeout) : 0)); + if (ret) { + goto fail; + } + } + + DEBUG(6, ("Storing info for user %s\n", state->name)); + + subreq = sysdb_store_user_send(state, state->ev, state->handle, + state->dom, state->name, pwd, + uid, gid, gecos, homedir, shell, + user_attrs, cache_timeout); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_save_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_save_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_save_user_state *state = tevent_req_data(req, + struct sdap_save_user_state); + int ret; + + ret = sysdb_store_user_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Failed to save user %s\n", state->name)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_save_user_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_save_user_state *state = tevent_req_data(req, + struct sdap_save_user_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->timestamp); + } + + return EOK; +} + + +/* ==Generic-Function-to-save-multiple-users============================= */ + +struct sdap_save_users_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + + struct sysdb_attrs **users; + int count; + int cur; + + struct sysdb_handle *handle; + + char *higher_timestamp; +}; + +static void sdap_save_users_trans(struct tevent_req *subreq); +static void sdap_save_users_store(struct tevent_req *req); +static void sdap_save_users_process(struct tevent_req *subreq); +struct tevent_req *sdap_save_users_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users) +{ + struct tevent_req *req, *subreq; + struct sdap_save_users_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_save_users_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->users = users; + state->count = num_users; + state->cur = 0; + state->handle = NULL; + state->higher_timestamp = NULL; + + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (!subreq) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, ev); + return req; + } + tevent_req_set_callback(subreq, sdap_save_users_trans, req); + + return req; +} + +static void sdap_save_users_trans(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_save_users_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_save_users_state); + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + sdap_save_users_store(req); +} + +static void sdap_save_users_store(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_save_users_state *state; + + state = tevent_req_data(req, struct sdap_save_users_state); + + subreq = sdap_save_user_send(state, state->ev, state->handle, + state->opts, state->dom, + state->users[state->cur], false); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_save_users_process, req); +} + +static void sdap_save_users_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_save_users_state *state; + char *timestamp = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_save_users_state); + + ret = sdap_save_user_recv(subreq, state, ×tamp); + talloc_zfree(subreq); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(2, ("Failed to store user %d. Ignoring.\n", state->cur)); + } else { + DEBUG(9, ("User %d processed!\n", state->cur)); + } + + if (timestamp) { + if (state->higher_timestamp) { + if (strcmp(timestamp, state->higher_timestamp) > 0) { + talloc_zfree(state->higher_timestamp); + state->higher_timestamp = timestamp; + } else { + talloc_zfree(timestamp); + } + } else { + state->higher_timestamp = timestamp; + } + } + + state->cur++; + if (state->cur < state->count) { + sdap_save_users_store(req); + } else { + subreq = sysdb_transaction_commit_send(state, state->ev, + state->handle); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + /* sysdb_transaction_complete will call tevent_req_done(req) */ + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + } +} + +static int sdap_save_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_save_users_state *state = tevent_req_data(req, + struct sdap_save_users_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + return EOK; +} + + +/* ==Search-Users-with-filter============================================= */ + +struct sdap_get_users_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 *filter; + + char *higher_timestamp; + struct sysdb_attrs **users; + size_t count; +}; + +static void sdap_get_users_process(struct tevent_req *subreq); +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_handle *sh, + const char **attrs, + const char *filter) +{ + struct tevent_req *req, *subreq; + struct sdap_get_users_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_users_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->filter = filter; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->users = NULL; + state->count = 0; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + dp_opt_get_string(state->opts->basic, + SDAP_USER_SEARCH_BASE), + LDAP_SCOPE_SUBTREE, + state->filter, state->attrs, + state->opts->user_map, SDAP_OPTS_USER); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_users_process, req); + + return req; +} + +static void sdap_get_users_process(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_get_generic_recv(subreq, state, + &state->count, &state->users); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(6, ("Search for users, returned %d results.\n", state->count)); + + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + subreq = sdap_save_users_send(state, state->ev, state->dom, + state->sysdb, state->opts, + state->users, state->count); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_users_done, 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; + + DEBUG(9, ("Saving %d Users - Done\n", state->count)); + + ret = sdap_save_users_recv(subreq, state, &state->higher_timestamp); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Failed to store users.\n")); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_get_users_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_get_users_state *state = tevent_req_data(req, + struct sdap_get_users_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + return EOK; +} + +/* ==Group-Parsing Routines=============================================== */ + +struct sdap_orig_entry_state { + int done; +}; + +static void sdap_find_entry_by_origDN_done(struct tevent_req *req) +{ + struct sdap_orig_entry_state *state = tevent_req_callback_data(req, + struct sdap_orig_entry_state); + state->done = 1; +} + +/* WARNING: this is a sync routine for now */ +static int sdap_find_entry_by_origDN(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_handle *handle, + struct sss_domain_info *domain, + const char *orig_dn, + char **localdn) +{ + struct tevent_req *req; + struct sdap_orig_entry_state *state; + static const char *attrs[] = { NULL }; + struct ldb_dn *base_dn; + char *filter; + struct ldb_message **msgs; + size_t num_msgs; + int ret; + + state = talloc_zero(memctx, struct sdap_orig_entry_state); + if (!state) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(state, "%s=%s", SYSDB_ORIG_DN, orig_dn); + if (!filter) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(sysdb_handle_get_ctx(handle), + state, domain->name); + if (!base_dn) { + ret = ENOMEM; + goto done; + } + + req = sysdb_search_entry_send(state, ev, handle, base_dn, + LDB_SCOPE_SUBTREE, filter, attrs); + if (!req) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(req, sdap_find_entry_by_origDN_done, state); + + /* WARNING: SYNC LOOP HERE */ + tevent_loop_allow_nesting(ev); + while (state->done == 0) { + tevent_loop_once(ev); + } + + ret = sysdb_search_entry_recv(req, state, &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; + } + + ret = EOK; + +done: + talloc_zfree(state); + return ret; +} + +static int sdap_fill_memberships(struct sysdb_attrs *group_attrs, + struct tevent_context *ev, + struct sysdb_handle *handle, + struct sdap_options *opts, + struct sss_domain_info *domain, + struct ldb_val *values, + int num_values) +{ + struct ldb_message_element *el; + int i, j; + int ret; + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + DEBUG(9, ("[RFC2307 Schema]\n")); + + ret = sysdb_attrs_users_from_ldb_vals(group_attrs, SYSDB_MEMBER, + domain->name, + values, num_values); + if (ret) { + goto done; + } + + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + DEBUG(9, ("[IPA or AD Schema]\n")); + + ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el); + if (ret) { + goto done; + } + + /* Just allocate both big enough to contain all members for now */ + el->values = talloc_realloc(el, el->values, struct ldb_val, + el->num_values + num_values); + if (!el->values) { + ret = ENOMEM; + goto done; + } + + for (i = 0, j = el->num_values; i < num_values; i++) { + + /* sync search entry with this as origDN */ + ret = sdap_find_entry_by_origDN(el->values, ev, + handle, domain, + (char *)values[i].data, + (char **)&el->values[j].data); + if (ret != EOK) { + if (ret != ENOENT) { + goto done; + } + + DEBUG(7, (" member #%d (%s): not found!\n", + i, (char *)values[i].data)); + } else { + DEBUG(7, (" 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++; + } + } + el->num_values = j; + + break; + + default: + DEBUG(0, ("FATAL ERROR: Unhandled schema type! (%d)\n", + opts->schema_type)); + ret = EFAULT; + goto done; + } + + ret = EOK; + +done: + return ret; +} + +/* ==Save-Group-Entry===================================================== */ + +struct sdap_save_group_state { + struct tevent_context *ev; + struct sysdb_handle *handle; + struct sdap_options *opts; + + struct sss_domain_info *dom; + + const char *name; + char *timestamp; +}; + +static void sdap_save_group_done(struct tevent_req *subreq); + + /* FIXME: support non legacy */ + /* FIXME: support storing additional attributes */ + +static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_handle *handle, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs, + bool store_members) +{ + struct tevent_req *req, *subreq; + struct sdap_save_group_state *state; + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs; + long int l; + gid_t gid; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_save_group_state); + if (!req) return NULL; + + state->ev = ev; + state->handle = handle; + state->dom = dom; + state->opts = opts; + state->timestamp = NULL; + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_NAME].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + state->name = (const char *)el->values[0].data; + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_GID].sys_name, &el); + if (ret) goto fail; + if (el->num_values == 0) { + DEBUG(1, ("no gid provided for [%s] in domain [%s].\n", + state->name, dom->name)); + ret = EINVAL; + goto fail; + } + errno = 0; + l = strtol((const char *)el->values[0].data, NULL, 0); + if (errno) { + ret = EINVAL; + goto fail; + } + gid = l; + + /* check that the gid is valid for this domain */ + if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) { + DEBUG(2, ("Group [%s] filtered out! (id out of range)\n", + state->name)); + ret = EINVAL; + goto fail; + } + + group_attrs = sysdb_new_attrs(state); + if (!group_attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("Original DN is not available for [%s].\n", state->name)); + } else { + DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n", + el->values[0].data, state->name)); + ret = sysdb_attrs_add_string(group_attrs, SYSDB_ORIG_DN, + (const char *)el->values[0].data); + if (ret) { + goto fail; + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n", + state->name)); + } else { + ret = sysdb_attrs_add_string(group_attrs, + opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, + (const char*)el->values[0].data); + if (ret) { + goto fail; + } + state->timestamp = talloc_strdup(state, + (const char*)el->values[0].data); + if (!state->timestamp) { + ret = ENOMEM; + goto fail; + } + } + + if (store_members) { + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("No members for group [%s]\n", state->name)); + + } else { + DEBUG(7, ("Adding member users to group [%s]\n", state->name)); + + ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom, + el->values, el->num_values); + if (ret) { + goto fail; + } + } + } + + DEBUG(6, ("Storing info for group %s\n", state->name)); + + subreq = sysdb_store_group_send(state, state->ev, + state->handle, state->dom, + state->name, gid, + group_attrs, + dp_opt_get_int(opts->basic, + SDAP_ENTRY_CACHE_TIMEOUT)); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_save_group_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_save_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_save_group_state *state = tevent_req_data(req, + struct sdap_save_group_state); + int ret; + + ret = sysdb_store_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Failed to save group %s [%d]\n", state->name, ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_save_group_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_save_group_state *state = tevent_req_data(req, + struct sdap_save_group_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if ( timestamp ) { + *timestamp = talloc_steal(mem_ctx, state->timestamp); + } + + return EOK; +} + + +/* ==Save-Group-Memebrs=================================================== */ + +struct sdap_save_grpmem_state { + struct tevent_context *ev; + struct sysdb_handle *handle; + struct sdap_options *opts; + + struct sss_domain_info *dom; + + const char *name; +}; + +static void sdap_save_grpmem_done(struct tevent_req *subreq); + + /* FIXME: support non legacy */ + /* FIXME: support storing additional attributes */ + +static struct tevent_req *sdap_save_grpmem_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_handle *handle, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *attrs) +{ + struct tevent_req *req, *subreq; + struct sdap_save_grpmem_state *state; + struct ldb_message_element *el; + struct sysdb_attrs *group_attrs = NULL; + int ret; + + req = tevent_req_create(memctx, &state, struct sdap_save_grpmem_state); + if (!req) return NULL; + + state->ev = ev; + state->handle = handle; + state->dom = dom; + state->opts = opts; + + ret = sysdb_attrs_get_string(attrs, + opts->group_map[SDAP_AT_GROUP_NAME].sys_name, + &state->name); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_get_el(attrs, + opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(7, ("No members for group [%s]\n", state->name)); + + } else { + DEBUG(7, ("Adding member users to group [%s]\n", state->name)); + + group_attrs = sysdb_new_attrs(state); + if (!group_attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom, + el->values, el->num_values); + if (ret) { + goto fail; + } + } + + DEBUG(6, ("Storing members for group %s\n", state->name)); + + subreq = sysdb_store_group_send(state, state->ev, + state->handle, state->dom, + state->name, 0, + group_attrs, + dp_opt_get_int(opts->basic, + SDAP_ENTRY_CACHE_TIMEOUT)); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sdap_save_grpmem_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sdap_save_grpmem_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_save_grpmem_state *state = tevent_req_data(req, + struct sdap_save_grpmem_state); + int ret; + + ret = sysdb_store_group_recv(subreq); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Failed to save group members for %s [%d]\n", + state->name, ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int sdap_save_grpmem_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +/* ==Generic-Function-to-save-multiple-groups============================= */ + +struct sdap_save_groups_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + + struct sysdb_attrs **groups; + int count; + int cur; + bool twopass; + + struct sysdb_handle *handle; + + char *higher_timestamp; +}; + +static void sdap_save_groups_trans(struct tevent_req *subreq); +static void sdap_save_groups_save(struct tevent_req *req); +static void sdap_save_groups_loop(struct tevent_req *subreq); +static void sdap_save_groups_mem_save(struct tevent_req *req); +static void sdap_save_groups_mem_loop(struct tevent_req *subreq); +struct tevent_req *sdap_save_groups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sysdb_attrs **groups, + int num_groups) +{ + struct tevent_req *req, *subreq; + struct sdap_save_groups_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_save_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->groups = groups; + state->count = num_groups; + state->cur = 0; + state->handle = NULL; + state->higher_timestamp = NULL; + + switch (opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + state->twopass = false; + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + state->twopass = true; + break; + + default: + tevent_req_error(req, EINVAL); + tevent_req_post(req, ev); + return req; + } + + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (!subreq) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, ev); + return req; + } + tevent_req_set_callback(subreq, sdap_save_groups_trans, req); + + return req; +} + +static void sdap_save_groups_trans(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_save_groups_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_save_groups_state); + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + sdap_save_groups_save(req); +} + +static void sdap_save_groups_save(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_save_groups_state *state; + + state = tevent_req_data(req, struct sdap_save_groups_state); + + /* if 2 pass savemembers = false */ + subreq = sdap_save_group_send(state, state->ev, state->handle, + state->opts, state->dom, + state->groups[state->cur], + (!state->twopass)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_save_groups_loop, req); +} + +static void sdap_save_groups_loop(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_save_groups_state *state; + char *timestamp = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_save_groups_state); + + ret = sdap_save_group_recv(subreq, state, ×tamp); + talloc_zfree(subreq); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur)); + } else { + DEBUG(9, ("Group %d processed!\n", state->cur)); + } + + if (timestamp) { + if (state->higher_timestamp) { + if (strcmp(timestamp, state->higher_timestamp) > 0) { + talloc_zfree(state->higher_timestamp); + state->higher_timestamp = timestamp; + } else { + talloc_zfree(timestamp); + } + } else { + state->higher_timestamp = timestamp; + } + } + + state->cur++; + if (state->cur < state->count) { + + sdap_save_groups_save(req); + + } else if (state->twopass) { + + state->cur = 0; + sdap_save_groups_mem_save(req); + + } else { + + subreq = sysdb_transaction_commit_send(state, state->ev, + state->handle); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + /* sysdb_transaction_complete will call tevent_req_done(req) */ + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + } +} + +static void sdap_save_groups_mem_save(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_save_groups_state *state; + + state = tevent_req_data(req, struct sdap_save_groups_state); + + subreq = sdap_save_grpmem_send(state, state->ev, state->handle, + state->opts, state->dom, + state->groups[state->cur]); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_save_groups_mem_loop, req); +} + +static void sdap_save_groups_mem_loop(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_save_groups_state *state; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sdap_save_groups_state); + + ret = sdap_save_grpmem_recv(subreq); + talloc_zfree(subreq); + + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + if (ret) { + DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur)); + } + + state->cur++; + if (state->cur < state->count) { + + sdap_save_groups_mem_save(req); + + } else { + + subreq = sysdb_transaction_commit_send(state, state->ev, + state->handle); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + /* sysdb_transaction_complete will call tevent_req_done(req) */ + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + } +} + +static int sdap_save_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_save_groups_state *state = tevent_req_data(req, + struct sdap_save_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + 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 sysdb_ctx *sysdb; + const char **attrs; + const char *filter; + + char *higher_timestamp; + struct sysdb_attrs **groups; + size_t count; +}; + +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 sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_handle *sh, + const char **attrs, + const char *filter) +{ + struct tevent_req *req, *subreq; + struct sdap_get_groups_state *state; + + req = tevent_req_create(memctx, &state, struct sdap_get_groups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->sysdb = sysdb; + state->filter = filter; + state->attrs = attrs; + state->higher_timestamp = NULL; + state->groups = NULL; + state->count = 0; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + dp_opt_get_string(state->opts->basic, + SDAP_GROUP_SEARCH_BASE), + LDAP_SCOPE_SUBTREE, + state->filter, state->attrs, + state->opts->group_map, SDAP_OPTS_GROUP); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_groups_process, req); + + return req; +} + +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; + + ret = sdap_get_generic_recv(subreq, state, + &state->count, &state->groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(6, ("Search for groups, returned %d results.\n", state->count)); + + if (state->count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + subreq = sdap_save_groups_send(state, state->ev, state->dom, + state->sysdb, state->opts, + state->groups, state->count); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_groups_done, req); +} + +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; + + DEBUG(9, ("Saving %d Groups - Done\n", state->count)); + + ret = sdap_save_groups_recv(subreq, state, &state->higher_timestamp); + talloc_zfree(subreq); + if (ret) { + DEBUG(2, ("Failed to store groups.\n")); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_get_groups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, char **timestamp) +{ + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (timestamp) { + *timestamp = talloc_steal(mem_ctx, state->higher_timestamp); + } + + return EOK; +} + + +/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307-Classic/BIS========= */ + +struct sdap_initgr_rfc2307_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + + struct sdap_op *op; +}; + +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq); +static void sdap_initgr_rfc2307_done(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 *dom, + struct sdap_handle *sh, + const char *base_dn, + const char *name, + const char **grp_attrs) +{ + struct tevent_req *req, *subreq; + struct sdap_initgr_rfc2307_state *state; + const char *filter; + + 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->dom = dom; + state->sh = sh; + state->op = NULL; + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + opts->group_map[SDAP_AT_GROUP_MEMBER].name, + name, opts->group_map[SDAP_OC_GROUP].name); + if (!filter) { + talloc_zfree(req); + return NULL; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, + state->sh, base_dn, LDAP_SCOPE_SUBTREE, + filter, grp_attrs, + state->opts->group_map, SDAP_OPTS_GROUP); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307_process, req); + + return req; +} + +static void sdap_initgr_rfc2307_process(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_initgr_rfc2307_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_rfc2307_state); + + ret = sdap_get_generic_recv(subreq, state, &count, &groups); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (count == 0) { + tevent_req_done(req); + return; + } + + subreq = sdap_save_groups_send(state, state->ev, state->dom, + state->sysdb, state->opts, + groups, count); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_rfc2307_done, req); +} + +static void sdap_initgr_rfc2307_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_save_groups_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret) { + 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; +} + + +/* ==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; + + const char **grp_attrs; + + char *filter; + char **group_dns; + int count; + int cur; + + struct sdap_op *op; + + struct sysdb_attrs **groups; + int groups_cur; +}; + +static void sdap_initgr_nested_search(struct tevent_req *subreq); +static void sdap_initgr_nested_store(struct tevent_req *req); +static void sdap_initgr_nested_done(struct tevent_req *subreq); +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, *subreq; + struct sdap_initgr_nested_state *state; + struct ldb_message_element *el; + int i, ret; + + 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->op = NULL; + + state->filter = talloc_asprintf(state, "(objectclass=%s)", + opts->group_map[SDAP_OC_GROUP].name); + if (!state->filter) { + talloc_zfree(req); + return NULL; + } + + /* TODO: test rootDSE for deref support and use it if available */ + /* TODO: or test rootDSE for ASQ support and use it if available */ + + ret = sysdb_attrs_get_el(user, SYSDB_MEMBEROF, &el); + if (ret || !el || el->num_values == 0) { + DEBUG(4, ("User entry lacks original memberof ?\n")); + /* user with no groups ? */ + tevent_req_error(req, ENOENT); + tevent_req_post(req, ev); + } + state->count = el->num_values; + + state->groups = talloc_zero_array(state, struct sysdb_attrs *, + state->count + 1);; + if (!state->groups) { + talloc_zfree(req); + return NULL; + } + state->groups_cur = 0; + + state->group_dns = talloc_array(state, char *, state->count + 1); + if (!state->group_dns) { + talloc_zfree(req); + return NULL; + } + for (i = 0; i < state->count; i++) { + state->group_dns[i] = talloc_strdup(state->group_dns, + (char *)el->values[i].data); + if (!state->group_dns[i]) { + talloc_zfree(req); + return NULL; + } + } + state->group_dns[i] = NULL; /* terminate */ + state->cur = 0; + + 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); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_search, req); + + return 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] = groups[0]; + state->groups_cur++; + } else { + DEBUG(2, ("Search for group %s, returned %d results. Skipping\n", + state->group_dns[state->cur], count)); + } + + state->cur++; + if (state->cur < state->count) { + 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); + 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 void sdap_initgr_nested_store(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_initgr_nested_state *state; + + state = tevent_req_data(req, struct sdap_initgr_nested_state); + + subreq = sdap_save_groups_send(state, state->ev, state->dom, + state->sysdb, state->opts, + state->groups, state->groups_cur); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_initgr_nested_done, req); +} + +static void sdap_initgr_nested_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_save_groups_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +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)============================= */ + +struct sdap_get_initgr_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; + const char **grp_attrs; + + struct sysdb_attrs *orig_user; + + struct sysdb_handle *handle; +}; + +static void sdap_get_initgr_user(struct tevent_req *subreq); +static void sdap_get_initgr_store(struct tevent_req *subreq); +static void sdap_get_initgr_commit(struct tevent_req *subreq); +static void sdap_get_initgr_process(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 sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sdap_handle *sh, + const char *name, + const char **grp_attrs) +{ + struct tevent_req *req, *subreq; + struct sdap_get_initgr_state *state; + const char *base_dn; + char *filter; + const char **attrs; + int ret; + + DEBUG(9, ("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 = opts; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->name = name; + state->grp_attrs = grp_attrs; + state->orig_user = NULL; + + filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + state->opts->user_map[SDAP_AT_USER_NAME].name, + state->name, + state->opts->user_map[SDAP_OC_USER].name); + if (!filter) { + talloc_zfree(req); + return NULL; + } + + base_dn = dp_opt_get_string(state->opts->basic, + SDAP_USER_SEARCH_BASE); + if (!base_dn) { + talloc_zfree(req); + return NULL; + } + + ret = build_attrs_from_map(state, state->opts->user_map, + SDAP_OPTS_USER, &attrs); + if (ret) { + talloc_zfree(req); + return NULL; + } + + subreq = sdap_get_generic_send(state, state->ev, + state->opts, state->sh, + base_dn, LDAP_SCOPE_SUBTREE, + filter, attrs, + state->opts->user_map, SDAP_OPTS_USER); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_get_initgr_user, req); + + return req; +} + +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; + + DEBUG(9, ("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 != 1) { + DEBUG(2, ("Expected one user entry and got %d\n", count)); + tevent_req_error(req, ENOENT); + return; + } + + state->orig_user = usr_attrs[0]; + + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_store, req); +} + +static void sdap_get_initgr_store(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; + + DEBUG(9, ("Storing the user\n")); + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sdap_save_user_send(state, state->ev, state->handle, + state->opts, state->dom, + state->orig_user, true); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_commit, req); +} + +static void sdap_get_initgr_commit(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; + + DEBUG(9, ("Commit change\n")); + + ret = sdap_save_user_recv(subreq, NULL, NULL); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_get_initgr_process, req); +} + +static void sdap_get_initgr_process(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; + + DEBUG(9, ("Process user's groups\n")); + + ret = sysdb_transaction_commit_recv(subreq); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + 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, + dp_opt_get_string(state->opts->basic, + SDAP_GROUP_SEARCH_BASE), + state->name, state->grp_attrs); + 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_IPA_V1: + case SDAP_SCHEMA_AD: + /* TODO: AD uses a different member/memberof schema + * We need an AD specific call that is able to unroll + * nested groups by doing extensive recursive searches */ + + 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; + } +} + +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; + + DEBUG(9, ("Initgroups done\n")); + + switch (state->opts->schema_type) { + case SDAP_SCHEMA_RFC2307: + + ret = sdap_initgr_rfc2307_recv(subreq); + break; + + case SDAP_SCHEMA_RFC2307BIS: + case SDAP_SCHEMA_IPA_V1: + case SDAP_SCHEMA_AD: + + ret = sdap_initgr_nested_recv(subreq); + break; + + default: + + ret = EINVAL; + break; + } + + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_get_initgr_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c new file mode 100644 index 000000000..18e47d3b7 --- /dev/null +++ b/src/providers/ldap/sdap_async_connection.c @@ -0,0 +1,1141 @@ +/* + 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 <sasl/sasl.h> +#include "util/util.h" +#include "util/sss_krb5.h" +#include "providers/ldap/sdap_async_private.h" + +#define LDAP_X_SSSD_PASSWORD_EXPIRED 0x555D + +/* ==Connect-to-LDAP-Server=============================================== */ + +struct sdap_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_handle *sh; + + struct sdap_op *op; + + struct sdap_msg *reply; + int result; +}; + +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, + bool use_start_tls) +{ + struct tevent_req *req; + struct sdap_connect_state *state; + struct timeval tv; + int ver; + int lret; + int ret = EOK; + int msgid; + bool ldap_referrals; + + req = tevent_req_create(memctx, &state, struct sdap_connect_state); + if (!req) return NULL; + + state->reply = talloc(state, struct sdap_msg); + if (!state->reply) { + talloc_zfree(req); + return NULL; + } + + state->ev = ev; + state->opts = opts; + state->sh = sdap_handle_create(state); + if (!state->sh) { + talloc_zfree(req); + return NULL; + } + /* Initialize LDAP handler */ + lret = ldap_initialize(&state->sh->ldap, uri); + if (lret != LDAP_SUCCESS) { + DEBUG(1, ("ldap_initialize failed: %s\n", ldap_err2string(lret))); + 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(1, ("Failed to set ldap version to 3\n")); + goto fail; + } + + /* Set Network Timeout */ + tv.tv_sec = dp_opt_get_int(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(1, ("Failed to set network timeout to %d\n", + dp_opt_get_int(opts->basic, SDAP_NETWORK_TIMEOUT))); + goto fail; + } + + /* Set Default Timeout */ + tv.tv_sec = dp_opt_get_int(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(1, ("Failed to set default timeout to %d\n", + dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT))); + goto fail; + } + + /* Set Referral chasing */ + ldap_referrals = dp_opt_get_bool(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(1, ("Failed to set referral chasing to %s\n", + (ldap_referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF"))); + goto fail; + } + +#ifdef HAVE_LDAP_CONNCB + struct ldap_cb_data *cb_data; + + /* add connection callback */ + state->sh->conncb = talloc_zero(state->sh, struct ldap_conncb); + if (state->sh->conncb == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + ret = ENOMEM; + goto fail; + } + + cb_data = talloc_zero(state->sh->conncb, struct ldap_cb_data); + if (cb_data == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + ret = ENOMEM; + goto fail; + } + cb_data->sh = state->sh; + cb_data->ev = state->ev; + + state->sh->conncb->lc_add = sdap_ldap_connect_callback_add; + state->sh->conncb->lc_del = sdap_ldap_connect_callback_del; + state->sh->conncb->lc_arg = cb_data; + + lret = ldap_set_option(state->sh->ldap, LDAP_OPT_CONNECT_CB, + state->sh->conncb); + if (lret != LDAP_OPT_SUCCESS) { + DEBUG(1, ("Failed to set connection callback\n")); + goto fail; + } +#endif + + /* 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 (!use_start_tls) { + tevent_req_post(req, ev); + return req; + } + + DEBUG(4, ("Executing START TLS\n")); + + lret = ldap_start_tls(state->sh->ldap, NULL, NULL, &msgid); + if (lret != LDAP_SUCCESS) { + DEBUG(3, ("ldap_start_tls failed: [%s]", ldap_err2string(ret))); + goto fail; + } + + state->sh->connected = true; +#ifndef HAVE_LDAP_CONNCB + ret = sdap_install_ldap_callbacks(state->sh, state->ev); + if (ret) goto fail; +#endif + + /* FIXME: get timeouts from configuration, for now 5 secs. */ + ret = sdap_op_add(state, ev, state->sh, msgid, + sdap_connect_done, req, 5, &state->op); + if (ret) { + DEBUG(1, ("Failed to set up operation!\n")); + goto fail; + } + + return req; + +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); + } + } + tevent_req_post(req, ev); + return req; +} + +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; + int ret; + + 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(2, ("ldap_parse_result failed (%d)\n", state->op->msgid)); + tevent_req_error(req, EIO); + return; + } + + DEBUG(3, ("START TLS result: %s(%d), %s\n", + ldap_err2string(state->result), state->result, errmsg)); + + if (ldap_tls_inplace(state->sh->ldap)) { + DEBUG(9, ("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) { + DEBUG(1, ("ldap_install_tls failed: [%d][%s]\n", ret, + ldap_err2string(ret))); + 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); + enum tevent_req_state tstate; + uint64_t err = EIO; + + if (tevent_req_is_error(req, &tstate, &err)) { + /* if tstate shows in progress, it is because + * we did not ask to perform tls, just pretend all is fine */ + if (tstate != TEVENT_REQ_IN_PROGRESS) { + return err; + } + } + + *sh = talloc_steal(memctx, state->sh); + if (!*sh) { + return ENOMEM; + } + return EOK; +} + +/* ==Simple-Bind========================================================== */ + +struct simple_bind_state { + struct tevent_context *ev; + struct sdap_handle *sh; + const char *user_dn; + struct berval *pw; + + struct sdap_op *op; + + struct sdap_msg *reply; + int result; +}; + +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, + 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[2]; + + 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; + state->pw = pw; + + ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST, + 0, NULL, 0, &request_controls[0]); + if (ret != LDAP_SUCCESS) { + DEBUG(1, ("sss_ldap_control_create failed.\n")); + goto fail; + } + request_controls[1] = NULL; + + DEBUG(4, ("Executing simple bind as: %s\n", state->user_dn)); + + ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE, + state->pw, request_controls, NULL, &msgid); + ldap_control_free(request_controls[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(1, ("ldap_bind failed (couldn't get ldap error)\n")); + ret = LDAP_LOCAL_ERROR; + } else { + DEBUG(1, ("ldap_bind failed (%d)[%s]\n", + ldap_err, ldap_err2string(ldap_err))); + ret = ldap_err; + } + goto fail; + } + DEBUG(8, ("ldap simple bind sent, msgid = %d\n", msgid)); + + if (!sh->connected) { + sh->connected = true; +#ifndef HAVE_LDAP_CONNCB + ret = sdap_install_ldap_callbacks(sh, ev); + if (ret) goto fail; +#endif + } + + /* FIXME: get timeouts from configuration, for now 5 secs. */ + ret = sdap_op_add(state, ev, sh, msgid, + simple_bind_done, req, 5, &state->op); + if (ret) { + DEBUG(1, ("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, EIO); + } + 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; + int ret; + LDAPControl **response_controls; + int c; + ber_int_t pp_grace; + ber_int_t pp_expire; + LDAPPasswordPolicyError pp_error; + + 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, + &response_controls, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid)); + ret = EIO; + goto done; + } + + if (response_controls == NULL) { + DEBUG(5, ("Server returned no controls.\n")); + } else { + for (c = 0; response_controls[c] != NULL; c++) { + DEBUG(9, ("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(1, ("ldap_parse_passwordpolicy_control failed.\n")); + ret = EIO; + goto done; + } + + DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] " + "error [%s].\n", pp_expire, pp_grace, + ldap_passwordpolicy_err2txt(pp_error))); + + if (state->result == LDAP_SUCCESS && + (pp_error == PP_changeAfterReset || pp_grace > 0)) { + DEBUG(4, ("User must set a new password.\n")); + state->result = LDAP_X_SSSD_PASSWORD_EXPIRED; + } + } + } + } + + DEBUG(3, ("Bind result: %s(%d), %s\n", + ldap_err2string(state->result), state->result, errmsg)); + + ret = LDAP_SUCCESS; +done: + ldap_controls_free(response_controls); + + if (ret == LDAP_SUCCESS) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int simple_bind_recv(struct tevent_req *req, int *ldaperr) +{ + struct simple_bind_state *state = tevent_req_data(req, + struct simple_bind_state); + + *ldaperr = LDAP_OTHER; + TEVENT_REQ_RETURN_ON_ERROR(req); + + *ldaperr = state->result; + 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; + + int result; +}; + +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; + + 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(4, ("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 */ + + ret = ldap_sasl_interactive_bind_s(state->sh->ldap, NULL, + sasl_mech, NULL, NULL, + LDAP_SASL_QUIET, + (*sdap_sasl_interact), state); + state->result = ret; + if (ret != LDAP_SUCCESS) { + DEBUG(1, ("ldap_sasl_bind failed (%d)[%s]\n", + ret, ldap_err2string(ret))); + goto fail; + } + + if (!sh->connected) { + sh->connected = true; +#ifndef HAVE_LDAP_CONNCB + ret = sdap_install_ldap_callbacks(sh, ev); + if (ret) goto fail; +#endif + } + + tevent_req_post(req, ev); + return req; + +fail: + if (ret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, EIO); + } + 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_AUTHNAME: + case SASL_CB_PASS: + if (in->defresult) { + in->result = in->defresult; + } else { + in->result = ""; + } + in->len = strlen(in->result); + break; + case SASL_CB_USER: + 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 int sasl_bind_recv(struct tevent_req *req, int *ldaperr) +{ + struct sasl_bind_state *state = tevent_req_data(req, + struct sasl_bind_state); + enum tevent_req_state tstate; + uint64_t err = EIO; + + if (tevent_req_is_error(req, &tstate, &err)) { + if (tstate != TEVENT_REQ_IN_PROGRESS) { + *ldaperr = LDAP_OTHER; + return err; + } + } + + *ldaperr = state->result; + return EOK; +} + +/* ==Perform-Kinit-given-keytab-and-principal============================= */ + +struct sdap_kinit_state { + int result; +}; + +static void sdap_kinit_done(struct tevent_req *subreq); + +struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_handle *sh, + int timeout, + const char *keytab, + const char *principal, + const char *realm) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_kinit_state *state; + int ret; + + DEBUG(6, ("Attempting kinit (%s, %s, %s)\n", keytab, principal, realm)); + + req = tevent_req_create(memctx, &state, struct sdap_kinit_state); + if (!req) return NULL; + + state->result = SDAP_AUTH_FAILED; + + if (keytab) { + ret = setenv("KRB5_KTNAME", keytab, 1); + if (ret == -1) { + DEBUG(2, ("Failed to set KRB5_KTNAME to %s\n", keytab)); + return NULL; + } + } + + subreq = sdap_get_tgt_send(state, ev, realm, principal, keytab, timeout); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_kinit_done, req); + + return 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; + + ret = sdap_get_tgt_recv(subreq, state, &result, &ccname); + talloc_zfree(subreq); + if (ret != EOK) { + state->result = SDAP_AUTH_FAILED; + DEBUG(1, ("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(2, ("Unable to set env. variable KRB5CCNAME!\n")); + state->result = SDAP_AUTH_FAILED; + tevent_req_error(req, EFAULT); + } + + state->result = SDAP_AUTH_SUCCESS; + tevent_req_done(req); + return; + } + + DEBUG(4, ("Could not get TGT: %d [%s]\n", result, strerror(result))); + state->result = SDAP_AUTH_FAILED; + tevent_req_error(req, EIO); +} + +int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result) +{ + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + enum tevent_req_state tstate; + uint64_t err = EIO; + + if (tevent_req_is_error(req, &tstate, &err)) { + if (tstate != TEVENT_REQ_IN_PROGRESS) { + *result = SDAP_ERROR; + return err; + } + } + + *result = state->result; + return EOK; +} + + +/* ==Authenticaticate-User-by-DN========================================== */ + +struct sdap_auth_state { + const char *user_dn; + struct berval pw; + + int result; + 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, + const char *authtok_type, + struct dp_opt_blob authtok) +{ + struct tevent_req *req, *subreq; + struct sdap_auth_state *state; + + if (authtok_type != NULL && strcasecmp(authtok_type,"password") != 0) { + DEBUG(1,("Authentication token type [%s] is not supported")); + return NULL; + } + + req = tevent_req_create(memctx, &state, struct sdap_auth_state); + if (!req) return NULL; + + state->user_dn = user_dn; + state->pw.bv_val = (char *)authtok.data; + state->pw.bv_len = authtok.length; + + 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 { + state->is_sasl = false; + subreq = simple_bind_send(state, ev, sh, user_dn, &state->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 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->result); + } else { + ret = simple_bind_recv(subreq, &state->result); + } + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result) +{ + struct sdap_auth_state *state = tevent_req_data(req, + struct sdap_auth_state); + + *result = SDAP_ERROR; + TEVENT_REQ_RETURN_ON_ERROR(req); + + switch (state->result) { + case LDAP_SUCCESS: + *result = SDAP_AUTH_SUCCESS; + break; + case LDAP_INVALID_CREDENTIALS: + *result = SDAP_AUTH_FAILED; + break; + case LDAP_X_SSSD_PASSWORD_EXPIRED: + *result = SDAP_AUTH_PW_EXPIRED; + break; + default: + break; + } + + return EOK; +} + +/* ==Client connect============================================ */ + +struct sdap_cli_connect_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct sdap_service *service; + + bool use_rootdse; + struct sysdb_attrs *rootdse; + + struct sdap_handle *sh; + + struct fo_server *srv; +}; + +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 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); + +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, + struct sysdb_attrs **rootdse) +{ + struct tevent_req *req, *subreq; + struct sdap_cli_connect_state *state; + + 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->srv = NULL; + + if (rootdse) { + state->use_rootdse = true; + state->rootdse = *rootdse; + } else { + state->use_rootdse = false; + state->rootdse = NULL; + } + + /* 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, ev, be, service->name); + if (!subreq) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, sdap_cli_resolve_done, req); + + return req; +} + +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->srv); + talloc_zfree(subreq); + if (ret) { + /* all servers have been tried and none + * was found good, go offline */ + tevent_req_error(req, EIO); + return; + } + + subreq = sdap_connect_send(state, state->ev, state->opts, + state->service->uri, + dp_opt_get_bool(state->opts->basic, + SDAP_ID_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; + + ret = sdap_connect_recv(subreq, state, &state->sh); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + if (state->use_rootdse && !state->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 (sasl_mech && state->use_rootdse) { + /* check if server claims to support GSSAPI */ + if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse, + sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) { + 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; + + 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 */ + state->sh->connected = true; +#ifndef HAVE_LDAP_CONNCB + int ret; + + ret = sdap_install_ldap_callbacks(state->sh, state->ev); + if (ret) { + tevent_req_error(req, ret); + } +#endif + } +} + +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) { + tevent_req_error(req, ret); + return; + } + + sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH); + + if (sasl_mech && state->use_rootdse) { + /* check if server claims to support GSSAPI */ + if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse, + sasl_mech)) { + tevent_req_error(req, ENOTSUP); + return; + } + } + + if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) { + 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_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->sh, + 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), + dp_opt_get_string(state->opts->basic, + SDAP_KRB5_REALM)); + 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); + enum sdap_result result; + int ret; + + ret = sdap_kinit_recv(subreq, &result); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + if (result != SDAP_AUTH_SUCCESS) { + tevent_req_error(req, EACCES); + return; + } + + 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; + + subreq = sdap_auth_send(state, + state->ev, + state->sh, + dp_opt_get_string(state->opts->basic, + SDAP_SASL_MECH), + dp_opt_get_string(state->opts->basic, + SDAP_SASL_AUTHID), + dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_BIND_DN), + dp_opt_get_string(state->opts->basic, + SDAP_DEFAULT_AUTHTOK_TYPE), + dp_opt_get_blob(state->opts->basic, + SDAP_DEFAULT_AUTHTOK)); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sdap_cli_auth_done, req); +} + +static void sdap_cli_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + enum sdap_result result; + int ret; + + ret = sdap_auth_recv(subreq, &result); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + if (result != SDAP_AUTH_SUCCESS) { + tevent_req_error(req, EACCES); + return; + } + + tevent_req_done(req); +} + +int sdap_cli_connect_recv(struct tevent_req *req, + TALLOC_CTX *memctx, + struct sdap_handle **gsh, + struct sysdb_attrs **rootdse) +{ + struct sdap_cli_connect_state *state = tevent_req_data(req, + struct sdap_cli_connect_state); + enum tevent_req_state tstate; + uint64_t err; + + if (tevent_req_is_error(req, &tstate, &err)) { + /* mark the server as bad if connection failed */ + if (state->srv) { + fo_set_port_status(state->srv, PORT_NOT_WORKING); + } + + if (tstate == TEVENT_REQ_USER_ERROR) { + return err; + } + return EIO; + } else if (state->srv) { + fo_set_port_status(state->srv, PORT_WORKING); + } + + if (gsh) { + *gsh = talloc_steal(memctx, state->sh); + if (!*gsh) { + return ENOMEM; + } + } else { + talloc_zfree(state->sh); + } + + if (rootdse) { + if (state->use_rootdse) { + *rootdse = talloc_steal(memctx, state->rootdse); + if (!*rootdse) { + return ENOMEM; + } + } else { + *rootdse = NULL; + } + } else { + talloc_zfree(rootdse); + } + + 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 000000000..55f76ed70 --- /dev/null +++ b/src/providers/ldap/sdap_async_private.h @@ -0,0 +1,68 @@ +/* + 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 "providers/ldap/sdap_async.h" + +void make_realm_upper_case(const char *upn); +struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx); + +#ifdef HAVE_LDAP_CONNCB +int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv, + struct sockaddr *addr, struct ldap_conncb *ctx); +void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb, + struct ldap_conncb *ctx); +#else +int sdap_install_ldap_callbacks(struct sdap_handle *sh, + struct tevent_context *ev); +#endif + +int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, + struct sdap_handle *sh, int msgid, + 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); + +/* 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, + int timeout); + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + char **ccname); + +#endif /* _SDAP_ASYNC_PRIVATE_H_ */ diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c new file mode 100644 index 000000000..0a95c8a0d --- /dev/null +++ b/src/providers/ldap/sdap_child_helpers.c @@ -0,0 +1,462 @@ +/* + 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 "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/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; + int read_from_child_fd; + int write_to_child_fd; +}; + +static void sdap_close_fd(int *fd) +{ + int ret; + + if (*fd == -1) { + DEBUG(6, ("fd already closed\n")); + return; + } + + ret = close(*fd); + if (ret) { + ret = errno; + DEBUG(2, ("Closing fd %d, return error %d (%s)\n", + *fd, ret, strerror(ret))); + } + + *fd = -1; +} + +static int sdap_child_destructor(void *ptr) +{ + struct sdap_child *child = talloc_get_type(ptr, struct sdap_child); + + child_cleanup(child->read_from_child_fd, child->write_to_child_fd); + + return 0; +} + +static errno_t sdap_fork_child(struct sdap_child *child) +{ + int pipefd_to_child[2]; + int pipefd_from_child[2]; + pid_t pid; + int ret; + errno_t err; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err))); + return err; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err))); + return err; + } + + pid = fork(); + + if (pid == 0) { /* child */ + err = exec_child(child, + pipefd_to_child, pipefd_from_child, + LDAP_CHILD, ldap_child_debug_fd); + if (err != EOK) { + DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n", + err, strerror(err))); + return err; + } + } else if (pid > 0) { /* parent */ + child->pid = pid; + child->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + child->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + fd_nonblocking(child->read_from_child_fd); + fd_nonblocking(child->write_to_child_fd); + + } else { /* error */ + err = errno; + DEBUG(1, ("fork failed [%d][%s].\n", err, strerror(err))); + return err; + } + + return EOK; +} + +static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + + buf = talloc(mem_ctx, struct io_buffer); + if (buf == NULL) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + buf->size = 3 * 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(7, ("buffer size: %d\n", buf->size)); + + buf->data = talloc_size(buf, buf->size); + if (buf->data == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + + /* realm */ + if (realm_str) { + COPY_UINT32_VALUE(&buf->data[rp], strlen(realm_str), rp); + COPY_MEM(&buf->data[rp], realm_str, rp, strlen(realm_str)); + } else { + COPY_UINT32_VALUE(&buf->data[rp], 0, rp); + } + + /* principal */ + if (princ_str) { + COPY_UINT32_VALUE(&buf->data[rp], strlen(princ_str), rp); + COPY_MEM(&buf->data[rp], princ_str, rp, strlen(princ_str)); + } else { + COPY_UINT32_VALUE(&buf->data[rp], 0, rp); + } + + /* keytab */ + if (keytab_name) { + COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab_name), rp); + COPY_MEM(&buf->data[rp], keytab_name, rp, strlen(realm_str)); + } else { + COPY_UINT32_VALUE(&buf->data[rp], 0, rp); + } + + *io_buf = buf; + return EOK; +} + +static int parse_child_response(TALLOC_CTX *mem_ctx, + uint8_t *buf, ssize_t size, + int *result, char **ccache) +{ + size_t p = 0; + uint32_t len; + uint32_t res; + char *ccn; + + /* operation result code */ + COPY_UINT32_CHECK(&res, buf + p, p, size); + + /* ccache name size */ + COPY_UINT32_CHECK(&len, buf + p, p, size); + + if ((p + len ) > size) return EINVAL; + + ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1)); + if (ccn == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + return ENOMEM; + } + memcpy(ccn, buf+p, sizeof(char) * (len + 1)); + ccn[len] = '\0'; + + *result = res; + *ccache = ccn; + 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; +}; + +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, + 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->read_from_child_fd = -1; + state->child->write_to_child_fd = -1; + talloc_set_destructor((TALLOC_CTX *)state->child, sdap_child_destructor); + + /* prepare the data to pass to child */ + ret = create_tgt_req_send_buffer(state, + realm_str, princ_str, keytab_name, + &buf); + if (ret != EOK) { + DEBUG(1, ("create_tgt_req_send_buffer failed.\n")); + goto fail; + } + + ret = sdap_fork_child(state->child); + if (ret != EOK) { + DEBUG(1, ("sdap_fork_child failed.\n")); + goto fail; + } + + ret = set_tgt_child_timeout(req, ev, timeout); + if (ret != EOK) { + DEBUG(1, ("activate_child_timeout_handler failed.\n")); + goto fail; + } + + subreq = write_pipe_send(state, ev, buf->data, buf->size, + state->child->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->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, + state->child->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->read_from_child_fd); + + tevent_req_done(req); +} + +int sdap_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + char **ccname) +{ + struct sdap_get_tgt_state *state = tevent_req_data(req, + struct sdap_get_tgt_state); + char *ccn; + int res; + int ret; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = parse_child_response(mem_ctx, state->buf, state->len, &res, &ccn); + if (ret != EOK) { + DEBUG(1, ("Cannot parse child response: [%d][%s]\n", ret, strerror(ret))); + return ret; + } + + DEBUG(6, ("Child responded: %d [%s]\n", res, ccn)); + *result = res; + *ccname = ccn; + return EOK; +} + + + +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(9, ("timeout for tgt child [%d] reached.\n", state->child->pid)); + + ret = kill(state->child->pid, SIGKILL); + if (ret == -1) { + DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno))); + } + + tevent_req_error(req, ETIMEDOUT); +} + +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(6, ("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(1, ("tevent_add_timer failed.\n")); + return ENOMEM; + } + + return EOK; +} + + + +/* Setup child logging */ +int setup_child(struct sdap_id_ctx *ctx) +{ + int ret; + const char *mech; + struct tevent_signal *sige; + unsigned v; + FILE *debug_filep; + + mech = dp_opt_get_string(ctx->opts->basic, + SDAP_SASL_MECH); + if (!mech) { + return EOK; + } + + sige = tevent_add_signal(ctx->be->ev, ctx, SIGCHLD, SA_SIGINFO, + child_sig_handler, NULL); + if (sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + return ENOMEM; + } + + if (debug_to_file != 0 && ldap_child_debug_fd == -1) { + ret = open_debug_file_ex("ldap_child", &debug_filep); + if (ret != EOK) { + DEBUG(0, ("Error setting up logging (%d) [%s]\n", + ret, strerror(ret))); + return ret; + } + + ldap_child_debug_fd = fileno(debug_filep); + if (ldap_child_debug_fd == -1) { + DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno))); + ret = errno; + return ret; + } + + v = fcntl(ldap_child_debug_fd, F_GETFD, 0); + fcntl(ldap_child_debug_fd, F_SETFD, v & ~FD_CLOEXEC); + } + + return EOK; +} |