From 4f3e9da9a27624017866579701dd9f30b976a533 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 15 Apr 2014 17:24:55 +0200 Subject: RESPONDERS: Add a new request sss_parse_inp_send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The responders were copying code to parse input and on encountering an uknown domain, send the discover subdomain request. This patch adds a reusable request that can always be called in responders and in case the name can be parsed, just shortcut. Reviewed-by: Pavel Březina (cherry picked from commit 7caf7ed4f2eae1ec1c0717b4ee6ce78bdacd5926) Conflicts: Makefile.am --- Makefile.am | 17 ++ src/responder/common/responder.h | 7 + src/responder/common/responder_get_domains.c | 121 ++++++++++ src/tests/cmocka/common_mock_resp_dp.c | 16 ++ src/tests/cmocka/test_nss_srv.c | 2 +- src/tests/cmocka/test_responder_common.c | 319 +++++++++++++++++++++++++++ src/util/util_errors.c | 1 + src/util/util_errors.h | 1 + 8 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 src/tests/cmocka/test_responder_common.c diff --git a/Makefile.am b/Makefile.am index 3996a9054..1aef2647b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -162,6 +162,7 @@ if HAVE_CMOCKA ad_access_filter_tests \ ad_common_tests \ dp_opt_tests \ + responder-get-domains-tests \ test_search_bases endif @@ -1388,6 +1389,22 @@ nss_srv_tests_LDADD = \ libsss_test_common.la \ libsss_idmap.la +EXTRA_responder_get_domains_tests_DEPENDENCIES = \ + $(ldblib_LTLIBRARIES) +responder_get_domains_tests_SOURCES = \ + src/responder/common/responder_get_domains.c \ + src/tests/cmocka/test_responder_common.c \ + src/tests/cmocka/common_mock_resp.c +responder_get_domains_tests_CFLAGS = \ + $(AM_CFLAGS) +responder_get_domains_tests_LDFLAGS = \ + -Wl,-wrap,sss_parse_name_for_domains +responder_get_domains_tests_LDADD = \ + $(CMOCKA_LIBS) \ + $(SSSD_LIBS) \ + $(SSSD_INTERNAL_LTLIBS) \ + libsss_test_common.la + test_find_uid_DEPENDENCIES = \ $(ldblib_LTLIBRARIES) test_find_uid_SOURCES = \ diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h index 44249b007..c2440b132 100644 --- a/src/responder/common/responder.h +++ b/src/responder/common/responder.h @@ -311,4 +311,11 @@ errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *cvs_string, errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, uid_t *allowed_uids); + +struct tevent_req * +sss_parse_inp_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + const char *rawinp); +errno_t sss_parse_inp_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **_name, char **_domname); + #endif /* __SSS_RESPONDER_H__ */ diff --git a/src/responder/common/responder_get_domains.c b/src/responder/common/responder_get_domains.c index 23b274b52..253728b26 100644 --- a/src/responder/common/responder_get_domains.c +++ b/src/responder/common/responder_get_domains.c @@ -421,3 +421,124 @@ errno_t schedule_get_domains_task(TALLOC_CTX *mem_ctx, return EOK; } + +struct sss_parse_inp_state { + struct resp_ctx *rctx; + const char *rawinp; + + char *name; + char *domname; +}; + +static void sss_parse_inp_done(struct tevent_req *subreq); + +struct tevent_req * +sss_parse_inp_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + const char *rawinp) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sss_parse_inp_state *state; + + req = tevent_req_create(mem_ctx, &state, struct sss_parse_inp_state); + if (req == NULL) { + return NULL; + } + state->rawinp = rawinp; + state->rctx = rctx; + + /* If the subdomains haven't been checked yet, we need to always + * attach to the post-startup subdomain request and only then parse + * the input. Otherwise, we might not be able to parse input with a + * flat domain name specifier */ + if (rctx->get_domains_last_call.tv_sec > 0) { + ret = sss_parse_name_for_domains(state, rctx->domains, + rctx->default_domain, rawinp, + &state->domname, &state->name); + if (ret == EOK) { + /* Was able to use cached domains */ + goto done; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawinp); + ret = ERR_INPUT_PARSE; + goto done; + } + } + + /* EAGAIN - check the DP for subdomains */ + + DEBUG(SSSDBG_FUNC_DATA, "Requesting info for [%s] from [%s]\n", + state->name, state->domname ? state->domname : ""); + + /* We explicitly use force=false here. This request should decide itself + * if it's time to re-use the cached subdomain list or refresh. If the + * caller needs to specify the 'force' parameter, they should use the + * sss_dp_get_domains_send() request itself + */ + subreq = sss_dp_get_domains_send(state, rctx, false, state->domname); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sss_parse_inp_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, rctx->ev); + return req; +} + +static void sss_parse_inp_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sss_parse_inp_state *state = tevent_req_data(req, + struct sss_parse_inp_state); + + ret = sss_dp_get_domains_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sss_parse_name_for_domains(state, state->rctx->domains, + state->rctx->default_domain, + state->rawinp, + &state->domname, &state->name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Invalid name received [%s]\n", state->rawinp); + tevent_req_error(req, ERR_INPUT_PARSE); + return; + } + + /* Was able to parse the name now */ + tevent_req_done(req); +} + +errno_t sss_parse_inp_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **_name, char **_domname) +{ + struct sss_parse_inp_state *state = tevent_req_data(req, + struct sss_parse_inp_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_name) { + *_name = talloc_steal(mem_ctx, state->name); + } + + if (_domname) { + *_domname = talloc_steal(mem_ctx, state->domname); + } + + return EOK; +} diff --git a/src/tests/cmocka/common_mock_resp_dp.c b/src/tests/cmocka/common_mock_resp_dp.c index 40fa7c94f..08d74179d 100644 --- a/src/tests/cmocka/common_mock_resp_dp.c +++ b/src/tests/cmocka/common_mock_resp_dp.c @@ -79,6 +79,22 @@ void mock_account_recv_simple(void) return mock_account_recv(0, 0, NULL, NULL, NULL); } +struct tevent_req * +sss_parse_inp_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + const char *rawinp) +{ + return test_req_succeed_send(mem_ctx, rctx->ev); +} + +errno_t sss_parse_inp_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **_name, char **_domname) +{ + *_name = sss_mock_ptr_type(char *); + *_domname = sss_mock_ptr_type(char *); + + return test_request_recv(req); +} + void mock_parse_inp(const char *name, const char *domname) { will_return(sss_parse_inp_recv, name); diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c index e2e81a65f..3ef6da87c 100644 --- a/src/tests/cmocka/test_nss_srv.c +++ b/src/tests/cmocka/test_nss_srv.c @@ -4,7 +4,7 @@ Copyright (C) 2013 Red Hat - SSSD tests: Common utilities for tests that exercise domains + SSSD tests: NSS responder tests 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 diff --git a/src/tests/cmocka/test_responder_common.c b/src/tests/cmocka/test_responder_common.c new file mode 100644 index 000000000..58aa32b5d --- /dev/null +++ b/src/tests/cmocka/test_responder_common.c @@ -0,0 +1,319 @@ +/* + Authors: + Jakub Hrozek + + Copyright (C) 2014 Red Hat + + SSSD tests: Common responder code tests + + 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 "tests/cmocka/common_mock.h" +#include "tests/cmocka/common_mock_resp.h" + +#define TESTS_PATH "tests_responder" +#define TEST_CONF_DB "test_responder_conf.ldb" +#define TEST_DOM_NAME "responder_test" +#define TEST_SYSDB_FILE "cache_"TEST_DOM_NAME".ldb" +#define TEST_ID_PROVIDER "ldap" + +#define NAME "username" + +static void +mock_sss_dp_done(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); + +errno_t +sss_dp_issue_request(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + const char *strkey, struct sss_domain_info *dom, + dbus_msg_constructor msg_create, void *pvt, + struct tevent_req *nreq) +{ + struct tevent_immediate *imm; + + imm = tevent_create_immediate(rctx->ev); + if (imm == NULL) { + return ENOMEM; + } + tevent_schedule_immediate(imm, rctx->ev, mock_sss_dp_done, nreq); + return EOK; +} + +static void +mock_sss_dp_done(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct tevent_req *req; + + talloc_free(imm); + req = talloc_get_type(pvt, struct tevent_req); + tevent_req_done(req); +} + +errno_t +sss_dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *sidereq, + dbus_uint16_t *dp_err, + dbus_uint32_t *dp_ret, + char **err_msg) +{ + return EOK; +} + +struct parse_inp_test_ctx { + struct sss_test_ctx *tctx; + struct resp_ctx *rctx; +}; + +void parse_inp_test_setup(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx; + + check_leaks_push(global_talloc_context); + + parse_inp_ctx = talloc_zero(global_talloc_context, struct parse_inp_test_ctx); + assert_non_null(parse_inp_ctx); + + parse_inp_ctx->tctx = create_dom_test_ctx(parse_inp_ctx, TESTS_PATH, + TEST_CONF_DB, TEST_SYSDB_FILE, TEST_DOM_NAME, + TEST_ID_PROVIDER, NULL); + assert_non_null(parse_inp_ctx->tctx); + + parse_inp_ctx->rctx = mock_rctx(parse_inp_ctx, + parse_inp_ctx->tctx->ev, + parse_inp_ctx->tctx->dom, + parse_inp_ctx); + assert_non_null(parse_inp_ctx->rctx); + + /* Testing the request race condition should be a special case */ + gettimeofday(&parse_inp_ctx->rctx->get_domains_last_call, NULL); + + check_leaks_push(parse_inp_ctx); + *state = parse_inp_ctx; +} + +void parse_inp_test_teardown(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx = talloc_get_type(*state, + struct parse_inp_test_ctx); + + assert_true(check_leaks_pop(parse_inp_ctx) == true); + + talloc_free(parse_inp_ctx); + assert_true(check_leaks_pop(global_talloc_context) == true); +} + +int __real_sss_parse_name_for_domains(TALLOC_CTX *memctx, + struct sss_domain_info *domains, + const char *default_domain, + const char *orig, char **domain, char **name); + +int __wrap_sss_parse_name_for_domains(TALLOC_CTX *memctx, + struct sss_domain_info *domains, + const char *default_domain, + const char *orig, char **domain, char **name) +{ + enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call); + errno_t ret; + + if (wtype == WRAP_CALL_REAL) { + return __real_sss_parse_name_for_domains(memctx, domains, + default_domain, orig, + domain, name); + } + + ret = sss_mock_type(errno_t); + return ret; +} + +void parse_inp_simple_done(struct tevent_req *req) +{ + errno_t ret; + struct parse_inp_test_ctx *parse_inp_ctx = + tevent_req_callback_data(req, struct parse_inp_test_ctx); + char *name = NULL; + char *domname = NULL; + + ret = sss_parse_inp_recv(req, parse_inp_ctx, &name, &domname); + parse_inp_ctx->tctx->done = true; + assert_int_equal(ret, EOK); + talloc_free(req); + + assert_string_equal(name, NAME); + assert_null(domname); + talloc_free(name); +} + +void parse_inp_simple(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx = talloc_get_type(*state, + struct parse_inp_test_ctx); + struct tevent_req *req; + errno_t ret; + + will_return(__wrap_sss_parse_name_for_domains, WRAP_CALL_REAL); + + req = sss_parse_inp_send(parse_inp_ctx, parse_inp_ctx->rctx, NAME); + assert_non_null(req); + tevent_req_set_callback(req, parse_inp_simple_done, parse_inp_ctx); + + ret = test_ev_loop(parse_inp_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void parse_inp_call_dp(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx = talloc_get_type(*state, + struct parse_inp_test_ctx); + struct tevent_req *req; + errno_t ret; + + /* First call will indicate we need to go to DP */ + will_return(__wrap_sss_parse_name_for_domains, WRAP_CALL_WRAPPER); + will_return(__wrap_sss_parse_name_for_domains, EAGAIN); + /* The second one will succeed as the domains are up-to-date */ + will_return(__wrap_sss_parse_name_for_domains, WRAP_CALL_REAL); + + req = sss_parse_inp_send(parse_inp_ctx, parse_inp_ctx->rctx, NAME); + assert_non_null(req); + tevent_req_set_callback(req, parse_inp_simple_done, parse_inp_ctx); + + ret = test_ev_loop(parse_inp_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void parse_inp_call_attach(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx = talloc_get_type(*state, + struct parse_inp_test_ctx); + struct tevent_req *req; + errno_t ret; + + /* simulate responder startup */ + parse_inp_ctx->rctx->get_domains_last_call.tv_sec = 0; + + /* The first parse wouldn't be called, the second one will succeed + * as the domains are up-to-date */ + will_return(__wrap_sss_parse_name_for_domains, WRAP_CALL_REAL); + + req = sss_parse_inp_send(parse_inp_ctx, parse_inp_ctx->rctx, NAME); + assert_non_null(req); + tevent_req_set_callback(req, parse_inp_simple_done, parse_inp_ctx); + + ret = test_ev_loop(parse_inp_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void parse_inp_neg_done(struct tevent_req *req) +{ + errno_t ret; + struct parse_inp_test_ctx *parse_inp_ctx = + tevent_req_callback_data(req, struct parse_inp_test_ctx); + char *name = NULL; + char *domname = NULL; + + ret = sss_parse_inp_recv(req, parse_inp_ctx, &name, &domname); + parse_inp_ctx->tctx->done = true; + assert_int_equal(ret, ERR_INPUT_PARSE); + talloc_free(req); + + assert_null(name); + assert_null(domname); +} + +void parse_inp_call_neg(void **state) +{ + struct parse_inp_test_ctx *parse_inp_ctx = talloc_get_type(*state, + struct parse_inp_test_ctx); + struct tevent_req *req; + errno_t ret; + + /* Simulate an error */ + will_return(__wrap_sss_parse_name_for_domains, WRAP_CALL_WRAPPER); + will_return(__wrap_sss_parse_name_for_domains, EINVAL); + + req = sss_parse_inp_send(parse_inp_ctx, parse_inp_ctx->rctx, NAME); + assert_non_null(req); + tevent_req_set_callback(req, parse_inp_neg_done, parse_inp_ctx); + + ret = test_ev_loop(parse_inp_ctx->tctx); + assert_int_equal(ret, EOK); +} + +int main(int argc, const char *argv[]) +{ + int rv; + int no_cleanup = 0; + poptContext pc; + int opt; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0, + _("Do not delete the test database after a test run"), NULL }, + POPT_TABLEEND + }; + + const UnitTest tests[] = { + unit_test_setup_teardown(parse_inp_simple, + parse_inp_test_setup, + parse_inp_test_teardown), + unit_test_setup_teardown(parse_inp_call_dp, + parse_inp_test_setup, + parse_inp_test_teardown), + unit_test_setup_teardown(parse_inp_call_attach, + parse_inp_test_setup, + parse_inp_test_teardown), + unit_test_setup_teardown(parse_inp_call_neg, + parse_inp_test_setup, + parse_inp_test_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_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(); + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + test_dom_suite_setup(TESTS_PATH); + + rv = run_tests(tests); + if (rv == 0 && !no_cleanup) { + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + } + return rv; +} diff --git a/src/util/util_errors.c b/src/util/util_errors.c index c9b507557..8dd4380b4 100644 --- a/src/util/util_errors.c +++ b/src/util/util_errors.c @@ -48,6 +48,7 @@ struct err_string error_to_str[] = { { "Dynamic DNS update failed" }, /* ERR_DYNDNS_FAILED */ { "Dynamic DNS update timed out" }, /* ERR_DYNDNS_TIMEOUT */ { "Dynamic DNS update not possible while offline" }, /* ERR_DYNDNS_OFFLINE */ + { "Cannot parse input" }, /* ERR_INPUT_PARSE */ { "Entry not found" }, /* ERR_NOT_FOUND */ { "Domain not found" }, /* ERR_DOMAIN_NOT_FOUND */ { "Missing configuration file" }, /* ERR_MISSING_CONF */ diff --git a/src/util/util_errors.h b/src/util/util_errors.h index 3dd94af1f..23048990d 100644 --- a/src/util/util_errors.h +++ b/src/util/util_errors.h @@ -70,6 +70,7 @@ enum sssd_errors { ERR_DYNDNS_FAILED, ERR_DYNDNS_TIMEOUT, ERR_DYNDNS_OFFLINE, + ERR_INPUT_PARSE, ERR_NOT_FOUND, ERR_DOMAIN_NOT_FOUND, ERR_MISSING_CONF, -- cgit