ad_id.c revision 4537e95f6741ae05ec620e5b46ca1d4a3a1ceae5
/*
SSSD
Authors:
Stephen Gallagher <sgallagh@redhat.com>
Copyright (C) 2012 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "util/util.h"
#include "util/strtonum.h"
#include "providers/ad/ad_common.h"
#include "providers/ad/ad_id.h"
#include "providers/ad/ad_domain_info.h"
#include "providers/ldap/sdap_async_enum.h"
#include "providers/ldap/sdap_idmap.h"
struct ad_handle_acct_info_state {
struct be_req *breq;
struct be_acct_req *ar;
struct sdap_id_ctx *ctx;
struct sdap_id_conn_ctx **conn;
struct sdap_domain *sdom;
size_t cindex;
int dp_error;
const char *err;
};
static errno_t ad_handle_acct_info_step(struct tevent_req *req);
static void ad_handle_acct_info_done(struct tevent_req *subreq);
struct tevent_req *
ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
struct be_req *breq,
struct be_acct_req *ar,
struct sdap_id_ctx *ctx,
struct sdap_domain *sdom,
struct sdap_id_conn_ctx **conn)
{
struct tevent_req *req;
struct ad_handle_acct_info_state *state;
struct be_ctx *be_ctx = be_req_get_be_ctx(breq);
errno_t ret;
req = tevent_req_create(mem_ctx, &state, struct ad_handle_acct_info_state);
if (req == NULL) {
return NULL;
}
state->breq = breq;
state->ar = ar;
state->ctx = ctx;
state->sdom = sdom;
state->conn = conn;
state->cindex = 0;
ret = ad_handle_acct_info_step(req);
if (ret == EOK) {
tevent_req_done(req);
tevent_req_post(req, be_ctx->ev);
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
tevent_req_post(req, be_ctx->ev);
}
/* Lookup in progress */
return req;
}
static errno_t
ad_handle_acct_info_step(struct tevent_req *req)
{
struct tevent_req *subreq;
struct ad_handle_acct_info_state *state = tevent_req_data(req,
struct ad_handle_acct_info_state);
bool noexist_delete = false;
if (state->conn[state->cindex] == NULL) {
return EOK;
}
if (state->conn[state->cindex+1] == NULL) {
noexist_delete = true;
}
subreq = sdap_handle_acct_req_send(state, state->breq,
state->ar, state->ctx,
state->sdom,
state->conn[state->cindex],
noexist_delete);
if (req == NULL) {
return ENOMEM;
}
tevent_req_set_callback(subreq, ad_handle_acct_info_done, req);
return EAGAIN;
}
static void
ad_handle_acct_info_done(struct tevent_req *subreq)
{
errno_t ret;
int dp_error;
int sdap_err;
const char *err;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct ad_handle_acct_info_state *state = tevent_req_data(req,
struct ad_handle_acct_info_state);
ret = sdap_handle_acct_req_recv(subreq, &dp_error, &err, &sdap_err);
if (dp_error == DP_ERR_OFFLINE
&& state->conn[state->cindex]->ignore_mark_offline) {
/* This is a special case: GC does not work.
* We need to Fall back to ldap
*/
ret = EOK;
sdap_err = ENOENT;
}
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
if (sdap_err == EOK) {
tevent_req_done(req);
return;
} else if (sdap_err != ENOENT) {
tevent_req_error(req, EIO);
return;
}
/* Ret is only ENOENT now. Try the next connection */
state->cindex++;
ret = ad_handle_acct_info_step(req);
if (ret != EAGAIN) {
/* No additional search in progress. Save the last
* error status, we'll be returning it.
*/
state->dp_error = dp_error;
state->err = err;
if (ret == EOK) {
/* No more connections */
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
return;
}
/* Another lookup in progress */
}
errno_t
ad_handle_acct_info_recv(struct tevent_req *req,
int *_dp_error, const char **_err)
{
struct ad_handle_acct_info_state *state = tevent_req_data(req,
struct ad_handle_acct_info_state);
if (_dp_error) {
*_dp_error = state->dp_error;
}
if (_err) {
*_err = state->err;
}
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
struct sdap_id_conn_ctx **
get_conn_list(struct be_req *breq, struct ad_id_ctx *ad_ctx,
struct sss_domain_info *dom, struct be_acct_req *ar)
{
struct sdap_id_conn_ctx **clist;
/* LDAP, GC, sentinel */
clist = talloc_zero_array(breq, struct sdap_id_conn_ctx *, 3);
if (clist == NULL) return NULL;
switch (ar->entry_type & BE_REQ_TYPE_MASK) {
case BE_REQ_USER: /* user */
case BE_REQ_BY_SECID: /* by SID */
case BE_REQ_USER_AND_GROUP: /* get SID */
case BE_REQ_GROUP: /* group */
case BE_REQ_INITGROUPS: /* init groups for user */
/* Always try GC first */
clist[0] = ad_ctx->gc_ctx;
if (IS_SUBDOMAIN(dom) == true) {
clist[0]->ignore_mark_offline = false;
/* Subdomain users are only present in GC. */
break;
}
/* fall back to ldap if gc is not available */
clist[0]->ignore_mark_offline = true;
/* With root domain users we have the option to
* fall back to LDAP in case ie POSIX attributes
* are used but not replicated to GC
*/
clist[1] = ad_ctx->ldap_ctx;
break;
default:
clist[0] = ad_ctx->ldap_ctx;
break;
}
return clist;
}
static errno_t ad_account_can_shortcut(struct be_ctx *be_ctx,
struct sdap_idmap_ctx *idmap_ctx,
int filter_type,
const char *filter_value,
const char *filter_domain,
bool *_shortcut)
{
struct sss_domain_info *domain = be_ctx->domain;
struct sss_domain_info *req_dom = NULL;
enum idmap_error_code err;
char *sid = NULL;
const char *csid = NULL;
uint32_t id;
bool shortcut = false;
errno_t ret;
if (!sdap_idmap_domain_has_algorithmic_mapping(idmap_ctx, domain->name,
domain->domain_id)) {
shortcut = false;
ret = EOK;
goto done;
}
switch (filter_type) {
case BE_FILTER_IDNUM:
/* convert value to ID */
errno = 0;
id = strtouint32(filter_value, NULL, 10);
if (errno != 0) {
ret = errno;
goto done;
}
/* convert the ID to its SID equivalent */
err = sss_idmap_unix_to_sid(idmap_ctx->map, id, &sid);
if (err != IDMAP_SUCCESS) {
DEBUG(SSSDBG_MINOR_FAILURE, ("Mapping ID [%s] to SID failed: "
"[%s]\n", filter_value, idmap_error_string(err)));
ret = EIO;
goto done;
}
/* fall through */
case BE_FILTER_SECID:
csid = sid == NULL ? filter_value : sid;
req_dom = find_subdomain_by_sid(domain, csid);
if (req_dom == NULL) {
DEBUG(SSSDBG_OP_FAILURE, ("Invalid domain\n"));
ret = ERR_DOMAIN_NOT_FOUND;
goto done;
}
if (strcasecmp(req_dom->name, filter_domain) != 0) {
shortcut = true;
} else {
shortcut = false;
}
break;
default:
shortcut = false;
break;
}
ret = EOK;
done:
if (sid != NULL) {
sss_idmap_free_sid(idmap_ctx->map, sid);
}
if (ret == EOK) {
*_shortcut = shortcut;
}
return ret;
}
static void ad_account_info_complete(struct tevent_req *req);
void
ad_account_info_handler(struct be_req *be_req)
{
struct ad_id_ctx *ad_ctx;
struct be_acct_req *ar;
struct sdap_id_ctx *sdap_id_ctx;
struct be_ctx *be_ctx = be_req_get_be_ctx(be_req);
struct tevent_req *req;
struct sss_domain_info *dom;
struct sdap_domain *sdom;
struct sdap_id_conn_ctx **clist;
bool shortcut;
errno_t ret;
ad_ctx = talloc_get_type(be_ctx->bet_info[BET_ID].pvt_bet_data,
struct ad_id_ctx);
ar = talloc_get_type(be_req_get_data(be_req), struct be_acct_req);
sdap_id_ctx = ad_ctx->sdap_id_ctx;
if (be_is_offline(be_ctx)) {
return be_req_terminate(be_req, DP_ERR_OFFLINE, EAGAIN, "Offline");
}
/* Try to shortcut if this is ID or SID search and it belongs to
* other domain range than is in ar->domain. */
ret = ad_account_can_shortcut(be_ctx, sdap_id_ctx->opts->idmap_ctx,
ar->filter_type, ar->filter_value,
ar->domain, &shortcut);
if (ret != EOK) {
goto fail;
}
if (shortcut) {
DEBUG(SSSDBG_TRACE_FUNC, ("This ID is from different domain\n"));
be_req_terminate(be_req, DP_ERR_OK, EOK, NULL);
return;
}
dom = be_ctx->domain;
if (strcasecmp(ar->domain, be_ctx->domain->name) != 0) {
/* Subdomain request, verify subdomain */
dom = find_subdomain_by_name(be_ctx->domain, ar->domain, true);
}
if (dom == NULL) {
ret = EINVAL;
goto fail;
}
/* Determine whether to connect to GC, LDAP or try both */
clist = get_conn_list(be_req, ad_ctx, dom, ar);
if (clist == NULL) {
ret = EIO;
goto fail;
}
sdom = sdap_domain_get(sdap_id_ctx->opts, dom);
if (sdom == NULL) {
ret = EIO;
goto fail;
}
req = ad_handle_acct_info_send(be_req, be_req, ar, sdap_id_ctx,
sdom, clist);
if (req == NULL) {
ret = ENOMEM;
goto fail;
}
tevent_req_set_callback(req, ad_account_info_complete, be_req);
return;
fail:
be_req_terminate(be_req, DP_ERR_FATAL, ret, NULL);
}
static void
ad_account_info_complete(struct tevent_req *req)
{
struct be_req *be_req;
errno_t ret;
int dp_error;
const char *error_text = "Internal error";
const char *req_error_text;
be_req = tevent_req_callback_data(req, struct be_req);
ret = ad_handle_acct_info_recv(req, &dp_error, &req_error_text);
talloc_zfree(req);
if (dp_error == DP_ERR_OK) {
if (ret == EOK) {
error_text = NULL;
} else {
DEBUG(SSSDBG_FATAL_FAILURE,
("Bug: dp_error is OK on failed request"));
dp_error = DP_ERR_FATAL;
error_text = req_error_text;
}
} else if (dp_error == DP_ERR_OFFLINE) {
error_text = "Offline";
} else if (dp_error == DP_ERR_FATAL && ret == ENOMEM) {
error_text = "Out of memory";
} else {
error_text = req_error_text;
}
return be_req_terminate(be_req, dp_error, ret, error_text);
}
void
ad_check_online(struct be_req *be_req)
{
struct ad_id_ctx *ad_ctx;
struct be_ctx *be_ctx = be_req_get_be_ctx(be_req);
ad_ctx = talloc_get_type(be_ctx->bet_info[BET_ID].pvt_bet_data,
struct ad_id_ctx);
return sdap_do_online_check(be_req, ad_ctx->sdap_id_ctx);
}
struct ad_enumeration_state {
struct ldap_enum_ctx *ectx;
struct sdap_id_op *sdap_op;
struct tevent_context *ev;
struct sdap_domain *sdom;
};
static void ad_enumeration_conn_done(struct tevent_req *subreq);
static void ad_enumeration_master_done(struct tevent_req *subreq);
static void ad_enumeration_done(struct tevent_req *subreq);
struct tevent_req *
ad_enumeration_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct be_ctx *be_ctx,
struct be_ptask *be_ptask,
void *pvt)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct ad_enumeration_state *state;
struct ldap_enum_ctx *ectx;
errno_t ret;
req = tevent_req_create(mem_ctx, &state, struct ad_enumeration_state);
if (req == NULL) return NULL;
ectx = talloc_get_type(pvt, struct ldap_enum_ctx);
if (ectx == NULL) {
ret = EFAULT;
goto fail;
}
state->ectx = ectx;
state->ev = ev;
state->sdom = ectx->sdom;
state->sdap_op = sdap_id_op_create(state, ectx->conn->conn_cache);
if (state->sdap_op == NULL) {
DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed.\n"));
ret = ENOMEM;
goto fail;
}
subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
if (subreq == NULL) {
DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: %d(%s).\n",
ret, strerror(ret)));
goto fail;
}
tevent_req_set_callback(subreq, ad_enumeration_conn_done, req);
return req;
fail:
tevent_req_error(req, ret);
tevent_req_post(req, ev);
return req;
}
static void
ad_enumeration_conn_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct ad_enumeration_state *state = tevent_req_data(req,
struct ad_enumeration_state);
int ret, dp_error;
ret = sdap_id_op_connect_recv(subreq, &dp_error);
talloc_zfree(subreq);
if (ret != EOK) {
if (dp_error == DP_ERR_OFFLINE) {
DEBUG(SSSDBG_TRACE_FUNC,
("Backend is marked offline, retry later!\n"));
tevent_req_done(req);
} else {
DEBUG(SSSDBG_MINOR_FAILURE,
("Domain enumeration failed to connect to " \
"LDAP server: (%d)[%s]\n", ret, strerror(ret)));
tevent_req_error(req, ret);
}
return;
}
subreq = ad_master_domain_send(state, state->ev,
state->ectx->conn,
state->sdap_op,
state->sdom->dom->name);
if (subreq == NULL) {
DEBUG(SSSDBG_OP_FAILURE, ("ad_master_domain_send failed.\n"));
tevent_req_error(req, ret);
return;
}
tevent_req_set_callback(subreq, ad_enumeration_master_done, req);
}
static void
ad_enumeration_master_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct ad_enumeration_state *state = tevent_req_data(req,
struct ad_enumeration_state);
char *flat_name;
char *master_sid;
ret = ad_master_domain_recv(subreq, state,
&flat_name, &master_sid);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot retrieve master domain info\n"));
tevent_req_error(req, ret);
return;
}
ret = sysdb_master_domain_add_info(state->sdom->dom,
flat_name, master_sid);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, ("Cannot save master domain info\n"));
tevent_req_error(req, ret);
return;
}
subreq = sdap_dom_enum_send(state, state->ev, state->ectx->ctx,
state->sdom, state->ectx->conn);
if (subreq == NULL) {
/* The ptask API will reschedule the enumeration on its own on
* failure */
DEBUG(SSSDBG_OP_FAILURE,
("Failed to schedule enumeration, retrying later!\n"));
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, ad_enumeration_done, req);
}
static void
ad_enumeration_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct ad_enumeration_state *state = tevent_req_data(req,
struct ad_enumeration_state);
ret = sdap_dom_enum_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
("Could not enumerate domain %s\n", state->sdom->dom->name));
tevent_req_error(req, ret);
return;
}
/* Ok, we've completed an enumeration. Save this to the
* sysdb so we can postpone starting up the enumeration
* process on the next SSSD service restart (to avoid
* slowing down system boot-up
*/
ret = sysdb_set_enumerated(state->sdom->dom->sysdb,
state->sdom->dom, true);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
("Could not mark domain as having enumerated.\n"));
/* This error is non-fatal, so continue */
}
tevent_req_done(req);
}
errno_t
ad_enumeration_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}