summaryrefslogtreecommitdiffstats
path: root/src/lib/krb5/krb/gc_frm_kdc.c
blob: ed6bc55a740aa408c397c2cdbf9af1dd73ac8038 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/*
 * Copyright (c) 1994 by the Massachusetts Institute of Technology.
 * Copyright (c) 1994 CyberSAFE Corporation
 * Copyright (c) 1993 Open Computing Security Group
 * Copyright (c) 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.  Neither M.I.T., the Open Computing Security Group, nor 
 * CyberSAFE Corporation make any representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 * 
 * krb5_get_cred_from_kdc()
 * Get credentials from some KDC somewhere, possibly accumulating tgts
 * along the way.
 */

#include "k5-int.h"
#include <stdio.h>
#include "int-proto.h"

/*
 * Retrieve credentials for principal in_cred->client,
 * server in_cred->server, ticket flags creds->ticket_flags, possibly
 * second_ticket if needed by ticket_flags.
 * 
 * Credentials are requested from the KDC for the server's realm.  Any
 * TGT credentials obtained in the process of contacting the KDC are
 * returned in an array of credentials; tgts is filled in to point to an
 * array of pointers to credential structures (if no TGT's were used, the
 * pointer is zeroed).  TGT's may be returned even if no useful end ticket
 * was obtained.
 * 
 * The returned credentials are NOT cached.
 *
 * This routine should not be called if the credentials are already in
 * the cache.
 * 
 * If credentials are obtained, creds is filled in with the results;
 * creds->ticket and creds->keyblock->key are set to allocated storage,
 * which should be freed by the caller when finished.
 * 
 * returns errors, system errors.
 */

/* helper macro: convert flags to necessary KDC options */

#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)

static krb5_error_code
krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, kdcopt)
    krb5_context context;
    krb5_ccache ccache;
    krb5_creds  *in_cred;
    krb5_creds  **out_cred;
    krb5_creds  ***tgts;
    int		kdcopt;
{
  krb5_creds      **ret_tgts = NULL;
  int             ntgts = 0;

  krb5_creds      tgt, tgtq, *tgtr = NULL;
  krb5_error_code retval;
  krb5_principal  int_server = NULL;    /* Intermediate server for request */

  krb5_principal  *tgs_list = NULL;
  krb5_principal  *top_server = NULL;
  krb5_principal  *next_server = NULL;
  int             nservers = 0;

  /* in case we never get a TGT, zero the return */

  *tgts = NULL;
     
  memset((char *)&tgtq, 0, sizeof(tgtq));
  memset((char *)&tgt, 0, sizeof(tgt));

  /*
   * we know that the desired credentials aren't in the cache yet.
   *
   * To get them, we first need a tgt for the realm of the server.
   * first, we see if we have such a TGT in cache.  if not, then
   * we ask the kdc to give us one.  if that doesn't work, then
   * we try to get a tgt for a realm that is closest to the target.
   * once we have that, then we ask that realm if it can give us
   * tgt for the target.  if not, we do the process over with this
   * new tgt.
   */
  
  /*
   * (the ticket may be issued by some other intermediate
   *  realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY)
   */
  if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
      goto cleanup;

  /* get target tgt from cache */
  if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server),
			     krb5_princ_realm(context, in_cred->client),
			     &int_server))) {
      goto cleanup;
  }

  if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) {
      goto cleanup;
  }

  if ((retval = krb5_cc_retrieve_cred(context, ccache,
				      KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
				      &tgtq, &tgt))) {

    if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
	goto cleanup;
    }

    /*
     * Note that we want to request a TGT from our local KDC, even
     * if we already have a TGT for some intermediate realm.  The 
     * reason is that our local KDC may have a shortcut to the
     * destination realm, and if it does we want to use the
     * shortcut because it will provide greater security. - bcn
     */
  
    /*
     * didn't find it in the cache so it is time to get a local
     * tgt and walk the realms tree.
     */
    krb5_free_principal(context, int_server);
    int_server = NULL;
    if ((retval = krb5_tgtname(context, 
			       krb5_princ_realm(context, in_cred->client),
			       krb5_princ_realm(context, in_cred->client),
			       &int_server))) {
	goto cleanup;
    }
  
    krb5_free_cred_contents(context, &tgtq);
    memset((char *)&tgtq, 0, sizeof(tgtq));
    if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
	goto cleanup;
    if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
	goto cleanup;

    if ((retval = krb5_cc_retrieve_cred(context, ccache,
					KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
					&tgtq, &tgt))) {
	goto cleanup;
    }
  
    /* get a list of realms to consult */
  
    if ((retval = krb5_walk_realm_tree(context, 
				       krb5_princ_realm(context,in_cred->client),
				       krb5_princ_realm(context,in_cred->server),
				       &tgs_list, 
				       KRB5_REALM_BRANCH_CHAR))) {
	goto cleanup;
    }
  
    for (nservers = 0; tgs_list[nservers]; nservers++)
      ;
  
    /* allocate storage for TGT pointers. */
    
    if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) {
      retval = ENOMEM;
      goto cleanup;
    }
    *tgts = ret_tgts;
    
    /*
     * step one is to take the current tgt and see if there is a tgt for
     * krbtgt/realmof(target)@realmof(tgt).  if not, try to get one with
     * the tgt.
     *
     * if we don't get a tgt for the target, then try to find a tgt as
     * close to the target realm as possible. at each step if there isn't
     * a tgt in the cache we have to try and get one with our latest tgt.
     * once we have a tgt for a closer realm, we go back to step one.
     *
     * once we have a tgt for the target, we go try and get credentials.
     */
  
    for (top_server = tgs_list;
         top_server < tgs_list + nservers;
         top_server = next_server) {
    
      /* look in cache for a tgt for the destination */
    
      krb5_free_cred_contents(context, &tgtq);
      memset(&tgtq, 0, sizeof(tgtq));
      if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
	  goto cleanup;

      krb5_free_principal(context, int_server);
      int_server = NULL;
      if ((retval = krb5_tgtname(context, 
				 krb5_princ_realm(context, in_cred->server),
				 krb5_princ_realm(context, *top_server),
				 &int_server))) {
	  goto cleanup;
      }
    
      if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
	  goto cleanup;

      if ((retval = krb5_cc_retrieve_cred(context, ccache,
					  KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
					  &tgtq, &tgt))) {
    
	if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
	    goto cleanup;
	}
  
	/* didn't find it in the cache so try and get one */
	/* with current tgt.                              */
    
	if (!valid_enctype(tgt.keyblock.enctype)) {
	    retval = KRB5_PROG_ETYPE_NOSUPP;
	    goto cleanup;
	}
    
	krb5_free_cred_contents(context, &tgtq);
	memset(&tgtq, 0, sizeof(tgtq));
#ifdef HAVE_C_STRUCTURE_ASSIGNMENT
	tgtq.times        = tgt.times;
#else
	memcpy(&tgtq.times, &tgt.times, sizeof(krb5_ticket_times));
#endif

	if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
	    goto cleanup;
	if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
	    goto cleanup;
	tgtq.is_skey      = FALSE;
	tgtq.ticket_flags = tgt.ticket_flags;
	if ((retval = krb5_get_cred_via_tkt(context, &tgt,
					    FLAGS2OPTS(tgtq.ticket_flags),
					    tgt.addresses, &tgtq, &tgtr))) {
	      
       /*
	* couldn't get one so now loop backwards through the realms
	* list and try and get a tgt for a realm as close to the
	* target as possible. the kdc should give us a tgt for the
	* closest one it knows about, but not all kdc's do this yet.
	*/
  
	  for (next_server = tgs_list + nservers - 1;
	       next_server > top_server;
	       next_server--) {
	    krb5_free_cred_contents(context, &tgtq);
	    memset(&tgtq, 0, sizeof(tgtq));
	    if ((retval = krb5_copy_principal(context, tgt.client,
					      &tgtq.client)))
		goto cleanup;

	    krb5_free_principal(context, int_server);
	    int_server = NULL;
	    if ((retval = krb5_tgtname(context, 
				       krb5_princ_realm(context, *next_server),
				       krb5_princ_realm(context, *top_server),
				       &int_server))) {
		goto cleanup;
	    }
    
	    if ((retval = krb5_copy_principal(context, int_server,
					      &tgtq.server)))
		goto cleanup;

	    if ((retval = krb5_cc_retrieve_cred(context, ccache,
						KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
						&tgtq, &tgt))) {
	      if (retval != KRB5_CC_NOTFOUND) {
		  goto cleanup;
	      }
  
	      /* not in the cache so try and get one with our current tgt. */
  
	      if (!valid_enctype(tgt.keyblock.enctype)) {
		  retval = KRB5_PROG_ETYPE_NOSUPP;
		  goto cleanup;
	      }
            
	      krb5_free_cred_contents(context, &tgtq);
	      memset(&tgtq, 0, sizeof(tgtq));
	      tgtq.times        = tgt.times;
	      if ((retval = krb5_copy_principal(context, tgt.client,
						&tgtq.client)))
		  goto cleanup;
	      if ((retval = krb5_copy_principal(context, int_server,
						&tgtq.server)))
		  goto cleanup;
	      tgtq.is_skey      = FALSE;
	      tgtq.ticket_flags = tgt.ticket_flags;
	      if ((retval = krb5_get_cred_via_tkt(context, &tgt,
						  FLAGS2OPTS(tgtq.ticket_flags),
						  tgt.addresses,
						  &tgtq, &tgtr))) {
		  continue;
	      }
	      
	      /* save tgt in return array */
	      if ((retval = krb5_copy_creds(context, tgtr,
					    &ret_tgts[ntgts]))) {
		  goto cleanup;
	      }
	      krb5_free_creds(context, tgtr);
	      tgtr = NULL;
	      
	      tgt = *ret_tgts[ntgts++];
	    }
          
	    /* got one as close as possible, now start all over */
  
	    break;
	  }
  
	  if (next_server == top_server) {
	      goto cleanup;
	  }
	  continue;
        }
 
	/*
	 * Got a tgt.  If it is for the target realm we can go try for the
	 * credentials.  If it is not for the target realm, then make sure it
	 * is in the realms hierarchy and if so, save it and start the loop
	 * over from there.  Note that we only need to compare the instance
	 * names since that is the target realm of the tgt.
	 */
    
	for (next_server = top_server; *next_server; next_server++) {
            krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1);
            krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1);
            if (realm_1->length == realm_2->length &&
                !memcmp(realm_1->data, realm_2->data, realm_1->length)) {
		break;
            }
	}

	if (!next_server) {
	    retval = KRB5_KDCREP_MODIFIED;
	    goto cleanup;
	}

	if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) {
	    goto cleanup;
	}
	krb5_free_creds(context, tgtr);
	tgtr = NULL;
    
        tgt = *ret_tgts[ntgts++];

        /* we're done if it is the target */

        if (!*next_server++) break;
      }
    }
  }

  /* got/finally have tgt!  try for the creds */

  if (!valid_enctype(tgt.keyblock.enctype)) {
    retval = KRB5_PROG_ETYPE_NOSUPP;
    goto cleanup;
  }

  retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgt.ticket_flags) |
				 kdcopt | 
  				 	(in_cred->second_ticket.length ? 
				  	 KDC_OPT_ENC_TKT_IN_SKEY : 0),
				 tgt.addresses, in_cred, out_cred);

  /* cleanup and return */

cleanup:

  if (tgtr) krb5_free_creds(context, tgtr);
  if(tgs_list)  krb5_free_realm_tree(context, tgs_list);
  krb5_free_cred_contents(context, &tgtq); 
  if (int_server) krb5_free_principal(context, int_server); 
  if (ntgts == 0) {
      *tgts = NULL;
      if (ret_tgts)  free(ret_tgts);
      krb5_free_cred_contents(context, &tgt);
  }
  return(retval);
}

krb5_error_code
krb5_get_cred_from_kdc(context, ccache, in_cred, out_cred, tgts)
    krb5_context context;
    krb5_ccache ccache;
    krb5_creds  *in_cred;
    krb5_creds  **out_cred;
    krb5_creds  ***tgts;
{

  return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
				    0);
}

krb5_error_code
krb5_get_cred_from_kdc_validate(context, ccache, in_cred, out_cred, tgts)
    krb5_context context;
    krb5_ccache ccache;
    krb5_creds  *in_cred;
    krb5_creds  **out_cred;
    krb5_creds  ***tgts;
{

  return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
				    KDC_OPT_VALIDATE);
}

krb5_error_code
krb5_get_cred_from_kdc_renew(context, ccache, in_cred, out_cred, tgts)
    krb5_context context;
    krb5_ccache ccache;
    krb5_creds  *in_cred;
    krb5_creds  **out_cred;
    krb5_creds  ***tgts;
{

  return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
				    KDC_OPT_RENEW);
}