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