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/Makefile.am | 19 + server/man/sssd-ldap.5.xml | 3 +- server/providers/ldap/ldap_child.c | 432 ++++++++++++++++++++++ server/providers/ldap/ldap_common.c | 3 + server/providers/ldap/ldap_common.h | 3 + server/providers/ldap/ldap_init.c | 54 +++ server/providers/ldap/sdap_async.h | 1 + server/providers/ldap/sdap_async_connection.c | 216 +++-------- server/providers/ldap/sdap_async_private.h | 15 + server/providers/ldap/sdap_child_helpers.c | 500 ++++++++++++++++++++++++++ 10 files changed, 1081 insertions(+), 165 deletions(-) create mode 100644 server/providers/ldap/ldap_child.c create mode 100644 server/providers/ldap/sdap_child_helpers.c (limited to 'server') diff --git a/server/Makefile.am b/server/Makefile.am index 69e5fd38e..c43eb470b 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -56,6 +56,7 @@ sssdlibexec_PROGRAMS = \ sssd_pam \ sssd_be \ krb5_child \ + ldap_child \ $(sssd_pk) \ $(sssd_info) @@ -566,6 +567,7 @@ libsss_ldap_la_SOURCES = \ providers/ldap/sdap_async.c \ providers/ldap/sdap_async_accounts.c \ providers/ldap/sdap_async_connection.c \ + providers/ldap/sdap_child_helpers.c \ providers/ldap/sdap.c \ util/sss_ldap.c \ util/sss_krb5.c @@ -622,6 +624,7 @@ libsss_ipa_la_SOURCES = \ providers/ldap/sdap_async.c \ providers/ldap/sdap_async_accounts.c \ providers/ldap/sdap_async_connection.c \ + providers/ldap/sdap_child_helpers.c \ providers/ldap/sdap.c \ util/sss_ldap.c \ util/sss_krb5.c \ @@ -659,6 +662,22 @@ krb5_child_LDADD = \ $(POPT_LIBS) \ $(KRB5_LIBS) +ldap_child_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + providers/ldap/ldap_child.c \ + providers/child_common.c \ + util/sss_krb5.c +ldap_child_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(KRB5_CFLAGS) +ldap_child_LDADD = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(OPENLDAP_LIBS) \ + $(KRB5_LIBS) + memberof_la_SOURCES = \ ldb_modules/memberof.c memberof_la_CFLAGS = \ diff --git a/server/man/sssd-ldap.5.xml b/server/man/sssd-ldap.5.xml index d944392f9..9172fa257 100644 --- a/server/man/sssd-ldap.5.xml +++ b/server/man/sssd-ldap.5.xml @@ -420,7 +420,8 @@ Specifies a timeout (in seconds) after which calls to synchronous LDAP APIs will abort if no - response is received. + response is received. Also controls the timeout + when communicating to KDC in case of SASL bind. Default: 5 diff --git a/server/providers/ldap/ldap_child.c b/server/providers/ldap/ldap_child.c new file mode 100644 index 000000000..9c11bf401 --- /dev/null +++ b/server/providers/ldap/ldap_child.c @@ -0,0 +1,432 @@ +/* + SSSD + + LDAP Backend Module -- prime ccache with TGT in a child process + + 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 "util/util.h" +#include "util/sss_krb5.h" +#include "providers/child_common.h" +#include "providers/dp_backend.h" + +static krb5_context krb5_error_ctx; + +struct input_buffer { + const char *realm_str; + const char *princ_str; + const char *keytab_name; +}; + +static errno_t unpack_buffer(uint8_t *buf, size_t size, struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t *len; + + /* realm_str size and length */ + DEBUG(7, ("total buffer size: %d\n", size)); + if ((p + sizeof(uint32_t)) > size) { + DEBUG(1, ("Error: buffer too big!\n")); + return EINVAL; + } + len = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + + DEBUG(7, ("realm_str size: %d\n", *len)); + if (*len) { + if ((p + *len ) > size) return EINVAL; + ibuf->realm_str = (char *) copy_buffer_and_add_zero(ibuf, buf+p, + sizeof(char) * (*len)); + DEBUG(7, ("got realm_str: %s\n", ibuf->realm_str)); + if (ibuf->realm_str == NULL) return ENOMEM; + p += *len; + } + + /* princ_str size and length */ + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + + DEBUG(7, ("princ_str size: %d\n", *len)); + if (*len) { + if ((p + *len ) > size) return EINVAL; + ibuf->princ_str = (char *) copy_buffer_and_add_zero(ibuf, buf+p, + sizeof(char) * (*len)); + DEBUG(7, ("got princ_str: %s\n", ibuf->princ_str)); + if (ibuf->princ_str == NULL) return ENOMEM; + p += *len; + } + + /* keytab_name size and length */ + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + + DEBUG(7, ("keytab_name size: %d\n", *len)); + if (*len) { + if ((p + *len ) > size) return EINVAL; + ibuf->keytab_name = (char *) copy_buffer_and_add_zero(ibuf, buf+p, + sizeof(char) * (*len)); + DEBUG(7, ("got keytab_name: %s\n", ibuf->keytab_name)); + if (ibuf->keytab_name == NULL) return ENOMEM; + p += *len; + } + + return EOK; +} + +static int pack_buffer(struct response *r, int result, const char *msg) +{ + int len; + int p = 0; + + len = strlen(msg); + r->size = 2 * sizeof(uint32_t) + len; + + /* result */ + ((uint32_t *)(&r->buf[p]))[0] = result; + p += sizeof(uint32_t); + + /* message size */ + ((uint32_t *)(&r->buf[p]))[0] = len; + p += sizeof(uint32_t); + + /* message itself */ + memcpy(&r->buf[p], msg, len); + p += len; + + return EOK; +} + +static int ldap_child_get_tgt_sync(TALLOC_CTX *memctx, + const char *realm_str, + const char *princ_str, + const char *keytab_name, + const char **ccname_out) +{ + char *ccname; + char *realm_name = NULL; + char *full_princ = NULL; + krb5_context context = NULL; + krb5_keytab keytab = NULL; + krb5_ccache ccache = NULL; + krb5_principal kprinc; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + krb5_error_code krberr; + int ret; + + krberr = krb5_init_context(&context); + if (krberr) { + DEBUG(2, ("Failed to init kerberos context\n")); + return EFAULT; + } + + if (!realm_str) { + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + DEBUG(2, ("Failed to get default realm name: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + } else { + realm_name = talloc_strdup(memctx, realm_str); + if (!realm_name) { + ret = ENOMEM; + goto done; + } + } + + if (princ_str) { + if (!strchr(princ_str, '@')) { + full_princ = talloc_asprintf(memctx, "%s@%s", + princ_str, realm_name); + } else { + full_princ = talloc_strdup(memctx, princ_str); + } + } else { + char hostname[512]; + + ret = gethostname(hostname, 511); + if (ret == -1) { + ret = errno; + goto done; + } + hostname[511] = '\0'; + + full_princ = talloc_asprintf(memctx, "host/%s@%s", + hostname, realm_name); + } + if (!full_princ) { + ret = ENOMEM; + goto done; + } + DEBUG(4, ("Principal name is: [%s]\n", full_princ)); + + krberr = krb5_parse_name(context, full_princ, &kprinc); + if (krberr) { + DEBUG(2, ("Unable to build principal: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + if (keytab_name) { + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + } else { + krberr = krb5_kt_default(context, &keytab); + } + if (krberr) { + DEBUG(2, ("Failed to read keytab file: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name); + if (!ccname) { + ret = ENOMEM; + goto done; + } + + krberr = krb5_cc_resolve(context, ccname, &ccache); + if (krberr) { + DEBUG(2, ("Failed to set cache name: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + memset(&my_creds, 0, sizeof(my_creds)); + memset(&options, 0, sizeof(options)); + + krb5_get_init_creds_opt_set_address_list(&options, NULL); + krb5_get_init_creds_opt_set_forwardable(&options, 0); + krb5_get_init_creds_opt_set_proxiable(&options, 0); + /* set a very short lifetime, we don't keep the ticket around */ + krb5_get_init_creds_opt_set_tkt_life(&options, 300); + + krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, + keytab, 0, NULL, &options); + + if (krberr) { + DEBUG(2, ("Failed to init credentials: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + krberr = krb5_cc_initialize(context, ccache, kprinc); + if (krberr) { + DEBUG(2, ("Failed to init ccache: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr) { + DEBUG(2, ("Failed to store creds: %s\n", + sss_krb5_get_error_message(context, krberr))); + ret = EFAULT; + goto done; + } + + ret = EOK; + *ccname_out = ccname; + +done: + if (keytab) krb5_kt_close(context, keytab); + if (context) krb5_free_context(context); + return ret; +} + +static int prepare_response(TALLOC_CTX *mem_ctx, + const char *ccname, + krb5_error_code kerr, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + const char *krb5_msg = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (!r) return ENOMEM; + + r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE); + if (r->buf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + return ENOMEM; + } + r->max_size = MAX_CHILD_MSG_SIZE; + r->size = 0; + + if (kerr == 0) { + ret = pack_buffer(r, EOK, ccname); + } else { + krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); + if (krb5_msg == NULL) { + DEBUG(1, ("sss_krb5_get_error_message failed.\n")); + return ENOMEM; + } + + ret = pack_buffer(r, EFAULT, krb5_msg); + sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); + } + + if (ret != EOK) { + DEBUG(1, ("pack_buffer failed\n")); + return ret; + } + + *rsp = r; + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int kerr; + int opt; + int debug_fd = -1; + poptContext pc; + TALLOC_CTX *main_ctx; + uint8_t *buf = NULL; + ssize_t len = 0; + const char *ccname = NULL; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, + "Debug level", NULL}, + {"debug-timestamps", 0, POPT_ARG_NONE, &debug_timestamps, 0, + "Add debug timestamps", NULL}, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + "Add debug timestamps", NULL}, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + _exit(-1); + } + + debug_prg_name = talloc_asprintf(main_ctx, "[sssd[ldap_child[%d]]]", getpid()); + + if (debug_fd != -1) { + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + DEBUG(1, ("set_debug_file_from_fd failed.\n")); + } + } + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + goto fail; + } + + while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) { + if (ret == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno))); + goto fail; + } else if (ret > 0) { + len += ret; + if (len > IN_BUF_SIZE) { + DEBUG(1, ("read too much, this should never happen.\n")); + goto fail; + } + continue; + } else { + DEBUG(1, ("unexpected return code of read [%d].\n", ret)); + goto fail; + } + } + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(1, ("unpack_buffer failed.[%d][%s].\n", ret, strerror(ret))); + goto fail; + } + + kerr = ldap_child_get_tgt_sync(main_ctx, + ibuf->realm_str, ibuf->princ_str, + ibuf->keytab_name, &ccname); + if (kerr != EOK) { + DEBUG(1, ("ldap_child_get_tgt_sync failed.\n")); + /* Do not return, must report failure */ + } + + ret = prepare_response(main_ctx, ccname, kerr, &resp); + if (ret != EOK) { + DEBUG(1, ("prepare_response failed. [%d][%s].\n", ret, strerror(ret))); + return ENOMEM; + } + + ret = write(STDOUT_FILENO, resp->buf, resp->size); + if (ret == -1) { + ret = errno; + DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret))); + return errno; + } + + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(0); + +fail: + close(STDOUT_FILENO); + talloc_free(main_ctx); + _exit(-1); +} diff --git a/server/providers/ldap/ldap_common.c b/server/providers/ldap/ldap_common.c index 58c6d692f..d43d14855 100644 --- a/server/providers/ldap/ldap_common.c +++ b/server/providers/ldap/ldap_common.c @@ -25,6 +25,9 @@ #include "providers/ldap/ldap_common.h" #include "providers/fail_over.h" +/* a fd the child process would log into */ +int ldap_child_debug_fd = -1; + struct dp_option default_basic_opts[] = { { "ldap_uri", DP_OPT_STRING, { "ldap://localhost" }, NULL_STRING }, { "ldap_search_base", DP_OPT_STRING, { "dc=example,dc=com" }, NULL_STRING }, diff --git a/server/providers/ldap/ldap_common.h b/server/providers/ldap/ldap_common.h index 111027672..188a7b986 100644 --- a/server/providers/ldap/ldap_common.h +++ b/server/providers/ldap/ldap_common.h @@ -30,6 +30,9 @@ #define PWD_POL_OPT_SHADOW "shadow" #define PWD_POL_OPT_MIT "mit_kerberos" +/* a fd the child process would log into */ +extern int ldap_child_debug_fd; + struct sdap_id_ctx { struct be_ctx *be; struct sdap_options *opts; diff --git a/server/providers/ldap/ldap_init.c b/server/providers/ldap/ldap_init.c index 5a64585d8..c6241bf5d 100644 --- a/server/providers/ldap/ldap_init.c +++ b/server/providers/ldap/ldap_init.c @@ -22,7 +22,11 @@ along with this program. If not, see . */ +#include + +#include "providers/child_common.h" #include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" static void sdap_shutdown(struct be_req *req); @@ -44,6 +48,49 @@ struct bet_ops sdap_chpass_ops = { .finalize = sdap_shutdown }; +static int setup_child(struct sdap_id_ctx *ctx) +{ + int ret; + const char *mech; + struct tevent_signal *sige; + unsigned v; + FILE *debug_filep; + + mech = dp_opt_get_string(ctx->opts->basic, + SDAP_SASL_MECH); + if (!mech) { + return EOK; + } + + sige = tevent_add_signal(ctx->be->ev, ctx, SIGCHLD, SA_SIGINFO, + child_sig_handler, NULL); + if (sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + return ENOMEM; + } + + if (debug_to_file != 0 && ldap_child_debug_fd == -1) { + ret = open_debug_file_ex("ldap_child", &debug_filep); + if (ret != EOK) { + DEBUG(0, ("Error setting up logging (%d) [%s]\n", + ret, strerror(ret))); + return ret; + } + + ldap_child_debug_fd = fileno(debug_filep); + if (ldap_child_debug_fd == -1) { + DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno))); + ret = errno; + return ret; + } + + v = fcntl(ldap_child_debug_fd, F_GETFD, 0); + fcntl(ldap_child_debug_fd, F_SETFD, v & ~FD_CLOEXEC); + } + + return EOK; +} + int sssm_ldap_init(struct be_ctx *bectx, struct bet_ops **ops, void **pvt_data) @@ -88,6 +135,13 @@ int sssm_ldap_init(struct be_ctx *bectx, goto done; } + ret = setup_child(ctx); + if (ret != EOK) { + DEBUG(1, ("setup_child failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + *ops = &sdap_id_ops; *pvt_data = ctx; ret = EOK; diff --git a/server/providers/ldap/sdap_async.h b/server/providers/ldap/sdap_async.h index d85507946..e18fb69a2 100644 --- a/server/providers/ldap/sdap_async.h +++ b/server/providers/ldap/sdap_async.h @@ -62,6 +62,7 @@ int sdap_get_groups_recv(struct tevent_req *req, struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_handle *sh, + int timeout, const char *keytab, const char *principal, const char *realm); diff --git a/server/providers/ldap/sdap_async_connection.c b/server/providers/ldap/sdap_async_connection.c index a5e6ccfa9..b742471c6 100644 --- a/server/providers/ldap/sdap_async_connection.c +++ b/server/providers/ldap/sdap_async_connection.c @@ -547,168 +547,22 @@ static int sasl_bind_recv(struct tevent_req *req, int *ldaperr) /* ==Perform-Kinit-given-keytab-and-principal============================= */ -static int sdap_krb5_get_tgt_sync(TALLOC_CTX *memctx, - const char *realm_str, - const char *princ_str, - const char *keytab_name) -{ - char *ccname; - char *realm_name = NULL; - char *full_princ = NULL; - krb5_context context = NULL; - krb5_keytab keytab = NULL; - krb5_ccache ccache = NULL; - krb5_principal kprinc; - krb5_creds my_creds; - krb5_get_init_creds_opt options; - krb5_error_code krberr; - int ret; - - krberr = krb5_init_context(&context); - if (krberr) { - DEBUG(2, ("Failed to init kerberos context\n")); - return EFAULT; - } - - if (!realm_str) { - krberr = krb5_get_default_realm(context, &realm_name); - if (krberr) { - DEBUG(2, ("Failed to get default realm name: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - } else { - realm_name = talloc_strdup(memctx, realm_str); - if (!realm_name) { - ret = ENOMEM; - goto done; - } - } - - if (princ_str) { - if (!strchr(princ_str, '@')) { - full_princ = talloc_asprintf(memctx, "%s@%s", - princ_str, realm_name); - } else { - full_princ = talloc_strdup(memctx, princ_str); - } - } else { - char hostname[512]; - - ret = gethostname(hostname, 511); - if (ret == -1) { - ret = errno; - goto done; - } - hostname[511] = '\0'; - - full_princ = talloc_asprintf(memctx, "host/%s@%s", - hostname, realm_name); - } - if (!full_princ) { - ret = ENOMEM; - goto done; - } - DEBUG(4, ("Principal name is: [%s]\n", full_princ)); - - krberr = krb5_parse_name(context, full_princ, &kprinc); - if (krberr) { - DEBUG(2, ("Unable to build principal: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - if (keytab_name) { - krberr = krb5_kt_resolve(context, keytab_name, &keytab); - } else { - krberr = krb5_kt_default(context, &keytab); - } - if (krberr) { - DEBUG(2, ("Failed to read keytab file: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name); - if (!ccname) { - ret = ENOMEM; - goto done; - } - - ret = setenv("KRB5CCNAME", ccname, 1); - if (ret == -1) { - DEBUG(2, ("Unable to set env. variable KRB5CCNAME!\n")); - ret = EFAULT; - goto done; - } - - krberr = krb5_cc_resolve(context, ccname, &ccache); - if (krberr) { - DEBUG(2, ("Failed to set cache name: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - memset(&my_creds, 0, sizeof(my_creds)); - memset(&options, 0, sizeof(options)); - - krb5_get_init_creds_opt_set_address_list(&options, NULL); - krb5_get_init_creds_opt_set_forwardable(&options, 0); - krb5_get_init_creds_opt_set_proxiable(&options, 0); - /* set a very short lifetime, we don't keep the ticket around */ - krb5_get_init_creds_opt_set_tkt_life(&options, 300); - - krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc, - keytab, 0, NULL, &options); - - if (krberr) { - DEBUG(2, ("Failed to init credentials: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - krberr = krb5_cc_initialize(context, ccache, kprinc); - if (krberr) { - DEBUG(2, ("Failed to init ccache: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - krberr = krb5_cc_store_cred(context, ccache, &my_creds); - if (krberr) { - DEBUG(2, ("Failed to store creds: %s\n", - sss_krb5_get_error_message(context, krberr))); - ret = EFAULT; - goto done; - } - - ret = EOK; - -done: - if (keytab) krb5_kt_close(context, keytab); - if (context) krb5_free_context(context); - return ret; -} - struct sdap_kinit_state { int result; }; -/* TODO: make it really async */ +static void sdap_kinit_done(struct tevent_req *subreq); + struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_handle *sh, + int timeout, const char *keytab, const char *principal, const char *realm) { struct tevent_req *req; + struct tevent_req *subreq; struct sdap_kinit_state *state; int ret; @@ -723,31 +577,62 @@ struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx, ret = setenv("KRB5_KTNAME", keytab, 1); if (ret == -1) { DEBUG(2, ("Failed to set KRB5_KTNAME to %s\n", keytab)); - ret = EFAULT; - goto fail; + return NULL; } } - ret = sdap_krb5_get_tgt_sync(state, realm, principal, keytab); - if (ret == EOK) { - state->result = SDAP_AUTH_SUCCESS; - } else { - goto fail; + subreq = sdap_krb5_get_tgt_send(state, ev, timeout, + realm, principal, keytab); + if (!subreq) { + talloc_zfree(req); + return NULL; } + tevent_req_set_callback(subreq, sdap_kinit_done, req); - tevent_req_post(req, ev); return req; +} -fail: - tevent_req_error(req, ret); - tevent_req_post(req, ev); - return req; +static void sdap_kinit_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_kinit_state *state = tevent_req_data(req, + struct sdap_kinit_state); + + int ret; + int result; + char *ccname = NULL; + + ret = sdap_krb5_get_tgt_recv(subreq, state, &result, &ccname); + talloc_zfree(subreq); + if (ret != EOK) { + state->result = ret; + DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret))); + tevent_req_error(req, ret); + } + + if (result == EOK) { + ret = setenv("KRB5CCNAME", ccname, 1); + if (ret == -1) { + DEBUG(2, ("Unable to set env. variable KRB5CCNAME!\n")); + state->result = EFAULT; + tevent_req_error(req, EFAULT); + } + + state->result = SDAP_AUTH_SUCCESS; + tevent_req_done(req); + return; + } + + DEBUG(4, ("Could not get TGT: %d [%s]\n", result, strerror(result))); + state->result = SDAP_AUTH_FAILED; + tevent_req_error(req, EIO); } int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result) { struct sdap_kinit_state *state = tevent_req_data(req, - struct sdap_kinit_state); + struct sdap_kinit_state); enum tevent_req_state tstate; uint64_t err = EIO; @@ -1068,7 +953,10 @@ static void sdap_cli_kinit_step(struct tevent_req *req) struct sdap_cli_connect_state); struct tevent_req *subreq; - subreq = sdap_kinit_send(state, state->ev, state->sh, + subreq = sdap_kinit_send(state, state->ev, + state->sh, + dp_opt_get_int(state->opts->basic, + SDAP_OPT_TIMEOUT), dp_opt_get_string(state->opts->basic, SDAP_KRB5_KEYTAB), dp_opt_get_string(state->opts->basic, diff --git a/server/providers/ldap/sdap_async_private.h b/server/providers/ldap/sdap_async_private.h index 3d891531d..af8d2b8e4 100644 --- a/server/providers/ldap/sdap_async_private.h +++ b/server/providers/ldap/sdap_async_private.h @@ -41,4 +41,19 @@ struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, int sdap_get_rootdse_recv(struct tevent_req *req, TALLOC_CTX *memctx, struct sysdb_attrs **rootdse); + +/* from sdap_child_helpers.c */ + +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); + +int sdap_krb5_get_tgt_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *result, + char **ccname); + #endif /* _SDAP_ASYNC_PRIVATE_H_ */ diff --git a/server/providers/ldap/sdap_child_helpers.c b/server/providers/ldap/sdap_child_helpers.c new file mode 100644 index 000000000..85f7d3c86 --- /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