From 60c11764c1536e98ed0269d1b7ee1e90fea76fa8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 1 Jun 2010 15:36:56 +0200
Subject: [PATCH] Add dns_discovery_domain option

The service discovery used to use the SSSD domain name to perform DNS
queries. This is not an optimal solution, for example from the point of
view of authconfig.

This patch introduces a new option "dns_discovery_domain" that allows to set
the domain part of a DNS SRV query. If this option is not set, the
default behavior is to use the domain part of the machine's hostname.

Fixes: #479
---
 src/confdb/confdb.h                   |    1 +
 src/config/SSSDConfig.py              |    1 +
 src/config/SSSDConfigTest.py          |    2 +
 src/config/etc/sssd.api.conf          |    1 +
 src/config/upgrade_config.py          |   26 +++++-
 src/man/include/service_discovery.xml |    9 ++-
 src/man/sssd.conf.5.xml               |   13 +++
 src/providers/data_provider_fo.c      |   13 ++-
 src/providers/dp_backend.h            |    3 +-
 src/providers/fail_over.c             |  187 +++++++++++++++++++++++++++++---
 src/providers/ipa/ipa_common.c        |    2 +-
 src/providers/krb5/krb5_common.c      |    2 +-
 src/providers/ldap/ldap_common.c      |    1 -
 13 files changed, 235 insertions(+), 26 deletions(-)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index be4dfb6..2f85e27 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -101,6 +101,7 @@
 #define CONFDB_DOMAIN_FQ "use_fully_qualified_names"
 #define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout"
 #define CONFDB_DOMAIN_RESOLV_TIMEOUT "dns_resolver_timeout"
+#define CONFDB_DOMAIN_DNS_DISCOVERY_NAME "dns_discovery_domain"
 #define CONFDB_DOMAIN_FAMILY_ORDER "lookup_family_order"
 #define CONFDB_DOMAIN_ACCOUNT_CACHE_EXPIRATION "account_cache_expiration"
 
diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index 7b9d96c..a7c96d6 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -82,6 +82,7 @@ option_strings = {
     'lookup_family_order' : _('Restrict or prefer a specific address family when performing DNS lookups'),
     'account_cache_expiration' : _('How long to keep cached entries after last successful login (days)'),
     'dns_resolver_timeout' : _('How long to wait for replies from DNS when resolving servers (seconds)'),
+    'dns_discovery_domain' : _('The domain part of service discovery DNS query'),
 
     # [provider/ipa]
     'ipa_domain' : _('IPA domain'),
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 04d438e..0aea748 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -479,6 +479,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'lookup_family_order',
             'account_cache_expiration',
             'dns_resolver_timeout',
+            'dns_discovery_domain',
             'id_provider',
             'auth_provider',
             'access_provider',
@@ -798,6 +799,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'account_cache_expiration',
             'lookup_family_order',
             'dns_resolver_timeout',
+            'dns_discovery_domain',
             'id_provider',
             'auth_provider',
             'access_provider',
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 7d0e20c..1f13d45 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -59,6 +59,7 @@ account_cache_expiration = int, None, false
 filter_users = list, str, false
 filter_groups = list, str, false
 dns_resolver_timeout = int, None, false
+dns_discovery_domain = str, None, false
 
 # Special providers
 [provider/permit]
diff --git a/src/config/upgrade_config.py b/src/config/upgrade_config.py
index d47fcd3..121f2f8 100644
--- a/src/config/upgrade_config.py
+++ b/src/config/upgrade_config.py
@@ -59,6 +59,28 @@ class SSSDConfigFile(SSSDChangeConf):
             if item:
                 item['name'] = new_name
 
+    def _add_dns_domain_name(self, domain):
+        id_provider = self.findOpts(domain['value'], 'option', 'id_provider')[1]
+        dns_domain_name = { 'type' : 'option',
+                            'name' : 'dns_discovery_domain',
+                            'value' : domain['name'].lstrip('domain/') }
+        if id_provider['value'] == 'ldap':
+            server = self.findOpts(domain['value'], 'option', 'ldap_uri')[1]
+            if not server:
+                domain['value'].insert(0, dns_domain_name)
+                return
+        elif id_provider['value'] == 'ipa':
+            server = self.findOpts(domain['value'], 'option', 'ipa_server')[1]
+            if not server:
+                domain['value'].insert(0, dns_domain_name)
+                return
+
+        auth_provider = self.findOpts(domain['value'], 'option', 'auth_provider')[1]
+        if auth_provider and auth_provider['value'] == 'krb5':
+            server = self.findOpts(domain['value'], 'option', 'krb5_kdcip')[1]
+            if not server:
+                domain['value'].insert(0, dns_domain_name)
+
     def _do_v2_changes(self):
         # remove Data Provider
         srvlist = self.get_option_index('sssd', 'services')[1]
@@ -69,9 +91,11 @@ class SSSDConfigFile(SSSDChangeConf):
             srvlist['value'] = ", ".join([srv for srv in services])
         self.delete_option('section', 'dp')
 
-        # remove magic_private_groups from all domains
         for domain in [ s for s in self.sections() if s['name'].startswith("domain/") ]:
+            # remove magic_private_groups from all domains
             self.delete_option_subtree(domain['value'], 'option', 'magic_private_groups')
+            # check if we need to add dns_domain
+            self._add_dns_domain_name(domain)
 
     def _update_option(self, to_section_name, from_section_name, opts):
         to_section = [ s for s in self.sections() if s['name'].strip() == to_section_name ]
diff --git a/src/man/include/service_discovery.xml b/src/man/include/service_discovery.xml
index 16d016e..d33b4c2 100644
--- a/src/man/include/service_discovery.xml
+++ b/src/man/include/service_discovery.xml
@@ -22,8 +22,13 @@
     <refsect2 id='domain_name'>
         <title>The domain name</title>
         <para>
-            The name of the SSSD domain is used as the domain part of the
-            service discovery DNS query.
+            Please refer to the <quote>dns_discovery_domain</quote>
+            parameter in the
+            <citerefentry>
+                <refentrytitle>sssd.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+            </citerefentry>
+            manual page for more defails.
         </para>
     </refsect2>
     <refsect2 id='reference'>
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index bd2d3ac..21eebc5 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -674,6 +674,19 @@
                         </para>
                     </listitem>
                 </varlistentry>
+
+                <varlistentry>
+                    <term>dns_discovery_domain (string)</term>
+                    <listitem>
+                        <para>
+                            If service discovery is used in the back end, specifies
+                            the domain part of the service discovery DNS query.
+                        </para>
+                        <para>
+                            Default: Use the domain part of machine's hostname
+                        </para>
+                    </listitem>
+                </varlistentry>
             </variablelist>
         </para>
 
diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c
index 56a072a..1654e22 100644
--- a/src/providers/data_provider_fo.c
+++ b/src/providers/data_provider_fo.c
@@ -243,9 +243,10 @@ int be_fo_service_add_callback(TALLOC_CTX *memctx,
 
 int be_fo_add_srv_server(struct be_ctx *ctx, const char *service_name,
                          const char *query_service, const char *proto,
-                         const char *domain, void *user_data)
+                         void *user_data)
 {
     struct be_svc_data *svc;
+    char *domain;
     int ret;
 
     DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
@@ -257,6 +258,15 @@ int be_fo_add_srv_server(struct be_ctx *ctx, const char *service_name,
         return ENOENT;
     }
 
+    ret = confdb_get_string(ctx->cdb, svc, ctx->conf_path,
+                            CONFDB_DOMAIN_DNS_DISCOVERY_NAME,
+                            NULL, &domain);
+    if (ret != EOK) {
+        DEBUG(1, ("Failed reading %s from confdb\n",
+                  CONFDB_DOMAIN_DNS_DISCOVERY_NAME));
+        return ret;
+    }
+
     ret = fo_add_srv_server(svc->fo_service, query_service,
                             domain, proto, user_data);
     if (ret && ret != EEXIST) {
@@ -461,3 +471,4 @@ int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx,
 
     return EOK;
 }
+
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
index 0668859..61917b9 100644
--- a/src/providers/dp_backend.h
+++ b/src/providers/dp_backend.h
@@ -167,7 +167,7 @@ int be_fo_service_add_callback(TALLOC_CTX *memctx,
                                be_svc_callback_fn_t *fn, void *private_data);
 int be_fo_add_srv_server(struct be_ctx *ctx, const char *service_name,
                          const char *query_service, const char *proto,
-                         const char *domain, void *user_data);
+                         void *user_data);
 int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
                      const char *server, int port, void *user_data);
 
@@ -179,4 +179,5 @@ int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv);
 
 int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx,
                                         const char *service_name);
+
 #endif /* __DP_BACKEND_H___ */
diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c
index b719dea..8502e8e 100644
--- a/src/providers/fail_over.c
+++ b/src/providers/fail_over.c
@@ -503,14 +503,15 @@ fo_add_srv_server(struct fo_service *service, const char *srv,
     struct fo_server *server;
 
     DEBUG(3, ("Adding new SRV server in domain '%s', to service '%s'\n",
-              domain, service->name));
+              domain ? domain : "unknown", service->name));
 
     DLIST_FOR_EACH(server, service->server_list) {
         if (server->user_data != user_data)
             continue;
 
         if (fo_is_srv_lookup(server)) {
-            if (strcasecmp(server->srv_data->domain, domain) == 0 &&
+            if (((domain == NULL && server->srv_data->domain == NULL) ||
+                  strcasecmp(server->srv_data->domain, domain) == 0) &&
                 strcasecmp(server->srv_data->proto, proto) == 0) {
                 return EEXIST;
             }
@@ -530,14 +531,18 @@ fo_add_srv_server(struct fo_service *service, const char *srv,
     if (server->srv_data == NULL)
         return ENOMEM;
 
-    server->srv_data->domain = talloc_strdup(server->srv_data, domain);
     server->srv_data->proto = talloc_strdup(server->srv_data, proto);
     server->srv_data->srv = talloc_strdup(server->srv_data, srv);
-    if (server->srv_data->domain == NULL ||
-        server->srv_data->proto == NULL ||
+    if (server->srv_data->proto == NULL ||
         server->srv_data->srv == NULL)
         return ENOMEM;
 
+    if (domain) {
+        server->srv_data->domain = talloc_strdup(server->srv_data, domain);
+        if (server->srv_data->domain == NULL)
+            return ENOMEM;
+    }
+
     server->srv_data->meta = server;
     server->srv_data->srv_lookup_status = DEFAULT_SRV_STATUS;
     server->srv_data->last_status_change.tv_sec = 0;
@@ -895,6 +900,17 @@ fo_resolve_service_recv(struct tevent_req *req, struct fo_server **server)
  *******************************************************************/
 
 static void resolve_srv_done(struct tevent_req *subreq);
+static void resolve_srv_cont(struct tevent_req *req);
+
+struct tevent_req *resolve_get_domain_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct fo_ctx *foctx,
+                                           struct resolv_ctx *resolv);
+
+static void resolve_getsrv_domain_done(struct tevent_req *req);
+int resolve_get_domain_recv(struct tevent_req *req,
+                            TALLOC_CTX *mem_ctx,
+                            char **domain);
 
 struct resolve_srv_state {
     struct fo_server *meta;
@@ -913,7 +929,6 @@ resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
                  struct fo_server *server)
 {
     int ret;
-    char *query;
     struct tevent_req *req;
     struct tevent_req *subreq;
     struct resolve_srv_state *state;
@@ -937,19 +952,18 @@ resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
         state->meta = collapse_srv_lookup(server);
         /* FALLTHROUGH */
     case SRV_NEUTRAL: /* Request SRV lookup */
-        query = get_srv_query(state, state->meta);
-        if (!query) {
-            ret = ENOMEM;
-            goto done;
-        }
-        DEBUG(4, ("Searching for servers via SRV query '%s'\n", query));
-
-        subreq = resolv_getsrv_send(state, ev, resolv, query);
-        if (subreq == NULL) {
-            ret = ENOMEM;
-            goto done;
+        if (server->srv_data->domain == NULL) {
+            /* we need to look up our domain first */
+            subreq = resolve_get_domain_send(state, ev, ctx, resolv);
+            if (subreq == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            tevent_req_set_callback(subreq, resolve_getsrv_domain_done, req);
+            break;
         }
-        tevent_req_set_callback(subreq, resolve_srv_done, req);
+        /* we know the domain, just do the lookup */
+        resolve_srv_cont(req);
         break;
     case SRV_NOT_RESOLVED: /* query could not be resolved but don't retry yet */
         ret = EIO;
@@ -975,6 +989,49 @@ done:
 }
 
 static void
+resolve_getsrv_domain_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct resolve_srv_state *state = tevent_req_data(req,
+                                                struct resolve_srv_state);
+    int ret;
+
+    ret = resolve_get_domain_recv(subreq, state->meta->srv_data,
+                                  &state->meta->srv_data->domain);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    resolve_srv_cont(req);
+}
+
+static void
+resolve_srv_cont(struct tevent_req *req)
+{
+    struct resolve_srv_state *state = tevent_req_data(req,
+                                                struct resolve_srv_state);
+    char *query;
+    struct tevent_req *subreq;
+
+    query = get_srv_query(state, state->meta);
+    if (!query) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    DEBUG(4, ("Searching for servers via SRV query '%s'\n", query));
+
+    subreq = resolv_getsrv_send(state, state->ev, state->resolv, query);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, resolve_srv_done, req);
+}
+
+static void
 resolve_srv_done(struct tevent_req *subreq)
 {
     struct tevent_req *req = tevent_req_callback_data(subreq,
@@ -1072,6 +1129,100 @@ resolve_srv_recv(struct tevent_req *req, struct fo_server **server)
     return EOK;
 }
 
+/*******************************************************************
+ *     Get Fully Qualified Domain Name of the host machine         *
+ *******************************************************************/
+struct resolve_get_domain_state {
+    char *fqdn;
+    char hostname[HOST_NAME_MAX];
+};
+
+static void resolve_get_domain_done(struct tevent_req *subreq);
+
+struct tevent_req *
+resolve_get_domain_send(TALLOC_CTX *mem_ctx,
+                      struct tevent_context *ev,
+                      struct fo_ctx *foctx,
+                      struct resolv_ctx *resolv)
+{
+    int ret;
+    struct resolve_get_domain_state *state;
+    struct tevent_req *req, *subreq;
+
+    req = tevent_req_create(mem_ctx, &state, struct resolve_get_domain_state);
+    if (!req) {
+        return NULL;
+    }
+
+    ret = gethostname(state->hostname, HOST_NAME_MAX);
+    if (ret) {
+        DEBUG(2, ("gethostname() failed: [%d]: %s\n",ret, strerror(ret)));
+        return NULL;
+    }
+    state->hostname[HOST_NAME_MAX-1] = 0;
+    DEBUG(7, ("Host name is: %s\n", state->hostname));
+
+    subreq = resolv_gethostbyname_send(mem_ctx, ev, resolv,
+                                       state->hostname,
+                                       foctx->opts->family_order);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, resolve_get_domain_done, req);
+
+    return req;
+}
+
+static void resolve_get_domain_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct resolve_get_domain_state *state = tevent_req_data(req,
+                                                      struct resolve_get_domain_state);
+    struct hostent *hostent;
+    int ret;
+
+    ret = resolv_gethostbyname_recv(subreq, req, NULL, NULL, &hostent);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(2, ("Could not get fully qualified name for host name %s "
+                  "resolver returned: [%d]: %s\n",
+                  state->hostname, ret, strerror(ret)));
+        /* We'll proceed with hostname in this case */
+    } else {
+        DEBUG(7, ("The full FQDN is: %s\n", hostent->h_name));
+        state->fqdn = hostent->h_name;
+    }
+    tevent_req_done(req);
+}
+
+int resolve_get_domain_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                            char **domain)
+{
+    struct resolve_get_domain_state *state = tevent_req_data(req,
+                                                    struct resolve_get_domain_state);
+    char *fqdn;
+    char *domptr;
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    fqdn = state->fqdn ? state->fqdn : state->hostname;
+    domptr = strchr(fqdn, '.');
+    domptr++;
+    if (!domptr) {
+        *domain = talloc_strdup(mem_ctx, fqdn);
+    } else {
+        *domain = talloc_strdup(mem_ctx, domptr);
+    }
+
+    if (*domain == NULL) {
+        return ENOMEM;
+    }
+
+    return EOK;
+}
+
 static void
 set_server_common_status(struct server_common *common,
                          enum server_status status)
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 580b66f..47e3867 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -571,7 +571,7 @@ int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
         if (be_fo_is_srv_identifier(list[i])) {
             ret = be_fo_add_srv_server(ctx, "IPA", "ldap",
-                                       FO_PROTO_TCP, ctx->domain->name, NULL);
+                                       FO_PROTO_TCP, NULL);
             if (ret) {
                 DEBUG(0, ("Failed to add server\n"));
                 goto done;
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
index c78f0e6..e6ccad1 100644
--- a/src/providers/krb5/krb5_common.c
+++ b/src/providers/krb5/krb5_common.c
@@ -354,7 +354,7 @@ int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
         if (be_fo_is_srv_identifier(server_spec)) {
             ret = be_fo_add_srv_server(ctx, service_name, service_name,
-                                       FO_PROTO_TCP, ctx->domain->name, NULL);
+                                       FO_PROTO_TCP, NULL);
             if (ret) {
                 DEBUG(0, ("Failed to add server\n"));
                 goto done;
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index f972671..a2d44e7 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -628,7 +628,6 @@ int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
             ret = be_fo_add_srv_server(ctx, service_name,
                                        dns_service_name, FO_PROTO_TCP,
-                                       ctx->domain->name,
                                        srv_user_data);
             if (ret) {
                 DEBUG(0, ("Failed to add server\n"));
-- 
1.6.6.1

