/*
SSSD
IPA Subdomains Module - server mode
Authors:
Sumit Bose <sbose@redhat.com>
Copyright (C) 2015 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "providers/ldap/sdap_async.h"
#include "providers/ldap/sdap_idmap.h"
#include "providers/ipa/ipa_subdomains.h"
#include "providers/ipa/ipa_common.h"
#include "providers/ipa/ipa_id.h"
/* These constants are defined in MS-ADTS 6.1.6.7.1
* https://msdn.microsoft.com/en-us/library/cc223768.aspx
*/
#define LSA_TRUST_DIRECTION_INBOUND 0x00000001
#define LSA_TRUST_DIRECTION_OUTBOUND 0x00000002
static char *forest_keytab(TALLOC_CTX *mem_ctx, const char *forest)
{
return talloc_asprintf(mem_ctx,
"%s/%s.keytab", IPA_TRUST_KEYTAB_DIR, forest);
}
static char *subdomain_trust_princ(TALLOC_CTX *mem_ctx,
const char *forest_realm,
struct sss_domain_info *sd)
{
if (sd->parent->flat_name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Unknown flat name for parent %s\n", sd->parent->name);
return NULL;
}
return talloc_asprintf(mem_ctx, "%s$@%s",
sd->parent->flat_name, forest_realm);
}
static uint32_t default_direction(TALLOC_CTX *mem_ctx,
struct ldb_context *ldb_ctx,
struct sysdb_attrs *attrs)
{
struct ldb_dn *dn = NULL;
uint32_t direction;
dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs);
if (dn == NULL) {
/* Shouldn't happen, but let's try system keytab in this case */
DEBUG(SSSDBG_CRIT_FAILURE,
"Cannot determine subdomain DN, falling back to two-way trust\n");
return (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND);
}
if (ipa_subdom_is_member_dom(dn) == true) {
/* It's expected member domains do not have the direction */
direction = 0;
} else {
/* Old server? Default to 2way trust */
direction = (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND);
}
talloc_free(dn);
return direction;
}
errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd,
struct ldb_context *ldb_ctx,
uint32_t *_direction)
{
uint32_t ipa_trust_direction = 0;
uint32_t direction;
int ret;
ret = sysdb_attrs_get_uint32_t(sd, IPA_TRUST_DIRECTION,
&ipa_trust_direction);
DEBUG(SSSDBG_TRACE_INTERNAL,
"Raw %s value: %d\n", IPA_TRUST_DIRECTION, ipa_trust_direction);
if (ret == ENOENT) {
direction = default_direction(sd, ldb_ctx, sd);
} else if (ret == EOK) {
/* Just store the AD value in SYSDB, we will check it while we're
* trying to use the trust */
direction = ipa_trust_direction;
} else {
return ret;
}
*_direction = direction;
return EOK;
}
const char *ipa_trust_dir2str(uint32_t direction)
{
if ((direction & LSA_TRUST_DIRECTION_OUTBOUND)
&& (direction & LSA_TRUST_DIRECTION_INBOUND)) {
return "two-way trust";
} else if (direction & LSA_TRUST_DIRECTION_OUTBOUND) {
return "one-way outbound: local domain is trusted by remote domain";
} else if (direction & LSA_TRUST_DIRECTION_INBOUND) {
return "one-way inbound: local domain trusts the remote domain";
} else if (direction == 0) {
return "not set";
}
return "unknown";
}
#ifndef IPA_GETKEYTAB_TIMEOUT
#define IPA_GETKEYTAB_TIMEOUT 5
#endif /* IPA_GETKEYTAB_TIMEOUT */
static struct ad_options *
ipa_create_1way_trust_ctx(struct ipa_id_ctx *id_ctx,
const char *forest,
const char *forest_realm,
struct sss_domain_info *subdom)
{
char *keytab;
char *principal;
struct ad_options *ad_options;
const char *ad_domain;
ad_domain = subdom->name;
keytab = forest_keytab(id_ctx, forest);
principal = subdomain_trust_princ(id_ctx, forest_realm, subdom);
if (keytab == NULL || principal == NULL) {
return NULL;
}
ad_options = ad_create_1way_trust_options(id_ctx,
ad_domain,
id_ctx->server_mode->hostname,
keytab,
principal);
if (ad_options == NULL) {
talloc_free(keytab);
talloc_free(principal);
return NULL;
}
return ad_options;
}
static struct ad_options *ipa_ad_options_new(struct ipa_id_ctx *id_ctx,
struct sss_domain_info *subdom)
{
struct ad_options *ad_options = NULL;
uint32_t direction;
const char *forest;
const char *forest_realm;
/* Trusts are only established with forest roots */
direction = subdom->forest_root->trust_direction;
forest_realm = subdom->forest_root->realm;
forest = subdom->forest_root->forest;
if (direction & LSA_TRUST_DIRECTION_OUTBOUND) {
ad_options = ad_create_2way_trust_options(id_ctx,
id_ctx->server_mode->realm,
subdom->name,
id_ctx->server_mode->hostname,
NULL);
} else if (direction & LSA_TRUST_DIRECTION_INBOUND) {
ad_options = ipa_create_1way_trust_ctx(id_ctx, forest,
forest_realm, subdom);
} else {
DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported trust direction!\n");
ad_options = NULL;
}
if (ad_options == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n");
return NULL;
}
return ad_options;
}
static errno_t
ipa_ad_ctx_new(struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx,
struct sss_domain_info *subdom,
struct ad_id_ctx **_ad_id_ctx)
{
struct ad_options *ad_options;
struct ad_id_ctx *ad_id_ctx;
const char *gc_service_name;
const char *service_name;
struct ad_srv_plugin_ctx *srv_ctx;
const char *ad_domain;
const char *ad_site_override;
struct sdap_domain *sdom;
errno_t ret;
const char *extra_attrs;
ad_domain = subdom->name;
DEBUG(SSSDBG_TRACE_LIBS, "Setting up AD subdomain %s\n", subdom->name);
ad_options = ipa_ad_options_new(id_ctx, subdom);
if (ad_options == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n");
talloc_free(ad_options);
return ENOMEM;
}
extra_attrs = dp_opt_get_string(id_ctx->sdap_id_ctx->opts->basic,
SDAP_USER_EXTRA_ATTRS);
if (extra_attrs != NULL) {
DEBUG(SSSDBG_TRACE_ALL,
"Setting extra attrs for subdomain [%s] to [%s].\n", ad_domain,
extra_attrs);
ret = dp_opt_set_string(ad_options->id->basic, SDAP_USER_EXTRA_ATTRS,
extra_attrs);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "dp_opt_get_string failed.\n");
talloc_free(ad_options);
return ret;
}
ret = sdap_extend_map_with_list(ad_options->id, ad_options->id,
SDAP_USER_EXTRA_ATTRS,
ad_options->id->user_map,
SDAP_OPTS_USER,
&ad_options->id->user_map,
&ad_options->id->user_map_cnt);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "sdap_extend_map_with_list failed.\n");
talloc_free(ad_options);
return ret;
}
} else {
DEBUG(SSSDBG_TRACE_ALL, "No extra attrs set.\n");
}
gc_service_name = talloc_asprintf(ad_options, "sd_gc_%s", subdom->forest);
if (gc_service_name == NULL) {
talloc_free(ad_options);
return ENOMEM;
}
service_name = talloc_asprintf(ad_options, "sd_%s", subdom->name);
if (service_name == NULL) {
talloc_free(ad_options);
return ENOMEM;
}
/* Set KRB5 realm to same as the one of IPA when IPA
* is able to attach PAC. For testing, use hardcoded. */
ret = ad_failover_init(ad_options, be_ctx, NULL, NULL,
id_ctx->server_mode->realm,
service_name, gc_service_name,
subdom->name, &ad_options->service);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD failover\n");
talloc_free(ad_options);
return ret;
}
ad_id_ctx = ad_id_ctx_init(ad_options, be_ctx);
if (ad_id_ctx == NULL) {
talloc_free(ad_options);
return ENOMEM;
}
ad_id_ctx->sdap_id_ctx->opts = ad_options->id;
ad_options->id_ctx = ad_id_ctx;
ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE);
/* use AD plugin */
srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx->be_res,
default_host_dbs,
ad_id_ctx->ad_options->id,
id_ctx->server_mode->hostname,
ad_domain,
ad_site_override);
if (srv_ctx == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n");
return ENOMEM;
}
be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send,
ad_srv_plugin_recv, srv_ctx, "AD");
ret = sdap_domain_subdom_add(ad_id_ctx->sdap_id_ctx,
ad_id_ctx->sdap_id_ctx->opts->sdom,
subdom->parent);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize sdap domain\n");
talloc_free(ad_options);
return ret;
}
sdom = sdap_domain_get(ad_id_ctx->sdap_id_ctx->opts, subdom);
if (sdom == NULL) {
return EFAULT;
}
sdap_inherit_options(subdom->parent->sd_inherit,
id_ctx->sdap_id_ctx->opts,
ad_id_ctx->sdap_id_ctx->opts);
ret = sdap_id_setup_tasks(be_ctx,
ad_id_ctx->sdap_id_ctx,
sdom,
ldap_enumeration_send,
ldap_enumeration_recv,
ad_id_ctx->sdap_id_ctx);
if (ret != EOK) {
talloc_free(ad_options);
return ret;
}
sdom->pvt = ad_id_ctx;
/* Set up the ID mapping object */
ad_id_ctx->sdap_id_ctx->opts->idmap_ctx =
id_ctx->sdap_id_ctx->opts->idmap_ctx;
*_ad_id_ctx = ad_id_ctx;
return EOK;
}
struct ipa_getkeytab_state {
int child_status;
struct sss_child_ctx_old *child_ctx;
struct tevent_timer *timeout_handler;
};
static void ipa_getkeytab_exec(const char *ccache,
const char *server,
const char *principal,
const char *keytab_path);
static void ipa_getkeytab_done(int child_status,
struct tevent_signal *sige,
void *pvt);
static void ipa_getkeytab_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval tv, void *pvt);
static struct tevent_req *ipa_getkeytab_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const char *ccache,
const char *server,
const char *principal,
const char *keytab)
{
errno_t ret;
struct tevent_req *req = NULL;
struct ipa_getkeytab_state *state;
pid_t child_pid;
struct timeval tv;
req = tevent_req_create(mem_ctx, &state, struct ipa_getkeytab_state);
if (req == NULL) {
return NULL;
}
state->child_status = EFAULT;
if (server == NULL || principal == NULL || keytab == NULL) {
ret = EINVAL;
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC,
"Retrieving keytab for %s from %s into %s using ccache %s\n",
principal, server, keytab, ccache);
child_pid = fork();
if (child_pid == 0) { /* child */
ipa_getkeytab_exec(ccache, server, principal, keytab);
} else if (child_pid > 0) { /* parent */
/* Set up SIGCHLD handler */
ret = child_handler_setup(ev, child_pid, ipa_getkeytab_done, req,
&state->child_ctx);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n",
ret, sss_strerror(ret));
ret = ERR_IPA_GETKEYTAB_FAILED;
goto done;
}
/* Set up timeout handler */
tv = tevent_timeval_current_ofs(IPA_GETKEYTAB_TIMEOUT, 0);
state->timeout_handler = tevent_add_timer(ev, req, tv,
ipa_getkeytab_timeout, req);
if(state->timeout_handler == NULL) {
ret = ERR_IPA_GETKEYTAB_FAILED;
goto done;
}
/* Now either wait for the timeout to fire or the child
* to finish
*/
} else { /* error */
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"fork failed [%d][%s].\n", ret, sss_strerror(ret));
goto done;
}
ret = EOK;
done:
if (ret != EOK) {
tevent_req_error(req, ret);
tevent_req_post(req, ev);
}
return req;
}
static void ipa_getkeytab_exec(const char *ccache,
const char *server,
const char *principal,
const char *keytab_path)
{
errno_t ret;
int debug_fd;
const char *gkt_env[2] = { NULL, NULL };
if (debug_level >= SSSDBG_TRACE_LIBS) {
debug_fd = get_fd_from_debug_file();
ret = dup2(debug_fd, STDERR_FILENO);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_MINOR_FAILURE,
"dup2 failed [%d][%s].\n", ret, sss_strerror(ret));
/* stderr is not fatal */
}
}
gkt_env[0] = talloc_asprintf(NULL, "KRB5CCNAME=%s", ccache);
if (gkt_env[0] == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to format KRB5CCNAME\n");
exit(1);
}
/* ipa-getkeytab cannot add keys to an empty file, let's unlink it and only
* use the filename */
ret = unlink(keytab_path);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to unlink the temporary ccname [%d][%s]\n",
ret, sss_strerror(ret));
exit(1);
}
errno = 0;
ret = execle(IPA_GETKEYTAB_PATH, IPA_GETKEYTAB_PATH,
"-r", "-s", server, "-p", principal, "-k", keytab_path, NULL,
gkt_env);
DEBUG(SSSDBG_CRIT_FAILURE,
"execle returned %d, this shouldn't happen!\n", ret);
/* The child should never end up here */
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"execle failed [%d][%s].\n", ret, sss_strerror(ret));
exit(1);
}
static void ipa_getkeytab_done(int child_status,
struct tevent_signal *sige,
void *pvt)
{
struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
struct ipa_getkeytab_state *state =
tevent_req_data(req, struct ipa_getkeytab_state);
state->child_status = child_status;
if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) {
DEBUG(SSSDBG_OP_FAILURE,
"ipa-getkeytab failed with status [%d]\n", child_status);
tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED);
return;
}
if (WIFSIGNALED(child_status)) {
DEBUG(SSSDBG_OP_FAILURE,
"ipa-getkeytab was terminated by signal [%d]\n",
WTERMSIG(child_status));
tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED);
return;
}
tevent_req_done(req);
}
static void ipa_getkeytab_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 ipa_getkeytab_state *state =
tevent_req_data(req, struct ipa_getkeytab_state);
DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for retrieving keytab from IPA server\n");
child_handler_destroy(state->child_ctx);
state->child_ctx = NULL;
state->child_status = ETIMEDOUT;
tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED);
}
static errno_t ipa_getkeytab_recv(struct tevent_req *req, int *child_status)
{
struct ipa_getkeytab_state *state =
tevent_req_data(req, struct ipa_getkeytab_state);
DEBUG(SSSDBG_TRACE_INTERNAL,
"ipa-getkeytab status %d\n", state->child_status);
if (child_status) {
*child_status = state->child_status;
}
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static errno_t ipa_check_keytab(const char *keytab,
uid_t kt_owner_uid,
gid_t kt_owner_gid)
{
errno_t ret;
ret = check_file(keytab, getuid(), getgid(), S_IFREG|0600, 0, NULL, false);
if (ret == ENOENT) {
DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab);
goto done;
} else if (ret != EOK) {
if (kt_owner_uid) {
ret = check_file(keytab, kt_owner_uid, kt_owner_gid,
S_IFREG|0600, 0, NULL, false);
}
if (ret != EOK) {
if (ret != ENOENT) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to check for %s\n", keytab);
} else {
DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab);
}
}
goto done;
}
DEBUG(SSSDBG_TRACE_ALL, "keytab %s already exists\n", keytab);
ret = EOK;
done:
return ret;
}
struct ipa_server_trusted_dom_setup_state {
struct tevent_context *ev;
struct be_ctx *be_ctx;
struct ipa_id_ctx *id_ctx;
struct sss_domain_info *subdom;
uint32_t direction;
const char *forest;
const char *keytab;
char *new_keytab;
const char *principal;
const char *forest_realm;
const char *ccache;
};
static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req);
static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq);
struct tevent_req *
ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx,
struct sss_domain_info *subdom)
{
struct tevent_req *req = NULL;
struct ipa_server_trusted_dom_setup_state *state = NULL;
errno_t ret;
req = tevent_req_create(mem_ctx, &state,
struct ipa_server_trusted_dom_setup_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->be_ctx = be_ctx;
state->id_ctx = id_ctx;
state->subdom = subdom;
/* Trusts are only established with forest roots */
if (subdom->forest_root == NULL) {
DEBUG(SSSDBG_OP_FAILURE,
"Subdomain %s has no forest root?\n", subdom->name);
ret = ERR_TRUST_FOREST_UNKNOWN;
goto immediate;
}
state->direction = subdom->forest_root->trust_direction;
state->forest = subdom->forest_root->forest;
state->forest_realm = subdom->forest_root->realm;
state->ccache = talloc_asprintf(state, "%s/ccache_%s",
DB_PATH, subdom->parent->realm);
if (state->ccache == NULL) {
ret = ENOMEM;
goto immediate;
}
DEBUG(SSSDBG_TRACE_LIBS,
"Trust direction of subdom %s from forest %s is: %s\n",
subdom->name, state->forest,
ipa_trust_dir2str(state->direction));
if (state->direction & LSA_TRUST_DIRECTION_OUTBOUND) {
/* Use system keytab, nothing to do here */
ret = EOK;
goto immediate;
} else if (state->direction & LSA_TRUST_DIRECTION_INBOUND) {
/* Need special keytab */
ret = ipa_server_trusted_dom_setup_1way(req);
if (ret == EAGAIN) {
/* In progress.. */
return req;
} else if (ret == EOK) {
/* Keytab available, shortcut */
ret = EOK;
goto immediate;
}
} else {
/* Even unset is an error at this point */
DEBUG(SSSDBG_OP_FAILURE,
"Subdomain %s has trust direction %d\n",
subdom->name, subdom->trust_direction);
ret = ERR_TRUST_NOT_SUPPORTED;
}
immediate:
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Could not add trusted subdomain %s from forest %s\n",
subdom->name, state->forest);
tevent_req_error(req, ret);
} else {
tevent_req_done(req);
}
tevent_req_post(req, ev);
return req;
}
static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req)
{
errno_t ret;
struct tevent_req *subreq = NULL;
struct ipa_server_trusted_dom_setup_state *state =
tevent_req_data(req, struct ipa_server_trusted_dom_setup_state);
const char *hostname;
state->keytab = forest_keytab(state, state->forest);
if (state->keytab == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n");
return EIO;
}
state->new_keytab = talloc_asprintf(state, "%sXXXXXX", state->keytab);
if (state->new_keytab == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n");
return ENOMEM;
}
ret = sss_unique_filename(state, state->new_keytab);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create temporary keytab name\n");
return ret;
}
DEBUG(SSSDBG_TRACE_FUNC,
"Will re-fetch keytab for %s\n", state->subdom->name);
hostname = dp_opt_get_string(state->id_ctx->ipa_options->basic,
IPA_HOSTNAME);
state->principal = subdomain_trust_princ(state,
state->forest_realm,
state->subdom);
if (state->principal == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n");
return EIO;
}
subreq = ipa_getkeytab_send(state->be_ctx, state->be_ctx->ev,
state->ccache,
hostname,
state->principal,
state->new_keytab);
if (subreq == NULL) {
return ENOMEM;
}
tevent_req_set_callback(subreq, ipa_server_trust_1way_kt_done, req);
return EAGAIN;
}
static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct ipa_server_trusted_dom_setup_state *state =
tevent_req_data(req, struct ipa_server_trusted_dom_setup_state);
ret = ipa_getkeytab_recv(subreq, NULL);
talloc_zfree(subreq);
if (ret != EOK) {
/* Do not fail here, but try to check and use the previous keytab,
* if any */
DEBUG(SSSDBG_MINOR_FAILURE, "ipa_getkeytab_recv failed: %d\n", ret);
} else {
DEBUG(SSSDBG_TRACE_FUNC,
"Keytab successfully retrieved to %s\n", state->new_keytab);
}
ret = ipa_check_keytab(state->new_keytab,
state->id_ctx->server_mode->kt_owner_uid,
state->id_ctx->server_mode->kt_owner_gid);
if (ret == EOK) {
ret = rename(state->new_keytab, state->keytab);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"rename failed [%d][%s].\n", ret, strerror(ret));
tevent_req_error(req, ret);
return;
}
DEBUG(SSSDBG_TRACE_INTERNAL, "Keytab renamed to %s\n", state->keytab);
} else if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Trying to recover and use the previous keytab, if available\n");
ret = ipa_check_keytab(state->keytab,
state->id_ctx->server_mode->kt_owner_uid,
state->id_ctx->server_mode->kt_owner_gid);
if (ret == EOK) {
DEBUG(SSSDBG_TRACE_FUNC,
"The previous keytab %s contains the expected principal\n",
state->keytab);
} else {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot use the old keytab: %d\n", ret);
/* Nothing we can do now */
tevent_req_error(req, ret);
return;
}
}
DEBUG(SSSDBG_TRACE_FUNC,
"Keytab %s contains the expected principals\n", state->new_keytab);
DEBUG(SSSDBG_TRACE_FUNC,
"Established trust context for %s\n", state->subdom->name);
tevent_req_done(req);
}
errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
struct ipa_server_create_trusts_state {
struct tevent_context *ev;
struct be_ctx *be_ctx;
struct ipa_id_ctx *id_ctx;
struct sss_domain_info *domiter;
};
static errno_t ipa_server_create_trusts_step(struct tevent_req *req);
static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req);
static void ipa_server_create_trusts_done(struct tevent_req *subreq);
struct tevent_req *
ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx,
struct sss_domain_info *parent)
{
struct tevent_req *req = NULL;
struct ipa_server_create_trusts_state *state = NULL;
errno_t ret;
req = tevent_req_create(mem_ctx, &state,
struct ipa_server_create_trusts_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->be_ctx = be_ctx;
state->id_ctx = id_ctx;
state->domiter = parent;
ret = ipa_server_create_trusts_step(req);
if (ret != EAGAIN) {
goto immediate;
}
return req;
immediate:
if (ret != EOK) {
tevent_req_error(req, ret);
} else {
tevent_req_done(req);
}
tevent_req_post(req, ev);
return req;
}
static errno_t ipa_server_create_trusts_step(struct tevent_req *req)
{
struct tevent_req *subreq = NULL;
struct ipa_ad_server_ctx *trust_iter;
struct ipa_server_create_trusts_state *state = NULL;
state = tevent_req_data(req, struct ipa_server_create_trusts_state);
for (state->domiter = get_next_domain(state->domiter, SSS_GND_DESCEND);
state->domiter && IS_SUBDOMAIN(state->domiter);
state->domiter = get_next_domain(state->domiter, 0)) {
/* Check if we already have an ID context for this subdomain */
DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) {
if (trust_iter->dom == state->domiter) {
break;
}
}
/* Newly detected trust */
if (trust_iter == NULL) {
subreq = ipa_server_trusted_dom_setup_send(state,
state->ev,
state->be_ctx,
state->id_ctx,
state->domiter);
if (subreq == NULL) {
return ENOMEM;
}
tevent_req_set_callback(subreq, ipa_server_create_trusts_done, req);
return EAGAIN;
}
}
return EOK;
}
static void ipa_server_create_trusts_done(struct tevent_req *subreq)
{
errno_t ret;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
ret = ipa_server_trusted_dom_setup_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
ret = ipa_server_create_trusts_ctx(req);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
ret = ipa_server_create_trusts_step(req);
if (ret == EOK) {
tevent_req_done(req);
return;
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
return;
}
/* Will cycle back */
}
static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req)
{
struct ipa_ad_server_ctx *trust_ctx;
struct ad_id_ctx *ad_id_ctx;
errno_t ret;
struct ipa_server_create_trusts_state *state = NULL;
state = tevent_req_data(req, struct ipa_server_create_trusts_state);
ret = ipa_ad_ctx_new(state->be_ctx, state->id_ctx, state->domiter, &ad_id_ctx);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot create ad_id_ctx for subdomain %s\n", state->domiter->name);
return ret;
}
trust_ctx = talloc(state->id_ctx->server_mode, struct ipa_ad_server_ctx);
if (trust_ctx == NULL) {
return ENOMEM;
}
trust_ctx->dom = state->domiter;
trust_ctx->ad_id_ctx = ad_id_ctx;
DLIST_ADD(state->id_ctx->server_mode->trusts, trust_ctx);
return EOK;
}
errno_t ipa_server_create_trusts_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
void ipa_ad_subdom_remove(struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx,
struct sss_domain_info *subdom)
{
struct ipa_ad_server_ctx *iter;
struct sdap_domain *sdom;
if (dp_opt_get_bool(id_ctx->ipa_options->basic,
IPA_SERVER_MODE) == false) {
return;
}
DLIST_FOR_EACH(iter, id_ctx->server_mode->trusts) {
if (iter->dom == subdom) break;
}
if (iter == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "No IPA-AD context for subdomain %s\n",
subdom->name);
return;
}
sdom = sdap_domain_get(iter->ad_id_ctx->sdap_id_ctx->opts, subdom);
if (sdom == NULL) return;
be_ptask_destroy(&sdom->enum_task);
be_ptask_destroy(&sdom->cleanup_task);
sdap_domain_remove(iter->ad_id_ctx->sdap_id_ctx->opts, subdom);
DLIST_REMOVE(id_ctx->server_mode->trusts, iter);
/* terminate all requests for this subdomain so we can free it */
dp_terminate_domain_requests(be_ctx->provider, subdom->name);
talloc_zfree(sdom);
}
struct ipa_ad_subdom_reinit_state {
struct tevent_context *ev;
struct be_ctx *be_ctx;
struct ipa_id_ctx *id_ctx;
struct sss_domain_info *parent;
};
static void create_trusts_at_startup_done(struct tevent_req *req)
{
errno_t ret;
ret = ipa_server_create_trusts_recv(req);
talloc_free(req);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"ipa_server_create_trusts_send request failed [%d]: %s\n",
ret, sss_strerror(ret));
}
}
static void create_trusts_at_startup(struct tevent_context *ev,
struct tevent_immediate *imm,
void *pvt)
{
struct tevent_req *req;
struct ipa_ad_subdom_reinit_state *state;
state = talloc_get_type(pvt, struct ipa_ad_subdom_reinit_state);
req = ipa_server_create_trusts_send(state, state->ev, state->be_ctx,
state->id_ctx, state->parent);
if (req == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "ipa_server_create_trusts_send failed.\n");
talloc_free(state);
return;
}
tevent_req_set_callback(req, create_trusts_at_startup_done, state);
return;
}
static errno_t ipa_ad_subdom_reinit(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx,
struct sss_domain_info *parent)
{
struct tevent_immediate *imm;
struct ipa_ad_subdom_reinit_state *state;
state = talloc(mem_ctx, struct ipa_ad_subdom_reinit_state);
if (state == NULL) {
return ENOMEM;
}
state->ev = ev;
state->be_ctx = be_ctx;
state->id_ctx = id_ctx;
state->parent = parent;
if (dp_opt_get_bool(id_ctx->ipa_options->basic,
IPA_SERVER_MODE) == false) {
return EOK;
}
imm = tevent_create_immediate(mem_ctx);
if (imm == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n");
talloc_free(state);
return ENOMEM;
}
tevent_schedule_immediate(imm, ev, create_trusts_at_startup, state);
return EOK;
}
int ipa_ad_subdom_init(struct be_ctx *be_ctx,
struct ipa_id_ctx *id_ctx)
{
char *realm;
char *hostname;
errno_t ret;
if (dp_opt_get_bool(id_ctx->ipa_options->basic,
IPA_SERVER_MODE) == false) {
return EOK;
}
/* The IPA code relies on the default FQDN format to unparse user
* names. Warn loudly if the full_name_format was customized on the
* IPA server
*/
if ((strcmp(be_ctx->domain->names->fq_fmt,
CONFDB_DEFAULT_FULL_NAME_FORMAT) != 0)
&& (strcmp(be_ctx->domain->names->fq_fmt,
CONFDB_DEFAULT_FULL_NAME_FORMAT_INTERNAL) != 0)) {
DEBUG(SSSDBG_FATAL_FAILURE, "%s is set to a non-default value [%s] " \
"lookups of subdomain users will likely fail!\n",
CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt);
sss_log(SSS_LOG_ERR, "%s is set to a non-default value [%s] " \
"lookups of subdomain users will likely fail!\n",
CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt);
/* Attempt to continue */
}
realm = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_KRB5_REALM);
if (realm == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n");
return EINVAL;
}
hostname = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_HOSTNAME);
if (hostname == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "No host name for IPA?\n");
return EINVAL;
}
id_ctx->server_mode = talloc_zero(id_ctx, struct ipa_server_mode_ctx);
if (id_ctx->server_mode == NULL) {
return ENOMEM;
}
id_ctx->server_mode->realm = realm;
id_ctx->server_mode->hostname = hostname;
id_ctx->server_mode->trusts = NULL;
id_ctx->server_mode->ext_groups = NULL;
id_ctx->server_mode->kt_owner_uid = 0;
id_ctx->server_mode->kt_owner_gid = 0;
if (getuid() == 0) {
/* We need to handle keytabs created by IPA oddjob script gracefully
* even if we're running as root and IPA creates them as the SSSD user
*/
ret = sss_user_by_name_or_uid(SSSD_USER,
&id_ctx->server_mode->kt_owner_uid,
&id_ctx->server_mode->kt_owner_gid);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get ID of %s\n", SSSD_USER);
}
}
ret = ipa_ad_subdom_reinit(be_ctx, be_ctx->ev,
be_ctx, id_ctx, be_ctx->domain);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "ipa_ad_subdom_refresh failed.\n");
return ret;
}
return EOK;
}