From f5aa0c046e75f2ecc2b96424847765c8ccb3a302 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Thu, 19 Nov 2009 18:47:56 -0500 Subject: Get TGT in a child process. To avoid blocking in a synchronous call, the TGT is saved in a separate process Fixes: #277 --- server/providers/ldap/sdap_child_helpers.c | 500 +++++++++++++++++++++++++++++ 1 file changed, 500 insertions(+) create mode 100644 server/providers/ldap/sdap_child_helpers.c (limited to 'server/providers/ldap/sdap_child_helpers.c') diff --git a/server/providers/ldap/sdap_child_helpers.c b/server/providers/ldap/sdap_child_helpers.c new file mode 100644 index 00000000..85f7d3c8 --- /dev/null +++ b/server/providers/ldap/sdap_child_helpers.c @@ -0,0 +1,500 @@ +/* + 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 "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/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 io_buffer { + uint8_t *data; + size_t size; +}; + +struct sdap_child_req { + /* child info */ + pid_t child_pid; + int read_from_child_fd; + int write_to_child_fd; + + /* for handling timeout */ + struct tevent_context *ev; + int timeout; + struct tevent_timer *timeout_handler; + struct tevent_req *req; + + /* parameters */ + const char *realm_str; + const char *princ_str; + const char *keytab_name; +}; + +static int sdap_child_req_destructor(void *ptr) +{ + int ret; + struct sdap_child_req *cr = talloc_get_type(ptr, struct sdap_child_req); + + if (cr == NULL) return EOK; + + if (cr->read_from_child_fd != -1) { + ret = close(cr->read_from_child_fd); + if (ret != EOK) { + ret = errno; + DEBUG(1, ("close failed [%d][%s].\n", ret, strerror(ret))); + } + } + if (cr->write_to_child_fd != -1) { + ret = close(cr->write_to_child_fd); + if (ret != EOK) { + ret = errno; + DEBUG(1, ("close failed [%d][%s].\n", ret, strerror(ret))); + } + } + + memset(cr, 0, sizeof(struct sdap_child_req)); + + return EOK; +} + +static void sdap_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct sdap_child_req *lr = talloc_get_type(pvt, struct sdap_child_req); + int ret; + + if (lr->timeout_handler == NULL) { + return; + } + + DEBUG(9, ("timeout for ldap child [%d] reached.\n", lr->child_pid)); + + ret = kill(lr->child_pid, SIGKILL); + if (ret == -1) { + DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno))); + } + + tevent_req_error(lr->req, EIO); +} + +static errno_t activate_child_timeout_handler(struct sdap_child_req *child_req) +{ + struct timeval tv; + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, + child_req->timeout, + 0); + child_req->timeout_handler = tevent_add_timer(child_req->ev, child_req, tv, + sdap_child_timeout, child_req); + if (child_req->timeout_handler == NULL) { + DEBUG(1, ("tevent_add_timer failed.\n")); + return ENOMEM; + } + + return EOK; +} + +static errno_t prepare_child_argv(TALLOC_CTX *mem_ctx, + struct sdap_child_req *child_req, + char ***_argv) +{ + uint_t argc = 3; /* program name, debug_level and NULL */ + char ** argv; + errno_t ret = EINVAL; + + /* Save the current state in case an interrupt changes it */ + bool child_debug_to_file = debug_to_file; + bool child_debug_timestamps = debug_timestamps; + + if (child_debug_to_file) argc++; + if (child_debug_timestamps) argc++; + + /* program name, debug_level, + * debug_to_file, debug_timestamps + * and NULL */ + argv = talloc_array(mem_ctx, char *, argc); + if (argv == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + return ENOMEM; + } + + argv[--argc] = NULL; + + argv[--argc] = talloc_asprintf(argv, "--debug-level=%d", + debug_level); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + if (child_debug_to_file) { + argv[--argc] = talloc_asprintf(argv, "--debug-fd=%d", + ldap_child_debug_fd); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } + + if (child_debug_timestamps) { + argv[--argc] = talloc_strdup(argv, "--debug-timestamps"); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } + + argv[--argc] = talloc_strdup(argv, LDAP_CHILD); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + if (argc != 0) { + ret = EINVAL; + goto fail; + } + + *_argv = argv; + + return EOK; + +fail: + talloc_free(argv); + return ret; +} + +static errno_t fork_ldap_child(struct sdap_child_req *child_req) +{ + int pipefd_to_child[2]; + int pipefd_from_child[2]; + pid_t pid; + int ret; + errno_t err; + char **argv; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err))); + return err; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err))); + return err; + } + + pid = fork(); + + if (pid == 0) { /* child */ + close(pipefd_to_child[1]); + ret = dup2(pipefd_to_child[0], STDIN_FILENO); + if (ret == -1) { + err = errno; + DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err))); + return err; + } + + close(pipefd_from_child[0]); + ret = dup2(pipefd_from_child[1], STDOUT_FILENO); + if (ret == -1) { + err = errno; + DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err))); + return err; + } + + ret = prepare_child_argv(child_req, child_req, &argv); + if (ret != EOK) { + DEBUG(1, ("prepare_child_argv.\n")); + return ret; + } + + ret = execv(LDAP_CHILD, argv); + if (ret == -1) { + err = errno; + DEBUG(1, ("execv failed [%d][%s].\n", err, strerror(err))); + return err; + } + } else if (pid > 0) { /* parent */ + child_req->child_pid = pid; + child_req->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + child_req->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + fd_nonblocking(child_req->read_from_child_fd); + fd_nonblocking(child_req->write_to_child_fd); + + } else { /* error */ + err = errno; + DEBUG(1, ("fork failed [%d][%s].\n", err, strerror(err))); + return err; + } + + return EOK; +} + +static errno_t create_ldap_send_buffer(struct sdap_child_req *child_req, struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + + buf = talloc(child_req, struct io_buffer); + if (buf == NULL) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + buf->size = 3*sizeof(int); + if (child_req->realm_str) + buf->size += strlen(child_req->realm_str); + if (child_req->princ_str) + buf->size += strlen(child_req->princ_str); + if (child_req->keytab_name) + buf->size += strlen(child_req->keytab_name); + DEBUG(7, ("buffer size: %d\n", buf->size)); + + buf->data = talloc_size(child_req, buf->size); + if (buf->data == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + /* realm */ + ((uint32_t *)(&buf->data[rp]))[0] = + (uint32_t) (child_req->realm_str ? + strlen(child_req->realm_str) : 0); + rp += sizeof(uint32_t); + if (child_req->realm_str) { + memcpy(&buf->data[rp], child_req->realm_str, strlen(child_req->realm_str)); + rp += strlen(child_req->realm_str); + } + + /* principal */ + ((uint32_t *)(&buf->data[rp]))[0] = + (uint32_t) (child_req->princ_str ? + strlen(child_req->princ_str) : 0); + rp += sizeof(uint32_t); + if (child_req->princ_str) { + memcpy(&buf->data[rp], child_req->princ_str, strlen(child_req->princ_str)); + rp += strlen(child_req->princ_str); + } + + /* keytab */ + ((uint32_t *)(&buf->data[rp]))[0] = + (uint32_t) (child_req->keytab_name ? + strlen(child_req->keytab_name) : 0); + rp += sizeof(uint32_t); + if (child_req->keytab_name) { + memcpy(&buf->data[rp], child_req->keytab_name, strlen(child_req->keytab_name)); + rp += strlen(child_req->keytab_name); + } + + *io_buf = buf; + return EOK; +} + +static int parse_child_response(TALLOC_CTX *mem_ctx, + uint8_t *buf, ssize_t size, + int *result, + char **ccache) +{ + size_t p = 0; + uint32_t *len; + uint32_t *res; + char *ccn; + + /* ccache size the ccache itself*/ + if ((p + sizeof(uint32_t)) > size) return EINVAL; + res = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + + /* ccache size the ccache itself*/ + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + + if ((p + *len ) > size) return EINVAL; + + ccn = talloc_size(mem_ctx, sizeof(char) * (*len + 1)); + if (ccn == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + return ENOMEM; + } + memcpy(ccn, buf+p, sizeof(char) * (*len + 1)); + ccn[*len] = '\0'; + + *result = *res; + *ccache = ccn; + return EOK; +} + +/* ==The-public-async-interface============================================*/ + +struct sdap_krb5_get_tgt_state { + struct sdap_child_req *lr; + ssize_t len; + uint8_t *buf; +}; + +static void sdap_krb5_get_tgt_done(struct tevent_req *subreq); + +struct tevent_req *sdap_krb5_get_tgt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + const char *realm_str, + const char *princ_str, + const char *keytab_name) +{ + struct sdap_child_req *child_req = NULL; + struct sdap_krb5_get_tgt_state *state = NULL; + int ret; + struct io_buffer *buf = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + + /* prepare the data to pass to child */ + child_req = talloc_zero(mem_ctx, struct sdap_child_req); + if (!child_req) goto fail; + + child_req->ev = ev; + child_req->read_from_child_fd = -1; + child_req->write_to_child_fd = -1; + child_req->realm_str = realm_str; + child_req->princ_str = princ_str; + child_req->keytab_name = keytab_name; + child_req->timeout = timeout; + talloc_set_destructor((TALLOC_CTX *) child_req, sdap_child_req_destructor); + + ret = create_ldap_send_buffer(child_req, &buf); + if (ret != EOK) { + DEBUG(1, ("create_ldap_send_buffer failed.\n")); + return NULL; + } + + ret = fork_ldap_child(child_req); + if (ret != EOK) { + DEBUG(1, ("fork_ldap_child failed.\n")); + goto fail; + } + + ret = write(child_req->write_to_child_fd, buf->data, buf->size); + close(child_req->write_to_child_fd); + child_req->write_to_child_fd = -1; + if (ret == -1) { + ret = errno; + DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret))); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct sdap_krb5_get_tgt_state); + if (req == NULL) { + return NULL; + } + + state->lr = child_req; + + child_req->req = req; + ret = activate_child_timeout_handler(child_req); + if (ret != EOK) { + DEBUG(1, ("activate_child_timeout_handler failed.\n")); + return NULL; + } + + subreq = read_pipe_send(state, ev, child_req->read_from_child_fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, sdap_krb5_get_tgt_done, req); + + return req; +fail: + return NULL; +} + +static void sdap_krb5_get_tgt_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_krb5_get_tgt_state *state = tevent_req_data(req, + struct sdap_krb5_get_tgt_state); + int ret; + + ret = read_pipe_recv(subreq, state, &state->buf, &state->len); + talloc_zfree(subreq); + talloc_zfree(state->lr->timeout_handler); + close(state->lr->read_from_child_fd); + state->lr->read_from_child_fd = -1; + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +int sdap_krb5_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + char **ccname) +{ + struct sdap_krb5_get_tgt_state *state = tevent_req_data(req, + struct sdap_krb5_get_tgt_state); + char *ccn; + int res; + int ret; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = parse_child_response(mem_ctx, state->buf, state->len, &res, &ccn); + if (ret != EOK) { + DEBUG(1, ("Cannot parse child response: [%d][%s]\n", ret, strerror(ret))); + return ret; + } + + DEBUG(6, ("Child responded: %d [%s]\n", res, ccn)); + *result = res; + *ccname = ccn; + return EOK; +} + -- cgit