diff options
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | src/config/SSSDConfig/__init__.py.in | 1 | ||||
-rwxr-xr-x | src/config/SSSDConfigTest.py | 2 | ||||
-rw-r--r-- | src/config/etc/sssd.api.conf | 1 | ||||
-rw-r--r-- | src/man/sssd-ipa.5.xml | 16 | ||||
-rw-r--r-- | src/providers/dp_dyndns.c | 53 | ||||
-rw-r--r-- | src/providers/dp_dyndns.h | 17 | ||||
-rw-r--r-- | src/providers/ipa/ipa_common.c | 4 | ||||
-rw-r--r-- | src/providers/ipa/ipa_dyndns.c | 107 | ||||
-rw-r--r-- | src/providers/ipa/ipa_dyndns.h | 3 | ||||
-rw-r--r-- | src/providers/ipa/ipa_opts.h | 1 | ||||
-rw-r--r-- | src/tests/cmocka/test_dyndns.c | 85 |
12 files changed, 286 insertions, 7 deletions
diff --git a/Makefile.am b/Makefile.am index 1bbfb01c..5514fc02 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1283,10 +1283,13 @@ test_io_CFLAGS = \ test_io_LDADD = \ $(CMOCKA_LIBS) +dyndns_tests_DEPENDENCIES = \ + $(ldblib_LTLIBRARIES) dyndns_tests_SOURCES = \ $(TEST_MOCK_OBJ) \ $(SSSD_RESOLV_OBJ) \ src/tests/common_tev.c \ + src/tests/common_dom.c \ src/tests/cmocka/test_dyndns.c \ src/providers/data_provider_opts.c dyndns_tests_CFLAGS = \ diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index 9f11d31c..003153d0 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -128,6 +128,7 @@ option_strings = { 'dyndns_update' : _("Whether to automatically update the client's DNS entry"), 'dyndns_ttl' : _("The TTL to apply to the client's DNS entry after updating it"), 'dyndns_iface' : _("The interface whose IP should be used for dynamic DNS updates"), + 'dyndns_refresh_interval' : _("How often to periodically update the client's DNS entry"), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 2b4df874..18328d06 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -510,6 +510,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'dyndns_update', 'dyndns_ttl', 'dyndns_iface', + 'dyndns_refresh_interval', 'override_gid', 'case_sensitive', 'override_homedir', @@ -856,6 +857,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'dyndns_update', 'dyndns_ttl', 'dyndns_iface', + 'dyndns_refresh_interval', 'override_gid', 'case_sensitive', 'override_homedir', diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 02e2a0a6..b09cbd18 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -125,6 +125,7 @@ entry_cache_sudo_timeout = int, None, false dyndns_update = bool, None, false dyndns_ttl = int, None, false dyndns_iface = str, None, false +dyndns_refresh_interval = int, None, false # Special providers [provider/permit] diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml index bedb1107..426e9a5b 100644 --- a/src/man/sssd-ipa.5.xml +++ b/src/man/sssd-ipa.5.xml @@ -204,6 +204,22 @@ </varlistentry> <varlistentry> + <term>dyndns_refresh_interval (integer)</term> + <listitem> + <para> + How often should the back end perform periodic DNS update in + addition to the automatic update performed when the back end + goes online. + This option is optional and applicable only when dyndns_update + is true. + </para> + <para> + Default: 0 (disabled) + </para> + </listitem> + </varlistentry> + + <varlistentry> <term>ipa_hbac_search_base (string)</term> <listitem> <para> diff --git a/src/providers/dp_dyndns.c b/src/providers/dp_dyndns.c index 81228d5c..337817fc 100644 --- a/src/providers/dp_dyndns.c +++ b/src/providers/dp_dyndns.c @@ -879,6 +879,45 @@ be_nsupdate_recv(struct tevent_req *req, int *child_status) return EOK; } +static void be_nsupdate_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct be_nsupdate_ctx *ctx = talloc_get_type(pvt, struct be_nsupdate_ctx); + + talloc_zfree(ctx->refresh_timer); + ctx->timer_callback(ctx->timer_pvt); + + /* timer_callback is responsible for calling be_nsupdate_timer_schedule + * again */ +} + +void be_nsupdate_timer_schedule(struct tevent_context *ev, + struct be_nsupdate_ctx *ctx) +{ + int refresh; + struct timeval tv; + + if (ctx->refresh_timer) { + DEBUG(SSSDBG_FUNC_DATA, ("Timer already scheduled\n")); + return; + } + + refresh = dp_opt_get_int(ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL); + if (refresh == 0) return; + DEBUG(SSSDBG_FUNC_DATA, ("Scheduling timer in %d seconds\n", refresh)); + + tv = tevent_timeval_current_ofs(refresh, 0); + ctx->refresh_timer = tevent_add_timer(ev, ctx, tv, + be_nsupdate_timer, ctx); + + if (!ctx->refresh_timer) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Failed to add dyndns refresh timer event\n")); + } +} + errno_t be_nsupdate_check(void) { @@ -906,6 +945,7 @@ be_nsupdate_check(void) static struct dp_option default_dyndns_opts[] = { { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, @@ -914,13 +954,16 @@ static struct dp_option default_dyndns_opts[] = { errno_t be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, - struct dp_option *defopts, struct be_nsupdate_ctx **_ctx) + struct dp_option *defopts, + nsupdate_timer_fn_t timer_callback, + void *timer_pvt, + struct be_nsupdate_ctx **_ctx) { errno_t ret; struct dp_option *src_opts; struct be_nsupdate_ctx *ctx; - ctx = talloc(mem_ctx, struct be_nsupdate_ctx); + ctx = talloc_zero(mem_ctx, struct be_nsupdate_ctx); if (ctx == NULL) return ENOMEM; src_opts = defopts ? defopts : default_dyndns_opts; @@ -932,6 +975,10 @@ be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, return ret; } + ctx->timer_callback = timer_callback; + ctx->timer_pvt = timer_pvt; + be_nsupdate_timer_schedule(be_ctx->ev, ctx); + *_ctx = ctx; - return EOK; + return ERR_OK; } diff --git a/src/providers/dp_dyndns.h b/src/providers/dp_dyndns.h index 95403a3a..e49ab8f0 100644 --- a/src/providers/dp_dyndns.h +++ b/src/providers/dp_dyndns.h @@ -29,12 +29,21 @@ /* dynamic dns helpers */ struct sss_iface_addr; +typedef void (*nsupdate_timer_fn_t)(void *pvt); + struct be_nsupdate_ctx { struct dp_option *opts; + + time_t last_refresh; + bool timer_in_progress; + struct tevent_timer *refresh_timer; + nsupdate_timer_fn_t timer_callback; + void *timer_pvt; }; enum dp_dyndns_opts { DP_OPT_DYNDNS_UPDATE, + DP_OPT_DYNDNS_REFRESH_INTERVAL, DP_OPT_DYNDNS_IFACE, DP_OPT_DYNDNS_TTL, @@ -48,7 +57,13 @@ errno_t be_nsupdate_check(void); errno_t be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, - struct dp_option *defopts, struct be_nsupdate_ctx **ctx); + struct dp_option *defopts, + nsupdate_timer_fn_t timer_callback, + void *timer_pvt, + struct be_nsupdate_ctx **_ctx); + +void be_nsupdate_timer_schedule(struct tevent_context *ev, + struct be_nsupdate_ctx *ctx); errno_t sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname, diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c index 51750b2a..baece274 100644 --- a/src/providers/ipa/ipa_common.c +++ b/src/providers/ipa/ipa_common.c @@ -28,6 +28,7 @@ #include "db/sysdb_selinux.h" #include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" #include "providers/ldap/sdap_async_private.h" #include "providers/dp_dyndns.h" #include "util/sss_krb5.h" @@ -1018,7 +1019,8 @@ errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, bool update; int ttl; - ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, &ctx->dyndns_ctx); + ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, ipa_dyndns_timer, + ctx, &ctx->dyndns_ctx); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Cannot initialize IPA dyndns opts [%d]: %s\n", diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c index 8023b533..52b8051b 100644 --- a/src/providers/ipa/ipa_dyndns.c +++ b/src/providers/ipa/ipa_dyndns.c @@ -55,6 +55,98 @@ errno_t ipa_dyndns_init(struct be_ctx *be_ctx, return EOK; } +struct ipa_dyndns_timer_ctx { + struct sdap_id_op *sdap_op; + struct tevent_context *ev; + + struct ipa_options *ctx; +}; + +static void ipa_dyndns_timer_connected(struct tevent_req *req); + +void ipa_dyndns_timer(void *pvt) +{ + struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options); + struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx; + struct tevent_req *req; + struct ipa_dyndns_timer_ctx *timer_ctx; + errno_t ret; + + timer_ctx = talloc_zero(ctx, struct ipa_dyndns_timer_ctx); + if (timer_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n")); + /* Not much we can do */ + return; + } + timer_ctx->ev = sdap_ctx->be->ev; + timer_ctx->ctx = ctx; + + /* In order to prevent the connection triggering an + * online callback which would in turn trigger a concurrent DNS + * update + */ + ctx->dyndns_ctx->timer_in_progress = true; + + /* Make sure to have a valid LDAP connection */ + timer_ctx->sdap_op = sdap_id_op_create(timer_ctx, sdap_ctx->conn_cache); + if (timer_ctx->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed\n")); + goto fail; + } + + req = sdap_id_op_connect_send(timer_ctx->sdap_op, timer_ctx, &ret); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret))); + goto fail; + } + tevent_req_set_callback(req, ipa_dyndns_timer_connected, timer_ctx); + return; + +fail: + ctx->dyndns_ctx->timer_in_progress = false; + be_nsupdate_timer_schedule(timer_ctx->ev, ctx->dyndns_ctx); + talloc_free(timer_ctx); +} + +static void ipa_dyndns_timer_connected(struct tevent_req *req) +{ + errno_t ret; + int dp_error; + struct ipa_dyndns_timer_ctx *timer_ctx = tevent_req_callback_data(req, + struct ipa_dyndns_timer_ctx); + struct tevent_context *ev; + struct ipa_options *ctx; + + ctx = timer_ctx->ctx; + ev = timer_ctx->ev; + ctx->dyndns_ctx->timer_in_progress = false; + + ret = sdap_id_op_connect_recv(req, &dp_error); + talloc_zfree(req); + talloc_free(timer_ctx); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, ("No IPA server is available, " + "dynamic DNS update is skipped in offline mode.\n")); + /* Another timer will be scheduled when provider goes online + * and ipa_dyndns_update() is called */ + } else { + DEBUG(SSSDBG_OP_FAILURE, + ("Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret))); + + /* Just schedule another dyndns retry */ + be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx); + } + return; + } + + /* all OK just call ipa_dyndns_update and schedule another refresh */ + be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx); + return ipa_dyndns_update(ctx); +} + static struct tevent_req *ipa_dyndns_update_send(struct ipa_options *ctx); static errno_t ipa_dyndns_update_recv(struct tevent_req *req); @@ -63,6 +155,11 @@ static void ipa_dyndns_nsupdate_done(struct tevent_req *subreq); void ipa_dyndns_update(void *pvt) { struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options); + struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + /* Schedule timer after provider went offline */ + be_nsupdate_timer_schedule(sdap_ctx->be->ev, ctx->dyndns_ctx); + struct tevent_req *req = ipa_dyndns_update_send(ctx); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Could not update DNS\n")); @@ -109,6 +206,16 @@ ipa_dyndns_update_send(struct ipa_options *ctx) } state->ipa_ctx = ctx; + if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) || + ctx->dyndns_ctx->timer_in_progress) { + DEBUG(SSSDBG_FUNC_DATA, ("Last periodic update ran recently or timer" + "in progress, not scheduling another update\n")); + tevent_req_done(req); + tevent_req_post(req, sdap_ctx->be->ev); + return req; + } + state->ipa_ctx->dyndns_ctx->last_refresh = time(NULL); + dns_zone = dp_opt_get_string(ctx->basic, IPA_DOMAIN); if (!dns_zone) { ret = EIO; diff --git a/src/providers/ipa/ipa_dyndns.h b/src/providers/ipa/ipa_dyndns.h index d86c6634..ced3f097 100644 --- a/src/providers/ipa/ipa_dyndns.h +++ b/src/providers/ipa/ipa_dyndns.h @@ -25,6 +25,9 @@ #ifndef IPA_DYNDNS_H_ #define IPA_DYNDNS_H_ +void ipa_dyndns_update(void *pvt); +void ipa_dyndns_timer(void *pvt); + errno_t ipa_dyndns_init(struct be_ctx *be_ctx, struct ipa_options *ctx); diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h index 392fcd86..57911082 100644 --- a/src/providers/ipa/ipa_opts.h +++ b/src/providers/ipa/ipa_opts.h @@ -53,6 +53,7 @@ struct dp_option ipa_basic_opts[] = { struct dp_option ipa_dyndns_opts[] = { { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, DP_OPTION_TERMINATOR diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c index 93d3373a..0657bf1b 100644 --- a/src/tests/cmocka/test_dyndns.c +++ b/src/tests/cmocka/test_dyndns.c @@ -35,6 +35,12 @@ #include "tests/cmocka/common_mock.h" #include "src/providers/dp_dyndns.h" +#define TESTS_PATH "tests_dyndns" +#define TEST_CONF_DB "test_dyndns_conf.ldb" +#define TEST_SYSDB_FILE "cache_dyndns_test.ldb" +#define TEST_DOM_NAME "dyndns_test" +#define TEST_ID_PROVIDER "ldap" + enum mock_nsupdate_states { MOCK_NSUPDATE_OK, MOCK_NSUPDATE_ERR, @@ -44,6 +50,9 @@ enum mock_nsupdate_states { struct dyndns_test_ctx { struct sss_test_ctx *tctx; + struct be_ctx *be_ctx; + struct be_nsupdate_ctx *update_ctx; + enum mock_nsupdate_states state; int child_status; int child_retval; @@ -275,15 +284,73 @@ void dyndns_test_timeout(void **state) talloc_free(tmp_ctx); } +void dyndns_test_timer(void *pvt) +{ + struct dyndns_test_ctx *ctx = talloc_get_type(pvt, struct dyndns_test_ctx); + static int ncalls = 0; + + ncalls++; + if (ncalls == 1) { + be_nsupdate_timer_schedule(ctx->tctx->ev, ctx->update_ctx); + } else if (ncalls == 2) { + ctx->tctx->done = true; + } + ctx->tctx->error = ERR_OK; +} + +void dyndns_test_interval(void **state) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(global_talloc_context); + assert_non_null(tmp_ctx); + check_leaks_push(tmp_ctx); + + ret = be_nsupdate_init(tmp_ctx, dyndns_test_ctx->be_ctx, NULL, + dyndns_test_timer, dyndns_test_ctx, + &dyndns_test_ctx->update_ctx); + assert_int_equal(ret, EOK); + + /* Wait until the timer hits */ + ret = test_ev_loop(dyndns_test_ctx->tctx); + DEBUG(SSSDBG_TRACE_LIBS, + ("Child request returned [%d]: %s\n", ret, strerror(ret))); + assert_int_equal(ret, ERR_OK); + + talloc_free(dyndns_test_ctx->update_ctx); + assert_true(check_leaks_pop(tmp_ctx) == true); + talloc_free(tmp_ctx); +} + /* Testsuite setup and teardown */ void dyndns_test_setup(void **state) { + struct sss_test_conf_param params[] = { + { "dyndns_update", "true" }, + { "dyndns_refresh_interval", "2" }, + { NULL, NULL }, /* Sentinel */ + }; + assert_true(leak_check_setup()); dyndns_test_ctx = talloc_zero(global_talloc_context, struct dyndns_test_ctx); assert_non_null(dyndns_test_ctx); - dyndns_test_ctx->tctx = create_ev_test_ctx(dyndns_test_ctx); + dyndns_test_ctx->tctx = create_dom_test_ctx(dyndns_test_ctx, TESTS_PATH, + TEST_CONF_DB, TEST_SYSDB_FILE, + TEST_DOM_NAME, TEST_ID_PROVIDER, + params); assert_non_null(dyndns_test_ctx->tctx); + + dyndns_test_ctx->be_ctx = talloc_zero(dyndns_test_ctx, struct be_ctx); + assert_non_null(dyndns_test_ctx->be_ctx); + + dyndns_test_ctx->be_ctx->cdb = dyndns_test_ctx->tctx->confdb; + dyndns_test_ctx->be_ctx->ev = dyndns_test_ctx->tctx->ev; + dyndns_test_ctx->be_ctx->conf_path = talloc_asprintf(dyndns_test_ctx, + CONFDB_DOMAIN_PATH_TMPL, + TEST_DOM_NAME); + assert_non_null(dyndns_test_ctx->be_ctx->conf_path); } void dyndns_test_teardown(void **state) @@ -295,11 +362,14 @@ void dyndns_test_teardown(void **state) 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 }; @@ -314,6 +384,8 @@ int main(int argc, const char *argv[]) dyndns_test_setup, dyndns_test_teardown), unit_test_setup_teardown(dyndns_test_timeout, dyndns_test_setup, dyndns_test_teardown), + unit_test_setup_teardown(dyndns_test_interval, + dyndns_test_setup, dyndns_test_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ @@ -332,8 +404,17 @@ int main(int argc, const char *argv[]) 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(); - rv = run_tests(tests); + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + test_dom_suite_setup(TESTS_PATH); + tests_set_cwd(); + rv = run_tests(tests); + if (rv == 0 && !no_cleanup) { + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + } return rv; } |