summaryrefslogtreecommitdiffstats
path: root/src/providers/ipa/ipa_subdomains_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ipa/ipa_subdomains_server.c')
-rw-r--r--src/providers/ipa/ipa_subdomains_server.c1215
1 files changed, 1215 insertions, 0 deletions
diff --git a/src/providers/ipa/ipa_subdomains_server.c b/src/providers/ipa/ipa_subdomains_server.c
new file mode 100644
index 0000000..aaedf62
--- /dev/null
+++ b/src/providers/ipa/ipa_subdomains_server.c
@@ -0,0 +1,1215 @@
+/*
+ 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
+#define LSA_TRUST_DIRECTION_MASK (LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND)
+
+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,
+ struct be_ctx *be_ctx,
+ const char *subdom_conf_path,
+ const char *forest,
+ const char *forest_realm,
+ struct sss_domain_info *subdom)
+{
+ char *keytab;
+ char *principal;
+ struct ad_options *ad_options;
+
+ 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,
+ be_ctx->cdb,
+ subdom_conf_path,
+ be_ctx->provider,
+ subdom,
+ 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 be_ctx *be_ctx,
+ 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;
+ char *subdom_conf_path;
+ int ret;
+
+ /* Trusts are only established with forest roots */
+ direction = subdom->forest_root->trust_direction;
+ forest_realm = subdom->forest_root->realm;
+ forest = subdom->forest_root->forest;
+
+ subdom_conf_path = subdomain_create_conf_path(id_ctx, subdom);
+ if (subdom_conf_path == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "subdom_conf_path failed\n");
+ return NULL;
+ }
+
+ /* In both inbound and outbound trust cases we should be
+ * using trusted domain object in a trusted domain space,
+ * thus we always should be initializing principals/keytabs
+ * as if we are running one-way trust */
+ if (direction & LSA_TRUST_DIRECTION_MASK) {
+ ad_options = ipa_create_1way_trust_ctx(id_ctx, be_ctx,
+ subdom_conf_path, 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");
+ talloc_free(subdom_conf_path);
+ return NULL;
+ }
+
+ ret = ad_inherit_opts_if_needed(id_ctx->ipa_options->id->basic,
+ ad_options->id->basic, be_ctx->cdb,
+ subdom_conf_path, SDAP_SASL_MECH);
+ talloc_free(subdom_conf_path);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to inherit option [%s] to sub-domain [%s]. "
+ "This error is ignored but might cause issues or unexpected "
+ "behavior later on.\n",
+ id_ctx->ipa_options->id->basic[SDAP_SASL_MECH].opt_name,
+ subdom->name);
+
+ 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;
+ const char *ad_servers;
+ const char *ad_backup_servers;
+ struct sdap_domain *sdom;
+ errno_t ret;
+ const char *extra_attrs;
+ bool use_kdcinfo = false;
+ size_t n_lookahead_primary = (size_t)-1;
+ size_t n_lookahead_backup = (size_t)-1;
+
+ ad_domain = subdom->name;
+ DEBUG(SSSDBG_TRACE_LIBS, "Setting up AD subdomain %s\n", subdom->name);
+
+ ad_options = ipa_ad_options_new(be_ctx, 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_set_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->name);
+ 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;
+ }
+
+ ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER);
+ ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER);
+
+ if (id_ctx->ipa_options != NULL && id_ctx->ipa_options->auth != NULL) {
+ use_kdcinfo = dp_opt_get_bool(id_ctx->ipa_options->auth,
+ KRB5_USE_KDCINFO);
+ sss_krb5_parse_lookahead(
+ dp_opt_get_string(id_ctx->ipa_options->auth, KRB5_KDCINFO_LOOKAHEAD),
+ &n_lookahead_primary,
+ &n_lookahead_backup);
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Init failover for [%s][%s] with use_kdcinfo [%s].\n",
+ subdom->name, subdom->realm, use_kdcinfo ? "true" : "false");
+
+ /* Set KRB5 realm to same as the one of IPA when IPA
+ * is able to attach PAC. For testing, use hardcoded. */
+ /* Why? */
+ ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers,
+ subdom->realm,
+ service_name, gc_service_name,
+ subdom->name, use_kdcinfo, false,
+ n_lookahead_primary, n_lookahead_backup,
+ &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_ctx->be_res,
+ default_host_dbs,
+ ad_id_ctx->ad_options->id,
+ ad_id_ctx->ad_options,
+ 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;
+ }
+
+ ret = ad_set_search_bases(ad_options->id, sdom);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD search bases\n");
+ talloc_free(ad_options);
+ return ret;
+ }
+
+ 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_id_enumeration_send,
+ ldap_id_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;
+
+ /* Set up the certificate mapping context */
+ ad_id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx =
+ id_ctx->sdap_id_ctx->opts->sdap_certmap_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[3] = { NULL, "_SSS_LOOPS=NO", 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_FATAL_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_FATAL_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_FATAL_FAILURE,
+ "execle returned %d, this shouldn't happen!\n", ret);
+
+ /* The child should never end up here */
+ ret = errno;
+ DEBUG(SSSDBG_FATAL_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));
+
+ /* For both inbound and outbound trusts use a special keytab
+ * as this allows us to reuse the same logic in FreeIPA for
+ * both Microsoft AD and Samba AD */
+ if (state->direction & LSA_TRUST_DIRECTION_MASK) {
+ /* 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. talloc_asprintf() failed\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_ad_server_ctx *trust_i;
+ 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;
+ }
+ }
+
+ /* Refresh all sdap_dom lists in all ipa_ad_server_ctx contexts */
+ DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) {
+ struct sdap_domain *sdom_a;
+
+ sdom_a = sdap_domain_get(trust_iter->ad_id_ctx->sdap_id_ctx->opts,
+ trust_iter->dom);
+ if (sdom_a == NULL) {
+ continue;
+ }
+
+ DLIST_FOR_EACH(trust_i, state->id_ctx->server_mode->trusts) {
+ struct sdap_domain *sdom_b;
+
+ if (strcmp(trust_iter->dom->name, trust_i->dom->name) == 0) {
+ continue;
+ }
+
+ sdom_b = sdap_domain_get(trust_i->ad_id_ctx->sdap_id_ctx->opts,
+ sdom_a->dom);
+ if (sdom_b == NULL) {
+ continue;
+ }
+
+ /* Replace basedn and search bases from sdom_b with values
+ * from sdom_a */
+ sdap_domain_copy_search_bases(sdom_b, sdom_a);
+ }
+ }
+
+ 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;
+
+ 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;
+}