Avoid overriding default ccache for ads operations.
authorSimo Sorce <idra@samba.org>
Fri, 7 Sep 2012 18:14:08 +0000 (14:14 -0400)
committerAlexander Bokovoy <ab@samba.org>
Wed, 12 Sep 2012 19:18:09 +0000 (21:18 +0200)
Avoid overriding default ccache for ads operations.

Nowadays various samba components may need to use GSSAPI and a default cred
cache to perform their tasks.
This code was completely overriding the whole process default ccache name, thus
altering the current credentials and sometimes hijacking them (or getting
preemptively hijaked).

By using gss_krb5_import_cred we can instead use a private ccache (necessary
sometimes to use a different set of credentials fromt he default
cifs/fqdn@realm one, for example when contacting foreign DCs using trust
credentials) that does not affect the rest of the process.

For the kerberos versions which don't have gss_krb5_import_cred
we fallback to temp override of KRB5CCNAME and gss_acquire_cred.

Signed-off-by: Alexander Bokovoy <ab@samba.org>
Signed-off-by: Günther Deschner <gd@samba.org>
Autobuild-User(master): Alexander Bokovoy <ab@samba.org>
Autobuild-Date(master): Wed Sep 12 21:18:09 CEST 2012 on sn-devel-104

source3/include/ads.h
source3/include/proto.h
source3/libads/ads_struct.c
source3/libads/kerberos_util.c
source3/libads/sasl.c
source3/libsmb/cliconnect.c
source3/libsmb/clispnego.c
source3/winbindd/winbindd_ads.c

index 91a0f8162f90b302ddfe48517520235902ec5353..3de1d8b199ef6fd669233605eed4c78b70ffbbef 100644 (file)
@@ -45,6 +45,7 @@ typedef struct ads_struct {
                char *kdc_server;
                unsigned flags;
                int time_offset;
+               char *ccache_name;
                time_t tgt_expire;
                time_t tgs_expire;
                time_t renewable;
index 6dbdf4eae1049f25cd573a1e2ef0a2676e9408e5..b3fa55a9143ffb2578fbdb4f450c565dcc490c81 100644 (file)
@@ -768,7 +768,7 @@ int spnego_gen_krb5_negTokenInit(TALLOC_CTX *ctx,
                            const char *principal, int time_offset,
                            DATA_BLOB *targ,
                            DATA_BLOB *session_key_krb5, uint32 extra_ap_opts,
-                           time_t *expire_time);
+                           const char *ccname, time_t *expire_time);
 bool spnego_parse_challenge(TALLOC_CTX *ctx, const DATA_BLOB blob,
                            DATA_BLOB *chal1, DATA_BLOB *chal2);
 DATA_BLOB spnego_gen_auth(TALLOC_CTX *ctx, DATA_BLOB blob);
index 45d00a39a3a2ea712ce1de47b6d6c1dfd6f9b6aa..fd7e417bad57317ab29fa0fb3b0dfa93f173fc77 100644 (file)
@@ -201,6 +201,7 @@ void ads_destroy(ADS_STRUCT **ads)
                SAFE_FREE((*ads)->auth.password);
                SAFE_FREE((*ads)->auth.user_name);
                SAFE_FREE((*ads)->auth.kdc_server);
+               SAFE_FREE((*ads)->auth.ccache_name);
 
                SAFE_FREE((*ads)->config.realm);
                SAFE_FREE((*ads)->config.bind_path);
index 7d49c02869f7a86279b0729d6cbe47f2546cd5fd..645b0582621d68dfd3454255c4a2350fe79d0e19 100644 (file)
@@ -63,9 +63,11 @@ int ads_kinit_password(ADS_STRUCT *ads)
                return KRB5_LIBOS_CANTREADPWD;
        }
 
-       ret = kerberos_kinit_password_ext(s, ads->auth.password, ads->auth.time_offset,
-                       &ads->auth.tgt_expire, NULL, NULL, False, False, ads->auth.renewable,
-                       NULL);
+       ret = kerberos_kinit_password_ext(s, ads->auth.password,
+                                         ads->auth.time_offset,
+                                         &ads->auth.tgt_expire, NULL,
+                                         ads->auth.ccache_name, false, false,
+                                         ads->auth.renewable, NULL);
 
        if (ret) {
                DEBUG(0,("kerberos_kinit_password %s failed: %s\n",
index 438db05df892dbd6367a17ae6db5aebec18d26c2..33f4e2419183681277b41e10622e2f1e6608e47e 100644 (file)
@@ -271,6 +271,74 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
 }
 
 #ifdef HAVE_KRB5
+static ADS_STATUS ads_init_gssapi_cred(ADS_STRUCT *ads, gss_cred_id_t *cred)
+{
+       ADS_STATUS status;
+       krb5_context kctx;
+       krb5_error_code kerr;
+       krb5_ccache kccache = NULL;
+       uint32_t maj, min;
+
+       *cred = GSS_C_NO_CREDENTIAL;
+
+       if (!ads->auth.ccache_name) {
+               return ADS_SUCCESS;
+       }
+
+       kerr = krb5_init_context(&kctx);
+       if (kerr) {
+               return ADS_ERROR_KRB5(kerr);
+       }
+
+#ifdef HAVE_GSS_KRB5_IMPORT_CRED
+       kerr = krb5_cc_resolve(kctx, ads->auth.ccache_name, &kccache);
+       if (kerr) {
+               status = ADS_ERROR_KRB5(kerr);
+               goto done;
+       }
+
+       maj = gss_krb5_import_cred(&min, kccache, NULL, NULL, cred);
+       if (maj != GSS_S_COMPLETE) {
+               status = ADS_ERROR_GSS(maj, min);
+               goto done;
+       }
+#else
+       /* We need to fallback to overriding the default creds.
+        * This operation is not thread safe as it changes the process
+        * environment variable, but we do not have any better option
+        * with older kerberos libraries */
+       {
+               const char *oldccname = NULL;
+
+               oldccname = getenv("KRB5CCNAME");
+               setenv("KRB5CCNAME", ads->auth.ccache_name, 1);
+
+               maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+                                      NULL, GSS_C_INITIATE, cred, NULL, NULL);
+
+               if (oldccname) {
+                       setenv("KRB5CCNAME", oldccname, 1);
+               } else {
+                       unsetenv("KRB5CCNAME");
+               }
+
+               if (maj != GSS_S_COMPLETE) {
+                       status = ADS_ERROR_GSS(maj, min);
+                       goto done;
+               }
+       }
+#endif
+
+       status = ADS_SUCCESS;
+
+done:
+       if (!ADS_ERR_OK(status) && kccache != NULL) {
+               krb5_cc_close(kctx, kccache);
+       }
+       krb5_free_context(kctx);
+       return status;
+}
+
 static ADS_STATUS ads_sasl_gssapi_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
 {
        gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
@@ -377,6 +445,7 @@ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t
        bool ok;
        uint32 minor_status;
        int gss_rc, rc;
+       gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
        gss_OID_desc krb5_mech_type =
        {9, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") };
        gss_OID mech_type = &krb5_mech_type;
@@ -390,6 +459,11 @@ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t
        DATA_BLOB wrapped;
        struct berval cred, *scred = NULL;
 
+       status = ads_init_gssapi_cred(ads, &gss_cred);
+       if (!ADS_ERR_OK(status)) {
+               goto failed;
+       }
+
        input_token.value = NULL;
        input_token.length = 0;
 
@@ -407,7 +481,7 @@ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t
 
        /* Note: here we explicit ask for the krb5 mech_type */
        gss_rc = gss_init_sec_context(&minor_status,
-                                     GSS_C_NO_CREDENTIAL,
+                                     gss_cred,
                                      &context_handle,
                                      serv_name,
                                      mech_type,
@@ -544,7 +618,7 @@ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t
         * to gssapi
         */
        gss_rc = gss_init_sec_context(&minor_status,
-                                     GSS_C_NO_CREDENTIAL,
+                                     gss_cred,
                                      &context_handle,
                                      serv_name,
                                      mech_type,
@@ -606,6 +680,8 @@ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t
        status = ADS_SUCCESS;
 
 failed:
+       if (gss_cred != GSS_C_NO_CREDENTIAL)
+               gss_release_cred(&minor_status, &gss_cred);
        if (context_handle != GSS_C_NO_CONTEXT)
                gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
        return status;
@@ -791,6 +867,7 @@ static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *prin
 
        rc = spnego_gen_krb5_negTokenInit(talloc_tos(), principal,
                                     ads->auth.time_offset, &blob, &session_key, 0,
+                                    ads->auth.ccache_name,
                                     &ads->auth.tgs_expire);
 
        if (rc) {
@@ -952,6 +1029,7 @@ failed:
 static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
 {
        uint32 minor_status;
+       gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
        gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
        gss_OID mech_type = GSS_C_NULL_OID;
        gss_buffer_desc output_token, input_token;
@@ -969,6 +1047,11 @@ static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv
        input_token.value = NULL;
        input_token.length = 0;
 
+       status = ads_init_gssapi_cred(ads, &gss_cred);
+       if (!ADS_ERR_OK(status)) {
+               goto failed;
+       }
+
        /*
         * Note: here we always ask the gssapi for sign and seal
         *       as this is negotiated later after the mutal
@@ -978,7 +1061,7 @@ static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv
 
        for (i=0; i < MAX_GSS_PASSES; i++) {
                gss_rc = gss_init_sec_context(&minor_status,
-                                         GSS_C_NO_CREDENTIAL,
+                                         gss_cred,
                                          &context_handle,
                                          serv_name,
                                          mech_type,
@@ -1137,7 +1220,8 @@ static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv
        }
 
 failed:
-
+       if (gss_cred != GSS_C_NO_CREDENTIAL)
+               gss_release_cred(&minor_status, &gss_cred);
        if (context_handle != GSS_C_NO_CONTEXT)
                gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
 
index b74faa6fd9cd039225442675c95b407b2a5e36a5..9ce013ee6427f5963f80ec1ba1fa074313c5b16f 100644 (file)
@@ -1461,7 +1461,7 @@ static struct tevent_req *cli_session_setup_kerberos_send(
         * we have to acquire a ticket. To be fixed later :-)
         */
        rc = spnego_gen_krb5_negTokenInit(state, principal, 0, &state->negTokenTarg,
-                                    &state->session_key_krb5, 0, NULL);
+                                    &state->session_key_krb5, 0, NULL, NULL);
        if (rc) {
                DEBUG(1, ("cli_session_setup_kerberos: "
                          "spnego_gen_krb5_negTokenInit failed: %s\n",
index 81f9dfb20c4055d2f7727ed43ed24346454b950c..a17efbf75b2eb7e2da287667a26e5cdd7ac49cc3 100644 (file)
@@ -255,7 +255,7 @@ int spnego_gen_krb5_negTokenInit(TALLOC_CTX *ctx,
                            const char *principal, int time_offset,
                            DATA_BLOB *targ,
                            DATA_BLOB *session_key_krb5, uint32 extra_ap_opts,
-                           time_t *expire_time)
+                           const char *ccname, time_t *expire_time)
 {
        int retval;
        DATA_BLOB tkt, tkt_wrapped;
@@ -264,7 +264,7 @@ int spnego_gen_krb5_negTokenInit(TALLOC_CTX *ctx,
        /* get a kerberos ticket for the service and extract the session key */
        retval = cli_krb5_get_ticket(ctx, principal, time_offset,
                                          &tkt, session_key_krb5,
-                                         extra_ap_opts, NULL,
+                                         extra_ap_opts, ccname,
                                          expire_time, NULL);
        if (retval) {
                return retval;
index 4efd829cb9d0e7cb79e04242375a447e01e15a17..628fd1c3f2790c7902b0e8651240c6779a1ce921 100644 (file)
@@ -78,15 +78,15 @@ static ADS_STRUCT *ads_cached_connection(struct winbindd_domain *domain)
                }
        }
 
-       /* we don't want this to affect the users ccache */
-       setenv("KRB5CCNAME", "MEMORY:winbind_ccache", 1);
-
        ads = ads_init(domain->alt_name, domain->name, NULL);
        if (!ads) {
                DEBUG(1,("ads_init for domain %s failed\n", domain->name));
                return NULL;
        }
 
+       /* we don't want ads operations to affect the default ccache */
+       ads->auth.ccache_name = SMB_STRDUP("MEMORY:winbind_ccache");
+
        /* the machine acct password might have change - fetch it every time */
 
        SAFE_FREE(ads->auth.password);