From 67c2ccb17096e0200835ca16dcadd23de25e908f Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 16 Apr 2010 17:58:52 +0200
Subject: [PATCH] Use service discovery in backends

Integrate the failover improvements with our back ends. The DNS domain
used in the SRV query is always the SSSD domain name.

Please note that this patch changes the default value of ldap_uri from
"ldap://localhost" to "NULL" in order to use service discovery with no
server set.
---
 src/man/include/service_discovery.xml |   38 +++++++++++++++++++++++
 src/man/sssd-ipa.5.xml                |    4 ++
 src/man/sssd-krb5.5.xml               |    5 +++
 src/man/sssd-ldap.5.xml               |    5 +--
 src/providers/data_provider_fo.c      |   32 +++++++++++++++++++
 src/providers/dp_backend.h            |    9 +++++
 src/providers/fail_over.h             |    2 +
 src/providers/ipa/ipa_common.c        |   21 ++++++++++--
 src/providers/ipa/ipa_init.c          |    3 +-
 src/providers/krb5/krb5_common.c      |   25 ++++++++++++++-
 src/providers/krb5/krb5_common.h      |    4 +-
 src/providers/krb5/krb5_init.c        |    8 ++--
 src/providers/ldap/ldap_common.c      |   54 ++++++++++++++++++++++++++++----
 src/providers/ldap/ldap_common.h      |    9 ++++--
 src/providers/ldap/ldap_init.c        |    9 ++---
 15 files changed, 196 insertions(+), 32 deletions(-)
 create mode 100644 src/man/include/service_discovery.xml

diff --git a/src/man/include/service_discovery.xml b/src/man/include/service_discovery.xml
new file mode 100644
index 0000000..a38e88d
--- /dev/null
+++ b/src/man/include/service_discovery.xml
@@ -0,0 +1,38 @@
+<refsect1 id='service_discovery'>
+    <title>SERVICE DISCOVERY</title>
+    <para>
+        The service discovery feature allows back ends to automatically
+        find the appropriate servers to connect to using a special DNS
+        query.
+    </para>
+    <refsect2 id='configuration'>
+        <title>Configuration</title>
+        <para>
+            When no servers are specified, the back end tries to find
+            a server using service discovery automatically. Optionally,
+            the user may choose to use both fixed server addresses
+            and service discovery by inserting a special keyword
+            <quote>_srv_</quote>
+            in the list of servers. The order of preference is still
+            retained - this feature is useful, if, for example, the user
+            wants to use service discovery where possible and fall back
+            to a specific server when no servers could be discovered from
+            DNS.
+        </para>
+    </refsect2>
+    <refsect2 id='domain_name'>
+        <title>The domain name</title>
+        <para>
+            The name of the SSSD domain is used as as the domain
+            part of the service discovery DNS query.
+        </para>
+    </refsect2>
+    <refsect2 id='reference'>
+        <title>See Also</title>
+        <para>
+            For more information on service discovery mechanism, please refer
+            to RFC 2782.
+        </para>
+    </refsect2>
+</refsect1>
+
diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml
index d1ba1c5..e5beeb2 100644
--- a/src/man/sssd-ipa.5.xml
+++ b/src/man/sssd-ipa.5.xml
@@ -82,6 +82,8 @@
                             on failover and server redundancy, see the
                             <quote>FAILOVER</quote> section.
                             This is optional if autodiscovery is enabled.
+                            For more information on service discovery, see
+                            the <quote>SERVICE DISCOVERY</quote> section.
                         </para>
                     </listitem>
                 </varlistentry>
@@ -120,6 +122,8 @@
 
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/failover.xml" />
 
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/service_discovery.xml" />
+
     <refsect1 id='example'>
         <title>EXAMPLE</title>
         <para>
diff --git a/src/man/sssd-krb5.5.xml b/src/man/sssd-krb5.5.xml
index c291eca..4601e52 100644
--- a/src/man/sssd-krb5.5.xml
+++ b/src/man/sssd-krb5.5.xml
@@ -72,6 +72,9 @@
                             see the <quote>FAILOVER</quote> section. An optional
                             port number (preceded by a colon) may be appended to
                             the addresses or hostnames.
+                            If empty, service discovery is enabled -
+                            for more information on the subject, see
+                            the <quote>SERVICE DISCOVERY</quote> section.
                         </para>
                     </listitem>
                 </varlistentry>
@@ -244,6 +247,8 @@
 
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/failover.xml" />
 
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/service_discovery.xml" />
+
     <refsect1 id='example'>
         <title>EXAMPLE</title>
         <para>
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index b79cbbc..6363c08 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -61,9 +61,8 @@
                             Specifies the list of URIs of the LDAP servers to which
                             SSSD should connect in the order of preference. Refer to the
                             <quote>FAILOVER</quote> section for more information on failover and server redundancy.
-                        </para>
-                        <para>
-                            Default: ldap://localhost
+                            If empty, service discovery is enabled - for more information on the subject, see
+                            the <quote>SERVICE DISCOVERY</quote> section.
                         </para>
                     </listitem>
                 </varlistentry>
diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c
index 482f744..66230af 100644
--- a/src/providers/data_provider_fo.c
+++ b/src/providers/data_provider_fo.c
@@ -53,6 +53,11 @@ struct be_failover_ctx {
     struct be_svc_data *svcs;
 };
 
+int be_fo_is_srv_identifier(const char *server)
+{
+    return server && strcasecmp(server, BE_SRV_IDENTIFIER) == 0;
+}
+
 static int be_fo_get_options(TALLOC_CTX *mem_ctx, struct be_ctx *ctx,
                              struct fo_options *opts)
 {
@@ -61,6 +66,7 @@ static int be_fo_get_options(TALLOC_CTX *mem_ctx, struct be_ctx *ctx,
 
     /* todo get timeout from configuration */
     opts->retry_timeout = 30;
+    opts->srv_retry_timeout = 14400;
 
     ret = confdb_get_string(ctx->cdb, mem_ctx, ctx->conf_path,
                             CONFDB_DOMAIN_FAMILY_ORDER,
@@ -226,6 +232,32 @@ int be_fo_service_add_callback(TALLOC_CTX *memctx,
     return EOK;
 }
 
+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)
+{
+    struct be_svc_data *svc;
+    int ret;
+
+    DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+        if (strcmp(svc->name, service_name) == 0) {
+            break;
+        }
+    }
+    if (NULL == svc) {
+        return ENOENT;
+    }
+
+    ret = fo_add_srv_server(svc->fo_service, query_service,
+                            domain, proto, user_data);
+    if (ret && ret != EEXIST) {
+        DEBUG(1, ("Failed to add SRV lookup reference to failover service\n"));
+        return ret;
+    }
+
+    return EOK;
+}
+
 int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
                      const char *server, int port, void *user_data)
 {
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
index f1069d0..08b0400 100644
--- a/src/providers/dp_backend.h
+++ b/src/providers/dp_backend.h
@@ -26,6 +26,11 @@
 #include "providers/fail_over.h"
 #include "db/sysdb.h"
 
+/* a special token, if used in place of the hostname, denotes that real
+ * hostnames should be looked up from DNS using SRV requests
+ */
+#define BE_SRV_IDENTIFIER  "_srv_"
+
 struct be_ctx;
 struct bet_ops;
 struct be_req;
@@ -126,10 +131,14 @@ void be_mark_offline(struct be_ctx *ctx);
 typedef void (be_svc_callback_fn_t)(void *, struct fo_server *);
 
 int be_init_failover(struct be_ctx *ctx);
+int be_fo_is_srv_identifier(const char *server);
 int be_fo_add_service(struct be_ctx *ctx, const char *service_name);
 int be_fo_service_add_callback(TALLOC_CTX *memctx,
                                struct be_ctx *ctx, const char *service_name,
                                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);
 int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
                      const char *server, int port, void *user_data);
 
diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h
index 0a1dfe3..da55047 100644
--- a/src/providers/fail_over.h
+++ b/src/providers/fail_over.h
@@ -159,4 +159,6 @@ const char *fo_get_server_name(struct fo_server *server);
 
 struct hostent *fo_get_server_hostent(struct fo_server *server);
 
+int fo_is_srv_lookup(struct fo_server *s);
+
 #endif /* !__FAIL_OVER_H__ */
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index bed0666..eea6742 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -155,12 +155,9 @@ int ipa_get_options(TALLOC_CTX *memctx,
         }
     }
 
-    /* FIXME: Make non-fatal once we have discovery */
     server = dp_opt_get_string(opts->basic, IPA_SERVER);
     if (!server) {
-        DEBUG(0, ("Can't find ipa server, missing option!\n"));
-        ret = EINVAL;
-        goto done;
+        DEBUG(1, ("No ipa server set, will use service discovery!\n"));
     }
 
     ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME);
@@ -536,6 +533,10 @@ int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
     }
     service->krb5_service->realm = realm;
 
+    if (!servers) {
+        servers = BE_SRV_IDENTIFIER;
+    }
+
     /* split server parm into a list */
     ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
     if (ret != EOK) {
@@ -548,6 +549,18 @@ int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
         talloc_steal(service, list[i]);
 
+        if (be_fo_is_srv_identifier(list[i])) {
+            ret = be_fo_add_srv_server(ctx, "IPA", "ldap",
+                                       FO_PROTO_TCP, ctx->domain->name, NULL);
+            if (ret) {
+                DEBUG(0, ("Failed to add server\n"));
+                goto done;
+            }
+
+            DEBUG(6, ("Added service lookup for service IPA\n"));
+            continue;
+        }
+
         ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL);
         if (ret && ret != EEXIST) {
             DEBUG(0, ("Failed to add server\n"));
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
index 1689ac2..596aecf 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -72,8 +72,7 @@ int common_ipa_init(struct be_ctx *bectx)
 
     ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER);
     if (!ipa_servers) {
-        DEBUG(0, ("Missing ipa_server option!\n"));
-        return EINVAL;
+        DEBUG(1, ("Missing ipa_server option - using service discovery!\n"));
     }
 
     ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN);
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
index 2b3331e..d8c7840 100644
--- a/src/providers/krb5/krb5_common.c
+++ b/src/providers/krb5/krb5_common.c
@@ -69,13 +69,13 @@ errno_t check_and_export_options(struct dp_option *opts,
 
     dummy = dp_opt_get_cstring(opts, KRB5_KDC);
     if (dummy == NULL) {
-        DEBUG(1, ("No KDC explicitly configured, using defaults"));
+        DEBUG(1, ("No KDC explicitly configured, using defaults\n"));
     }
 
     dummy = dp_opt_get_cstring(opts, KRB5_KPASSWD);
     if (dummy == NULL) {
         DEBUG(1, ("No kpasswd server explicitly configured, "
-                  "using the KDC or defaults"));
+                  "using the KDC or defaults\n"));
     }
 
     dummy = dp_opt_get_cstring(opts, KRB5_CCNAME_TMPL);
@@ -332,6 +332,10 @@ int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
         goto done;
     }
 
+    if (!servers) {
+        servers = BE_SRV_IDENTIFIER;
+    }
+
     ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
     if (ret != EOK) {
         DEBUG(1, ("Failed to parse server list!\n"));
@@ -342,6 +346,23 @@ int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
         talloc_steal(service, list[i]);
         server_spec = talloc_strdup(service, list[i]);
+        if (!server_spec) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        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);
+            if (ret) {
+                DEBUG(0, ("Failed to add server\n"));
+                goto done;
+            }
+
+            DEBUG(6, ("Added service lookup\n"));
+            continue;
+        }
+
         port_str = strrchr(server_spec, ':');
         if (port_str == NULL) {
             port = 0;
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
index 0b0da31..f6ecbf0 100644
--- a/src/providers/krb5/krb5_common.h
+++ b/src/providers/krb5/krb5_common.h
@@ -40,8 +40,8 @@
 #define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s"
 #define KPASSWDINFO_TMPL PUBCONF_PATH"/kpasswdinfo.%s"
 
-#define SSS_KRB5KDC_FO_SRV "KRB5KDC"
-#define SSS_KRB5KPASSWD_FO_SRV "KRB5KPASSWD"
+#define SSS_KRB5KDC_FO_SRV "KERBEROS"
+#define SSS_KRB5KPASSWD_FO_SRV "KPASSWD"
 
 enum krb5_opts {
     KRB5_KDC = 0,
diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c
index 83129d9..6899982 100644
--- a/src/providers/krb5/krb5_init.c
+++ b/src/providers/krb5/krb5_init.c
@@ -89,8 +89,7 @@ int sssm_krb5_auth_init(struct be_ctx *bectx,
 
     krb5_servers = dp_opt_get_string(ctx->opts, KRB5_KDC);
     if (krb5_servers == NULL) {
-        DEBUG(0, ("Missing krb5_kdcip option!\n"));
-        return EINVAL;
+        DEBUG(1, ("Missing krb5_kdcip option, using service discovery!\n"));
     }
 
     krb5_realm = dp_opt_get_string(ctx->opts, KRB5_REALM);
@@ -107,8 +106,9 @@ int sssm_krb5_auth_init(struct be_ctx *bectx,
     }
 
     krb5_kpasswd_servers = dp_opt_get_string(ctx->opts, KRB5_KPASSWD);
-    if (krb5_kpasswd_servers == NULL) {
-        DEBUG(0, ("Missing krb5_kpasswd option, using KDC!\n"));
+    if (krb5_kpasswd_servers == NULL && krb5_servers != NULL) {
+        DEBUG(0, ("Missing krb5_kpasswd option and KDC set explicitly, "
+                  "will use KDC for pasword change operations!\n"));
         ctx->kpasswd_service = NULL;
     } else {
         ret = krb5_service_init(ctx, bectx, SSS_KRB5KPASSWD_FO_SRV,
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index b5765c2..ecf0fc7 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -31,7 +31,7 @@
 int ldap_child_debug_fd = -1;
 
 struct dp_option default_basic_opts[] = {
-    { "ldap_uri", DP_OPT_STRING, { "ldap://localhost" }, NULL_STRING },
+    { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_search_base", DP_OPT_STRING, { "dc=example,dc=com" }, NULL_STRING },
     { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING},
@@ -537,30 +537,46 @@ static void sdap_uri_callback(void *private_data, struct fo_server *server)
     if (!service) return;
 
     tmp = (const char *)fo_get_server_user_data(server);
-    if (tmp && ldap_is_ldap_url(tmp)) {
-        new_uri = talloc_strdup(service, tmp);
+
+    if (fo_is_srv_lookup(server)) {
+        if (!tmp) {
+            DEBUG(1, ("Unknown service, using ldap\n"));
+            tmp = SSS_LDAP_ID_SRV;
+        }
+        new_uri = talloc_asprintf(service, "%s://%s:%d",
+                                  tmp,
+                                  fo_get_server_name(server),
+                                  fo_get_server_port(server));
     } else {
-        new_uri = talloc_asprintf(service, "ldap://%s",
-                                  fo_get_server_name(server));
+        if (tmp && ldap_is_ldap_url(tmp)) {
+            new_uri = talloc_strdup(service, tmp);
+        } else {
+            new_uri = talloc_asprintf(service, "ldap://%s",
+                                    fo_get_server_name(server));
+        }
     }
+
     if (!new_uri) {
         DEBUG(2, ("Failed to copy URI ...\n"));
         return;
     }
 
+    DEBUG(6, ("Constructed uri '%s'\n", new_uri));
+
     /* free old one and replace with new one */
     talloc_zfree(service->uri);
     service->uri = new_uri;
 }
 
 int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
-                      const char *service_name, const char *urls,
-                      struct sdap_service **_service)
+                      const char *service_name, const char *dns_service_name,
+                      const char *urls, struct sdap_service **_service)
 {
     TALLOC_CTX *tmp_ctx;
     struct sdap_service *service;
     LDAPURLDesc *lud;
     char **list = NULL;
+    char *srv_user_data;
     int ret;
     int i;
 
@@ -587,6 +603,10 @@ int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
         goto done;
     }
 
+    if (!urls) {
+        urls = BE_SRV_IDENTIFIER;
+    }
+
     /* split server parm into a list */
     ret = split_on_separator(tmp_ctx, urls, ',', true, &list, NULL);
     if (ret != EOK) {
@@ -596,6 +616,26 @@ int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
 
     /* now for each URI add a new server to the failover service */
     for (i = 0; list[i]; i++) {
+        if (be_fo_is_srv_identifier(list[i])) {
+            srv_user_data = talloc_strdup(service, dns_service_name);
+            if (!srv_user_data) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            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"));
+                goto done;
+            }
+
+            DEBUG(6, ("Added service lookup\n"));
+            continue;
+        }
+
         ret = ldap_url_parse(list[i], &lud);
         if (ret != LDAP_SUCCESS) {
             DEBUG(0, ("Failed to parse ldap URI (%s)!\n", list[i]));
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index ff1ffb7..cc276f4 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -30,6 +30,9 @@
 #define PWD_POL_OPT_SHADOW "shadow"
 #define PWD_POL_OPT_MIT "mit_kerberos"
 
+#define SSS_LDAP_ID_SRV "ldap"
+#define SSS_LDAP_AUTH_SRV "ldaps"
+
 /* a fd the child process would log into */
 extern int ldap_child_debug_fd;
 
@@ -76,9 +79,9 @@ void sdap_pam_chpass_handler(struct be_req *breq);
 void sdap_handler_done(struct be_req *req, int dp_err,
                        int error, const char *errstr);
 
-int sdap_service_init(TALLOC_CTX *mmectx, struct be_ctx *ctx,
-                      const char *service_name, const char *urls,
-                      struct sdap_service **service);
+int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+                      const char *service_name, const char *dns_service_name,
+                      const char *urls, struct sdap_service **_service);
 
 /* options parser */
 int ldap_get_options(TALLOC_CTX *memctx,
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index b74ffc2..6ed46d2 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -67,12 +67,10 @@ int sssm_ldap_id_init(struct be_ctx *bectx,
 
     urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI);
     if (!urls) {
-        DEBUG(0, ("Missing ldap_uri\n"));
-        ret = EINVAL;
-        goto done;
+        DEBUG(1, ("Missing ldap_uri, will use service discovery\n"));
     }
 
-    ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+    ret = sdap_service_init(ctx, ctx->be, "LDAP", SSS_LDAP_ID_SRV, urls, &ctx->service);
     if (ret != EOK) {
         DEBUG(1, ("Failed to initialize failover service!\n"));
         goto done;
@@ -134,7 +132,8 @@ int sssm_ldap_auth_init(struct be_ctx *bectx,
         goto done;
     }
 
-    ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+    ret = sdap_service_init(ctx, ctx->be, "LDAP", SSS_LDAP_AUTH_SRV,
+                            urls, &ctx->service);
     if (ret != EOK) {
         DEBUG(1, ("Failed to initialize failover service!\n"));
         goto done;
-- 
1.6.6.1

