diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/config/etc/sssd.api.d/sssd-ad.conf | 2 | ||||
-rw-r--r-- | src/man/sssd-ad.5.xml | 4 | ||||
-rw-r--r-- | src/man/sssd.conf.5.xml | 4 | ||||
-rw-r--r-- | src/providers/ad/ad_init.c | 31 | ||||
-rw-r--r-- | src/providers/ad/ad_subdomains.c | 522 | ||||
-rw-r--r-- | src/providers/ad/ad_subdomains.h | 37 |
7 files changed, 602 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 3abea76c..b72384a7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1621,6 +1621,8 @@ libsss_ad_la_SOURCES = \ src/providers/ad/ad_access.h \ src/providers/ad/ad_opts.h \ src/providers/ad/ad_srv.c \ + src/providers/ad/ad_subdomains.c \ + src/providers/ad/ad_subdomains.h \ src/util/find_uid.c \ src/util/user_info_msg.c \ src/util/sss_krb5.c \ diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf index b4b1d0ba..3be25e8d 100644 --- a/src/config/etc/sssd.api.d/sssd-ad.conf +++ b/src/config/etc/sssd.api.d/sssd-ad.conf @@ -126,3 +126,5 @@ krb5_use_enterprise_principal = bool, None, false [provider/ad/chpass] krb5_kpasswd = str, None, false krb5_backup_kpasswd = str, None, false + +[provider/ad/subdomains] diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml index c1960771..4dcd552d 100644 --- a/src/man/sssd-ad.5.xml +++ b/src/man/sssd-ad.5.xml @@ -95,6 +95,10 @@ ldap_id_mapping = False specified as the lower-case version of the long version of the Active Directory domain. </para> + <para> + The short domain name (also known as the NetBIOS + or the flat name) is autodetected by the SSSD. + </para> </listitem> </varlistentry> diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 04c69994..99337fbb 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1481,6 +1481,10 @@ override_homedir = /home/%u Regular expression for this domain that describes how to parse the string containing user name and domain into these components. + The "domain" can match either the SSSD + configuration domain name, or, in the case + of IPA trust subdomains and Active Directory + domains, the flat (NetBIOS) name of the domain. </para> <para> Default for the AD and IPA provider: diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c index 2f5a5da1..f90df2a6 100644 --- a/src/providers/ad/ad_init.c +++ b/src/providers/ad/ad_init.c @@ -37,6 +37,7 @@ #include "providers/ad/ad_id.h" #include "providers/ad/ad_srv.h" #include "providers/dp_dyndns.h" +#include "providers/ad/ad_subdomains.h" struct ad_options *ad_options = NULL; @@ -361,3 +362,33 @@ ad_shutdown(struct be_req *req) /* TODO: Clean up any internal data */ sdap_handler_done(req, DP_ERR_OK, EOK, NULL); } + +int sssm_ad_subdomains_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_data) +{ + int ret; + struct ad_id_ctx *id_ctx; + const char *ad_domain; + + ret = sssm_ad_id_init(bectx, ops, (void **) &id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("sssm_ad_id_init failed.\n")); + return ret; + } + + if (ad_options == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Global AD options not available.\n")); + return EINVAL; + } + + ad_domain = dp_opt_get_string(ad_options->basic, AD_DOMAIN); + + ret = ad_subdom_init(bectx, id_ctx, ad_domain, ops, pvt_data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("ad_subdom_init failed.\n")); + return ret; + } + + return EOK; +} diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c new file mode 100644 index 00000000..1da343f8 --- /dev/null +++ b/src/providers/ad/ad_subdomains.c @@ -0,0 +1,522 @@ +/* + SSSD + + AD Subdomains Module + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ad/ad_subdomains.h" +#include <ctype.h> +#include <ndr.h> +#include <ndr/ndr_nbt.h> + +#define AD_AT_OBJECT_SID "objectSID" +#define AD_AT_DNS_DOMAIN "DnsDomain" +#define AD_AT_NT_VERSION "NtVer" +#define AD_AT_NETLOGON "netlogon" + +#define MASTER_DOMAIN_SID_FILTER "objectclass=domain" + +/* do not refresh more often than every 5 seconds for now */ +#define AD_SUBDOMAIN_REFRESH_LIMIT 5 + +/* refresh automatically every 4 hours */ +#define AD_SUBDOMAIN_REFRESH_PERIOD (3600 * 4) + +struct ad_subdomains_ctx { + struct be_ctx *be_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sss_idmap_ctx *idmap_ctx; + char *domain_name; + + time_t last_refreshed; + struct tevent_timer *timer_event; +}; + +struct ad_subdomains_req_ctx { + struct be_req *be_req; + struct ad_subdomains_ctx *sd_ctx; + struct sdap_id_op *sdap_op; + + char *current_filter; + size_t base_iter; + + size_t reply_count; + struct sysdb_attrs **reply; + + char *master_sid; + char *flat_name; +}; + +static void ad_subdomains_get_conn_done(struct tevent_req *req); +static errno_t ad_subdomains_get_master_sid(struct ad_subdomains_req_ctx *ctx); +static void ad_subdomains_get_master_sid_done(struct tevent_req *req); +static void ad_subdomains_get_netlogon_done(struct tevent_req *req); + +static void ad_subdomains_retrieve(struct ad_subdomains_ctx *ctx, + struct be_req *be_req) +{ + struct ad_subdomains_req_ctx *req_ctx = NULL; + struct tevent_req *req; + int dp_error = DP_ERR_FATAL; + int ret; + + req_ctx = talloc(be_req, struct ad_subdomains_req_ctx); + if (req_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + req_ctx->be_req = be_req; + req_ctx->sd_ctx = ctx; + req_ctx->current_filter = NULL; + req_ctx->base_iter = 0; + req_ctx->reply_count = 0; + req_ctx->reply = NULL; + + req_ctx->sdap_op = sdap_id_op_create(req_ctx, + ctx->sdap_id_ctx->conn_cache); + if (req_ctx->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed.\n")); + ret = ENOMEM; + goto done; + } + + req = sdap_id_op_connect_send(req_ctx->sdap_op, req_ctx, &ret); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret))); + goto done; + } + + tevent_req_set_callback(req, ad_subdomains_get_conn_done, req_ctx); + + return; + +done: + talloc_free(req_ctx); + if (ret == EOK) { + dp_error = DP_ERR_OK; + } + be_req_terminate(be_req, dp_error, ret, NULL); +} + +static void ad_subdomains_get_conn_done(struct tevent_req *req) +{ + int ret; + int dp_error = DP_ERR_FATAL; + struct ad_subdomains_req_ctx *ctx; + + ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); + + ret = sdap_id_op_connect_recv(req, &dp_error); + talloc_zfree(req); + if (ret) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("No AD server is available, cannot get the " + "subdomain list while offline\n")); + } else { + DEBUG(SSSDBG_OP_FAILURE, + ("Failed to connect to AD server: [%d](%s)\n", + ret, strerror(ret))); + } + + goto fail; + } + + ret = ad_subdomains_get_master_sid(ctx); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_OP_FAILURE, ("No search base available.\n")); + ret = EINVAL; + +fail: + be_req_terminate(ctx->be_req, dp_error, ret, NULL); +} + +static errno_t ad_subdomains_get_master_sid(struct ad_subdomains_req_ctx *ctx) +{ + struct tevent_req *req; + struct sdap_search_base *base; + const char *master_sid_attrs[] = {AD_AT_OBJECT_SID, NULL}; + + + base = ctx->sd_ctx->sdap_id_ctx->opts->search_bases[ctx->base_iter]; + if (base == NULL) { + return EOK; + } + + req = sdap_get_generic_send(ctx, ctx->sd_ctx->be_ctx->ev, + ctx->sd_ctx->sdap_id_ctx->opts, + sdap_id_op_handle(ctx->sdap_op), + base->basedn, LDAP_SCOPE_BASE, + MASTER_DOMAIN_SID_FILTER, master_sid_attrs, + NULL, 0, + dp_opt_get_int(ctx->sd_ctx->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_get_generic_send failed.\n")); + return ENOMEM; + } + + tevent_req_set_callback(req, ad_subdomains_get_master_sid_done, ctx); + + return EAGAIN; +} + +static void ad_subdomains_get_master_sid_done(struct tevent_req *req) +{ + int ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + struct ad_subdomains_req_ctx *ctx; + struct ldb_message_element *el; + char *sid_str; + enum idmap_error_code err; + static const char *attrs[] = {AD_AT_NETLOGON, NULL}; + char *filter; + char *ntver; + + ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); + + ret = sdap_get_generic_recv(req, ctx, &reply_count, &reply); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_get_generic_send request failed.\n")); + goto done; + } + + if (reply_count == 0) { + ctx->base_iter++; + ret = ad_subdomains_get_master_sid(ctx); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto done; + } + } else if (reply_count == 1) { + ret = sysdb_attrs_get_el(reply[0], AD_AT_OBJECT_SID, &el); + if (ret != EOK || el->num_values != 1) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_attrs_get_el failed.\n")); + goto done; + } + + err = sss_idmap_bin_sid_to_sid(ctx->sd_ctx->idmap_ctx, + el->values[0].data, + el->values[0].length, + &sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not convert SID: [%s].\n", idmap_error_string(err))); + ret = EFAULT; + goto done; + } + + ctx->master_sid = talloc_steal(ctx, sid_str); + } else { + DEBUG(SSSDBG_OP_FAILURE, + ("More than one result for domain SID found.\n")); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Found SID [%s].\n", ctx->master_sid)); + + ntver = sss_ldap_encode_ndr_uint32(ctx, NETLOGON_NT_VERSION_5EX | + NETLOGON_NT_VERSION_WITH_CLOSEST_SITE); + if (ntver == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sss_ldap_encode_ndr_uint32 failed.\n")); + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(ctx, "(&(%s=%s)(%s=%s))", + AD_AT_DNS_DOMAIN, ctx->sd_ctx->domain_name, + AD_AT_NT_VERSION, ntver); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + + req = sdap_get_generic_send(ctx, ctx->sd_ctx->be_ctx->ev, + ctx->sd_ctx->sdap_id_ctx->opts, + sdap_id_op_handle(ctx->sdap_op), + "", LDAP_SCOPE_BASE, filter, attrs, NULL, 0, + dp_opt_get_int(ctx->sd_ctx->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, ad_subdomains_get_netlogon_done, ctx); + return; + +done: + be_req_terminate(ctx->be_req, DP_ERR_FATAL, ret, NULL); +} + +static void ad_subdomains_get_netlogon_done(struct tevent_req *req) +{ + int ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + struct ad_subdomains_req_ctx *ctx; + struct ldb_message_element *el; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct ndr_pull *ndr_pull = NULL; + struct netlogon_samlogon_response response; + + ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); + + ret = sdap_get_generic_recv(req, ctx, &reply_count, &reply); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_get_generic_send request failed.\n")); + goto done; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, ("No netlogon data available.\n")); + } else if (reply_count > 1) { + DEBUG(SSSDBG_OP_FAILURE, + ("More than one netlogon info returned.\n")); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_get_el(reply[0], AD_AT_NETLOGON, &el); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("sysdb_attrs_get_el() failed\n")); + goto done; + } + + if (el->num_values == 0) { + DEBUG(SSSDBG_OP_FAILURE, ("netlogon has no value\n")); + ret = ENOENT; + goto done; + } else if (el->num_values > 1) { + DEBUG(SSSDBG_OP_FAILURE, ("More than one netlogon value?\n")); + ret = EIO; + goto done; + } + + blob.data = el->values[0].data; + blob.length = el->values[0].length; + + ndr_pull = ndr_pull_init_blob(&blob, ctx); + if (ndr_pull == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("ndr_pull_init_blob() failed.\n")); + ret = ENOMEM; + goto done; + } + + ndr_err = ndr_pull_netlogon_samlogon_response(ndr_pull, NDR_SCALARS, + &response); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(SSSDBG_OP_FAILURE, ("ndr_pull_netlogon_samlogon_response() " + "failed [%d]\n", ndr_err)); + ret = EBADMSG; + goto done; + } + + if (!(response.ntver & NETLOGON_NT_VERSION_5EX)) { + DEBUG(SSSDBG_OP_FAILURE, ("Wrong version returned [%x]\n", + response.ntver)); + ret = EBADMSG; + goto done; + } + + if (response.data.nt5_ex.domain_name != NULL && + *response.data.nt5_ex.domain_name != '\0') { + ctx->flat_name = talloc_strdup(ctx, response.data.nt5_ex.domain_name); + if (ctx->flat_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + ret = ENOMEM; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, ("Found flat name [%s].\n", ctx->flat_name)); + + ret = sysdb_master_domain_add_info(ctx->sd_ctx->be_ctx->domain, + NULL, ctx->flat_name, ctx->master_sid); + + ret = EOK; + +done: + + if (ret == EOK) { + ctx->sd_ctx->last_refreshed = time(NULL); + } + be_req_terminate(ctx->be_req, DP_ERR_FATAL, ret, NULL); +} + +static void ad_subdom_online_cb(void *pvt); + +static void ad_subdom_timer_refresh(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + ad_subdom_online_cb(pvt); +} + +static void ad_subdom_be_req_callback(struct be_req *be_req, + int dp_err, int dp_ret, + const char *errstr) +{ + talloc_free(be_req); +} + +static void ad_subdom_online_cb(void *pvt) +{ + struct ad_subdomains_ctx *ctx; + struct be_req *be_req; + struct timeval tv; + + ctx = talloc_get_type(pvt, struct ad_subdomains_ctx); + if (!ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Bad private pointer\n")); + return; + } + + be_req = be_req_create(ctx, NULL, ctx->be_ctx, + ad_subdom_be_req_callback, NULL); + if (be_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("be_req_create() failed.\n")); + return; + } + + ad_subdomains_retrieve(ctx, be_req); + + tv = tevent_timeval_current_ofs(AD_SUBDOMAIN_REFRESH_PERIOD, 0); + ctx->timer_event = tevent_add_timer(ctx->be_ctx->ev, ctx, tv, + ad_subdom_timer_refresh, ctx); + if (!ctx->timer_event) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to add subdom timer event\n")); + } +} + +static void ad_subdom_offline_cb(void *pvt) +{ + struct ad_subdomains_ctx *ctx; + + ctx = talloc_get_type(pvt, struct ad_subdomains_ctx); + + if (ctx) { + talloc_zfree(ctx->timer_event); + } +} + +void ad_subdomains_handler(struct be_req *be_req) +{ + struct be_ctx *be_ctx = be_req_get_be_ctx(be_req); + struct ad_subdomains_ctx *ctx; + time_t now; + + ctx = talloc_get_type(be_ctx->bet_info[BET_SUBDOMAINS].pvt_bet_data, + struct ad_subdomains_ctx); + if (!ctx) { + be_req_terminate(be_req, DP_ERR_FATAL, EINVAL, NULL); + return; + } + + now = time(NULL); + + if (ctx->last_refreshed > now - AD_SUBDOMAIN_REFRESH_LIMIT) { + be_req_terminate(be_req, DP_ERR_OK, EOK, NULL); + return; + } + + ad_subdomains_retrieve(ctx, be_req); +} + +struct bet_ops ad_subdomains_ops = { + .handler = ad_subdomains_handler, + .finalize = NULL +}; + +static void *idmap_talloc(size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +static void idmap_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} + +int ad_subdom_init(struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + const char *ad_domain, + struct bet_ops **ops, + void **pvt_data) +{ + struct ad_subdomains_ctx *ctx; + int ret; + enum idmap_error_code err; + + ctx = talloc_zero(id_ctx, struct ad_subdomains_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ctx->be_ctx = be_ctx; + ctx->sdap_id_ctx = id_ctx->sdap_id_ctx; + ctx->domain_name = talloc_strdup(ctx, ad_domain); + if (ctx->domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_strdup failed.\n")); + return ENOMEM; + } + *ops = &ad_subdomains_ops; + *pvt_data = ctx; + + ret = be_add_online_cb(ctx, be_ctx, ad_subdom_online_cb, ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to add subdom online callback")); + } + + ret = be_add_offline_cb(ctx, be_ctx, ad_subdom_offline_cb, ctx, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to add subdom offline callback")); + } + + err = sss_idmap_init(idmap_talloc, ctx, idmap_free, &ctx->idmap_ctx); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to initialize idmap context.\n")); + return EFAULT; + } + + return EOK; +} diff --git a/src/providers/ad/ad_subdomains.h b/src/providers/ad/ad_subdomains.h new file mode 100644 index 00000000..b1a418f1 --- /dev/null +++ b/src/providers/ad/ad_subdomains.h @@ -0,0 +1,37 @@ +/* + SSSD + + AD Subdomains Module + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_SUBDOMAINS_H_ +#define _IPA_SUBDOMAINS_H_ + +#include "providers/dp_backend.h" +#include "providers/ad/ad_common.h" + +int ad_subdom_init(struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + const char *ad_domain, + struct bet_ops **ops, + void **pvt_data); + +#endif /* _IPA_SUBDOMAINS_H_ */ |