1 /*
2  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * lib/krb5/krb/walk_rtree.c
6  *
7  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
8  * All Rights Reserved.
9  *
10  * Export of this software from the United States of America may
11  *   require a specific license from the United States Government.
12  *   It is the responsibility of any person or organization contemplating
13  *   export to obtain such a license before exporting.
14  *
15  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
16  * distribute this software and its documentation for any purpose and
17  * without fee is hereby granted, provided that the above copyright
18  * notice appear in all copies and that both that copyright notice and
19  * this permission notice appear in supporting documentation, and that
20  * the name of M.I.T. not be used in advertising or publicity pertaining
21  * to distribution of the software without specific, written prior
22  * permission.  Furthermore if you modify this software you must label
23  * your software as modified software and not distribute it in such a
24  * fashion that it might be confused with the original M.I.T. software.
25  * M.I.T. makes no representations about the suitability of
26  * this software for any purpose.  It is provided "as is" without express
27  * or implied warranty.
28  *
29  *
30  * krb5_walk_realm_tree()
31  */
32 
33 /* ANL - Modified to allow Configurable Authentication Paths.
34  * This modification removes the restriction on the choice of realm
35  * names, i.e. they nolonger have to be hierarchical. This
36  * is allowed by RFC 1510: "If a hierarchical orginization is not used
37  * it may be necessary to consult some database in order to construct
38  * an authentication path between realms."  The database is contained
39  * in the [capaths] section of the krb5.conf file.
40  * Client to server paths are defined. There are n**2 possible
41  * entries, but only those entries which are needed by the client
42  * or server need be present in its krb5.conf file. (n entries or 2*n
43  * entries if the same krb5.conf is used for clients and servers)
44  *
45  * for example: ESnet will be running a KDC which will share
46  * inter-realm keys with its many orginizations which include among
47  * other ANL, NERSC and PNL. Each of these orginizations wants to
48  * use its DNS name in the realm, ANL.GOV. In addition ANL wants
49  * to authenticatite to HAL.COM via a K5.MOON and K5.JUPITER
50  * A [capaths] section of the krb5.conf file for the ANL.GOV clients
51  * and servers would look like:
52  *
53  * [capaths]
54  * ANL.GOV = {
55  *		NERSC.GOV = ES.NET
56  *		PNL.GOV = ES.NET
57  *		ES.NET = .
58  * 		HAL.COM = K5.MOON
59  * 		HAL.COM = K5.JUPITER
60  * }
61  * NERSC.GOV = {
62  *		ANL.GOV = ES.NET
63  * }
64  * PNL.GOV = {
65  *		ANL.GOV = ES.NET
66  * }
67  * ES.NET = {
68  * 		ANL.GOV = .
69  * }
70  * HAL.COM = {
71  *		ANL.GOV = K5.JUPITER
72  *		ANL.GOV = K5.MOON
73  * }
74  *
75  * In the above a "." is used to mean directly connected since the
76  * the profile routines cannot handle a null entry.
77  *
78  * If no client-to-server path is found, the default hierarchical path
79  * is still generated.
80  *
81  * This version of the Configurable Authentication Path modification
82  * differs from the previous versions prior to K5 beta 5 in that
83  * the profile routines are used, and the explicite path from
84  * client's realm to server's realm must be given. The modifications
85  * will work together.
86  * DEE - 5/23/95
87  */
88 #define CONFIGURABLE_AUTHENTICATION_PATH
89 #include "k5-int.h"
90 #include "int-proto.h"
91 #include <locale.h>
92 
93 /* internal function, used by krb5_get_cred_from_kdc() */
94 
95 #ifndef min
96 #define min(x,y) ((x) < (y) ? (x) : (y))
97 #define max(x,y) ((x) > (y) ? (x) : (y))
98 #endif
99 
100 /*
101  * xxx The following function is very confusing to read and probably
102  * is buggy.  It should be documented better.  Here is what I've
103  * learned about it doing a quick bug fixing walk through.  The
104  * function takes a client and server realm name and returns the set
105  * of realms (in a field called tree) that you need to get tickets in
106  * in order to get from the source realm to the destination realm.  It
107  * takes a realm separater character (normally ., but presumably there
108  * for all those X.500 realms) .  There are two modes it runs in: the
109  * ANL krb5.conf mode and the hierarchy mode.  The ANL mode is
110  * fairly obvious.  The hierarchy mode looks for common components in
111  * both the client and server realms.  In general, the pointer scp and
112  * ccp are used to walk through the client and server realms.  The
113  * com_sdot and com_cdot pointers point to (I think) the beginning of
114  * the common part of the realm names.  I.E. strcmp(com_cdot,
115  * com_sdot) ==0 is roughly an invarient.  However, there are cases
116  * where com_sdot and com_cdot are set to point before the start of
117  * the client or server strings.  I think this only happens when there
118  * are no common components.  --hartmans 2002/03/14
119  */
120 
121 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)122 krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_data *server, krb5_principal **tree, int realm_branch_char)
123 {
124     krb5_error_code retval;
125     krb5_principal *rettree;
126     register char *ccp, *scp;
127     register char *prevccp = 0, *prevscp = 0;
128     char *com_sdot = 0, *com_cdot = 0;
129     register int i, links = 0;
130     int clen, slen = -1;
131     krb5_data tmpcrealm, tmpsrealm;
132     int nocommon = 1;
133 
134 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
135     const char *cap_names[4];
136     char *cap_client, *cap_server;
137     char **cap_nodes;
138     krb5_error_code cap_code;
139 #endif
140 
141 #ifdef DEBUG_REFERRALS
142     printf("krb5_walk_realm_tree starting\n");
143     printf("  client is %s\n",client->data);
144     printf("  server is %s\n",server->data);
145 #endif
146 
147     if (!(client->data && server->data)) {
148 	/* Solaris Kerberos - enhance error message */
149 	if (!client->data && !server->data) {
150 	    krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM,
151 				dgettext(TEXT_DOMAIN,
152 					"Cannot find ticket for requested realm: unknown client and server"));
153 	} else {
154 	    if (!client->data) {
155 		krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM,
156 				    dgettext(TEXT_DOMAIN,
157 					    "Cannot find ticket for requested realm: unknown client"));
158 	    } else {
159 	       krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM,
160 				    dgettext(TEXT_DOMAIN,
161 					    "Cannot find ticket for requested realm: unknown server"));
162 	    }
163 	}
164 	return KRB5_NO_TKT_IN_RLM;
165     }
166 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
167     if ((cap_client = (char *)malloc(client->length + 1)) == NULL)
168 	return ENOMEM;
169     strncpy(cap_client, client->data, client->length);
170     cap_client[client->length] = '\0';
171     if ((cap_server = (char *)malloc(server->length + 1)) == NULL) {
172 	krb5_xfree(cap_client);
173 	return ENOMEM;
174     }
175     strncpy(cap_server, server->data, server->length);
176     cap_server[server->length] = '\0';
177     cap_names[0] = "capaths";
178     cap_names[1] = cap_client;
179     cap_names[2] = cap_server;
180     cap_names[3] = 0;
181     cap_code = profile_get_values(context->profile, cap_names, &cap_nodes);
182     krb5_xfree(cap_client);  /* done with client string */
183     cap_names[1] = 0;
184     if (cap_code == 0) {     /* found a path, so lets use it */
185 	links = 0;
186 	if (*cap_nodes[0] != '.') { /* a link of . means direct */
187 	    while(cap_nodes[links]) {
188 		links++;
189 	    }
190 	}
191 	if (cap_nodes[links] != NULL)
192 	    krb5_xfree(cap_nodes[links]);
193 
194 	cap_nodes[links] = cap_server; /* put server on end of list */
195 	/* this simplifies the code later and make */
196 	/* cleanup eaiser as well */
197 	links++;		/* count the null entry at end */
198     } else {			/* no path use hierarchical method */
199 	krb5_xfree(cap_server); /* failed, don't need server string */
200 	cap_names[2] = 0;
201 #endif
202 	clen = client->length;
203 	slen = server->length;
204 
205 	for (com_cdot = ccp = client->data + clen - 1,
206 		 com_sdot = scp = server->data + slen - 1;
207 	     clen && slen && *ccp == *scp ;
208 	     ccp--, scp--, 	clen--, slen--) {
209 	    if (*ccp == realm_branch_char) {
210 		com_cdot = ccp;
211 		com_sdot = scp;
212 		nocommon = 0;
213 	    }
214 	}
215 
216 	/* ccp, scp point to common root.
217 	   com_cdot, com_sdot point to common components. */
218 	/* handle case of one ran out */
219 	if (!clen) {
220 	    /* construct path from client to server, down the tree */
221 	    if (!slen) {
222 		/* in the same realm--this means there is no ticket
223 		   in this realm. */
224 	        krb5_set_error_message(context, KRB5_NO_TKT_IN_RLM,
225 				    dgettext(TEXT_DOMAIN,
226 					    "Cannot find ticket for requested realm: client is '%s', server is '%s'"),
227 				    client->data, server->data);
228 		return KRB5_NO_TKT_IN_RLM;
229 	    }
230 	    if (*scp == realm_branch_char) {
231 		/* one is a subdomain of the other */
232 		com_cdot = client->data;
233 		com_sdot = scp;
234 		nocommon = 0;
235 	    } /* else normal case of two sharing parents */
236 	}
237 	if (!slen) {
238 	    /* construct path from client to server, up the tree */
239 	    if (*ccp == realm_branch_char) {
240 		/* one is a subdomain of the other */
241 		com_sdot = server->data;
242 		com_cdot = ccp;
243 		nocommon = 0;
244 	    } /* else normal case of two sharing parents */
245 	}
246 	/* determine #links to/from common ancestor */
247 	if (nocommon)
248 	    links = 1;
249 	else
250 	    links = 2;
251 	/* if no common ancestor, artificially set up common root at the last
252 	   component, then join with special code */
253 	for (ccp = client->data; ccp < com_cdot; ccp++) {
254 	    if (*ccp == realm_branch_char) {
255 		links++;
256 		if (nocommon)
257 		    prevccp = ccp;
258 	    }
259 	}
260 
261 	for (scp = server->data; scp < com_sdot; scp++) {
262 	    if (*scp == realm_branch_char) {
263 		links++;
264 		if (nocommon)
265 		    prevscp = scp;
266 	    }
267 	}
268 	if (nocommon) {
269 	    if (prevccp)
270 		com_cdot = prevccp;
271 	    if (prevscp)
272 		com_sdot = prevscp;
273 
274 	    if(com_cdot == client->data + client->length -1)
275 		com_cdot = client->data - 1 ;
276 	    if(com_sdot == server->data + server->length -1)
277 		com_sdot = server->data - 1 ;
278 	}
279 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
280     }		/* end of if use hierarchical method */
281 #endif
282 
283     if (!(rettree = (krb5_principal *)calloc(links+2,
284 					     sizeof(krb5_principal)))) {
285 	return ENOMEM;
286     }
287     i = 1;
288     if ((retval = krb5_tgtname(context, client, client, &rettree[0]))) {
289 	krb5_xfree(rettree);
290 	return retval;
291     }
292 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
293     links--;				/* dont count the null entry on end */
294     if (cap_code == 0) {    /* found a path above */
295 	tmpcrealm.data = client->data;
296 	tmpcrealm.length = client->length;
297 	while( i-1 <= links) {
298 
299 	    tmpsrealm.data = cap_nodes[i-1];
300 	    /* don't count trailing whitespace from profile_get */
301 	    tmpsrealm.length = strcspn(cap_nodes[i-1],"\t ");
302 	    if ((retval = krb5_tgtname(context,
303 				       &tmpsrealm,
304 				       &tmpcrealm,
305 				       &rettree[i]))) {
306 		while (i) {
307 		    krb5_free_principal(context, rettree[i-1]);
308 		    i--;
309 		}
310 		krb5_xfree(rettree);
311 				/* cleanup the cap_nodes from profile_get */
312 		for (i = 0; i<=links; i++) {
313 		    krb5_xfree(cap_nodes[i]);
314 		}
315 		krb5_xfree((char *)cap_nodes);
316 		return retval;
317 	    }
318 	    tmpcrealm.data = tmpsrealm.data;
319 	    tmpcrealm.length = tmpsrealm.length;
320 	    i++;
321 	}
322 	/* cleanup the cap_nodes from profile_get last one has server */
323 	for (i = 0; i<=links; i++) {
324 	    krb5_xfree(cap_nodes[i]);
325 	}
326 	krb5_xfree((char *)cap_nodes);
327     } else {  /* if not cap then use hierarchical method */
328 #endif
329 	for (prevccp = ccp = client->data;
330 	     ccp <= com_cdot;
331 	     ccp++) {
332 	    if (*ccp != realm_branch_char)
333 		continue;
334 	    ++ccp;				/* advance past dot */
335 	    tmpcrealm.data = prevccp;
336 	    tmpcrealm.length = client->length -
337 		(prevccp - client->data);
338 	    tmpsrealm.data = ccp;
339 	    tmpsrealm.length = client->length -
340 		(ccp - client->data);
341 	    if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm,
342 				       &rettree[i]))) {
343 		while (i) {
344 		    krb5_free_principal(context, rettree[i-1]);
345 		    i--;
346 		}
347 		krb5_xfree(rettree);
348 		return retval;
349 	    }
350 	    prevccp = ccp;
351 	    i++;
352 	}
353 	if (nocommon) {
354 	    tmpcrealm.data = com_cdot + 1;
355 	    tmpcrealm.length = client->length -
356 		(com_cdot + 1 - client->data);
357 	    tmpsrealm.data = com_sdot + 1;
358 	    tmpsrealm.length = server->length -
359 		(com_sdot + 1 - server->data);
360 	    if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm,
361 				       &rettree[i]))) {
362 		while (i) {
363 		    krb5_free_principal(context, rettree[i-1]);
364 		    i--;
365 		}
366 		krb5_xfree(rettree);
367 		return retval;
368 	    }
369 	    i++;
370 	}
371 
372 	for (prevscp = com_sdot + 1, scp = com_sdot - 1;
373 	     scp > server->data;
374 	     scp--) {
375 	    if (*scp != realm_branch_char)
376 		continue;
377 	    if (scp - 1 < server->data)
378 		break;			/* XXX only if . starts realm? */
379 	    tmpcrealm.data = prevscp;
380 	    tmpcrealm.length = server->length -
381 		(prevscp - server->data);
382 	    tmpsrealm.data = scp + 1;
383 	    tmpsrealm.length = server->length -
384 		(scp + 1 - server->data);
385 	    if ((retval = krb5_tgtname(context, &tmpsrealm, &tmpcrealm,
386 				       &rettree[i]))) {
387 		while (i) {
388 		    krb5_free_principal(context, rettree[i-1]);
389 		    i--;
390 		}
391 		krb5_xfree(rettree);
392 		return retval;
393 	    }
394 	    prevscp = scp + 1;
395 	    i++;
396 	}
397 	if (slen && com_sdot >= server->data) {
398 	    /* only necessary if building down tree from ancestor or client */
399 	    /* however, we can get here if we have only one component
400 	       in the server realm name, hence we make sure we found a component
401 	       separator there... */
402 	    tmpcrealm.data = prevscp;
403 	    tmpcrealm.length = server->length -
404 		(prevscp - server->data);
405 	    if ((retval = krb5_tgtname(context, server, &tmpcrealm,
406 				       &rettree[i]))) {
407 		while (i) {
408 		    krb5_free_principal(context, rettree[i-1]);
409 		    i--;
410 		}
411 		krb5_xfree(rettree);
412 		return retval;
413 	    }
414 	}
415 #ifdef CONFIGURABLE_AUTHENTICATION_PATH
416     }
417 #endif
418     *tree = rettree;
419 
420 #ifdef DEBUG_REFERRALS
421     printf("krb5_walk_realm_tree ending; tree (length %d) is:\n",links);
422     for(i=0;i<links+2;i++) {
423         if ((*tree)[i])
424 	    krb5int_dbgref_dump_principal("krb5_walk_realm_tree tree",(*tree)[i]);
425 	else
426 	    printf("tree element %i null\n");
427     }
428 #endif
429     return 0;
430 }
431 
432 #ifdef DEBUG_REFERRALS
krb5int_dbgref_dump_principal(char * d,krb5_principal p)433 void krb5int_dbgref_dump_principal(char *d, krb5_principal p)
434 {
435     int n;
436 
437     printf("  **%s: ",d);
438     for (n=0;n<p->length;n++)
439 	printf("%s<%.*s>",(n>0)?"/":"",p->data[n].length,p->data[n].data);
440     printf("@<%.*s>  (length %d, type %d)\n",p->realm.length,p->realm.data,
441 	   p->length, p->type);
442 }
443 #endif
444