/* SSSD LDAP Backend Module -- child helpers Authors: Jakub Hrozek Copyright (C) 2009 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 #include #include #include #include #include #include "util/util.h" #include "util/sss_krb5.h" #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async_private.h" #include "util/child_common.h" #ifndef SSSD_LIBEXEC_PATH #error "SSSD_LIBEXEC_PATH not defined" #else #define LDAP_CHILD SSSD_LIBEXEC_PATH"/ldap_child" #endif #ifndef LDAP_CHILD_USER #define LDAP_CHILD_USER "nobody" #endif struct sdap_child { /* child info */ pid_t pid; struct child_io_fds *io; }; static void sdap_close_fd(int *fd) { int ret; if (*fd == -1) { DEBUG(SSSDBG_TRACE_FUNC, "fd already closed\n"); return; } ret = close(*fd); if (ret) { ret = errno; DEBUG(SSSDBG_OP_FAILURE, "Closing fd %d, return error %d (%s)\n", *fd, ret, strerror(ret)); } *fd = -1; } static void child_callback(int child_status, struct tevent_signal *sige, void *pvt) { if (WEXITSTATUS(child_status) == CHILD_TIMEOUT_EXIT_CODE) { DEBUG(SSSDBG_CRIT_FAILURE, "LDAP child was terminated due to timeout\n"); struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); tevent_req_error(req, ETIMEDOUT); } } static errno_t sdap_fork_child(struct tevent_context *ev, struct sdap_child *child, struct tevent_req *req) { int pipefd_to_child[2] = PIPE_INIT; int pipefd_from_child[2] = PIPE_INIT; pid_t pid; errno_t ret; ret = pipe(pipefd_from_child); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "pipe(from) failed [%d][%s].\n", ret, strerror(ret)); goto fail; } ret = pipe(pipefd_to_child); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "pipe(to) failed [%d][%s].\n", ret, strerror(ret)); goto fail; } pid = fork(); if (pid == 0) { /* child */ exec_child(child, pipefd_to_child, pipefd_from_child, LDAP_CHILD, LDAP_CHILD_LOG_FILE); /* We should never get here */ DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec LDAP child\n"); } else if (pid > 0) { /* parent */ child->pid = pid; child->io->read_from_child_fd = pipefd_from_child[0]; PIPE_FD_CLOSE(pipefd_from_child[1]); child->io->write_to_child_fd = pipefd_to_child[1]; PIPE_FD_CLOSE(pipefd_to_child[0]); sss_fd_nonblocking(child->io->read_from_child_fd); sss_fd_nonblocking(child->io->write_to_child_fd); ret = child_handler_setup(ev, pid, child_callback, req, NULL); if (ret != EOK) { goto fail; } } else { /* error */ ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", ret, strerror(ret)); goto fail; } return EOK; fail: PIPE_CLOSE(pipefd_from_child); PIPE_CLOSE(pipefd_to_child); return ret; } static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx, const char *realm_str, const char *princ_str, const char *keytab_name, int32_t lifetime, struct io_buffer **io_buf) { struct io_buffer *buf; size_t rp; buf = talloc(mem_ctx, struct io_buffer); if (buf == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); return ENOMEM; } buf->size = 6 * sizeof(uint32_t); if (realm_str) { buf->size += strlen(realm_str); } if (princ_str) { buf->size += strlen(princ_str); } if (keytab_name) { buf->size += strlen(keytab_name); } DEBUG(SSSDBG_TRACE_FUNC, "buffer size: %zu\n", buf->size); buf->data = talloc_size(buf, buf->size); if (buf->data == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); talloc_free(buf); return ENOMEM; } rp = 0; /* realm */ if (realm_str) { SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(realm_str), &rp); safealign_memcpy(&buf->data[rp], realm_str, strlen(realm_str), &rp); } else { SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); } /* principal */ if (princ_str) { SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(princ_str), &rp); safealign_memcpy(&buf->data[rp], princ_str, strlen(princ_str), &rp); } else { SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); } /* keytab */ if (keytab_name) { SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab_name), &rp); safealign_memcpy(&buf->data[rp], keytab_name, strlen(keytab_name), &rp); } else { SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp); } /* lifetime */ SAFEALIGN_SET_UINT32(&buf->data[rp], lifetime, &rp); /* UID and GID to drop privileges to, if needed. The ldap_child process runs as * setuid if the back end runs unprivileged as it needs to access the keytab */ SAFEALIGN_SET_UINT32(&buf->data[rp], geteuid(), &rp); SAFEALIGN_SET_UINT32(&buf->data[rp], getegid(), &rp); *io_buf = buf; return EOK; } static int parse_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t size, int *result, krb5_error_code *kerr, char **ccache, time_t *expire_time_out) { size_t p = 0; uint32_t len; uint32_t res; char *ccn; time_t expire_time; krb5_error_code krberr; /* operation result code */ SAFEALIGN_COPY_UINT32_CHECK(&res, buf + p, size, &p); /* krb5 error code */ safealign_memcpy(&krberr, buf+p, sizeof(krberr), &p); /* ccache name size */ SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); if (len > size - p) return EINVAL; ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1)); if (ccn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); return ENOMEM; } safealign_memcpy(ccn, buf+p, sizeof(char) * len, &p); ccn[len] = '\0'; if (p + sizeof(time_t) > size) { talloc_free(ccn); return EINVAL; } safealign_memcpy(&expire_time, buf+p, sizeof(time_t), &p); *result = res; *ccache = ccn; *expire_time_out = expire_time; *kerr = krberr; return EOK; } /* ==The-public-async-interface============================================*/ struct sdap_get_tgt_state { struct tevent_context *ev; struct sdap_child *child; ssize_t len; uint8_t *buf; struct tevent_timer *kill_te; }; static errno_t set_tgt_child_timeout(struct tevent_req *req, struct tevent_context *ev, int timeout); static void sdap_get_tgt_step(struct tevent_req *subreq); static void sdap_get_tgt_done(struct tevent_req *subreq); struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *realm_str, const char *princ_str, const char *keytab_name, int32_t lifetime, int timeout) { struct tevent_req *req, *subreq; struct sdap_get_tgt_state *state; struct io_buffer *buf; int ret; req = tevent_req_create(mem_ctx, &state, struct sdap_get_tgt_state); if (!req) { return NULL; } state->ev = ev; state->child = talloc_zero(state, struct sdap_child); if (!state->child) { ret = ENOMEM; goto fail; } state->child->io = talloc(state, struct child_io_fds); if (state->child->io == NULL) { ret = ENOMEM; goto fail; } state->child->io->read_from_child_fd = -1; state->child->io->write_to_child_fd = -1; talloc_set_destructor((TALLOC_CTX *) state->child->io, child_io_destructor); /* prepare the data to pass to child */ ret = create_tgt_req_send_buffer(state, realm_str, princ_str, keytab_name, lifetime, &buf); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "create_tgt_req_send_buffer failed.\n"); goto fail; } ret = sdap_fork_child(state->ev, state->child, req); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_fork_child failed.\n"); goto fail; } ret = set_tgt_child_timeout(req, ev, timeout); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "set_tgt_child_timeout failed.\n"); goto fail; } subreq = write_pipe_send(state, ev, buf->data, buf->size, state->child->io->write_to_child_fd); if (!subreq) { ret = ENOMEM; goto fail; } tevent_req_set_callback(subreq, sdap_get_tgt_step, req); return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void sdap_get_tgt_step(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_get_tgt_state *state = tevent_req_data(req, struct sdap_get_tgt_state); int ret; ret = write_pipe_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } sdap_close_fd(&state->child->io->write_to_child_fd); subreq = read_pipe_send(state, state->ev, state->child->io->read_from_child_fd); if (!subreq) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, sdap_get_tgt_done, req); } static void sdap_get_tgt_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_get_tgt_state *state = tevent_req_data(req, struct sdap_get_tgt_state); int ret; ret = read_pipe_recv(subreq, state, &state->buf, &state->len); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } sdap_close_fd(&state->child->io->read_from_child_fd); if (state->kill_te == NULL) { tevent_req_done(req); return; } /* wait for child callback to terminate the request */ } int sdap_get_tgt_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, int *result, krb5_error_code *kerr, char **ccname, time_t *expire_time_out) { struct sdap_get_tgt_state *state = tevent_req_data(req, struct sdap_get_tgt_state); char *ccn; time_t expire_time; int res; int ret; krb5_error_code krberr; TEVENT_REQ_RETURN_ON_ERROR(req); ret = parse_child_response(mem_ctx, state->buf, state->len, &res, &krberr, &ccn, &expire_time); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot parse child response: [%d][%s]\n", ret, strerror(ret)); return ret; } DEBUG(SSSDBG_TRACE_FUNC, "Child responded: %d [%s], expired on [%"SPRItime"]\n", res, ccn, expire_time); *result = res; *kerr = krberr; *ccname = ccn; *expire_time_out = expire_time; return EOK; } static void get_tgt_sigkill_handler(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 sdap_get_tgt_state *state = tevent_req_data(req, struct sdap_get_tgt_state); int ret; DEBUG(SSSDBG_TRACE_ALL, "timeout for sending SIGKILL to TGT child [%d] reached.\n", state->child->pid); ret = kill(state->child->pid, SIGKILL); if (ret == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "kill failed [%d][%s].\n", errno, strerror(errno)); } tevent_req_error(req, ETIMEDOUT); } static void get_tgt_timeout_handler(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 sdap_get_tgt_state *state = tevent_req_data(req, struct sdap_get_tgt_state); int ret; DEBUG(SSSDBG_TRACE_ALL, "timeout for sending SIGTERM to TGT child [%d] reached.\n", state->child->pid); ret = kill(state->child->pid, SIGTERM); if (ret == -1) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, "Sending SIGTERM failed [%d][%s].\n", ret, strerror(ret)); } DEBUG(SSSDBG_TRACE_FUNC, "Setting %d seconds timeout for sending SIGKILL to TGT child\n", SIGTERM_TO_SIGKILL_TIME); tv = tevent_timeval_current_ofs(SIGTERM_TO_SIGKILL_TIME, 0); state->kill_te = tevent_add_timer(ev, req, tv, get_tgt_sigkill_handler, req); if (state->kill_te == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); tevent_req_error(req, ECANCELED); } } static errno_t set_tgt_child_timeout(struct tevent_req *req, struct tevent_context *ev, int timeout) { struct tevent_timer *te; struct timeval tv; DEBUG(SSSDBG_TRACE_FUNC, "Setting %d seconds timeout for TGT child\n", timeout); tv = tevent_timeval_current_ofs(timeout, 0); te = tevent_add_timer(ev, req, tv, get_tgt_timeout_handler, req); if (te == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); return ENOMEM; } return EOK; }