>From fc1cadc30c43477453ad633e79d42d895bfeb721 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek 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 ca8921bb132db6b7a57d9436a3219057d08b64c6..d1d750ba92f069206436a81f796784a15ca49aed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -221,6 +221,10 @@ if HAVE_CMOCKA responder_cache_req-tests \ $(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 @@ -2205,6 +2209,29 @@ responder_cache_req_tests_LDADD = \ libsss_test_common.la \ $(NULL) +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 e5ec204ad9671d15deb1830c60168e066a66f198..dd64df3d9a1072347677cd3f6d81edb8a2509a67 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 + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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, -- 2.1.0