From ff4fbeac9381b08dbe0653343f93017d7cf8c9f5 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 22 Feb 2010 10:28:26 +0100
Subject: [PATCH] Restrict family lookups

Adds a new option that tells resolver which address family to prefer or
use exclusively.

Fixes: #404
---
 src/confdb/confdb.h              |    1 +
 src/config/SSSDConfig.py         |    1 +
 src/config/SSSDConfigTest.py     |    2 +
 src/config/etc/sssd.api.conf     |    1 +
 src/man/sssd.conf.5.xml          |   28 ++++++++++++++++++++
 src/providers/data_provider_fo.c |   52 +++++++++++++++++++++++++++++++++++--
 src/providers/fail_over.c        |   25 +++++++++++------
 src/providers/fail_over.h        |   22 ++++++++++++++--
 src/resolv/async_resolv.c        |   36 ++++++++++++++++++--------
 src/resolv/async_resolv.h        |   10 ++++++-
 src/tests/fail_over-tests.c      |   10 ++++++-
 src/tests/resolv-tests.c         |   18 +++++++++---
 12 files changed, 172 insertions(+), 34 deletions(-)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 28098b8..66576c3 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -100,6 +100,7 @@
 #define CONFDB_DOMAIN_MPG "magic_private_groups"
 #define CONFDB_DOMAIN_FQ "use_fully_qualified_names"
 #define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout"
+#define CONFDB_DOMAIN_FAMILY_ORDER "lookup_family_order"
 
 /* Local Provider */
 #define CONFDB_LOCAL_DEFAULT_SHELL   "default_shell"
diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index a004c33..471ecb6 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -79,6 +79,7 @@ option_strings = {
     'store_legacy_passwords' : _('Store password hashes'),
     'use_fully_qualified_names' : _('Display users/groups in fully-qualified form'),
     'entry_cache_timeout' : _('Entry cache timeout length (seconds)'),
+    'lookup_family_order' : _('Restrict or prefer a specific address family when performing DNS lookups'),
 
     # [provider/ipa]
     'ipa_domain' : _('IPA domain'),
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 153146f..eed1de3 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -414,6 +414,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'store_legacy_passwords',
             'use_fully_qualified_names',
             'entry_cache_timeout',
+            'lookup_family_order',
             'id_provider',
             'auth_provider',
             'access_provider',
@@ -724,6 +725,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'store_legacy_passwords',
             'use_fully_qualified_names',
             'entry_cache_timeout',
+            'lookup_family_order',
             'id_provider',
             'auth_provider',
             'access_provider',
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 1905353..35890ac 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -54,6 +54,7 @@ cache_credentials = bool, None, true, false
 store_legacy_passwords = bool, None, false
 use_fully_qualified_names = bool, None, false
 entry_cache_timeout = int, None, false
+lookup_family_order = str, None, false
 
 # Special providers
 [provider/permit]
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 7b240c8..496c171 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -589,6 +589,34 @@
                         </para>
                     </listitem>
                 </varlistentry>
+
+                <varlistentry>
+                    <term>lookup_family_order (string)</term>
+                    <listitem>
+                        <para>
+                            Provides the ability to select preferred address family
+                            to use when performing DNS lookups.
+                        </para>
+                        <para>
+                            Supported values:
+                        </para>
+                        <para>
+                            ipv4_first: Try looking up IPv4 address, if that fails, try IPv6
+                        </para>
+                        <para>
+                            ipv4_only: Only attempt to resolve hostnames to IPv4 addresses.
+                        </para>
+                        <para>
+                            ipv6_first: Try looking up IPv6 address, if that fails, try IPv4
+                        </para>
+                        <para>
+                            ipv6_only: Only attempt to resolve hostnames to IPv6 addresses.
+                        </para>
+                        <para>
+                            Default: ipv4_first
+                        </para>
+                    </listitem>
+                </varlistentry>
             </variablelist>
         </para>
 
diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c
index 7d02404..482f744 100644
--- a/src/providers/data_provider_fo.c
+++ b/src/providers/data_provider_fo.c
@@ -53,9 +53,47 @@ struct be_failover_ctx {
     struct be_svc_data *svcs;
 };
 
+static int be_fo_get_options(TALLOC_CTX *mem_ctx, struct be_ctx *ctx,
+                             struct fo_options *opts)
+{
+    char *str_opt;
+    int ret;
+
+    /* todo get timeout from configuration */
+    opts->retry_timeout = 30;
+
+    ret = confdb_get_string(ctx->cdb, mem_ctx, ctx->conf_path,
+                            CONFDB_DOMAIN_FAMILY_ORDER,
+                            "ipv4_first", &str_opt);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    DEBUG(7, ("Lookup order: %s\n", str_opt));
+
+    if (strcasecmp(str_opt, "ipv4_first") == 0) {
+        opts->family_order = IPV4_FIRST;
+    } else if (strcasecmp(str_opt, "ipv4_only") == 0) {
+        opts->family_order = IPV4_ONLY;
+    } else if (strcasecmp(str_opt, "ipv6_first") == 0) {
+        opts->family_order = IPV6_FIRST;
+    } else if (strcasecmp(str_opt, "ipv6_only") == 0) {
+        opts->family_order = IPV6_ONLY;
+    } else {
+        DEBUG(1, ("Unknown value for option %s: %s\n",
+                  CONFDB_DOMAIN_FAMILY_ORDER, str_opt));
+        talloc_free(str_opt);
+        return EINVAL;
+    }
+
+    talloc_free(str_opt);
+    return EOK;
+}
+
 int be_init_failover(struct be_ctx *ctx)
 {
     int ret;
+    struct fo_options fopts;
 
     if (ctx->be_fo != NULL) {
         return EOK;
@@ -72,8 +110,13 @@ int be_init_failover(struct be_ctx *ctx)
         return ret;
     }
 
-    /* todo get timeout from configuration */
-    ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, 30);
+    ret = be_fo_get_options(ctx->be_fo, ctx, &fopts);
+    if (ret != EOK) {
+        talloc_zfree(ctx->be_fo);
+        return ret;
+    }
+
+    ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, &fopts);
     if (!ctx->be_fo->fo_ctx) {
         talloc_zfree(ctx->be_fo);
         return ENOMEM;
@@ -250,7 +293,9 @@ struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx,
     state->attempts = 0;
 
     subreq = fo_resolve_service_send(state, ev,
-                                     ctx->be_fo->resolv, svc->fo_service);
+                                     ctx->be_fo->resolv,
+                                     ctx->be_fo->fo_ctx,
+                                     svc->fo_service);
     if (!subreq) {
         talloc_zfree(req);
         return NULL;
@@ -305,6 +350,7 @@ static void be_resolve_server_done(struct tevent_req *subreq)
         DEBUG(6, ("Trying with the next one!\n"));
         subreq = fo_resolve_service_send(state, state->ev,
                                          state->ctx->be_fo->resolv,
+                                         state->ctx->be_fo->fo_ctx,
                                          state->svc->fo_service);
         if (!subreq) {
             tevent_req_error(req, ENOMEM);
diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c
index 7560b89..54ad032 100644
--- a/src/providers/fail_over.c
+++ b/src/providers/fail_over.c
@@ -45,8 +45,7 @@ struct fo_ctx {
     struct fo_service *service_list;
     struct server_common *server_common_list;
 
-    /* Settings. */
-    time_t retry_timeout;
+    struct fo_options *opts;
 };
 
 struct fo_service {
@@ -99,7 +98,7 @@ struct status {
 };
 
 struct fo_ctx *
-fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout)
+fo_context_init(TALLOC_CTX *mem_ctx, struct fo_options *opts)
 {
     struct fo_ctx *ctx;
 
@@ -108,11 +107,17 @@ fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout)
         DEBUG(1, ("No memory\n"));
         return NULL;
     }
+    ctx->opts = talloc_zero(ctx, struct fo_options);
+    if (ctx->opts == NULL) {
+        DEBUG(1, ("No memory\n"));
+        return NULL;
+    }
 
-    ctx->retry_timeout = retry_timeout;
+    ctx->opts->retry_timeout = opts->retry_timeout;
+    ctx->opts->family_order  = opts->family_order;
 
     DEBUG(3, ("Created new fail over context, retry timeout is %d\n",
-              retry_timeout));
+              ctx->opts->retry_timeout));
     return ctx;
 }
 
@@ -166,7 +171,7 @@ get_server_status(struct fo_server *server)
     DEBUG(7, ("Status of server '%s' is '%s'\n", SERVER_NAME(server),
               str_server_status(server->common->server_status)));
 
-    timeout = server->service->ctx->retry_timeout;
+    timeout = server->service->ctx->opts->retry_timeout;
     if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) {
         gettimeofday(&tv, NULL);
         if (STATUS_DIFF(server->common, tv) > timeout) {
@@ -193,7 +198,7 @@ get_port_status(struct fo_server *server)
     DEBUG(7, ("Port status of port %d for server '%s' is '%s'\n", server->port,
               SERVER_NAME(server), str_port_status(server->port_status)));
 
-    timeout = server->service->ctx->retry_timeout;
+    timeout = server->service->ctx->opts->retry_timeout;
     if (timeout != 0 && server->port_status == PORT_NOT_WORKING) {
         gettimeofday(&tv, NULL);
         if (STATUS_DIFF(server, tv) > timeout) {
@@ -467,7 +472,8 @@ static void fo_resolve_service_done(struct tevent_req *subreq);
 
 struct tevent_req *
 fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
-                        struct resolv_ctx *resolv, struct fo_service *service)
+                        struct resolv_ctx *resolv, struct fo_ctx *ctx,
+                        struct fo_service *service)
 {
     int ret;
     struct fo_server *server;
@@ -498,7 +504,8 @@ fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
     switch (get_server_status(server)) {
     case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */
         subreq = resolv_gethostbyname_send(server->common, ev, resolv,
-                                           server->common->name);
+                                           server->common->name,
+                                           ctx->opts->family_order);
         if (subreq == NULL) {
             ret = ENOMEM;
             goto done;
diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h
index f118483..ffcd068 100644
--- a/src/providers/fail_over.h
+++ b/src/providers/fail_over.h
@@ -28,9 +28,10 @@
 #include <stdbool.h>
 #include <talloc.h>
 
+#include "resolv/async_resolv.h"
+
 /* Some forward declarations that don't have to do anything with fail over. */
 struct hostent;
-struct resolv_ctx;
 struct tevent_context;
 struct tevent_req;
 
@@ -53,12 +54,26 @@ struct fo_service;
 struct fo_server;
 
 /*
- * Create a new fail over context. The 'retry_timeout' argument specifies the
+ * Failover settings.
+ *
+ * The 'retry_timeout' member specifies the
  * duration in seconds of how long a server or port will be considered
  * non-working after being marked as such.
+ *
+ * The family_order member specifies the order of address families to
+ * try when looking up the service.
+ */
+struct fo_options {
+    time_t retry_timeout;
+    enum restrict_family family_order;
+};
+
+/*
+ * Create a new fail over context based on options passed in the
+ * opts parameter
  */
 struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx,
-                               time_t retry_timeout);
+                               struct fo_options *opts);
 
 /*
  * Create a new service structure for 'ctx', saving it to the location pointed
@@ -94,6 +109,7 @@ int fo_add_server(struct fo_service *service,
 struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx,
                                            struct tevent_context *ev,
                                            struct resolv_ctx *resolv,
+                                           struct fo_ctx *ctx,
                                            struct fo_service *service);
 
 int fo_resolve_service_recv(struct tevent_req *req,
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
index 70d8d11..d0c5b7a 100644
--- a/src/resolv/async_resolv.c
+++ b/src/resolv/async_resolv.c
@@ -459,6 +459,8 @@ struct gethostbyname_state {
     /* Part of the query. */
     const char *name;
     int family;
+    /* In which order to use IPv4, or v6 */
+    enum restrict_family family_order;
     /* These are returned by ares. The hostent struct will be freed
      * when the user callback returns. */
     struct hostent *hostent;
@@ -472,14 +474,13 @@ ares_gethostbyname_wakeup(struct tevent_req *req);
 
 struct tevent_req *
 resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
-                          struct resolv_ctx *ctx, const char *name)
+                          struct resolv_ctx *ctx, const char *name,
+                          enum restrict_family family_order)
 {
     struct tevent_req *req, *subreq;
     struct gethostbyname_state *state;
     struct timeval tv = { 0, 0 };
 
-    DEBUG(4, ("Trying to resolve A record of '%s'\n", name));
-
     if (ctx->channel == NULL) {
         DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
         return NULL;
@@ -491,11 +492,16 @@ resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
 
     state->resolv_ctx = ctx;
     state->name = name;
-    state->family = AF_INET;
     state->hostent = NULL;
     state->status = 0;
     state->timeouts = 0;
     state->retrying = 0;
+    state->family_order = family_order;
+    state->family = (family_order < IPV6_ONLY) ? AF_INET : AF_INET6;
+
+    DEBUG(4, ("Trying to resolve %s record of '%s'\n",
+              state->family == AF_INET ? "A" : "AAAA",
+              state->name));
 
     /* We need to have a wrapper around ares_gethostbyname(), because
      * ares_gethostbyname() can in some cases call it's callback immediately.
@@ -513,7 +519,7 @@ resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
 }
 
 static void
-resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent);
+resolv_gethostbyname_next_done(void *arg, int status, int timeouts, struct hostent *hostent);
 
 static void
 resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent)
@@ -545,14 +551,22 @@ resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *h
 
     if (status != ARES_SUCCESS) {
         if (status == ARES_ENOTFOUND || status == ARES_ENODATA) {
-            /* IPv4 failure. Try IPv6 */
-            state->family = AF_INET6;
+            if (state->family_order == IPV4_FIRST) {
+                state->family = AF_INET6;
+            } else if (state->family_order == IPV6_FIRST) {
+                state->family = AF_INET;
+            } else {
+                tevent_req_error(req, return_code(status));
+                return;
+            }
+
             state->retrying = 0;
             state->timeouts = 0;
-            DEBUG(4, ("Trying to resolve AAAA record of '%s'\n",
+            DEBUG(4, ("Trying to resolve %s record of '%s'\n",
+                      state->family == AF_INET ? "A" : "AAAA",
                       state->name));
             ares_gethostbyname(state->resolv_ctx->channel, state->name,
-                               state->family, resolv_gethostbyname6_done,
+                               state->family, resolv_gethostbyname_next_done,
                                req);
             return;
         }
@@ -568,7 +582,7 @@ resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *h
 }
 
 static void
-resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent)
+resolv_gethostbyname_next_done(void *arg, int status, int timeouts, struct hostent *hostent)
 {
     struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
     struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
@@ -576,7 +590,7 @@ resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *
     if (state->retrying == 0 && status == ARES_EDESTRUCTION) {
         state->retrying = 1;
         ares_gethostbyname(state->resolv_ctx->channel, state->name,
-                           state->family, resolv_gethostbyname6_done, req);
+                           state->family, resolv_gethostbyname_next_done, req);
         return;
     }
 
diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h
index 2ba6449..5f87f12 100644
--- a/src/resolv/async_resolv.h
+++ b/src/resolv/async_resolv.h
@@ -57,10 +57,18 @@ struct hostent *resolv_copy_hostent(TALLOC_CTX *mem_ctx,
                                     struct hostent *src);
 
 /** Get host by name **/
+enum restrict_family {
+    IPV4_ONLY,
+    IPV4_FIRST,
+    IPV6_ONLY,
+    IPV6_FIRST
+};
+
 struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx,
                                             struct tevent_context *ev,
                                             struct resolv_ctx *ctx,
-                                            const char *name);
+                                            const char *name,
+                                            enum restrict_family family_order);
 
 int resolv_gethostbyname_recv(struct tevent_req *req,
                               TALLOC_CTX *mem_ctx,
diff --git a/src/tests/fail_over-tests.c b/src/tests/fail_over-tests.c
index e794f03..4e97a1e 100644
--- a/src/tests/fail_over-tests.c
+++ b/src/tests/fail_over-tests.c
@@ -60,6 +60,7 @@ static struct test_ctx *
 setup_test(void)
 {
     struct test_ctx *ctx;
+    struct fo_options fopts;
     int ret;
 
     ctx = talloc_zero(global_talloc_context, struct test_ctx);
@@ -77,7 +78,10 @@ setup_test(void)
         fail("Could not init resolv context");
     }
 
-    ctx->fo_ctx = fo_context_init(ctx, 5 * 60);
+    fopts.retry_timeout = 30;
+    fopts.family_order  = IPV4_FIRST;
+
+    ctx->fo_ctx = fo_context_init(ctx, &fopts);
     if (ctx->fo_ctx == NULL) {
         talloc_free(ctx);
         fail("Could not init fail over context");
@@ -198,7 +202,9 @@ _get_request(struct test_ctx *test_ctx, struct fo_service *service,
     task->location = location;
     test_ctx->tasks++;
 
-    req = fo_resolve_service_send(test_ctx, test_ctx->ev, test_ctx->resolv, service);
+    req = fo_resolve_service_send(test_ctx, test_ctx->ev,
+                                  test_ctx->resolv,
+                                  test_ctx->fo_ctx, service);
     fail_if(req == NULL, "%s: fo_resolve_service_send() failed", location);
 
     tevent_req_set_callback(req, test_resolve_service_callback, task);
diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c
index 04b9e2e..e93d0f1 100644
--- a/src/tests/resolv-tests.c
+++ b/src/tests/resolv-tests.c
@@ -178,7 +178,8 @@ START_TEST(test_resolv_localhost)
     }
 
     check_leaks_push(test_ctx);
-    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev,
+                                    test_ctx->resolv, hostname, IPV4_FIRST);
     DEBUG(7, ("Sent resolv_gethostbyname\n"));
     if (req == NULL) {
         ret = ENOMEM;
@@ -232,7 +233,8 @@ START_TEST(test_resolv_negative)
     }
 
     check_leaks_push(test_ctx);
-    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev,
+                                    test_ctx->resolv, hostname, IPV4_FIRST);
     DEBUG(7, ("Sent resolv_gethostbyname\n"));
     if (req == NULL) {
         ret = ENOMEM;
@@ -292,6 +294,9 @@ static void test_internet(struct tevent_req *req)
                       srvptr->host));
         }
         break;
+    default:
+        recv_status = EINVAL;
+        break;
     }
     talloc_zfree(req);
     fail_if(recv_status != EOK, "The recv function failed: %d", recv_status);
@@ -322,7 +327,8 @@ START_TEST(test_resolv_internet)
     test_ctx->tested_function = TESTING_HOSTNAME;
 
     check_leaks_push(test_ctx);
-    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev,
+                                    test_ctx->resolv, hostname, IPV4_FIRST);
     DEBUG(7, ("Sent resolv_gethostbyname\n"));
     if (req == NULL) {
         ret = ENOMEM;
@@ -425,7 +431,8 @@ START_TEST(test_resolv_free_context)
         return;
     }
 
-    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev,
+                                    test_ctx->resolv, hostname, IPV4_FIRST);
     DEBUG(7, ("Sent resolv_gethostbyname\n"));
     if (req == NULL) {
         fail("Error calling resolv_gethostbyname_send");
@@ -484,7 +491,8 @@ START_TEST(test_resolv_free_req)
     }
 
     check_leaks_push(test_ctx);
-    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+    req = resolv_gethostbyname_send(test_ctx, test_ctx->ev,
+                                    test_ctx->resolv, hostname, IPV4_FIRST);
     DEBUG(7, ("Sent resolv_gethostbyname\n"));
     if (req == NULL) {
         fail("Error calling resolv_gethostbyname_send");
-- 
1.6.6

