diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | src/config/SSSDConfig/__init__.py.in | 2 | ||||
-rw-r--r-- | src/config/etc/sssd.api.d/sssd-ad.conf | 2 | ||||
-rw-r--r-- | src/man/sssd-ad.5.xml | 33 | ||||
-rw-r--r-- | src/providers/ad/ad_common.h | 5 | ||||
-rw-r--r-- | src/providers/ad/ad_init.c | 7 | ||||
-rw-r--r-- | src/providers/ad/ad_machine_pw_renewal.c | 372 | ||||
-rw-r--r-- | src/providers/ad/ad_opts.c | 2 | ||||
-rw-r--r-- | src/util/util_errors.c | 1 | ||||
-rw-r--r-- | src/util/util_errors.h | 1 |
10 files changed, 426 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index c5ed2857..6008b06d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3058,6 +3058,7 @@ libsss_ad_la_SOURCES = \ src/providers/ad/ad_common.h \ src/providers/ad/ad_init.c \ src/providers/ad/ad_dyndns.c \ + src/providers/ad/ad_machine_pw_renewal.c \ src/providers/ad/ad_id.c \ src/providers/ad/ad_id.h \ src/providers/ad/ad_access.c \ diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index fe2971d9..647d0812 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -200,6 +200,8 @@ option_strings = { 'ad_gpo_map_deny' : _('PAM service names for which GPO-based access is always denied'), 'ad_gpo_default_right' : _('Default logon right (or permit/deny) to use for unmapped PAM service names'), 'ad_site' : _('a particular site to be used by the client'), + 'ad_maximum_machine_account_password_age' : _('Maximum age in days before the machine account password should be renewed'), + 'ad_machine_account_password_renewal_opts' : _('Option for tuing the machine account renewal task'), # [provider/krb5] 'krb5_kdcip' : _('Kerberos server address'), diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf index 5eb546ca..0ea73d14 100644 --- a/src/config/etc/sssd.api.d/sssd-ad.conf +++ b/src/config/etc/sssd.api.d/sssd-ad.conf @@ -17,6 +17,8 @@ ad_gpo_map_permit = str, None, false ad_gpo_map_deny = str, None, false ad_gpo_default_right = str, None, false ad_site = str, None, false +ad_maximum_machine_account_password_age = int, None, false +ad_machine_account_password_renewal_opts = str, None, false ldap_uri = str, None, false ldap_backup_uri = str, None, false ldap_search_base = str, None, false diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml index 725663b7..05520d14 100644 --- a/src/man/sssd-ad.5.xml +++ b/src/man/sssd-ad.5.xml @@ -719,6 +719,39 @@ ad_gpo_map_deny = +my_pam_service </varlistentry> <varlistentry> + <term>ad_maximum_machine_account_password_age (integer)</term> + <listitem> + <para> + SSSD will check once a day if the machine account + password is older than the given age in days and try + to renew it. A value of 0 will disable the renewal + attempt. + </para> + <para> + Default: 30 days + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>ad_machine_account_password_renewal_opts (string)</term> + <listitem> + <para> + This option should only be used to test the machine + account renewal task. The option expect 2 integers + seperated by a colon (':'). The first integer + defines the interval in seconds how often the task + is run. The second specifies the inital timeout in + seconds before the task is run for the first time + after startup. + </para> + <para> + Default: 86400:750 (24h and 15m) + </para> + </listitem> + </varlistentry> + + <varlistentry> <term>dyndns_update (boolean)</term> <listitem> <para> diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h index 49e97b0b..d61be42c 100644 --- a/src/providers/ad/ad_common.h +++ b/src/providers/ad/ad_common.h @@ -62,6 +62,8 @@ enum ad_basic_opt { AD_GPO_DEFAULT_RIGHT, AD_SITE, AD_KRB5_CONFD_PATH, + AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE, + AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS, AD_OPTS_BASIC /* opts counter */ }; @@ -179,4 +181,7 @@ int ad_autofs_init(struct be_ctx *be_ctx, struct bet_ops **ops, void **pvt_data); +errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx, + struct ad_options *ad_opts); + #endif /* AD_COMMON_H_ */ diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c index 72ce5536..e40fb6f1 100644 --- a/src/providers/ad/ad_init.c +++ b/src/providers/ad/ad_init.c @@ -308,6 +308,13 @@ sssm_ad_id_init(struct be_ctx *bectx, "will not work [%d]: %s\n", ret, strerror(ret)); } + ret = ad_machine_account_password_renewal_init(bectx, ad_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot setup task for machine account " + "password renewal.\n"); + goto done; + } + *ops = &ad_id_ops; *pvt_data = ad_ctx; diff --git a/src/providers/ad/ad_machine_pw_renewal.c b/src/providers/ad/ad_machine_pw_renewal.c new file mode 100644 index 00000000..e42c700e --- /dev/null +++ b/src/providers/ad/ad_machine_pw_renewal.c @@ -0,0 +1,372 @@ +/* + SSSD + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 "util/util.h" +#include "util/strtonum.h" +#include "providers/dp_ptask.h" +#include "providers/ad/ad_common.h" + +#ifndef RENEWAL_PROG_PATH +#define RENEWAL_PROG_PATH "/usr/sbin/adcli" +#endif + +struct renewal_data { + char *prog_path; + const char **extra_args; +}; + +static errno_t get_adcli_extra_args(const char *ad_domain, + const char *ad_hostname, + const char *ad_keytab, + size_t pw_lifetime_in_days, + size_t period, + size_t initial_delay, + struct renewal_data *renewal_data) +{ + const char **args; + size_t c = 0; + + if (ad_domain == NULL || ad_hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing AD domain or hostname.\n"); + return EINVAL; + } + + renewal_data->prog_path = talloc_strdup(renewal_data, RENEWAL_PROG_PATH); + if (renewal_data->prog_path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + + args = talloc_array(renewal_data, const char *, 7); + if (args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + /* extra_args are added in revers order */ + args[c++] = talloc_asprintf(args, "--computer-password-lifetime=%zu", + pw_lifetime_in_days); + args[c++] = talloc_asprintf(args, "--host-fqdn=%s", ad_hostname); + if (ad_keytab != NULL) { + args[c++] = talloc_asprintf(args, "--host-keytab=%s", ad_keytab); + } + args[c++] = talloc_asprintf(args, "--domain=%s", ad_domain); + if (DEBUG_IS_SET(SSSDBG_TRACE_LIBS)) { + args[c++] = talloc_strdup(args, "--verbose"); + } + args[c++] = talloc_strdup(args, "update"); + args[c] = NULL; + + do { + if (args[--c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc failed while copying arguments.\n"); + talloc_free(args); + return ENOMEM; + } + } while (c != 0); + + renewal_data->extra_args = args; + + return EOK; +} + +struct renewal_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct tevent_context *ev; + + int write_to_child_fd; + int read_from_child_fd; +}; + +static void ad_machine_account_password_renewal_done(struct tevent_req *subreq); +static void +ad_machine_account_password_renewal_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static struct tevent_req * +ad_machine_account_password_renewal_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct renewal_data *renewal_data; + struct renewal_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + pid_t child_pid; + struct timeval tv; + int pipefd_to_child[2]; + int pipefd_from_child[2]; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct renewal_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + renewal_data = talloc_get_type(pvt, struct renewal_data); + + state->ev = ev; + state->child_status = EFAULT; + state->read_from_child_fd = -1; + state->write_to_child_fd = -1; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + ret = exec_child_ex(state, pipefd_to_child, pipefd_from_child, + renewal_data->prog_path, -1, + renewal_data->extra_args, true, + STDIN_FILENO, STDERR_FILENO); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec renewal child: [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + } else if (child_pid > 0) { /* parent */ + + state->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + sss_fd_nonblocking(state->read_from_child_fd); + + state->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + sss_fd_nonblocking(state->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_RENEWAL_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(be_ptask_get_timeout(be_ptask), 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + ad_machine_account_password_renewal_timeout, + req); + if(state->timeout_handler == NULL) { + ret = ERR_RENEWAL_CHILD; + goto done; + } + + subreq = read_pipe_send(state, ev, state->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + ret = ERR_RENEWAL_CHILD; + goto done; + } + tevent_req_set_callback(subreq, + ad_machine_account_password_renewal_done, req); + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void ad_machine_account_password_renewal_done(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct renewal_state *state = tevent_req_data(req, struct renewal_state); + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "--- adcli output start---\n" + "%.*s" + "---adcli output end---\n", + (int) buf_len, buf); + + close(state->read_from_child_fd); + state->read_from_child_fd = -1; + + + tevent_req_done(req); + return; +} + +static void +ad_machine_account_password_renewal_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct renewal_state *state = tevent_req_data(req, struct renewal_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for AD renewal child.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_RENEWAL_CHILD); +} + +static errno_t +ad_machine_account_password_renewal_recv(struct tevent_req *req) +{ + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx, + struct ad_options *ad_opts) +{ + int ret; + struct renewal_data *renewal_data; + int lifetime; + size_t period; + size_t initial_delay; + const char *dummy; + char **opt_list; + int opt_list_size; + char *endptr; + + lifetime = dp_opt_get_int(ad_opts->basic, + AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE); + + if (lifetime == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "Automatic machine account renewal disabled.\n"); + return EOK; + } + + if (lifetime < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Illegal value [%d] for password lifetime.\n", lifetime); + return EINVAL; + } + + renewal_data = talloc(be_ctx, struct renewal_data); + if (renewal_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + dummy = dp_opt_get_cstring(ad_opts->basic, + AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS); + ret = split_on_separator(renewal_data, dummy, ':', true, false, + &opt_list, &opt_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed.\n"); + goto done; + } + + if (opt_list_size != 2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong number of renewal options.\n"); + ret = EINVAL; + goto done; + } + + errno = 0; + period = strtouint32(opt_list[0], &endptr, 10); + if (errno != 0 || *endptr != '\0' || opt_list[0] == endptr) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse first renewal option.\n"); + ret = EINVAL; + goto done; + } + + errno = 0; + initial_delay = strtouint32(opt_list[1], &endptr, 10); + if (errno != 0 || *endptr != '\0' || opt_list[0] == endptr) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse second renewal option.\n"); + ret = EINVAL; + goto done; + } + + ret = get_adcli_extra_args(dp_opt_get_cstring(ad_opts->basic, AD_DOMAIN), + dp_opt_get_cstring(ad_opts->basic, AD_HOSTNAME), + dp_opt_get_cstring(ad_opts->id_ctx->sdap_id_ctx->opts->basic, + SDAP_KRB5_KEYTAB), + lifetime, period, initial_delay, renewal_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_adcli_extra_args failed.\n"); + goto done; + } + + ret = be_ptask_create(be_ctx, be_ctx, period, initial_delay, 0, 0, 60, + BE_PTASK_OFFLINE_DISABLE, 0, + ad_machine_account_password_renewal_send, + ad_machine_account_password_renewal_recv, + renewal_data, + "AD machine account password renewal", NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "be_ptask_create failed.\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(renewal_data); + } + + return ret; +} diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c index 4ea96637..8b2841ea 100644 --- a/src/providers/ad/ad_opts.c +++ b/src/providers/ad/ad_opts.c @@ -48,6 +48,8 @@ struct dp_option ad_basic_opts[] = { { "ad_gpo_default_right", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "ad_site", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING }, + { "ad_maximum_machine_account_password_age", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ad_machine_account_password_renewal_opts", DP_OPT_STRING, { "86400:750" }, NULL_STRING }, DP_OPTION_TERMINATOR }; diff --git a/src/util/util_errors.c b/src/util/util_errors.c index e7f30ab3..59ae63ab 100644 --- a/src/util/util_errors.c +++ b/src/util/util_errors.c @@ -83,6 +83,7 @@ struct err_string error_to_str[] = { { "Message sender is the bus" }, /* ERR_SBUS_SENDER_BUS */ { "Subdomain is inactive" }, /* ERR_SUBDOM_INACTIVE */ { "Account is locked" }, /* ERR_ACCOUNT_LOCKED */ + { "AD renewal child failed" }, /* ERR_RENEWAL_CHILD */ { "ERR_LAST" } /* ERR_LAST */ }; diff --git a/src/util/util_errors.h b/src/util/util_errors.h index a1c822c4..05791f2f 100644 --- a/src/util/util_errors.h +++ b/src/util/util_errors.h @@ -105,6 +105,7 @@ enum sssd_errors { ERR_SBUS_SENDER_BUS, ERR_SUBDOM_INACTIVE, ERR_ACCOUNT_LOCKED, + ERR_RENEWAL_CHILD, ERR_LAST /* ALWAYS LAST */ }; |