/* SSSD Authors: Sumit Bose 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 . */ #include "util/util.h" #include "util/strtonum.h" #include "providers/be_ptask.h" #include "providers/ad/ad_common.h" #ifndef RENEWAL_PROG_PATH #define RENEWAL_PROG_PATH "/usr/sbin/adcli" #endif struct renewal_data { struct be_ctx *be_ctx; 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 *, 8); if (args == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); return ENOMEM; } /* extra_args are added in revers order */ /* first add NULL as a placeholder for the server name which is determined * at runtime */ args[c++] = NULL; 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 != 1); /* is is expected that the first element is NULL */ 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; struct child_io_fds *io; }; 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] = PIPE_INIT; int pipefd_from_child[2] = PIPE_INIT; int ret; const char **extra_args; const char *server_name; 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->io = talloc(state, struct child_io_fds); if (state->io == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); ret = ENOMEM; goto done; } state->io->write_to_child_fd = -1; state->io->read_from_child_fd = -1; talloc_set_destructor((void *) state->io, child_io_destructor); server_name = be_fo_get_active_server_name(be_ctx, AD_SERVICE_NAME); talloc_zfree(renewal_data->extra_args[0]); if (server_name != NULL) { renewal_data->extra_args[0] = talloc_asprintf(renewal_data->extra_args, "--domain-controller=%s", server_name); /* if talloc_asprintf() fails we let adcli try to find a server */ } extra_args = renewal_data->extra_args; if (extra_args[0] == NULL) { extra_args = &renewal_data->extra_args[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 */ exec_child_ex(state, pipefd_to_child, pipefd_from_child, renewal_data->prog_path, -1, extra_args, true, STDIN_FILENO, STDERR_FILENO); /* We should never get here */ DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec renewal child\n"); } else if (child_pid > 0) { /* parent */ state->io->read_from_child_fd = pipefd_from_child[0]; PIPE_FD_CLOSE(pipefd_from_child[1]); sss_fd_nonblocking(state->io->read_from_child_fd); state->io->write_to_child_fd = pipefd_to_child[1]; PIPE_FD_CLOSE(pipefd_to_child[0]); sss_fd_nonblocking(state->io->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->io->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) { PIPE_CLOSE(pipefd_from_child); PIPE_CLOSE(pipefd_to_child); 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); 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; ret = access(RENEWAL_PROG_PATH, X_OK); if (ret != 0) { ret = errno; DEBUG(SSSDBG_CONF_SETTINGS, "The helper program ["RENEWAL_PROG_PATH"] for renewal " "doesn't exist [%d]: %s\n", ret, strerror(ret)); return EOK; } 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; }