/* * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * lib/krb5/krb/walk_rtree.c * * Copyright 1990,1991 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * krb5_walk_realm_tree() */ /* ANL - Modified to allow Configurable Authentication Paths. * This modification removes the restriction on the choice of realm * names, i.e. they nolonger have to be hierarchical. This * is allowed by RFC 1510: "If a hierarchical orginization is not used * it may be necessary to consult some database in order to construct * an authentication path between realms." The database is contained * in the [capaths] section of the krb5.conf file. * Client to server paths are defined. There are n**2 possible * entries, but only those entries which are needed by the client * or server need be present in its krb5.conf file. (n entries or 2*n * entries if the same krb5.conf is used for clients and servers) * * for example: ESnet will be running a KDC which will share * inter-realm keys with its many orginizations which include among * other ANL, NERSC and PNL. Each of these orginizations wants to * use its DNS name in the realm, ANL.GOV. In addition ANL wants * to authenticatite to HAL.COM via a K5.MOON and K5.JUPITER * A [capaths] section of the krb5.conf file for the ANL.GOV clients * and servers would look like: * * [capaths] * ANL.GOV = { * NERSC.GOV = ES.NET * PNL.GOV = ES.NET * ES.NET = . * HAL.COM = K5.MOON * HAL.COM = K5.JUPITER * } * NERSC.GOV = { * ANL.GOV = ES.NET * } * PNL.GOV = { * ANL.GOV = ES.NET * } * ES.NET = { * ANL.GOV = . * } * HAL.COM = { * ANL.GOV = K5.JUPITER * ANL.GOV = K5.MOON * } * * In the above a "." is used to mean directly connected since the * the profile routines cannot handle a null entry. * * If no client-to-server path is found, the default hierarchical path * is still generated. * * This version of the Configurable Authentication Path modification * differs from the previous versions prior to K5 beta 5 in that * the profile routines are used, and the explicite path from * client's realm to server's realm must be given. The modifications * will work together. * DEE - 5/23/95 */ #define CONFIGURABLE_AUTHENTICATION_PATH #include "k5-int.h" #include "int-proto.h" #include /* internal function, used by krb5_get_cred_from_kdc() */ #ifndef min #define min(x,y) ((x) < (y) ? (x) : (y)) #define max(x,y) ((x) > (y) ? (x) : (y)) #endif /* * xxx The following function is very confusing to read and probably * is buggy. It should be documented better. Here is what I've * learned about it doing a quick bug fixing walk through. The * function takes a client and server realm name and returns the set * of realms (in a field called tree) that you need to get tickets in * 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.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 * com_sdot and com_cdot pointers point to (I think) the beginning of * the common part of the realm names. I.E. strcmp(com_cdot, * com_sdot) ==0 is roughly an invarient. However, there are cases * where com_sdot and com_cdot are set to point before the start of * the client or server strings. I think this only happens when there * are no common components. --hartmans 2002/03/14 */ krb5_error_code krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_data *server, krb5_principal **tree, int realm_branch_char) { krb5_error_code retval; krb5_principal *rettree; register char *ccp, *scp; register char *prevccp = 0, *prevscp = 0; char *com_sdot = 0, *com_cdot = 0; register int i, links = 0; int clen, slen = -1; krb5_data tmpcrealm, tmpsrealm; int nocommon = 1; #ifdef CONFIGURABLE_AUTHENTICATION_PATH const char *cap_names[4]; char *cap_client, *cap_server; 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)) { /* Solaris Kerberos - enhance error message */ if (!client->data && !server->data) { krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM, dgettext(TEXT_DOMAIN, "Cannot find ticket for requested realm: unknown client and server")); } else { if (!client->data) { krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM, dgettext(TEXT_DOMAIN, "Cannot find ticket for requested realm: unknown client")); } else { krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM, dgettext(TEXT_DOMAIN, "Cannot find ticket for requested realm: unknown server")); } } return KRB5_NO_TKT_IN_RLM; } #ifdef CONFIGURABLE_AUTHENTICATION_PATH if ((cap_client = (char *)malloc(client->length + 1)) == NULL) return ENOMEM; strncpy(cap_client, client->data, client->length); cap_client[client->length] = '\0'; if ((cap_server = (char *)malloc(server->length + 1)) == NULL) { krb5_xfree(cap_client); return ENOMEM; } strncpy(cap_server, server->data, server->length); cap_server[server->length] = '\0'; cap_names[0] = "capaths"; cap_names[1] = cap_client; cap_names[2] = cap_server; cap_names[3] = 0; cap_code = profile_get_values(context->profile, cap_names, &cap_nodes); krb5_xfree(cap_client); /* done with client string */ cap_names[1] = 0; if (cap_code == 0) { /* found a path, so lets use it */ links = 0; if (*cap_nodes[0] != '.') { /* a link of . means direct */ while(cap_nodes[links]) { links++; } } if (cap_nodes[links] != NULL) krb5_xfree(cap_nodes[links]); cap_nodes[links] = cap_server; /* put server on end of list */ /* this simplifies the code later and make */ /* cleanup eaiser as well */ links++; /* count the null entry at end */ } else { /* no path use hierarchical method */ krb5_xfree(cap_server); /* failed, don't need server string */ cap_names[2] = 0; #endif clen = client->length; slen = server->length; for (com_cdot = ccp = client->data + clen - 1, com_sdot = scp = server->data + slen - 1; clen && slen && *ccp == *scp ; ccp--, scp--, clen--, slen--) { if (*ccp == realm_branch_char) { com_cdot = ccp; com_sdot = scp; nocommon = 0; } } /* ccp, scp point to common root. com_cdot, com_sdot point to common components. */ /* handle case of one ran out */ if (!clen) { /* construct path from client to server, down the tree */ if (!slen) { /* in the same realm--this means there is no ticket in this realm. */ krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM, dgettext(TEXT_DOMAIN, "Cannot find ticket for requested realm: client is '%s', server is '%s'"), client->data, server->data); return KRB5_NO_TKT_IN_RLM; } if (*scp == realm_branch_char) { /* one is a subdomain of the other */ com_cdot = client->data; com_sdot = scp; nocommon = 0; } /* else normal case of two sharing parents */ } if (!slen) { /* construct path from client to server, up the tree */ if (*ccp == realm_branch_char) { /* one is a subdomain of the other */ com_sdot = server->data; com_cdot = ccp; nocommon = 0; } /* else normal case of two sharing parents */ } /* determine #links to/from common ancestor */ if (nocommon) links = 1; else links = 2; /* if no common ancestor, artificially set up common root at the last component, then join with special code */ for (ccp = client->data; ccp < com_cdot; ccp++) { if (*ccp == realm_branch_char) { links++; if (nocommon) prevccp = ccp; } } for (scp = server->data; scp < com_sdot; scp++) { if (*scp == realm_branch_char) { links++; if (nocommon) prevscp = scp; } } if (nocommon) { if (prevccp) com_cdot = prevccp; if (prevscp) com_sdot = prevscp; if(com_cdot == client->data + client->length -1) com_cdot = client->data - 1 ; if(com_sdot == server->data + server->length -1) com_sdot = server->data - 1 ; } #ifdef CONFIGURABLE_AUTHENTICATION_PATH } /* end of if use hierarchical method */ #endif if (!(rettree = (krb5_principal *)calloc(links+2, sizeof(krb5_principal)))) { return ENOMEM; } i = 1; if ((retval = krb5_tgtname(context, client, client, &rettree[0]))) { krb5_xfree(rettree); return retval; } #ifdef CONFIGURABLE_AUTHENTICATION_PATH links--; /* dont count the null entry on end */ if (cap_code == 0) { /* found a path above */ tmpcrealm.data = client->data; tmpcrealm.length = client->length; while( i-1 <= links) { tmpsrealm.data = cap_nodes[i-1]; /* don't count trailing whitespace from profile_get */ tmpsrealm.length = strcspn(cap_nodes[i-1],"\t "); if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm, &rettree[i]))) { while (i) { krb5_free_principal(context, rettree[i-1]); i--; } krb5_xfree(rettree); /* cleanup the cap_nodes from profile_get */ for (i = 0; i<=links; i++) { krb5_xfree(cap_nodes[i]); } krb5_xfree((char *)cap_nodes); return retval; } tmpcrealm.data = tmpsrealm.data; tmpcrealm.length = tmpsrealm.length; i++; } /* cleanup the cap_nodes from profile_get last one has server */ for (i = 0; i<=links; i++) { krb5_xfree(cap_nodes[i]); } krb5_xfree((char *)cap_nodes); } else { /* if not cap then use hierarchical method */ #endif for (prevccp = ccp = client->data; ccp <= com_cdot; ccp++) { if (*ccp != realm_branch_char) continue; ++ccp; /* advance past dot */ tmpcrealm.data = prevccp; tmpcrealm.length = client->length - (prevccp - client->data); tmpsrealm.data = ccp; tmpsrealm.length = client->length - (ccp - client->data); if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm, &rettree[i]))) { while (i) { krb5_free_principal(context, rettree[i-1]); i--; } krb5_xfree(rettree); return retval; } prevccp = ccp; i++; } if (nocommon) { tmpcrealm.data = com_cdot + 1; tmpcrealm.length = client->length - (com_cdot + 1 - client->data); tmpsrealm.data = com_sdot + 1; tmpsrealm.length = server->length - (com_sdot + 1 - server->data); if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm, &rettree[i]))) { while (i) { krb5_free_principal(context, rettree[i-1]); i--; } krb5_xfree(rettree); return retval; } i++; } for (prevscp = com_sdot + 1, scp = com_sdot - 1; scp > server->data; scp--) { if (*scp != realm_branch_char) continue; if (scp - 1 < server->data) break; /* XXX only if . starts realm? */ tmpcrealm.data = prevscp; tmpcrealm.length = server->length - (prevscp - server->data); tmpsrealm.data = scp + 1; tmpsrealm.length = server->length - (scp + 1 - server->data); if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm, &rettree[i]))) { while (i) { krb5_free_principal(context, rettree[i-1]); i--; } krb5_xfree(rettree); return retval; } prevscp = scp + 1; i++; } if (slen && com_sdot >= server->data) { /* only necessary if building down tree from ancestor or client */ /* however, we can get here if we have only one component in the server realm name, hence we make sure we found a component separator there... */ tmpcrealm.data = prevscp; tmpcrealm.length = server->length - (prevscp - server->data); if ((retval = krb5_tgtname(context, server, &tmpcrealm, &rettree[i]))) { while (i) { krb5_free_principal(context, rettree[i-1]); i--; } krb5_xfree(rettree); return retval; } } #ifdef CONFIGURABLE_AUTHENTICATION_PATH } #endif *tree = rettree; #ifdef DEBUG_REFERRALS printf("krb5_walk_realm_tree ending; tree (length %d) is:\n",links); for(i=0;ilength;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