1 /*
2  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * Copyright (c) 1994 by the Massachusetts Institute of Technology.
10  * Copyright (c) 1994 CyberSAFE Corporation
11  * Copyright (c) 1993 Open Computing Security Group
12  * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology.
13  * All Rights Reserved.
14  *
15  * Export of this software from the United States of America may
16  *   require a specific license from the United States Government.
17  *   It is the responsibility of any person or organization contemplating
18  *   export to obtain such a license before exporting.
19  *
20  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
21  * distribute this software and its documentation for any purpose and
22  * without fee is hereby granted, provided that the above copyright
23  * notice appear in all copies and that both that copyright notice and
24  * this permission notice appear in supporting documentation, and that
25  * the name of M.I.T. not be used in advertising or publicity pertaining
26  * to distribution of the software without specific, written prior
27  * permission.  Furthermore if you modify this software you must label
28  * your software as modified software and not distribute it in such a
29  * fashion that it might be confused with the original M.I.T. software.
30  * Neither M.I.T., the Open Computing Security Group, nor
31  * CyberSAFE Corporation make any representations about the suitability of
32  * this software for any purpose.  It is provided "as is" without express
33  * or implied warranty.
34  *
35  * krb5_get_cred_from_kdc()
36  * Get credentials from some KDC somewhere, possibly accumulating tgts
37  * along the way.
38  */
39 
40 #include <k5-int.h>
41 #include <stdio.h>
42 #include "int-proto.h"
43 
44 /*
45  * Retrieve credentials for principal in_cred->client,
46  * server in_cred->server, ticket flags creds->ticket_flags, possibly
47  * second_ticket if needed by ticket_flags.
48  *
49  * Credentials are requested from the KDC for the server's realm.  Any
50  * TGT credentials obtained in the process of contacting the KDC are
51  * returned in an array of credentials; tgts is filled in to point to an
52  * array of pointers to credential structures (if no TGT's were used, the
53  * pointer is zeroed).  TGT's may be returned even if no useful end ticket
54  * was obtained.
55  *
56  * The returned credentials are NOT cached.
57  *
58  * This routine should not be called if the credentials are already in
59  * the cache.
60  *
61  * If credentials are obtained, creds is filled in with the results;
62  * creds->ticket and creds->keyblock->key are set to allocated storage,
63  * which should be freed by the caller when finished.
64  *
65  * returns errors, system errors.
66  */
67 
68 /* helper macro: convert flags to necessary KDC options */
69 
70 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
71 
72 static krb5_error_code
73 krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, kdcopt)
74     krb5_context context;
75     krb5_ccache ccache;
76     krb5_creds  *in_cred;
77     krb5_creds  **out_cred;
78     krb5_creds  ***tgts;
79     int		kdcopt;
80 {
81   krb5_creds      **ret_tgts = NULL;
82   int             ntgts = 0;
83 
84   krb5_creds      tgt, tgtq, *tgtr = NULL;
85   krb5_error_code retval;
86   krb5_principal  int_server = NULL;    /* Intermediate server for request */
87 
88   krb5_principal  *tgs_list = NULL;
89   krb5_principal  *top_server = NULL;
90   krb5_principal  *next_server = NULL;
91   int             nservers = 0;
92   krb5_boolean	  old_use_conf_ktypes = context->use_conf_ktypes;
93 
94   /* in case we never get a TGT, zero the return */
95 
96   *tgts = NULL;
97 
98   memset((char *)&tgtq, 0, sizeof(tgtq));
99   memset((char *)&tgt, 0, sizeof(tgt));
100 
101   /*
102    * we know that the desired credentials aren't in the cache yet.
103    *
104    * To get them, we first need a tgt for the realm of the server.
105    * first, we see if we have such a TGT in cache.  if not, then
106    * we ask the kdc to give us one.  if that doesn't work, then
107    * we try to get a tgt for a realm that is closest to the target.
108    * once we have that, then we ask that realm if it can give us
109    * tgt for the target.  if not, we do the process over with this
110    * new tgt.
111    */
112 
113   /*
114    * (the ticket may be issued by some other intermediate
115    *  realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY)
116    */
117   if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
118       goto cleanup;
119 
120   /* get target tgt from cache */
121   if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server),
122 			     krb5_princ_realm(context, in_cred->client),
123 			     &int_server))) {
124       goto cleanup;
125   }
126 
127   if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) {
128       goto cleanup;
129   }
130 
131   /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */
132   if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
133 	goto cleanup;
134   }
135 
136   context->use_conf_ktypes = 1;
137   if ((retval = krb5_cc_retrieve_cred(context, ccache,
138 				    KRB5_TC_MATCH_SRV_NAMEONLY |
139 				    KRB5_TC_SUPPORTED_KTYPES |
140 				    KRB5_TC_MATCH_TIMES,
141 				    &tgtq, &tgt)) != 0) {
142 
143     if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
144 	goto cleanup;
145     }
146 
147     /*
148      * Note that we want to request a TGT from our local KDC, even
149      * if we already have a TGT for some intermediate realm.  The
150      * reason is that our local KDC may have a shortcut to the
151      * destination realm, and if it does we want to use the
152      * shortcut because it will provide greater security. - bcn
153      */
154 
155     /*
156      * didn't find it in the cache so it is time to get a local
157      * tgt and walk the realms tree.
158      */
159     krb5_free_principal(context, int_server);
160     int_server = NULL;
161     if ((retval = krb5_tgtname(context,
162 			       krb5_princ_realm(context, in_cred->client),
163 			       krb5_princ_realm(context, in_cred->client),
164 			       &int_server))) {
165 	goto cleanup;
166     }
167 
168     krb5_free_cred_contents(context, &tgtq);
169     memset((char *)&tgtq, 0, sizeof(tgtq));
170     if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
171 	goto cleanup;
172     if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
173 	goto cleanup;
174 
175     if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
176 	goto cleanup;
177     }
178 
179     if ((retval = krb5_cc_retrieve_cred(context, ccache,
180 					KRB5_TC_MATCH_SRV_NAMEONLY |
181 					KRB5_TC_SUPPORTED_KTYPES |
182 					KRB5_TC_MATCH_TIMES,
183 					&tgtq, &tgt)) != 0) {
184 	goto cleanup;
185     }
186 
187     /* get a list of realms to consult */
188 
189     if ((retval = krb5_walk_realm_tree(context,
190 				       krb5_princ_realm(context,in_cred->client),
191 				       krb5_princ_realm(context,in_cred->server),
192 				       &tgs_list,
193 				       KRB5_REALM_BRANCH_CHAR))) {
194 	goto cleanup;
195     }
196 
197     for (nservers = 0; tgs_list[nservers]; nservers++)
198       ;
199 
200     /* allocate storage for TGT pointers. */
201 
202     if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) {
203       retval = ENOMEM;
204       goto cleanup;
205     }
206     *tgts = ret_tgts;
207 
208     /*
209      * step one is to take the current tgt and see if there is a tgt for
210      * krbtgt/realmof(target)@realmof(tgt).  if not, try to get one with
211      * the tgt.
212      *
213      * if we don't get a tgt for the target, then try to find a tgt as
214      * close to the target realm as possible. at each step if there isn't
215      * a tgt in the cache we have to try and get one with our latest tgt.
216      * once we have a tgt for a closer realm, we go back to step one.
217      *
218      * once we have a tgt for the target, we go try and get credentials.
219      */
220 
221     for (top_server = tgs_list;
222          top_server < tgs_list + nservers;
223          top_server = next_server) {
224 
225       /* look in cache for a tgt for the destination */
226 
227       krb5_free_cred_contents(context, &tgtq);
228       memset(&tgtq, 0, sizeof(tgtq));
229       if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
230 	  goto cleanup;
231 
232       krb5_free_principal(context, int_server);
233       int_server = NULL;
234       if ((retval = krb5_tgtname(context,
235 				 krb5_princ_realm(context, in_cred->server),
236 				 krb5_princ_realm(context, *top_server),
237 				 &int_server))) {
238 	  goto cleanup;
239       }
240 
241       if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
242 	  goto cleanup;
243 
244       if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
245 	    goto cleanup;
246       }
247 
248       if ((retval = krb5_cc_retrieve_cred(context, ccache,
249 					KRB5_TC_MATCH_SRV_NAMEONLY |
250 					KRB5_TC_SUPPORTED_KTYPES |
251 					KRB5_TC_MATCH_TIMES,
252 					  &tgtq, &tgt)) != 0) {
253 
254 	if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
255 	    goto cleanup;
256 	}
257 
258 	/* didn't find it in the cache so try and get one */
259 	/* with current tgt.                              */
260 
261 	if (!valid_enctype(tgt.keyblock.enctype)) {
262 	    retval = KRB5_PROG_ETYPE_NOSUPP;
263 	    goto cleanup;
264 	}
265 
266 	krb5_free_cred_contents(context, &tgtq);
267 	memset(&tgtq, 0, sizeof(tgtq));
268 #ifdef HAVE_C_STRUCTURE_ASSIGNMENT
269 	tgtq.times        = tgt.times;
270 #else
271 	memcpy(&tgtq.times, &tgt.times, sizeof(krb5_ticket_times));
272 #endif
273 
274 	if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
275 	    goto cleanup;
276 	if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
277 	    goto cleanup;
278 	tgtq.is_skey      = FALSE;
279 	tgtq.ticket_flags = tgt.ticket_flags;
280 	if ((retval = krb5_get_cred_via_tkt(context, &tgt,
281 					    FLAGS2OPTS(tgtq.ticket_flags),
282 					    tgt.addresses, &tgtq, &tgtr))) {
283 
284        /*
285 	* couldn't get one so now loop backwards through the realms
286 	* list and try and get a tgt for a realm as close to the
287 	* target as possible. the kdc should give us a tgt for the
288 	* closest one it knows about, but not all kdc's do this yet.
289 	*/
290 
291 	  for (next_server = tgs_list + nservers - 1;
292 	       next_server > top_server;
293 	       next_server--) {
294 	    krb5_free_cred_contents(context, &tgtq);
295 	    memset(&tgtq, 0, sizeof(tgtq));
296 	    if ((retval = krb5_copy_principal(context, tgt.client,
297 					      &tgtq.client)))
298 		goto cleanup;
299 
300 	    krb5_free_principal(context, int_server);
301 	    int_server = NULL;
302 	    if ((retval = krb5_tgtname(context,
303 				       krb5_princ_realm(context, *next_server),
304 				       krb5_princ_realm(context, *top_server),
305 				       &int_server))) {
306 		goto cleanup;
307 	    }
308 
309 	    if ((retval = krb5_copy_principal(context, int_server,
310 					      &tgtq.server)))
311 		goto cleanup;
312 
313 	    if ((retval = krb5_timeofday(context,
314 					&(tgtq.times.endtime))) != 0) {
315 		goto cleanup;
316 	    }
317 
318 	    if ((retval = krb5_cc_retrieve_cred(context, ccache,
319 						KRB5_TC_MATCH_SRV_NAMEONLY |
320 						KRB5_TC_SUPPORTED_KTYPES |
321 						KRB5_TC_MATCH_TIMES,
322 						&tgtq, &tgt)) != 0) {
323 	      if (retval != KRB5_CC_NOTFOUND) {
324 		  goto cleanup;
325 	      }
326 
327 	      /* not in the cache so try and get one with our current tgt. */
328 
329 	      if (!valid_enctype(tgt.keyblock.enctype)) {
330 		  retval = KRB5_PROG_ETYPE_NOSUPP;
331 		  goto cleanup;
332 	      }
333 
334 	      krb5_free_cred_contents(context, &tgtq);
335 	      memset(&tgtq, 0, sizeof(tgtq));
336 	      tgtq.times        = tgt.times;
337 	      if ((retval = krb5_copy_principal(context, tgt.client,
338 						&tgtq.client)))
339 		  goto cleanup;
340 	      if ((retval = krb5_copy_principal(context, int_server,
341 						&tgtq.server)))
342 		  goto cleanup;
343 	      tgtq.is_skey      = FALSE;
344 	      tgtq.ticket_flags = tgt.ticket_flags;
345 	      if ((retval = krb5_get_cred_via_tkt(context, &tgt,
346 						  FLAGS2OPTS(tgtq.ticket_flags),
347 						  tgt.addresses,
348 						  &tgtq, &tgtr))) {
349 		  continue;
350 	      }
351 
352 	      /* save tgt in return array */
353 	      if ((retval = krb5_copy_creds(context, tgtr,
354 					    &ret_tgts[ntgts]))) {
355 		  goto cleanup;
356 	      }
357 	      krb5_free_creds(context, tgtr);
358 	      tgtr = NULL;
359 
360 	      tgt = *ret_tgts[ntgts++];
361 	    }
362 
363 	    /* got one as close as possible, now start all over */
364 
365 	    break;
366 	  }
367 
368 	  if (next_server == top_server) {
369 	      goto cleanup;
370 	  }
371 	  continue;
372         }
373 
374 	/*
375 	 * Got a tgt.  If it is for the target realm we can go try for the
376 	 * credentials.  If it is not for the target realm, then make sure it
377 	 * is in the realms hierarchy and if so, save it and start the loop
378 	 * over from there.  Note that we only need to compare the instance
379 	 * names since that is the target realm of the tgt.
380 	 */
381 
382 	for (next_server = top_server; *next_server; next_server++) {
383             krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1);
384             krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1);
385             if (realm_1->length == realm_2->length &&
386                 !memcmp(realm_1->data, realm_2->data, realm_1->length)) {
387 		break;
388             }
389 	}
390 
391 	if (!next_server) {
392 	    retval = KRB5_KDCREP_MODIFIED;
393 	    goto cleanup;
394 	}
395 
396 	if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) {
397 	    goto cleanup;
398 	}
399 	krb5_free_creds(context, tgtr);
400 	tgtr = NULL;
401 
402         tgt = *ret_tgts[ntgts++];
403 
404         /* we're done if it is the target */
405 
406         if (!*next_server++) break;
407       }
408     }
409   }
410 
411   /* got/finally have tgt!  try for the creds */
412 
413   if (!valid_enctype(tgt.keyblock.enctype)) {
414     retval = KRB5_PROG_ETYPE_NOSUPP;
415     goto cleanup;
416   }
417 
418   context->use_conf_ktypes = old_use_conf_ktypes;
419   retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgt.ticket_flags) |
420 				 kdcopt |
421   				 	(in_cred->second_ticket.length ?
422 				  	 KDC_OPT_ENC_TKT_IN_SKEY : 0),
423 				 tgt.addresses, in_cred, out_cred);
424 
425   /* cleanup and return */
426 
427 cleanup:
428 
429   if (tgtr) krb5_free_creds(context, tgtr);
430   if(tgs_list)  krb5_free_realm_tree(context, tgs_list);
431   krb5_free_cred_contents(context, &tgtq);
432   if (int_server) krb5_free_principal(context, int_server);
433   if (ntgts == 0) {
434       *tgts = NULL;
435       if (ret_tgts)  free(ret_tgts);
436       krb5_free_cred_contents(context, &tgt);
437   }
438   context->use_conf_ktypes = old_use_conf_ktypes;
439   return(retval);
440 }
441 
442 krb5_error_code
443 krb5_get_cred_from_kdc(context, ccache, in_cred, out_cred, tgts)
444     krb5_context context;
445     krb5_ccache ccache;
446     krb5_creds  *in_cred;
447     krb5_creds  **out_cred;
448     krb5_creds  ***tgts;
449 {
450 
451   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
452 				    0);
453 }
454 
455 krb5_error_code
456 krb5_get_cred_from_kdc_validate(context, ccache, in_cred, out_cred, tgts)
457     krb5_context context;
458     krb5_ccache ccache;
459     krb5_creds  *in_cred;
460     krb5_creds  **out_cred;
461     krb5_creds  ***tgts;
462 {
463 
464   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
465 				    KDC_OPT_VALIDATE);
466 }
467 
468 krb5_error_code
469 krb5_get_cred_from_kdc_renew(context, ccache, in_cred, out_cred, tgts)
470     krb5_context context;
471     krb5_ccache ccache;
472     krb5_creds  *in_cred;
473     krb5_creds  **out_cred;
474     krb5_creds  ***tgts;
475 {
476 
477   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
478 				    KDC_OPT_RENEW);
479 }
480