summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2009-11-19 18:47:56 -0500
committerStephen Gallagher <sgallagh@redhat.com>2009-11-25 11:03:12 -0500
commitf5aa0c046e75f2ecc2b96424847765c8ccb3a302 (patch)
treedc757760d4579e55172d90a5dae57084c8dccfac /server
parentb67dbadc979cbe86545e2275223483d429b74747 (diff)
downloadsssd-f5aa0c046e75f2ecc2b96424847765c8ccb3a302.tar.gz
sssd-f5aa0c046e75f2ecc2b96424847765c8ccb3a302.tar.xz
sssd-f5aa0c046e75f2ecc2b96424847765c8ccb3a302.zip
Get TGT in a child process.
To avoid blocking in a synchronous call, the TGT is saved in a separate process Fixes: #277
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am19
-rw-r--r--server/man/sssd-ldap.5.xml3
-rw-r--r--server/providers/ldap/ldap_child.c432
-rw-r--r--server/providers/ldap/ldap_common.c3
-rw-r--r--server/providers/ldap/ldap_common.h3
-rw-r--r--server/providers/ldap/ldap_init.c54
-rw-r--r--server/providers/ldap/sdap_async.h1
-rw-r--r--server/providers/ldap/sdap_async_connection.c216
-rw-r--r--server/providers/ldap/sdap_async_private.h15
-rw-r--r--server/providers/ldap/sdap_child_helpers.c500
10 files changed, 1081 insertions, 165 deletions
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 @@
<para>
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.
</para>
<para>
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 <jhrozek@redhat.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#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 <http://www.gnu.org/licenses/>.
*/
+#include <fcntl.h>
+
+#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 <jhrozek@redhat.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#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;
+}
+