From d9e32a84142c17f664aafdb2e0c868a73ba8b538 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 9 Aug 2010 16:09:37 -0400
Subject: [PATCH 4/4] Initialize kerberos service for GSSAPI

---
 Makefile.am                                |    1 +
 src/providers/ipa/ipa_common.c             |    1 +
 src/providers/krb5/krb5_common.c           |    2 +-
 src/providers/krb5/krb5_common.h           |    6 +
 src/providers/ldap/ldap_common.c           |  171 ++++++++++++++++++++++++++++
 src/providers/ldap/ldap_common.h           |   20 ++++
 src/providers/ldap/ldap_init.c             |   11 ++
 src/providers/ldap/sdap.h                  |    1 +
 src/providers/ldap/sdap_async.h            |    3 +
 src/providers/ldap/sdap_async_connection.c |   92 ++++++++++++++-
 10 files changed, 303 insertions(+), 5 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 0919900..4b797a7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -770,6 +770,7 @@ libsss_ldap_la_SOURCES = \
     src/providers/ldap/sdap_fd_events.c \
     src/providers/ldap/sdap_id_op.c \
     src/providers/ldap/sdap.c \
+    src/providers/krb5/krb5_common.c \
     src/util/user_info_msg.c \
     src/util/sss_ldap.c \
     src/util/sss_krb5.c
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 3d999f5..dea1a73 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -542,6 +542,7 @@ int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
         ret = ENOMEM;
         goto done;
     }
+    service->sdap->kinit_service_name = service->krb5_service->name;
 
     realm = talloc_strdup(service, domain);
     if (!realm) {
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
index d41fdb9..3863acd 100644
--- a/src/providers/krb5/krb5_common.c
+++ b/src/providers/krb5/krb5_common.c
@@ -426,7 +426,7 @@ done:
 }
 
 
-static errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm)
+errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm)
 {
     int ret;
     errno_t err;
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
index f3bbbb8..6398ea2 100644
--- a/src/providers/krb5/krb5_common.h
+++ b/src/providers/krb5/krb5_common.h
@@ -136,4 +136,10 @@ errno_t krb5_install_offline_callback(struct be_ctx *be_ctx,
 
 errno_t krb5_install_sigterm_handler(struct tevent_context *ev,
                                      struct krb5_ctx *krb5_ctx);
+
+errno_t write_krb5info_file(const char *realm, const char *kdc,
+                            const char *service);
+
+errno_t remove_krb5_info_files(TALLOC_CTX *mem_ctx, const char *realm);
+
 #endif /* __KRB5_COMMON_H__ */
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index cf2cd69..87fd43a 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -25,6 +25,7 @@
 #include "providers/ldap/ldap_common.h"
 #include "providers/fail_over.h"
 #include "providers/ldap/sdap_async_private.h"
+#include "providers/krb5/krb5_common.h"
 
 #include "util/sss_krb5.h"
 
@@ -420,6 +421,176 @@ static void sdap_uri_callback(void *private_data, struct fo_server *server)
     service->uri = new_uri;
 }
 
+static void sdap_finalize(struct tevent_context *ev,
+                          struct tevent_signal *se,
+                          int signum,
+                          int count,
+                          void *siginfo,
+                          void *private_data)
+{
+    char *realm = (char *) private_data;
+    int ret;
+
+    ret = remove_krb5_info_files(se, realm);
+    if (ret != EOK) {
+        DEBUG(1, ("remove_krb5_info_files failed.\n"));
+    }
+
+    sig_term(signum);
+}
+
+errno_t sdap_install_sigterm_handler(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     const char *realm)
+{
+    char *sig_realm;
+    struct tevent_signal *sige;
+
+    BlockSignals(false, SIGTERM);
+
+    sig_realm = talloc_strdup(mem_ctx, realm);
+    if (sig_realm == NULL) {
+        DEBUG(1, ("talloc_strdup failed!\n"));
+        return ENOMEM;
+    }
+
+    sige = tevent_add_signal(ev, mem_ctx, SIGTERM, SA_SIGINFO, sdap_finalize,
+                             sig_realm);
+    if (sige == NULL) {
+        DEBUG(1, ("tevent_add_signal failed.\n"));
+        talloc_free(sig_realm);
+        return ENOMEM;
+    }
+    talloc_steal(sige, sig_realm);
+
+    return EOK;
+}
+
+void sdap_remove_kdcinfo_files_callback(void *pvt)
+{
+    int ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct remove_info_files_ctx *ctx = talloc_get_type(pvt,
+                                                  struct remove_info_files_ctx);
+
+    ret = be_fo_run_callbacks_at_next_request(ctx->be_ctx,
+                                              ctx->kdc_service_name);
+    if (ret != EOK) {
+        DEBUG(1, ("be_fo_run_callbacks_at_next_request failed, "
+                  "krb5 info files will not be removed, because "
+                  "it is unclear if they will be recreated properly.\n"));
+        return;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(1, ("talloc_new failed, cannot remove krb5 info files.\n"));
+        return;
+    }
+
+    ret = remove_krb5_info_files(tmp_ctx, ctx->realm);
+    if (ret != EOK) {
+        DEBUG(1, ("remove_krb5_info_files failed.\n"));
+    }
+
+    talloc_zfree(tmp_ctx);
+}
+
+
+errno_t sdap_install_offline_callback(TALLOC_CTX *mem_ctx,
+                                      struct be_ctx *be_ctx,
+                                      const char *realm,
+                                      const char *service_name)
+{
+    int ret;
+    struct remove_info_files_ctx *ctx;
+
+    ctx = talloc_zero(mem_ctx, struct remove_info_files_ctx);
+    if (ctx == NULL) {
+        DEBUG(1, ("talloc_zfree failed.\n"));
+        return ENOMEM;
+    }
+
+    ctx->be_ctx = be_ctx;
+    ctx->realm = talloc_strdup(ctx, realm);
+    ctx->kdc_service_name = talloc_strdup(ctx, service_name);
+    if (ctx->realm == NULL || ctx->kdc_service_name == NULL) {
+        DEBUG(1, ("talloc_strdup failed!\n"));
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = be_add_offline_cb(ctx, be_ctx,
+                            sdap_remove_kdcinfo_files_callback,
+                            ctx, NULL);
+    if (ret != EOK) {
+        DEBUG(1, ("be_add_offline_cb failed.\n"));
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    if (ret != EOK) {
+        talloc_zfree(ctx);
+    }
+    return ret;
+}
+
+int sdap_gssapi_init(TALLOC_CTX *mem_ctx,
+                     struct dp_option *opts,
+                     struct be_ctx *bectx,
+                     struct sdap_service *sdap_service,
+                     struct krb5_service **krb5_service)
+{
+    int ret;
+    const char *krb5_servers;
+    const char *krb5_realm;
+    struct krb5_service *service = NULL;
+
+    krb5_servers = dp_opt_get_string(opts, SDAP_KRB5_KDCIP);
+    if (krb5_servers == NULL) {
+        DEBUG(1, ("Missing krb5_kdcip option, using service discovery!\n"));
+    }
+
+    krb5_realm = dp_opt_get_string(opts, SDAP_KRB5_REALM);
+    if (krb5_realm == NULL) {
+        DEBUG(0, ("Missing krb5_realm option, will use libkrb default\n"));
+    }
+
+    ret = krb5_service_init(mem_ctx, bectx, SSS_KRB5KDC_FO_SRV, krb5_servers,
+                            krb5_realm, &service);
+    if (ret != EOK) {
+        DEBUG(0, ("Failed to init KRB5 failover service!\n"));
+        goto done;
+    }
+
+    ret = sdap_install_sigterm_handler(mem_ctx, bectx->ev, krb5_realm);
+    if (ret != EOK) {
+        DEBUG(0, ("Failed to install sigterm handler\n"));
+        goto done;
+    }
+
+    ret = sdap_install_offline_callback(mem_ctx, bectx,
+                                        krb5_realm, SSS_KRB5KDC_FO_SRV);
+    if (ret != EOK) {
+        DEBUG(0, ("Failed to install sigterm handler\n"));
+        goto done;
+    }
+
+    sdap_service->kinit_service_name = talloc_strdup(sdap_service,
+                                                     service->name);
+    if (sdap_service->kinit_service_name == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+    *krb5_service = service;
+done:
+    if (ret != EOK) talloc_free(service);
+    return ret;
+}
+
 int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
                       const char *service_name, const char *dns_service_name,
                       const char *urls, struct sdap_service **_service)
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 2a0e976..e2d74e4 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -26,6 +26,7 @@
 #include "providers/ldap/sdap.h"
 #include "providers/ldap/sdap_id_op.h"
 #include "providers/fail_over.h"
+#include "providers/krb5/krb5_common.h"
 
 #define PWD_POL_OPT_NONE "none"
 #define PWD_POL_OPT_SHADOW "shadow"
@@ -44,6 +45,8 @@ struct sdap_id_ctx {
 
     /* what rootDSE returns */
     struct sysdb_attrs *rootDSE;
+    /* If using GSSAPI */
+    struct krb5_service *krb5_service;
 
     /* LDAP connection cache */
     struct sdap_id_conn_cache *conn_cache;
@@ -86,6 +89,23 @@ int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
                       const char *service_name, const char *dns_service_name,
                       const char *urls, struct sdap_service **_service);
 
+int sdap_gssapi_init(TALLOC_CTX *mem_ctx,
+                     struct dp_option *opts,
+                     struct be_ctx *bectx,
+                     struct sdap_service *sdap_service,
+                     struct krb5_service **krb5_service);
+
+errno_t sdap_install_offline_callback(TALLOC_CTX *mem_ctx,
+                                      struct be_ctx *be_ctx,
+                                      const char *realm,
+                                      const char *service_name);
+
+errno_t sdap_install_sigterm_handler(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     const char *realm);
+
+void sdap_remove_kdcinfo_files_callback(void *pvt);
+
 /* options parser */
 int ldap_get_options(TALLOC_CTX *memctx,
                      struct confdb_ctx *cdb,
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index b0b967d..86598ca 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -98,6 +98,17 @@ int sssm_ldap_id_init(struct be_ctx *bectx,
         goto done;
     }
 
+    if (dp_opt_get_bool(ctx->opts->basic, SDAP_KRB5_KINIT)) {
+        ret = sdap_gssapi_init(ctx, ctx->opts->basic,
+                               ctx->be, ctx->service,
+                               &ctx->krb5_service);
+        if (ret !=  EOK) {
+            DEBUG(1, ("sdap_gssapi_init failed [%d][%s].\n",
+                       ret, strerror(ret)));
+            goto done;
+        }
+    }
+
     ret = setup_tls_config(ctx->opts->basic);
     if (ret != EOK) {
         DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index a5fab7a..2b4318e 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -106,6 +106,7 @@ struct sdap_handle {
 struct sdap_service {
     char *name;
     char *uri;
+    char *kinit_service_name;
 };
 
 struct sdap_ppolicy_data {
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index e5a60da..2088801 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -61,12 +61,15 @@ int sdap_get_groups_recv(struct tevent_req *req,
 
 struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
                                    struct tevent_context *ev,
+                                   struct be_ctx *be,
                                    struct sdap_handle *sh,
+                                   const char *service_name,
                                    int    timeout,
                                    const char *keytab,
                                    const char *principal,
                                    const char *realm,
                                    int lifetime);
+
 int sdap_kinit_recv(struct tevent_req *req,
                     enum sdap_result *result,
                     time_t *expire_time);
diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c
index 3c536a4..b620505 100644
--- a/src/providers/ldap/sdap_async_connection.c
+++ b/src/providers/ldap/sdap_async_connection.c
@@ -627,15 +627,30 @@ static int sasl_bind_recv(struct tevent_req *req, int *ldaperr)
 /* ==Perform-Kinit-given-keytab-and-principal============================= */
 
 struct sdap_kinit_state {
+    const char *keytab;
+    const char *principal;
+    const char *realm;
+    int    timeout;
+    int    lifetime;
+
+    const char *krb_service_name;
+    struct tevent_context *ev;
+    struct be_ctx *be;
+
+    struct fo_server *kdc_srv;
     int result;
     time_t expire_time;
 };
 
 static void sdap_kinit_done(struct tevent_req *subreq);
+static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req);
+static void sdap_kinit_kdc_resolved(struct tevent_req *subreq);
 
 struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
                                    struct tevent_context *ev,
+                                   struct be_ctx *be,
                                    struct sdap_handle *sh,
+                                   const char *krb_service_name,
                                    int    timeout,
                                    const char *keytab,
                                    const char *principal,
@@ -659,26 +674,83 @@ struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
     if (!req) return NULL;
 
     state->result = SDAP_AUTH_FAILED;
+    state->keytab = keytab;
+    state->principal = principal;
+    state->realm = realm;
+    state->ev = ev;
+    state->be = be;
+    state->timeout = timeout;
+    state->lifetime = lifetime;
+    state->krb_service_name = krb_service_name;
 
     if (keytab) {
         ret = setenv("KRB5_KTNAME", keytab, 1);
         if (ret == -1) {
             DEBUG(2, ("Failed to set KRB5_KTNAME to %s\n", keytab));
+            talloc_free(req);
             return NULL;
         }
     }
 
-    subreq = sdap_get_tgt_send(state, ev, realm, principal, keytab, lifetime,
-                               timeout);
+    subreq = sdap_kinit_next_kdc(req);
     if (!subreq) {
-        talloc_zfree(req);
+        talloc_free(req);
         return NULL;
     }
-    tevent_req_set_callback(subreq, sdap_kinit_done, req);
 
     return req;
 }
 
+static struct tevent_req *sdap_kinit_next_kdc(struct tevent_req *req)
+{
+    struct tevent_req *next_req;
+    struct sdap_kinit_state *state = tevent_req_data(req,
+                                                    struct sdap_kinit_state);
+
+    DEBUG(7, ("Resolving next KDC for service %s\n", state->krb_service_name));
+
+    next_req = be_resolve_server_send(state, state->ev,
+                                      state->be,
+                                      state->krb_service_name);
+    if (next_req == NULL) {
+        DEBUG(1, ("be_resolve_server_send failed.\n"));
+        return NULL;
+    }
+    tevent_req_set_callback(next_req, sdap_kinit_kdc_resolved, req);
+
+    return next_req;
+}
+
+static void sdap_kinit_kdc_resolved(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);
+    struct tevent_req *tgtreq;
+    int ret;
+
+    ret = be_resolve_server_recv(subreq, &state->kdc_srv);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* all servers have been tried and none
+         * was found good, go offline */
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    DEBUG(7, ("KDC resolved, attempting to get TGT...\n"));
+
+    tgtreq = sdap_get_tgt_send(state, state->ev, state->realm,
+                               state->principal, state->keytab,
+                               state->lifetime, state->timeout);
+    if (!tgtreq) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(tgtreq, sdap_kinit_done, req);
+}
+
 static void sdap_kinit_done(struct tevent_req *subreq)
 {
     struct tevent_req *req = tevent_req_callback_data(subreq,
@@ -691,6 +763,7 @@ static void sdap_kinit_done(struct tevent_req *subreq)
     char *ccname = NULL;
     time_t expire_time;
     krb5_error_code kerr;
+    struct tevent_req *nextreq;
 
     ret = sdap_get_tgt_recv(subreq, state, &result,
                             &kerr, &ccname, &expire_time);
@@ -714,6 +787,15 @@ static void sdap_kinit_done(struct tevent_req *subreq)
         state->result = SDAP_AUTH_SUCCESS;
         tevent_req_done(req);
         return;
+    } else {
+        if (kerr == KRB5_KDC_UNREACH) {
+            nextreq = sdap_kinit_next_kdc(req);
+            if (!nextreq) {
+                tevent_req_error(req, ENOMEM);
+            }
+            return;
+        }
+
     }
 
     DEBUG(4, ("Could not get TGT: %d [%s]\n", result, strerror(result)));
@@ -1152,7 +1234,9 @@ static void sdap_cli_kinit_step(struct tevent_req *req)
     struct tevent_req *subreq;
 
     subreq = sdap_kinit_send(state, state->ev,
+                             state->be,
                              state->sh,
+                             state->service->kinit_service_name,
                         dp_opt_get_int(state->opts->basic,
                                                    SDAP_OPT_TIMEOUT),
                         dp_opt_get_string(state->opts->basic,
-- 
1.7.2.3

