Pass extra argument to krb5_walk_realm_tree.
[krb5.git] / src / lib / krb5 / krb / gc_frm_kdc.c
1 /*
2  * $Source$
3  * $Author$
4  *
5  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
6  * All Rights Reserved.
7  *
8  * Export of this software from the United States of America is assumed
9  *   to 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.  M.I.T. makes no representations about the suitability of
21  * this software for any purpose.  It is provided "as is" without express
22  * or implied warranty.
23  * 
24  *
25  * Get credentials from some KDC somewhere, possibly accumulating tgts
26  * along the way.
27  */
28
29 #if !defined(lint) && !defined(SABER)
30 static char rcsid_gcfkdc_c[] =
31 "$Id$";
32 #endif  /* !lint & !SABER */
33
34
35 #include <krb5/krb5.h>
36 #include <krb5/ext-proto.h>
37 #include "int-proto.h"
38
39 /*
40  * Retrieve credentials for principal creds->client,
41  * server creds->server, ticket flags creds->ticket_flags, possibly
42  * second_ticket if needed by ticket_flags.
43  * 
44  * Credentials are requested from the KDC for the server's realm.  Any
45  * TGT credentials obtained in the process of contacting the KDC are
46  * returned in an array of credentials; tgts is filled in to point to an
47  * array of pointers to credential structures (if no TGT's were used, the
48  * pointer is zeroed).  TGT's may be returned even if no useful end ticket
49  * was obtained.
50  * 
51  * The returned credentials are NOT cached.
52  *
53  * This routine should not be called if the credentials are already in
54  * the cache.
55  * 
56  * If credentials are obtained, creds is filled in with the results;
57  * creds->ticket and creds->keyblock->key are set to allocated storage,
58  * which should be freed by the caller when finished.
59  * 
60  * returns errors, system errors.
61  */
62
63 extern krb5_cksumtype krb5_kdc_req_sumtype;
64
65 /* helper function: convert flags to necessary KDC options */
66 #define flags2options(flags) (flags & KDC_TKT_COMMON_MASK)
67
68 krb5_error_code
69 krb5_get_cred_from_kdc (ccache, cred, tgts)
70     krb5_ccache ccache;
71     krb5_creds *cred;
72     krb5_creds ***tgts;
73 {
74     krb5_creds tgt, tgtq;
75     krb5_creds **ret_tgts = 0;
76     krb5_principal *tgs_list, *next_server;
77     krb5_principal final_server;
78     krb5_error_code retval;
79     int nservers;
80     int returning_tgt = 0;
81     krb5_enctype etype;
82
83     /* in case we never get a TGT, zero the return */
84     *tgts = 0;
85
86     /*
87      * we know that the desired credentials aren't in the cache yet.
88      *
89      * To get them, we first need a tgt for the realm of the server.
90      * first, we see if we have such a TGT in cache.
91      */
92     
93     /*
94      * look for ticket with:
95      * client == cred->client,
96      * server == "krbtgt/realmof(cred->server)@realmof(cred->client)"
97      *
98      * (actually, the ticket may be issued by some other intermediate
99      *  realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY below)
100      */
101
102     /*
103      * we're sharing some substructure here, which is dangerous.
104      * Be sure that if you muck with things here that tgtq.* doesn't share
105      * any substructure before you deallocate/clean up/whatever.
106      */
107     memset((char *)&tgtq, 0, sizeof(tgtq));
108     tgtq.client = cred->client;
109
110     if (retval = krb5_tgtname(krb5_princ_realm(cred->server),
111                               krb5_princ_realm(cred->client), &final_server))
112         return retval;
113     tgtq.server = final_server;
114
115     /* try to fetch it directly */
116     retval = krb5_cc_retrieve_cred (ccache,
117                                     KRB5_TC_MATCH_SRV_NAMEONLY,
118                                     &tgtq,
119                                     &tgt);
120
121     if (retval != 0) {
122         if (retval != KRB5_CC_NOTFOUND)
123             goto out;
124         /* don't have the right TGT in the cred cache.  Time to iterate
125            across realms to get the right TGT. */
126
127         /* get a list of realms to consult */
128         retval = krb5_walk_realm_tree(krb5_princ_realm(cred->client),
129                                       krb5_princ_realm(cred->server),
130                                       &tgs_list, KRB5_REALM_BRANCH_CHAR);
131         if (retval)
132             goto out;
133         /* walk the list BACKWARDS until we find a cached
134            TGT, then move forward obtaining TGTs until we get the last
135            TGT needed */
136         for (next_server = tgs_list; *next_server; next_server++);
137         nservers = next_server - tgs_list;
138         next_server--;
139
140         /* next_server now points to the last TGT */
141         for (; next_server >= tgs_list; next_server--) {
142             tgtq.server = *next_server;
143             retval = krb5_cc_retrieve_cred (ccache,
144                                             KRB5_TC_MATCH_SRV_NAMEONLY,
145                                             &tgtq,
146                                             &tgt);
147             if (retval) {
148                 if (retval != KRB5_CC_NOTFOUND) {
149                     krb5_free_realm_tree(tgs_list);
150                     goto out;
151                 }
152                 continue;
153             }
154             next_server++;
155             break;                      /* found one! */
156         }
157         if (next_server < tgs_list) {
158             /* didn't find any */
159             retval = KRB5_NO_TKT_IN_RLM;
160             krb5_free_realm_tree(tgs_list);
161             goto out;
162         }
163         /* allocate storage for TGT pointers. */
164         ret_tgts = (krb5_creds **)calloc(nservers+1, sizeof(krb5_creds));
165         if (!ret_tgts) {
166             retval = ENOMEM;
167             krb5_free_realm_tree(tgs_list);
168             goto out;
169         }
170         *tgts = ret_tgts;
171         for (nservers = 0; *next_server; next_server++, nservers++) {
172
173             krb5_data *tmpdata;
174
175             if (!valid_keytype(tgt.keyblock.keytype)) {
176                 retval = KRB5_PROG_KEYTYPE_NOSUPP;
177                 krb5_free_realm_tree(tgs_list);
178                 goto out;
179             }
180             /* now get the TGTs */
181             memset((char *)&tgtq, 0, sizeof(tgtq));
182             tgtq.times = tgt.times;
183             tgtq.client = tgt.client;
184
185             /* ask each realm for a tgt to the end */
186             if (retval = krb5_copy_data(krb5_princ_realm(*next_server), &tmpdata)) {
187                 krb5_free_realm_tree(tgs_list);
188                 goto out;
189             }
190             krb5_free_data(krb5_princ_realm(final_server));
191             krb5_princ_set_realm(final_server, tmpdata);
192             tgtq.server = final_server;
193
194             tgtq.is_skey = FALSE;
195             tgtq.ticket_flags = tgt.ticket_flags;
196
197             etype = krb5_keytype_array[tgt.keyblock.keytype]->system->proto_enctype;
198             if (retval = krb5_get_cred_via_tgt(&tgt,
199                                                flags2options(tgtq.ticket_flags),
200                                                etype,
201                                                krb5_kdc_req_sumtype,
202                                                &tgtq)) {
203                 krb5_free_realm_tree(tgs_list);
204                 goto out;
205             }
206             /* make sure the returned ticket is somewhere in the remaining
207                list, but we can tolerate different expected issuing realms */
208             while (*++next_server &&
209                    !krb5_principal_compare(&(next_server[0])[1],
210                                            &(tgtq.server[1])));
211             if (!next_server) {
212                 /* what we got back wasn't in the list! */
213                 krb5_free_realm_tree(tgs_list);
214                 retval = KRB5_KDCREP_MODIFIED;
215                 goto out;
216             }
217                                                            
218             /* save tgt in return array */
219             if (retval = krb5_copy_creds(&tgtq, &ret_tgts[nservers])) {
220                 krb5_free_realm_tree(tgs_list);
221                 goto out;
222             }
223             tgt = *ret_tgts[nservers];
224             returning_tgt = 1;          /* don't free it below... */
225             tgtq.client = 0;
226             tgtq.server = 0;
227             krb5_free_cred_contents(&tgtq);
228         }
229         krb5_free_realm_tree(tgs_list);
230     }
231     /* got/finally have tgt! */
232     if (!valid_keytype(tgt.keyblock.keytype)) {
233         retval = KRB5_PROG_KEYTYPE_NOSUPP;
234         goto out;
235     }
236     etype = krb5_keytype_array[tgt.keyblock.keytype]->system->proto_enctype;
237
238     if (cred->second_ticket.length)
239         retval = krb5_get_cred_via_2tgt(&tgt,
240                                        KDC_OPT_ENC_TKT_IN_SKEY | flags2options(tgt.ticket_flags),
241                                        etype, krb5_kdc_req_sumtype, cred);
242
243     else
244         retval = krb5_get_cred_via_tgt(&tgt,
245                                        flags2options(tgt.ticket_flags),
246                                        etype,
247                                        krb5_kdc_req_sumtype,
248                                        cred);
249     
250     if (!returning_tgt)
251         krb5_free_cred_contents(&tgt);
252 out:
253     krb5_free_principal(final_server);
254     return retval;
255 }