>From 73f1087e9101ae88026a704216a73ff5df25a3d3 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 12 Dec 2014 17:10:25 +0100
Subject: [PATCH 2/2] RESOLV: Add an internal function to read TTL from a DNS
 packet

Related:
https://fedorahosted.org/sssd/ticket/1884

Adds an internal resolver function that reads the TTL for SRV records as
specified by RFC-2181. Several internal c-ares definitions are used
until c-ares contains a function that exposes all this information via a
parsing function.
---
 Makefile.am                         |  27 +++
 configure.ac                        |   1 +
 src/external/libresolv.m4           |  12 ++
 src/providers/fail_over_srv.c       |   2 +-
 src/resolv/async_resolv.c           | 113 ++++++++++-
 src/resolv/async_resolv.h           |   4 +-
 src/resolv/async_resolv_utils.c     |   9 +-
 src/tests/cmocka/test_resolv_fake.c | 374 ++++++++++++++++++++++++++++++++++++
 src/tests/resolv-tests.c            |   2 +-
 9 files changed, 539 insertions(+), 5 deletions(-)
 create mode 100644 src/external/libresolv.m4
 create mode 100644 src/tests/cmocka/test_resolv_fake.c

diff --git a/Makefile.am b/Makefile.am
index ff17b41c1d7d1d071fb23393a67378b1620ba45f..0975dc2ba86fedfa150cde5c1ab32cd5cdae27ee 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -222,6 +222,10 @@ if HAVE_CMOCKA
         test_sbus_opath \
         $(NULL)
 
+if HAVE_LIBRESOLV
+non_interactive_cmocka_based_tests += test_resolv_fake
+endif   # HAVE_LIBRESOLV
+
 if BUILD_IFP
 non_interactive_cmocka_based_tests += ifp_tests
 endif   # BUILD_IFP
@@ -2221,6 +2225,29 @@ test_sbus_opath_LDADD = \
     libsss_debug.la \
     libsss_test_common.la
 
+if HAVE_LIBRESOLV
+test_resolv_fake_SOURCES = \
+    src/tests/cmocka/test_resolv_fake.c \
+    src/resolv/async_resolv.c \
+    $(NULL)
+test_resolv_fake_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+test_resolv_fake_LDFLAGS = \
+    -Wl,-wrap,ares_query \
+    $(NULL)
+test_resolv_fake_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(POPT_LIBS) \
+    $(TALLOC_LIBS) \
+    $(CARES_LIBS) \
+    $(DHASH_LIBS) \
+    $(RESOLV_LIBS) \
+    libsss_debug.la \
+    libsss_test_common.la \
+    $(NULL)
+endif # HAVE_LIBRESOLV
+
 endif # HAVE_CMOCKA
 
 noinst_PROGRAMS = pam_test_client
diff --git a/configure.ac b/configure.ac
index 8824d6854a7db14de156cbb4ef310e20d28f94a7..ac8cbc357ffe08e5d92c9e8934a88512efc2b2bc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -173,6 +173,7 @@ m4_include([src/external/sasl.m4])
 m4_include([src/external/configlib.m4])
 m4_include([src/external/libnfsidmap.m4])
 m4_include([src/external/cwrap.m4])
+m4_include([src/external/libresolv.m4])
 
 if test x$build_config_lib = xyes; then
     m4_include([src/external/libaugeas.m4])
diff --git a/src/external/libresolv.m4 b/src/external/libresolv.m4
new file mode 100644
index 0000000000000000000000000000000000000000..225cf2be8ddbdd9aea19bf8c716c5a956b07b97f
--- /dev/null
+++ b/src/external/libresolv.m4
@@ -0,0 +1,12 @@
+AC_SUBST(RESOLV_CFLAGS)
+AC_SUBST(RESOLV_LIBS)
+
+# Some unit tests require libresolv to fake DNS packets
+SSS_AC_EXPAND_LIB_DIR()
+AC_CHECK_LIB([resolv],
+             [ns_name_compress],
+             [RESOLV_LIBS="-L$sss_extra_libdir -lresolv"],
+             [AC_MSG_WARN([No libresolv detected, some tests will not run])],
+             [-L$sss_extra_libdir])
+
+AM_CONDITIONAL([HAVE_LIBRESOLV], [test x"$RESOLV_LIBS" != "x"])
diff --git a/src/providers/fail_over_srv.c b/src/providers/fail_over_srv.c
index 1b99f3150d49d23a137815586aba0d4def6c5c42..5c06d28767b8afbc4e573a7881e7855f35747460 100644
--- a/src/providers/fail_over_srv.c
+++ b/src/providers/fail_over_srv.c
@@ -83,7 +83,7 @@ static void fo_discover_srv_done(struct tevent_req *subreq)
     state = tevent_req_data(req, struct fo_discover_srv_state);
 
     ret = resolv_discover_srv_recv(state, subreq,
-                                   &reply_list, &state->dns_domain);
+                                   &reply_list, NULL, &state->dns_domain);
     talloc_zfree(subreq);
     if (ret == ENOENT) {
         ret = ERR_SRV_NOT_FOUND;
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
index f93612440ec8f9ce393bede8316b512e46d577bc..85c4d99a4ef3c5071353f176c78caba115286fe4 100644
--- a/src/resolv/async_resolv.c
+++ b/src/resolv/async_resolv.c
@@ -62,7 +62,21 @@
 #endif
 
 #define DNS__16BIT(p)                   (((p)[0] << 8) | (p)[1])
+
+/*
+ * Macro DNS__32BIT reads a network long (32 bit) given in network
+ * byte order, and returns its value as an unsigned int. Copied
+ * from c-ares source code.
+ */
+#define DNS__32BIT(p)  ((unsigned int) \
+                         (((unsigned int)((unsigned char)(p)[0]) << 24U) | \
+                          ((unsigned int)((unsigned char)(p)[1]) << 16U) | \
+                          ((unsigned int)((unsigned char)(p)[2]) <<  8U) | \
+                          ((unsigned int)((unsigned char)(p)[3]))))
+
 #define DNS_HEADER_ANCOUNT(h)           DNS__16BIT((h) + 6)
+#define DNS_RR_LEN(r)                   DNS__16BIT((r) + 8)
+#define DNS_RR_TTL(r)                   DNS__32BIT((r) + 4)
 
 #define RESOLV_TIMEOUTMS  2000
 
@@ -1572,6 +1586,7 @@ struct getsrv_state {
 
     /* parsed data returned by ares */
     struct ares_srv_reply *reply_list;
+    uint32_t ttl;
     int status;
     int timeouts;
     int retrying;
@@ -1607,6 +1622,7 @@ resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
     state->resolv_ctx = ctx;
     state->query = query;
     state->reply_list = NULL;
+    state->ttl = 0;
     state->status = 0;
     state->timeouts = 0;
     state->retrying = 0;
@@ -1624,6 +1640,92 @@ resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
     return req;
 }
 
+/*
+ * Implemented based on http://tools.ietf.org/html/rfc2181#section-5
+ *
+ * Especially:
+ * 5.2. TTLs of RRs in an RRSet
+ *  Consequently the use of differing TTLs in an RRSet is hereby
+ *  deprecated, the TTLs of all RRs in an RRSet must be the same.
+ *  ...
+ *  Should an authoritative source send such a malformed RRSet, the
+ *  client should treat the RRs for all purposes as if all TTLs in the
+ *  RRSet had been set to the value of the lowest TTL in the RRSet.
+ *
+ *  On success, returns true and sets the TTL in the _ttl parameter. On
+ *  failure, returns false and _ttl is undefined.
+ */
+static bool
+resolv_get_ttl(const unsigned char *abuf, const int alen, uint32_t *_ttl)
+{
+    const unsigned char *aptr;
+    int ret;
+    char *name = NULL;
+    long len;
+    uint32_t ttl = 0;
+    uint32_t rr_ttl;
+    unsigned int rr_len;
+    unsigned int ancount;
+    unsigned int i;
+
+    /* Read the number of RRs and then skip past the header */
+    if (alen < NS_HFIXEDSZ) {
+        return false;
+    }
+
+    ancount = DNS_HEADER_ANCOUNT(abuf);
+    if (ancount == 0) {
+        return false;
+    }
+
+    aptr = abuf + NS_HFIXEDSZ;
+
+    /* We only care about len from the question data,
+     * so that we can move past hostname */
+    ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+    ares_free_string(name);
+    if (ret != ARES_SUCCESS) {
+        return false;
+    }
+
+    /* Skip past the question */
+    aptr += len + NS_QFIXEDSZ;
+    if (aptr > abuf + alen) {
+        return false;
+    }
+
+    /* Examine each RR in turn and read the lowest TTL */
+    for (i = 0; i < ancount; i++) {
+        /* Decode the RR up to the data field. */
+        ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+        ares_free_string(name);
+        if (ret != ARES_SUCCESS) {
+            return false;
+        }
+
+        aptr += len;
+        if (aptr + NS_RRFIXEDSZ > abuf + alen) {
+            return false;
+        }
+
+        rr_len = DNS_RR_LEN(aptr);
+        rr_ttl = DNS_RR_TTL(aptr);
+        if (aptr + rr_len > abuf + alen) {
+            return false;
+        }
+        aptr += NS_RRFIXEDSZ + rr_len;
+
+        if (ttl > 0) {
+            ttl = MIN(ttl, rr_ttl);
+        } else {
+            ttl = rr_ttl; /* special-case for first TTL */
+        }
+    }
+
+    *_ttl = ttl;
+    return true;
+}
+
 static void
 resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
 {
@@ -1631,6 +1733,7 @@ resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int
     struct tevent_req *req;
     struct getsrv_state *state;
     int ret;
+    bool ok;
     struct ares_srv_reply *reply_list;
 
     if (rreq->rwatch == NULL) {
@@ -1671,6 +1774,10 @@ resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int
         goto fail;
     }
     state->reply_list = reply_list;
+    ok = resolv_get_ttl(abuf, alen, &state->ttl);
+    if (ok == false) {
+        state->ttl = RESOLV_DEFAULT_TTL;
+    }
 
     tevent_req_done(req);
     return;
@@ -1682,7 +1789,8 @@ fail:
 
 int
 resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
-                   int *timeouts, struct ares_srv_reply **reply_list)
+                   int *timeouts, struct ares_srv_reply **reply_list,
+                   uint32_t *ttl)
 {
     struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
 
@@ -1692,6 +1800,9 @@ resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
         *timeouts = state->timeouts;
     if (reply_list)
         *reply_list = talloc_steal(mem_ctx, state->reply_list);
+    if (ttl) {
+        *ttl = state->ttl;
+    }
 
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h
index 919ba370b87304735bff279d1502386d2ac28735..9b08f12ae159d377b50b9ddfe9f71397a461635d 100644
--- a/src/resolv/async_resolv.h
+++ b/src/resolv/async_resolv.h
@@ -144,7 +144,8 @@ int resolv_getsrv_recv(TALLOC_CTX *mem_ctx,
                        struct tevent_req *req,
                        int *status,
                        int *timeouts,
-                       struct ares_srv_reply **reply_list);
+                       struct ares_srv_reply **reply_list,
+                       uint32_t *ttl);
 
 /* This is an implementation of section "Usage rules" of RFC 2782 */
 int
@@ -187,6 +188,7 @@ resolv_discover_srv_send(TALLOC_CTX *mem_ctx,
 errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
                                  struct tevent_req *req,
                                  struct ares_srv_reply **_reply_list,
+                                 uint32_t *_ttl,
                                  char **_dns_domain);
 
 #endif /* __ASYNC_RESOLV_H__ */
diff --git a/src/resolv/async_resolv_utils.c b/src/resolv/async_resolv_utils.c
index c5ae5f9d43734c997e6f4e3e396e1d367f80784a..250681717f9548e4e9e2d8ee2951218605140ed4 100644
--- a/src/resolv/async_resolv_utils.c
+++ b/src/resolv/async_resolv_utils.c
@@ -162,6 +162,7 @@ struct resolv_discover_srv_state {
     int domain_index;
 
     struct ares_srv_reply *reply_list;
+    uint32_t ttl;
 };
 
 static errno_t resolv_discover_srv_next_domain(struct tevent_req *req);
@@ -272,7 +273,8 @@ static void resolv_discover_srv_done(struct tevent_req *subreq)
     req = tevent_req_callback_data(subreq, struct tevent_req);
     state = tevent_req_data(req, struct resolv_discover_srv_state);
 
-    ret = resolv_getsrv_recv(state, subreq, &status, NULL, &state->reply_list);
+    ret = resolv_getsrv_recv(state, subreq, &status, NULL,
+                             &state->reply_list, &state->ttl);
     talloc_zfree(subreq);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "SRV query failed [%d]: %s\n",
@@ -307,6 +309,7 @@ done:
 errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
                                  struct tevent_req *req,
                                  struct ares_srv_reply **_reply_list,
+                                 uint32_t *_ttl,
                                  char **_dns_domain)
 {
     struct resolv_discover_srv_state *state = NULL;
@@ -331,5 +334,9 @@ errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
         *_reply_list = talloc_steal(mem_ctx, state->reply_list);
     }
 
+    if (*_ttl) {
+        *_ttl = state->ttl;
+    }
+
     return EOK;
 }
diff --git a/src/tests/cmocka/test_resolv_fake.c b/src/tests/cmocka/test_resolv_fake.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c201e7023c4138703a8057f6eb10c9210385499
--- /dev/null
+++ b/src/tests/cmocka/test_resolv_fake.c
@@ -0,0 +1,374 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    SSSD tests: Resolver tests using a fake resolver library
+
+    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 <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <resolv.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+
+#define TEST_BUFSIZE         1024
+#define TEST_DEFAULT_TIMEOUT 5
+#define TEST_SRV_QUERY "_ldap._tcp.sssd.com"
+
+static TALLOC_CTX *global_mock_context = NULL;
+
+struct srv_rrdata {
+    uint16_t port;
+    uint16_t prio;
+    uint16_t weight;
+    uint32_t ttl;
+    const char *hostname;
+};
+
+static ssize_t dns_header(unsigned char **buf, size_t ancount)
+{
+    uint8_t *hb;
+    HEADER *h;
+
+    hb = *buf;
+    memset(hb, 0, NS_HFIXEDSZ);
+
+    h = (HEADER *) hb;
+    h->id = res_randomid();     /* random query ID */
+    h->qr = 1;                  /* response flag */
+    h->rd = 1;                  /* recursion desired */
+    h->ra = 1;                  /* resursion available */
+
+    h->qdcount = htons(1);          /* no. of questions */
+    h->ancount = htons(ancount);    /* no. of answers */
+    h->arcount = htons(0);          /* no. of add'tl records */
+
+    hb += NS_HFIXEDSZ;              /* move past the header */
+    *buf = hb;
+
+    return NS_HFIXEDSZ;
+}
+
+static ssize_t dns_question(const char *question,
+                            uint16_t type,
+                            uint8_t **question_ptr,
+                            size_t remaining)
+{
+    unsigned char *qb = *question_ptr;
+    int n;
+
+    n = ns_name_compress(question, qb, remaining, NULL, NULL);
+    assert_true(n > 0);
+
+    qb += n;
+    remaining -= n;
+
+    NS_PUT16(type, qb);
+    NS_PUT16(ns_c_in, qb);
+
+    *question_ptr = qb;
+    return n + 2 * sizeof(uint16_t);
+}
+
+static ssize_t add_rr_common(uint16_t type,
+                             uint32_t ttl,
+                             size_t rdata_size,
+                             const char *key,
+                             size_t remaining,
+                             uint8_t **rdata_ptr)
+{
+    uint8_t *rd = *rdata_ptr;
+    ssize_t written = 0;
+
+    written = ns_name_compress(key, rd, remaining, NULL, NULL);
+    assert_int_not_equal(written, -1);
+    rd += written;
+    remaining -= written;
+
+    assert_true(remaining > 3 * sizeof(uint16_t) + sizeof(uint32_t));
+    NS_PUT16(type, rd);
+    NS_PUT16(ns_c_in, rd);
+    NS_PUT32(ttl, rd);
+    NS_PUT16(rdata_size, rd);
+
+    assert_true(remaining > rdata_size);
+    *rdata_ptr = rd;
+    return written + 3 * sizeof(uint16_t) + sizeof(uint32_t) + rdata_size;
+}
+
+static ssize_t add_srv_rr(struct srv_rrdata *rr,
+                          const char *question,
+                          uint8_t *answer,
+                          size_t anslen)
+{
+    uint8_t *a = answer;
+    ssize_t resp_size;
+    size_t rdata_size;
+    unsigned char hostname_compressed[MAXDNAME];
+    ssize_t compressed_len;
+
+    rdata_size = 3 * sizeof(uint16_t);
+
+    /* Prepare the data to write */
+    compressed_len = ns_name_compress(rr->hostname,
+                                      hostname_compressed, MAXDNAME,
+                                      NULL, NULL);
+    assert_int_not_equal(compressed_len, -1);
+    rdata_size += compressed_len;
+
+    resp_size = add_rr_common(ns_t_srv, rr->ttl, rdata_size,
+                              question, anslen, &a);
+
+    NS_PUT16(rr->prio, a);
+    NS_PUT16(rr->weight, a);
+    NS_PUT16(rr->port, a);
+    memcpy(a, hostname_compressed, compressed_len);
+
+    return resp_size;
+}
+
+unsigned char *create_srv_buffer(TALLOC_CTX *mem_ctx,
+                                 const char *question,
+                                 struct srv_rrdata *rrs,
+                                 size_t n_rrs,
+                                 size_t *_buflen)
+{
+    unsigned char *buf;
+    unsigned char *buf_head;
+    ssize_t len;
+    ssize_t i;
+    ssize_t total = 0;
+
+    buf = talloc_zero_array(mem_ctx, unsigned char, TEST_BUFSIZE);
+    assert_non_null(buf);
+    buf_head = buf;
+
+    len = dns_header(&buf, n_rrs);
+    assert_true(len > 0);
+    total += len;
+
+    len = dns_question(question, ns_t_srv, &buf, TEST_BUFSIZE - total);
+    assert_true(len > 0);
+    total += len;
+
+    /* answer */
+    for (i = 0; i < n_rrs; i++) {
+        len = add_srv_rr(&rrs[i], question, buf, TEST_BUFSIZE - total);
+        assert_true(len > 0);
+        total += len;
+        buf += len;
+    }
+
+    *_buflen = total;
+    return buf_head;
+}
+
+struct fake_ares_query {
+    int status;
+    int timeouts;
+    unsigned char *abuf;
+    int alen;
+};
+
+void mock_ares_query(int status, int timeouts, unsigned char *abuf, int alen)
+{
+    will_return(__wrap_ares_query, status);
+    will_return(__wrap_ares_query, timeouts);
+    will_return(__wrap_ares_query, abuf);
+    will_return(__wrap_ares_query, alen);
+}
+
+void __wrap_ares_query(ares_channel channel, const char *name, int dnsclass,
+                       int type, ares_callback callback, void *arg)
+{
+    struct fake_ares_query query;
+
+    query.status = sss_mock_type(int);
+    query.timeouts = sss_mock_type(int);
+    query.abuf = sss_mock_ptr_type(unsigned char *);
+    query.alen = sss_mock_type(int);
+
+    callback(arg, query.status, query.timeouts, query.abuf, query.alen);
+}
+
+/* The unit test */
+struct resolv_fake_ctx {
+    struct resolv_ctx *resolv;
+    struct sss_test_ctx *ctx;
+};
+
+void test_resolv_fake_setup(void **state)
+{
+    struct resolv_fake_ctx *test_ctx;
+    int ret;
+
+    assert_true(leak_check_setup());
+    global_mock_context = talloc_new(global_talloc_context);
+    assert_non_null(global_mock_context);
+
+    test_ctx = talloc_zero(global_mock_context,
+                           struct resolv_fake_ctx);
+    assert_non_null(test_ctx);
+
+    test_ctx->ctx = create_ev_test_ctx(test_ctx);
+    assert_non_null(test_ctx->ctx);
+
+    ret = resolv_init(test_ctx, test_ctx->ctx->ev,
+                      TEST_DEFAULT_TIMEOUT, &test_ctx->resolv);
+    assert_int_equal(ret, EOK);
+
+    *state = test_ctx;
+}
+
+void test_resolv_fake_teardown(void **state)
+{
+    struct resolv_fake_ctx *test_ctx =
+        talloc_get_type(*state, struct resolv_fake_ctx);
+
+    talloc_free(test_ctx);
+    talloc_free(global_mock_context);
+    assert_true(leak_check_teardown());
+}
+
+void test_resolv_fake_srv_done(struct tevent_req *req)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    int status;
+    uint32_t ttl;
+    struct ares_srv_reply *srv_replies = NULL;
+    struct resolv_fake_ctx *test_ctx =
+        tevent_req_callback_data(req, struct resolv_fake_ctx);
+
+    tmp_ctx = talloc_new(test_ctx);
+    assert_non_null(tmp_ctx);
+
+    ret = resolv_getsrv_recv(tmp_ctx, req, &status, NULL,
+                             &srv_replies, &ttl);
+    assert_int_equal(ret, EOK);
+
+    assert_non_null(srv_replies);
+    assert_int_equal(srv_replies->priority, 1);
+    assert_int_equal(srv_replies->weight, 40);
+    assert_int_equal(srv_replies->port, 389);
+    assert_string_equal(srv_replies->host, "ldap.sssd.com");
+
+    srv_replies = srv_replies->next;
+    assert_non_null(srv_replies);
+    assert_int_equal(srv_replies->priority, 1);
+    assert_int_equal(srv_replies->weight, 60);
+    assert_int_equal(srv_replies->port, 389);
+    assert_string_equal(srv_replies->host, "ldap2.sssd.com");
+
+    srv_replies = srv_replies->next;
+    assert_null(srv_replies);
+
+    assert_int_equal(ttl, 500);
+
+    talloc_free(tmp_ctx);
+    test_ctx->ctx->error = EOK;
+    test_ctx->ctx->done = true;
+}
+
+void test_resolv_fake_srv(void **state)
+{
+    int ret;
+    struct tevent_req *req;
+    struct resolv_fake_ctx *test_ctx =
+        talloc_get_type(*state, struct resolv_fake_ctx);
+
+    unsigned char *buf;
+    size_t buflen;
+
+    struct srv_rrdata rr[2];
+
+    rr[0].prio = 1;
+    rr[0].port = 389;
+    rr[0].weight = 40;
+    rr[0].ttl = 600;
+    rr[0].hostname = "ldap.sssd.com";
+
+    rr[1].prio = 1;
+    rr[1].port = 389;
+    rr[1].weight = 60;
+    rr[1].ttl = 500;
+    rr[1].hostname = "ldap2.sssd.com";
+
+    buf = create_srv_buffer(test_ctx, TEST_SRV_QUERY, rr, 2, &buflen);
+    assert_non_null(buf);
+    mock_ares_query(0, 0, buf, buflen);
+
+    req = resolv_getsrv_send(test_ctx, test_ctx->ctx->ev,
+                             test_ctx->resolv, TEST_SRV_QUERY);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_resolv_fake_srv_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->ctx);
+    assert_int_equal(ret, ERR_OK);
+}
+
+int main(int argc, const char *argv[])
+{
+    int rv;
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const UnitTest tests[] = {
+        unit_test_setup_teardown(test_resolv_fake_srv,
+                                 test_resolv_fake_setup,
+                                 test_resolv_fake_teardown),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+
+    rv = run_tests(tests);
+    return rv;
+}
diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c
index 30d551f636b73605ab3daabf5bdddf2c5884b5a2..6c6783b622b63d3fdb7144d59e8107fe5d705251 100644
--- a/src/tests/resolv-tests.c
+++ b/src/tests/resolv-tests.c
@@ -501,7 +501,7 @@ static void test_internet(struct tevent_req *req)
         break;
     case TESTING_SRV:
         recv_status = resolv_getsrv_recv(tmp_ctx, req, &status, NULL,
-                                         &srv_replies);
+                                         &srv_replies, NULL);
         test_ctx->error = (srv_replies == NULL) ? ENOENT : EOK;
         for (srvptr = srv_replies; srvptr != NULL; srvptr = srvptr->next) {
             DEBUG(SSSDBG_OP_FAILURE,
-- 
1.9.3

