Set the canonicalize flag in TGS requests and accept cross-realm referral tickets.
authorSam Hartman <hartmans@mit.edu>
Thu, 21 Sep 2006 01:48:50 +0000 (01:48 +0000)
committerSam Hartman <hartmans@mit.edu>
Thu, 21 Sep 2006 01:48:50 +0000 (01:48 +0000)
We do not yet accept tickets in which the server name changes.

* krb5_sname_to_principal:   If there is no domain realm mapping return null realm
*krb5_get_cred_via_tkt: New behavior as described below

1) the referrals case:
  - check for TGT for initial realm
    - if a remote realm was specified (which must have happened via a
      domain_realm mapping), obtain a TGT for it the standard way and
      start with that.
  - use client realm for server if not specified
  - iterate through this loop:
    - request ticket with referrals turned on
    - if that fails:
      - if this was the first request, punt to non-referrals case
      - otherwise, retry once without referrals turned on then terminate
        either way
    - if it works, either use the service ticket or follow the referral path
    - if loop count exceeded, hardfail
2) the nonreferrals case
  - this is mostly the old walk_realm_tree TGT-finding (which allows
    limited shortcut referrals per 4120) followed by a standard tgs-req.
  - originally requested principal is used for this, although if we were
    handed something without a realm, determine a fallback realm based on
    DNS TXT records or a truncation of the domain name.

ticket: 2652
Owner: amb

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@18598 dc483132-0cff-0310-8789-dd5450dbe970

13 files changed:
src/appl/telnet/libtelnet/kerberos5.c
src/include/k5-int.h
src/include/krb5/krb5.hin
src/lib/krb5/krb/copy_princ.c
src/lib/krb5/krb/gc_frm_kdc.c
src/lib/krb5/krb/gc_via_tkt.c
src/lib/krb5/krb/parse.c
src/lib/krb5/krb/princ_comp.c
src/lib/krb5/krb/walk_rtree.c
src/lib/krb5/libkrb5.exports
src/lib/krb5/os/hst_realm.c
src/lib/krb5/os/sn2princ.c
src/lib/krb5_32.def

index 958a4ce4a6897cc5e0e6d78008d6a0890b44682c..9761f6dd99d80e7f294ac4480b7412eddf5294a5 100644 (file)
@@ -249,6 +249,7 @@ kerberos5_send(ap)
        }
 
        memset((char *)&creds, 0, sizeof(creds));
+       printf("calling krb5_sname_to_principal\n");
        if ((r = krb5_sname_to_principal(telnet_context, RemoteHostName,
                                         "host", KRB5_NT_SRV_HST,
                                         &creds.server))) {
@@ -256,6 +257,7 @@ kerberos5_send(ap)
                printf("telnet: Kerberos V5: error while constructing service name: %s\r\n", error_message(r));
            return(0);
        }
+       printf("done calling krb5_sname_to_principal\n");
 
        if (telnet_krb5_realm != NULL) {
            krb5_data rdata;
index a55fea7a49eadc420bbd2d9c30642d79f717dd81..46f4e03e33ff9535e0726d6c6cf32a93f0b20748 100644 (file)
@@ -1893,4 +1893,21 @@ extern int krb5int_prng_init(void);
 #define krb5_copy_error_state(CTX, OCTX) \
        krb5int_set_error(&(CTX)->errinfo, (OCTX)->errinfo.code, "%s", (OCTX)->errinfo.msg)
 
+/*
+ * Referral definitions, debugging hooks, and subfunctions.
+ */
+#define        KRB5_REFERRAL_MAXHOPS   5
+/* #define DEBUG_REFERRALS */
+
+#ifdef DEBUG_REFERRALS
+void krb5int_dbgref_dump_principal(char *, krb5_principal);
+#endif
+
+/* Common hostname-parsing code. */
+krb5_error_code KRB5_CALLCONV krb5int_clean_hostname
+       (krb5_context,
+               const char *,
+               char *,
+               size_t);
+
 #endif /* _KRB5_INT_H */
index 23f93c7a9bacd053deee396e4c0eb5660383142b..418a21c0a48bf0ef8e9ba67c6f028ebae33d68f0 100644 (file)
@@ -259,6 +259,16 @@ typedef const krb5_principal_data *krb5_const_principal;
             ? (princ)->data + (i)                      \
             : NULL)
 
+/*
+ * Constants for realm referrals.
+ */
+#define        KRB5_REFERRAL_REALM     ""
+
+/*
+ * Referral-specific functions.
+ */
+krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(krb5_data *);
+
 /*
  * end "base-defs.h"
  */
@@ -554,6 +564,9 @@ krb5_error_code KRB5_CALLCONV
 #define KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID 26
 #define KRB5_KEYUSAGE_PA_SAM_RESPONSE          27
 
+/* Defined in KDC referrals draft */
+#define KRB5_KEYUSAGE_PA_REFERRAL              26 /* XXX note conflict with above */
+
 krb5_boolean KRB5_CALLCONV krb5_c_valid_enctype
        (krb5_enctype ktype);
 krb5_boolean KRB5_CALLCONV krb5_c_valid_cksumtype
@@ -690,7 +703,7 @@ krb5_error_code krb5_decrypt_data
 /* #define     KDC_OPT_RESERVED        0x00080000 */
 /* #define     KDC_OPT_RESERVED        0x00040000 */
 #define        KDC_OPT_REQUEST_ANONYMOUS       0x00020000
-/* #define     KDC_OPT_RESERVED        0x00010000 */
+#define        KDC_OPT_CANONICALIZE            0x00010000
 /* #define     KDC_OPT_RESERVED        0x00008000 */
 /* #define     KDC_OPT_RESERVED        0x00004000 */
 /* #define     KDC_OPT_RESERVED        0x00002000 */
@@ -873,7 +886,8 @@ krb5_error_code krb5_decrypt_data
 #define KRB5_PADATA_SAM_RESPONSE       13 /* draft challenge system response */
 #define KRB5_PADATA_PK_AS_REQ          14 /* PKINIT */
 #define KRB5_PADATA_PK_AS_REP          15 /* PKINIT */
-#define KRB5_PADATA_ETYPE_INFO2 19
+#define KRB5_PADATA_ETYPE_INFO2                19
+#define KRB5_PADATA_REFERRAL           25 /* draft referral system */
 #define KRB5_PADATA_SAM_CHALLENGE_2    30 /* draft challenge system, updated */
 #define KRB5_PADATA_SAM_RESPONSE_2     31 /* draft challenge system, updated */
     
@@ -2242,6 +2256,10 @@ krb5_error_code KRB5_CALLCONV krb5_get_host_realm
        (krb5_context,
                const char *,
                char *** );
+krb5_error_code KRB5_CALLCONV krb5_get_fallback_host_realm
+       (krb5_context,
+               krb5_data *,
+               char *** );
 krb5_error_code KRB5_CALLCONV krb5_free_host_realm
        (krb5_context,
                char * const * );
index d16c17b5c6fcd57e8bfeae4e0d38897de700a90d..85dc816ad809800385f19a24da5efccd12744ca7 100644 (file)
@@ -71,20 +71,18 @@ krb5_copy_principal(krb5_context context, krb5_const_principal inprinc, krb5_pri
             krb5_princ_component(context, tempprinc, i)->data = 0;
     }
 
-    if (tempprinc->realm.length) {
-        tempprinc->realm.data =
-           malloc(tempprinc->realm.length = inprinc->realm.length);
-        if (!tempprinc->realm.data) {
-           for (i = 0; i < nelems; i++)
-                free(krb5_princ_component(context, tempprinc, i)->data);
-           free(tempprinc->data);
-           free(tempprinc);
-           return ENOMEM;
-        }
-       memcpy(tempprinc->realm.data, inprinc->realm.data,
-              inprinc->realm.length);
-    } else
-        tempprinc->realm.data = 0;
+    tempprinc->realm.data =
+        malloc((tempprinc->realm.length = inprinc->realm.length) + 1);
+    if (!tempprinc->realm.data) {
+        for (i = 0; i < nelems; i++)
+           free(krb5_princ_component(context, tempprinc, i)->data);
+       free(tempprinc->data);
+       free(tempprinc);
+       return ENOMEM;
+    }
+    memcpy(tempprinc->realm.data, inprinc->realm.data,
+          inprinc->realm.length);
+    tempprinc->realm.data[tempprinc->realm.length] = 0;
 
     *outprinc = tempprinc;
     return 0;
index 807b81a86d418b1035670ea9dcfbb06ed278a440..44e1b6c3946bab49ff613f6b9e5a49c0b191f661 100644 (file)
@@ -753,6 +753,11 @@ cleanup:
  * On success, OUT_CRED contains the desired credentials; the caller
  * must free them.
  *
+ * Beware memory management issues if you have modifications in mind.
+ * With the addition of referral support, it is now the case that *tgts,
+ * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to
+ * the same credential at different times.
+ *
  * Returns errors, system errors.
  */
 
@@ -761,35 +766,241 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
                           krb5_creds *in_cred, krb5_creds **out_cred,
                           krb5_creds ***tgts, int kdcopt)
 {
-    krb5_error_code retval;
-    krb5_principal client, server;
-    krb5_creds tgtq, cc_tgt, *tgtptr;
+    krb5_error_code retval, subretval;
+    krb5_principal client, server, supplied_server, out_supplied_server;
+    krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS];
     krb5_boolean old_use_conf_ktypes;
+    char **hrealms;
+    int referral_count, i;
 
+    /* 
+     * Set up client and server pointers.  Make a fresh and modifyable
+     * copy of the in_cred server and save the supplied version.
+     */
     client = in_cred->client;
-    server = in_cred->server;
+    if ((retval=krb5_copy_principal(context, in_cred->server, &server)))
+        return retval;
+    /* We need a second copy for the output creds. */
+    if ((retval = krb5_copy_principal(context, server, &out_supplied_server)) != 0 ) {
+       krb5_free_principal(context, server);
+       return retval;
+    }
+    supplied_server = in_cred->server;
+    in_cred->server=server;
+
+
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc initial client", client);
+    krb5int_dbgref_dump_principal("gc_from_kdc initial server", server);
+#endif
     memset(&cc_tgt, 0, sizeof(cc_tgt));
     memset(&tgtq, 0, sizeof(tgtq));
+    memset(&referral_tgts, 0, sizeof(referral_tgts));
+
     tgtptr = NULL;
     *tgts = NULL;
+    *out_cred=NULL;
     old_use_conf_ktypes = context->use_conf_ktypes;
 
+    /* Copy client realm to server if no hint. */
+    if (krb5_is_referral_realm(&server->realm)) {
+        /* Use the client realm. */
+#ifdef DEBUG_REFERRALS
+        printf("gc_from_kdc: no server realm supplied, using client realm.\n");
+#endif
+       krb5_free_data_contents(context, &server->realm);
+       if (!( server->realm.data = (char *)malloc(client->realm.length+1)))
+           return ENOMEM;
+       memcpy(server->realm.data, client->realm.data, client->realm.length);
+       server->realm.length = client->realm.length;
+       server->realm.data[server->realm.length] = 0;
+    }
     /*
-     * Get a TGT for the target realm.
+     * Retreive initial TGT to match the specified server, either for the
+     * local realm in the default (referral) case or for the remote
+     * realm if we're starting someplace non-local.
+     */
+    retval = tgt_mcred(context, client, server, client, &tgtq);
+    if (retval)
+       goto cleanup;
+
+    /* Fast path: Is it in the ccache? */
+    context->use_conf_ktypes = 1;
+    retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
+                                  &tgtq, &cc_tgt);
+    if (!retval) {
+       tgtptr = &cc_tgt;
+    } else if (!HARD_CC_ERR(retval)) {
+#ifdef DEBUG_REFERRALS
+        printf("gc_from_kdc: starting do_traversal to find initial TGT for referral\n");
+#endif
+       retval = do_traversal(context, ccache, client, server,
+                             &cc_tgt, &tgtptr, tgts);
+    }
+    if (retval) {
+#ifdef DEBUG_REFERRALS
+        printf("gc_from_kdc: failed to find initial TGT for referral\n");
+#endif
+        goto cleanup;
+    }
+
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc: server as requested", supplied_server);
+#endif
+
+    /*
+     * Try requesting a service ticket from our local KDC with referrals
+     * turned on.  If the first referral succeeds, follow a referral-only
+     * path, otherwise fall back to old-style assumptions.
      */
 
+    for (referral_count=0;referral_count<KRB5_REFERRAL_MAXHOPS;referral_count++) {
+#ifdef DEBUG_REFERRALS
+#if 0
+        krb5int_dbgref_dump_principal("gc_from_kdc: referral loop: tgt in use", tgtptr->server);
+        krb5int_dbgref_dump_principal("gc_from_kdc: referral loop: request is for", server);
+#endif
+#endif
+        retval = krb5_get_cred_via_tkt(context, tgtptr,
+                                      KDC_OPT_CANONICALIZE | 
+                                      FLAGS2OPTS(tgtptr->ticket_flags) |  
+                                      kdcopt |
+                                      (in_cred->second_ticket.length ?
+                                       KDC_OPT_ENC_TKT_IN_SKEY : 0),
+                                      tgtptr->addresses, in_cred, out_cred);
+       if (retval) {
+#ifdef DEBUG_REFERRALS
+           printf("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",error_message(retval));
+#endif
+           /* If we haven't gone anywhere yet, fail through to the
+              non-referral case. */
+           if (referral_count==0) {
+#ifdef DEBUG_REFERRALS
+               printf("gc_from_kdc: initial referral failed; punting to fallback.\n");
+#endif
+               break;
+           }
+           /* Otherwise, try the same query without canonicalization
+              set, and fail hard if that doesn't work. */
+#ifdef DEBUG_REFERRALS
+           printf("gc_from_kdc: referral #%d failed; retrying without option.\n",
+                  referral_count+1);
+#endif
+           retval = krb5_get_cred_via_tkt(context, tgtptr,
+                                          FLAGS2OPTS(tgtptr->ticket_flags) |  
+                                          kdcopt |
+                                          (in_cred->second_ticket.length ?
+                                           KDC_OPT_ENC_TKT_IN_SKEY : 0),
+                                          tgtptr->addresses,
+                                          in_cred, out_cred);
+           /* Whether or not that succeeded, we're done. */
+           goto cleanup;
+       }
+       else {
+           /* Referral request succeeded; let's see what it is. */
+           if (krb5_principal_compare(context, in_cred->server, (*out_cred)->server)) {
+#ifdef DEBUG_REFERRALS
+               printf("gc_from_kdc: request generated ticket for requested server principal\n");
+               krb5int_dbgref_dump_principal("gc_from_kdc final referred reply",in_cred->server);
+#endif
+               goto cleanup;
+           }
+           else {
+#ifdef DEBUG_REFERRALS
+               printf("gc_from_kdc: request generated referral tgt\n");
+               krb5int_dbgref_dump_principal("gc_from_kdc credential received", (*out_cred)->server);
+#endif
+               /* Check for referral routing loop. */
+               for (i=0;i<referral_count;i++) {
+#ifdef DEBUG_REFERRALS
+#if 0
+                   krb5int_dbgref_dump_principal("gc_from_kdc: loop compare #1", (*out_cred)->server);
+                   krb5int_dbgref_dump_principal("gc_from_kdc: loop compare #2", referral_tgts[i]->server);
+#endif
+#endif
+                   if (krb5_principal_compare(context, (*out_cred)->server, referral_tgts[i]->server)) {
+                       fprintf(stderr, "krb5_get_cred_from_kdc_opt: referral routing loop afer %d hops\n",i);
+                       retval=KRB5_KDC_UNREACH;
+                       goto cleanup;
+                   }
+               }
+               /* Point current tgt pointer at newly-received TGT. */
+               if (tgtptr == &cc_tgt)
+                   krb5_free_cred_contents(context, tgtptr);
+               tgtptr=*out_cred;
+               /* Save pointer to tgt in referral_tgts. */
+               referral_tgts[referral_count]=*out_cred;
+               /* Copy krbtgt realm to server principal. */
+               krb5_free_data_contents(context, &server->realm);
+               if ((retval=krb5int_copy_data_contents(context, &tgtptr->server->data[1], &server->realm)))
+                   return retval;
+               /* Future work: rewrite server principal per any supplied padata. */
+           }
+       }
+    }
+
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc client at fallback", client);
+    krb5int_dbgref_dump_principal("gc_from_kdc server at fallback", server);
+#endif
+
+    /*
+     * At this point referrals have been tried and have failed.  Go back
+     * to the server principal as originally issued and try the conventional path.
+     */
+  
+    /* Referrals have failed.  Look up fallback realm if not originally provided. */
+    if (krb5_is_referral_realm(&supplied_server->realm)) {
+        if (server->length >= 2) {
+           retval=krb5_get_fallback_host_realm(context, &server->data[1],
+                                               &hrealms);
+           if (retval) goto cleanup;
+#ifdef DEBUG_REFERRALS
+#if 0
+           printf("gc_from_kdc: using fallback realm of %s\n",hrealms[0]);
+#endif
+#endif
+           krb5_free_data_contents(context,&in_cred->server->realm);
+           server->realm.data=hrealms[0];
+           server->realm.length=strlen(hrealms[0]);
+           free(hrealms);
+       }
+       else {
+           /*
+            * Problem case: Realm tagged for referral but apparently not
+            * in a <type>/<host> format that
+            * krb5_get_fallback_host_realm can deal with.
+            */
+#ifdef DEBUG_REFERRALS
+           printf("gc_from_kdc: referral specified but no fallback realm avaiable!\n");
+#endif
+           return KRB5_ERR_HOST_REALM_UNKNOWN;
+       }
+    }
+
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc server at fallback after fallback rewrite", server);
+#endif
+
+    /*
+     * Get a TGT for the target realm.
+     */
+    
+    krb5_free_cred_contents(context, &tgtq);
     retval = tgt_mcred(context, client, server, client, &tgtq);
     if (retval)
        goto cleanup;
 
     /* Fast path: Is it in the ccache? */
+    /* Free tgtptr data if reused from above. */
+    if (tgtptr == &cc_tgt)
+       krb5_free_cred_contents(context, tgtptr);
     context->use_conf_ktypes = 1;
     retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
                                   &tgtq, &cc_tgt);
     if (!retval) {
        tgtptr = &cc_tgt;
     } else if (!HARD_CC_ERR(retval)) {
-       /* Not in ccache, so traverse the transit path. */
        retval = do_traversal(context, ccache, client, server,
                              &cc_tgt, &tgtptr, tgts);
     }
@@ -818,7 +1029,76 @@ cleanup:
     if (tgtptr == &cc_tgt)
        krb5_free_cred_contents(context, tgtptr);
     context->use_conf_ktypes = old_use_conf_ktypes;
+    /* Drop the original principal back into in_cred so that it's cached
+       in the expected format. */
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc: final hacked server principal at cleanup",server);
+#endif
+    krb5_free_principal(context, server);
+    in_cred->server = supplied_server;
+    if (*out_cred && !retval) {
+        /* Success: free server, swap supplied server back in. */
+        krb5_free_principal (context, (*out_cred)->server);
+       (*out_cred)->server= out_supplied_server;
+    }
+    else {
+        /* 
+        * Failure: free out_supplied_server.  Don't free out_cred here
+        * since it's either null or a referral TGT that we free below,
+        * and we may need it to return.
+        */
+        krb5_free_principal (context, out_supplied_server);
+    }
+#ifdef DEBUG_REFERRALS
+    krb5int_dbgref_dump_principal("gc_from_kdc: final server after reversion",in_cred->server);
+#endif
+    /*
+     * Deal with ccache TGT management: If tgts has been set from
+     * initial non-referral TGT discovery, leave it alone.  Otherwise, if
+     * referral_tgts[0] exists return it as the only entry in tgts.
+     * (Further referrals are never cached, only the referral from the
+     * local KDC.)  This is part of cleanup because useful received TGTs
+     * should be cached even if the main request resulted in failure.
+     */
 
+    if (*tgts == NULL) {
+        if (referral_tgts[0]) {
+#if 0
+           /*
+            * This should possibly be a check on the candidate return
+            * credential against the cache, in the circumstance where we
+            * don't want to clutter the cache with near-duplicate
+            * credentials on subsequent iterations.  For now, it is
+            * disabled.
+            */
+           subretval=...?;
+           if (subretval) {
+#endif
+               /* Allocate returnable TGT list. */
+               if (!(*tgts=calloc(sizeof (krb5_creds *), 2)))
+                   return ENOMEM;
+               subretval=krb5_copy_creds(context, referral_tgts[0], &((*tgts)[0]));
+               if(subretval)
+                   return subretval;
+               (*tgts)[1]=NULL;
+#ifdef DEBUG_REFERRALS
+               krb5int_dbgref_dump_principal("gc_from_kdc: returning referral TGT for ccache",(*tgts)[0]->server);
+#endif
+#if 0
+           }
+#endif
+       }
+    }
+
+    /* Free referral TGTs list. */
+    for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
+        if(referral_tgts[i]) {
+           krb5_free_creds(context, referral_tgts[i]);
+       }
+    }
+#ifdef DEBUG_REFERRALS
+    printf("gc_from_kdc finishing with %s\n", retval?error_message(retval):"no error");
+#endif
     return retval;
 }
 
index a1ed6e90d28b52d415575b8fd8dc3c37961436ed..d96f84aaa57a81ca75b3773d29d063bddd4165e6 100644 (file)
@@ -50,7 +50,7 @@ krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *con
         goto cleanup;
 
     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
-                                     &(*ppcreds)->server)))
+                                     &(*ppcreds)->server)))
         goto cleanup;
 
     if ((retval = krb5_copy_keyblock_contents(context, 
@@ -107,6 +107,12 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
     krb5_response tgsrep;
     krb5_enctype *enctypes = 0;
 
+#ifdef DEBUG_REFERRALS
+    printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
+    krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
+    krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
+#endif
+
     /* tkt->client must be equal to in_cred->client */
     if (!krb5_principal_compare(context, tkt->client, in_cred->client))
        return KRB5_PRINC_NOMATCH;
@@ -154,8 +160,13 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
                           tkt, &tgsrep);
     if (enctypes)
        free(enctypes);
-    if (retval)
+    if (retval) {
+#ifdef DEBUG_REFERRALS
+        printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
+              error_message(retval));
+#endif
        return retval;
+    }
 
     switch (tgsrep.message_type) {
     case KRB5_TGS_REP:
@@ -167,7 +178,7 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
        else
            retval = KRB5KRB_AP_ERR_MSG_TYPE;
 
-       if (retval)                     /* neither proper reply nor error! */
+       if (retval)                     /* neither proper reply nor error! */
            goto error_4;
 
        retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
@@ -218,11 +229,26 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
     if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
        retval = KRB5_KDCREP_MODIFIED;
 
-    if (!krb5_principal_compare(context, dec_rep->enc_part2->server, in_cred->server))
-       retval = KRB5_KDCREP_MODIFIED;
-
-    if (!krb5_principal_compare(context, dec_rep->ticket->server, in_cred->server))
-       retval = KRB5_KDCREP_MODIFIED;
+    if ((!krb5_principal_compare(context, dec_rep->enc_part2->server, in_cred->server)) ||
+       (!krb5_principal_compare(context, dec_rep->ticket->server, in_cred->server))) {
+        if (krb5_principal_compare(context, dec_rep->ticket->server, dec_rep->enc_part2->server)
+           && (kdcoptions&KDC_OPT_CANONICALIZE) ) {
+           /* in_cred server differs from ticket returned, but ticket
+              returned is consistent and we requested canonicalization. */
+#if 0
+#ifdef DEBUG_REFERRALS
+           printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
+           krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
+           krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
+#endif
+#endif
+       }
+       else {
+           /* in_cred server differs from ticket returned, and ticket
+              returned is *not* consistent. */
+           retval = KRB5_KDCREP_MODIFIED;
+       }
+    }
 
     if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
        retval = KRB5_KDCREP_MODIFIED;
@@ -267,5 +293,8 @@ error_3:;
 
 error_4:;
     free(tgsrep.response.data);
+#ifdef DEBUG_REFERRALS
+    printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
+#endif
     return retval;
 }
index cd23d14d1701ec9da4cd45d6340f398ddfb0c5d3..fbcc49db0d6eea4eef51f716a6e4fb1dbc1262e0 100644 (file)
@@ -30,6 +30,7 @@
  * characters in the principal name.
  */
 
+
 #include "k5-int.h"
 
 /*
@@ -108,10 +109,10 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip
                        size = 0;
                        i++;
                } else if (c == REALM_SEP) {
-                       if (parsed_realm || !*(cp+1)) 
+                       if (parsed_realm)
                                /*
-                                * Multiple realm separaters or null
-                                * realm names are not allowed!
+                                * Multiple realm separaters
+                                * not allowed; zero-length realms are.
                                 */
                                return(KRB5_PARSE_MALFORMED);
                        parsed_realm = cp+1;
@@ -278,8 +279,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip
        principal->magic = KV5M_PRINCIPAL;
        principal->realm.magic = KV5M_DATA;
        *nprincipal = principal;
+
        krb5_xfree(default_realm);
        return(0);
 }
-
-
index b61f525f40477ab6d56004dd037756d2a4dacfbb..8b52330b34ebbe078c9359be4861d3cc54bb2699 100644 (file)
@@ -65,3 +65,23 @@ krb5_principal_compare(krb5_context context, krb5_const_principal princ1, krb5_c
     }
     return TRUE;
 }
+
+krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(krb5_data *r)
+{
+    /*
+     * Check for a match with KRB5_REFERRAL_REALM.  Currently this relies
+     * on that string constant being zero-length.  (Unlike principal realm
+     * names, KRB5_REFERRAL_REALM is known to be a string.)
+     */
+#ifdef DEBUG_REFERRALS
+#if 0
+    printf("krb5_is_ref_realm: checking <%s> for referralness: %s\n",
+          r->data,(r->length==0)?"true":"false");
+#endif
+#endif
+    assert(strlen(KRB5_REFERRAL_REALM)==0);
+    if (r->length==0)
+        return TRUE;
+    else
+        return FALSE;
+}
index 074c7e1d07608ec08cf6b21203185a5dcc5e0497..7210be7c716c33b80fb913b85e6a9617acdee0ef 100644 (file)
  * in order to get from the source realm to the destination realm.  It
  * takes a realm separater character (normally ., but presumably there
  * for all those X.500 realms) .  There are two modes it runs in: the
- * ANL krb5.confmode and the hierarchy mode.  The ANL mode is
+ * ANL krb5.conf mode and the hierarchy mode.  The ANL mode is
  * fairly obvious.  The hierarchy mode looks for common components in
  * both the client and server realms.  In general, the pointer scp and
  * ccp are used to walk through the client and server realms.  The
@@ -133,6 +133,13 @@ krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_d
     char **cap_nodes;
     krb5_error_code cap_code;
 #endif
+
+#ifdef DEBUG_REFERRALS
+    printf("krb5_walk_realm_tree starting\n");
+    printf("  client is %s\n",client->data);
+    printf("  server is %s\n",server->data);
+#endif
+
     if (!(client->data &&server->data))
       return KRB5_NO_TKT_IN_RLM;
 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
@@ -380,5 +387,28 @@ krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_d
     }
 #endif
     *tree = rettree;
+
+#ifdef DEBUG_REFERRALS
+    printf("krb5_walk_realm_tree ending; tree (length %d) is:\n",links);
+    for(i=0;i<links+2;i++) {
+        if ((*tree)[i])
+           krb5int_dbgref_dump_principal("krb5_walk_realm_tree tree",(*tree)[i]);
+       else
+           printf("tree element %i null\n");
+    }
+#endif
     return 0;
 }
+
+#ifdef DEBUG_REFERRALS
+void krb5int_dbgref_dump_principal(char *d, krb5_principal p)
+{
+    int n;
+             
+    printf("  **%s: ",d);
+    for (n=0;n<p->length;n++)
+       printf("%s<%.*s>",(n>0)?"/":"",p->data[n].length,p->data[n].data);
+    printf("@<%.*s>  (length %d, type %d)\n",p->realm.length,p->realm.data,
+          p->length, p->type);
+}
+#endif
index 94e94e45dbb9f9292eeadfe52b71931daebefd24..952ab2f75600b7284a3f9ed8b9df4b6d16d2b239 100644 (file)
@@ -458,6 +458,7 @@ krb5_init_keyblock
 krb5_init_secure_context
 krb5_internalize_opaque
 krb5_is_permitted_enctype
+krb5_is_referral_realm
 krb5_is_thread_safe
 krb5_kdc_rep_decrypt_proc
 krb5_kt_add_entry
index 17b1e42c492350bf3828fa089a90cd5015e9bf67..6646d2da9aebdaa3bab7b9b2fde29f7c4dfc5717 100644 (file)
@@ -196,52 +196,15 @@ krb5_error_code KRB5_CALLCONV
 krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
 {
     char **retrealms;
-    char *default_realm, *realm, *cp, *temp_realm;
+    char *realm, *cp, *temp_realm;
     krb5_error_code retval;
-    int l;
     char local_host[MAXDNAME+1];
 
-    if (host) {
-       /* Filter out numeric addresses if the caller utterly failed to
-          convert them to names.  */
-       /* IPv4 - dotted quads only */
-       if (strspn(host, "01234567890.") == strlen(host)) {
-           /* All numbers and dots... if it's three dots, it's an
-              IP address, and we reject it.  But "12345" could be
-              a local hostname, couldn't it?  We'll just assume
-              that a name with three dots is not meant to be an
-              all-numeric hostname three all-numeric domains down
-              from the current domain.  */
-           int ndots = 0;
-           const char *p;
-           for (p = host; *p; p++)
-               if (*p == '.')
-                   ndots++;
-           if (ndots == 3)
-               return KRB5_ERR_NUMERIC_REALM;
-       }
-       if (strchr(host, ':'))
-           /* IPv6 numeric address form?  Bye bye.  */
-           return KRB5_ERR_NUMERIC_REALM;
-
-       /* Should probably error out if strlen(host) > MAXDNAME.  */
-       strncpy(local_host, host, sizeof(local_host));
-       local_host[sizeof(local_host) - 1] = '\0';
-    } else {
-       retval = krb5int_get_fq_local_hostname (local_host,
-                                               sizeof (local_host));
-       if (retval)
-           return retval;
-    }
+#ifdef DEBUG_REFERRALS
+    printf("get_host_realm(host:%s) called\n",host);
+#endif
 
-    for (cp = local_host; *cp; cp++) {
-       if (isupper((unsigned char) (*cp)))
-           *cp = tolower((unsigned char) *cp);
-    }
-    l = strlen(local_host);
-    /* strip off trailing dot */
-    if (l && local_host[l-1] == '.')
-           local_host[l-1] = 0;
+    krb5int_clean_hostname(context, host, local_host, sizeof local_host);
 
     /*
        Search for the best match for the host or domain.
@@ -256,9 +219,15 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
      */
 
     cp = local_host;
-    realm = default_realm = (char *)NULL;
+#ifdef DEBUG_REFERRALS
+    printf("  local_host: %s\n",local_host);
+#endif
+    realm = (char *)NULL;
     temp_realm = 0;
     while (cp) {
+#ifdef DEBUG_REFERRALS
+        printf("  trying to look up %s in the domain_realm map\n",cp);
+#endif
        retval = profile_get_string(context->profile, "domain_realm", cp,
                                    0, (char *)NULL, &temp_realm);
        if (retval)
@@ -269,15 +238,17 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
        /* Setup for another test */
        if (*cp == '.') {
            cp++;
-           if (default_realm == (char *)NULL) {
-               /* If nothing else works, use the host's domain */
-               default_realm = cp;
-           }
        } else {
            cp = strchr(cp, '.');
        }
     }
+#ifdef DEBUG_REFERRALS
+    printf("  done searching the domain_realm map\n");
+#endif
     if (temp_realm) {
+#ifdef DEBUG_REFERRALS
+    printf("  temp_realm is %s\n",temp_realm);
+#endif
         realm = malloc(strlen(temp_realm) + 1);
         if (!realm) {
             profile_release_string(temp_realm);
@@ -287,47 +258,13 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp)
         profile_release_string(temp_realm);
     }
 
-#ifdef KRB5_DNS_LOOKUP
-    if (realm == (char *)NULL) {
-        int use_dns = _krb5_use_dns_realm(context);
-        if ( use_dns ) {
-            /*
-             * Since this didn't appear in our config file, try looking
-             * it up via DNS.  Look for a TXT records of the form:
-             *
-             * _kerberos.<hostname>
-             *
-             */
-            cp = local_host;
-            do {
-                retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
-                cp = strchr(cp,'.');
-                if (cp) 
-                    cp++;
-            } while (retval && cp && cp[0]);
-        }
-    }
-#endif /* KRB5_DNS_LOOKUP */
     if (realm == (char *)NULL) {
-        if (default_realm != (char *)NULL) {
-            /* We are defaulting to the realm of the host */
-            if (!(cp = (char *)malloc(strlen(default_realm)+1)))
-                return ENOMEM;
-            strcpy(cp, default_realm);
-            realm = cp;
-
-            /* Assume the realm name is upper case */
-            for (cp = realm; *cp; cp++)
-                if (islower((unsigned char) (*cp)))
-                    *cp = toupper((unsigned char) *cp);
-        } else {    
-            /* We are defaulting to the local realm */
-            retval = krb5_get_default_realm(context, &realm);
-            if (retval) {
-                return retval;
-            }
-        }
+        if (!(cp = (char *)malloc(strlen(KRB5_REFERRAL_REALM)+1)))
+           return ENOMEM;
+       strcpy(cp, KRB5_REFERRAL_REALM);
+       realm = cp;
     }
+    
     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
        if (realm != (char *)NULL)
            free(realm);
@@ -383,3 +320,171 @@ krb5int_translate_gai_error (int num)
     abort ();
     return -1;
 }
+
+
+/*
+ * Ganked from krb5_get_host_realm; handles determining a fallback realm
+ * to try in the case where referrals have failed and it's time to go
+ * look at TXT records or make a DNS-based assumption.
+ */
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realmsp)
+{
+    char **retrealms;
+    char *default_realm, *realm, *cp, *temp_realm;
+    krb5_error_code retval;
+    char local_host[MAXDNAME+1], host[MAXDNAME+1];
+
+    /* Convert what we hope is a hostname to a string. */
+    memcpy(host, hdata->data, hdata->length);
+    host[hdata->length]=0;
+
+#ifdef DEBUG_REFERRALS
+    printf("get_fallback_host_realm(host >%s<) called\n",host);
+#endif
+
+    krb5int_clean_hostname(context, host, local_host, sizeof local_host);
+
+    /* Scan hostname for DNS realm, and save as last-ditch realm
+       assumption. */
+    cp = local_host;
+#ifdef DEBUG_REFERRALS
+    printf("  local_host: %s\n",local_host);
+#endif
+    realm = default_realm = (char *)NULL;
+    temp_realm = 0;
+    while (cp && !default_realm) {
+       if (*cp == '.') {
+           cp++;
+           if (default_realm == (char *)NULL) {
+               /* If nothing else works, use the host's domain */
+               default_realm = cp;
+           }
+       } else {
+           cp = strchr(cp, '.');
+       }
+    }
+#ifdef DEBUG_REFERRALS
+    printf("  done finding DNS-based default realm: >%s<\n",default_realm);
+#endif
+
+#ifdef KRB5_DNS_LOOKUP
+    if (realm == (char *)NULL) {
+        int use_dns = _krb5_use_dns_realm(context);
+        if ( use_dns ) {
+            /*
+             * Since this didn't appear in our config file, try looking
+             * it up via DNS.  Look for a TXT records of the form:
+             *
+             * _kerberos.<hostname>
+             *
+             */
+            cp = local_host;
+            do {
+                retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
+                cp = strchr(cp,'.');
+                if (cp) 
+                    cp++;
+            } while (retval && cp && cp[0]);
+        }
+    }
+#endif /* KRB5_DNS_LOOKUP */
+
+      
+    if (realm == (char *)NULL) {
+        if (default_realm != (char *)NULL) {
+            /* We are defaulting to the realm of the host */
+            if (!(cp = (char *)malloc(strlen(default_realm)+1)))
+                return ENOMEM;
+            strcpy(cp, default_realm);
+            realm = cp;
+
+            /* Assume the realm name is upper case */
+            for (cp = realm; *cp; cp++)
+                if (islower((int) (*cp)))
+                    *cp = toupper((int) *cp);
+        } else {    
+            /* We are defaulting to the local realm */
+            retval = krb5_get_default_realm(context, &realm);
+            if (retval) {
+                return retval;
+            }
+        }
+    }
+    if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
+       if (realm != (char *)NULL)
+           free(realm);
+       return ENOMEM;
+    }
+
+    retrealms[0] = realm;
+    retrealms[1] = 0;
+    
+    *realmsp = retrealms;
+    return 0;
+}
+
+/*
+ * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm
+ * to do basic sanity checks on supplied hostname.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5int_clean_hostname(krb5_context context, const char *host, char *local_host, size_t lhsize)
+{
+    char **retrealms;
+    char *realm, *cp, *temp_realm;
+    krb5_error_code retval;
+    int l;
+
+    local_host[0]=0;
+#ifdef DEBUG_REFERRALS
+    printf("krb5int_clean_hostname called: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize);
+#endif
+    if (host) {
+       /* Filter out numeric addresses if the caller utterly failed to
+          convert them to names.  */
+       /* IPv4 - dotted quads only */
+       if (strspn(host, "01234567890.") == strlen(host)) {
+           /* All numbers and dots... if it's three dots, it's an
+              IP address, and we reject it.  But "12345" could be
+              a local hostname, couldn't it?  We'll just assume
+              that a name with three dots is not meant to be an
+              all-numeric hostname three all-numeric domains down
+              from the current domain.  */
+           int ndots = 0;
+           const char *p;
+           for (p = host; *p; p++)
+               if (*p == '.')
+                   ndots++;
+           if (ndots == 3)
+               return KRB5_ERR_NUMERIC_REALM;
+       }
+       if (strchr(host, ':'))
+           /* IPv6 numeric address form?  Bye bye.  */
+           return KRB5_ERR_NUMERIC_REALM;
+
+       /* Should probably error out if strlen(host) > MAXDNAME.  */
+       strncpy(local_host, host, lhsize);
+       local_host[lhsize - 1] = '\0';
+    } else {
+        retval = krb5int_get_fq_local_hostname (local_host, lhsize);
+       if (retval)
+           return retval;
+    }
+
+    /* fold to lowercase */
+    for (cp = local_host; *cp; cp++) {
+       if (isupper((unsigned char) (*cp)))
+           *cp = tolower((unsigned char) *cp);
+    }
+    l = strlen(local_host);
+    /* strip off trailing dot */
+    if (l && local_host[l-1] == '.')
+           local_host[l-1] = 0;
+
+#ifdef DEBUG_REFERRALS
+    printf("krb5int_clean_hostname ending: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize);
+#endif
+    return 0;
+}
index 2ba7e8ae2a351e5f1b231e8a49c6e4315a39876f..683cc10cb829714fbb829ff00e9d45806c004372 100644 (file)
@@ -68,6 +68,12 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char *
     register char *cp;
     char localname[MAXHOSTNAMELEN];
 
+    FILE *log;
+
+#ifdef DEBUG_REFERRALS
+    printf("krb5_sname_to_principal(host=%s, sname=%s, type=%d)\n",hostname,sname,type);
+    printf("      name types: 0=unknown, 3=srv_host\n");
+#endif
 
     if ((type == KRB5_NT_UNKNOWN) ||
        (type == KRB5_NT_SRV_HST)) {
@@ -105,6 +111,9 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char *
        try_getaddrinfo_again:
            err = getaddrinfo(hostname, 0, &hints, &ai);
            if (err) {
+#ifdef DEBUG_REFERRALS
+               printf("sname_to_princ: probably punting due to bad hostname of %s\n",hostname);
+#endif
                if (hints.ai_family == AF_INET) {
                    /* Just in case it's an IPv6-only name.  */
                    hints.ai_family = 0;
@@ -145,6 +154,9 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char *
        }
        if (!remote_host)
            return ENOMEM;
+#ifdef DEBUG_REFERRALS
+       printf("sname_to_princ: hostname <%s> after rdns processing\n",remote_host);
+#endif
 
        if (type == KRB5_NT_SRV_HST)
            for (cp = remote_host; *cp; cp++)
@@ -167,6 +179,11 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char *
            free(remote_host);
            return retval;
        }
+
+#ifdef DEBUG_REFERRALS
+       printf("sname_to_princ:  realm <%s> after krb5_get_host_realm\n",hrealms[0]);
+#endif
+
        if (!hrealms[0]) {
            free(remote_host);
            krb5_xfree(hrealms);
@@ -180,6 +197,13 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char *
 
        krb5_princ_type(context, *ret_princ) = type;
 
+#ifdef DEBUG_REFERRALS
+       printf("krb5_sname_to_principal returning\n");
+       printf("realm: <%s>, sname: <%s>, remote_host: <%s>\n",
+              realm,sname,remote_host);
+       krb5int_dbgref_dump_principal("krb5_sname_to_principal",*ret_princ);
+#endif
+
        free(remote_host);
 
        krb5_free_host_realm(context, hrealms);
index 9785df56300e730c7a815ffbc4ff5e926e6b4da1..b2a8361c1f4f15eaf1f61c42e789e3de23804f90 100644 (file)
@@ -171,6 +171,7 @@ krb5_c_string_to_key_with_params
        krb5_init_keyblock
        krb5_init_random_key
        krb5_init_secure_context
+       krb5_is_referral_realm
         krb5_is_thread_safe
        krb5_kt_add_entry
        krb5_kt_close