>From 23307aaee0473332ec17ec42109c927adc5063ae Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Thu, 19 Nov 2009 18:47:56 -0500
Subject: [PATCH 2/2] 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

diff --git a/server/Makefile.am b/server/Makefile.am
index b80cdfd..c33b8fe 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)
 
@@ -565,6 +566,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
@@ -621,6 +623,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 \
@@ -658,6 +661,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 d944392..9172fa2 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 0000000..9c11bf4
--- /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 58c6d69..d43d148 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 1110276..188a7b9 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 5a64585..c6241bf 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 d855079..e18fb69 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 a5e6ccf..b742471 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 3d89153..af8d2b8 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 0000000..85f7d3c
--- /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;
+}
+
-- 
1.6.2.5

