Merge mskrb-integ onto trunk
[krb5.git] / src / lib / krb5 / krb / gc_via_tkt.c
1 /*
2  * lib/krb5/krb/gc_via_tgt.c
3  *
4  * Copyright 1990,1991,2007,2008 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  * 
26  *
27  * Given a tkt, and a target cred, get it.
28  * Assumes that the kdc_rep has been decrypted.
29  */
30
31 #include "k5-int.h"
32 #include "int-proto.h"
33
34 static krb5_error_code
35 krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds)
36 {
37     krb5_error_code retval;  
38     krb5_data *pdata;
39   
40     if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
41         return ENOMEM;
42     }
43
44     if ((retval = krb5_copy_principal(context, pkdcrep->client,
45                                      &(*ppcreds)->client)))
46         goto cleanup;
47
48     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
49                                       &(*ppcreds)->server)))
50         goto cleanup;
51
52     if ((retval = krb5_copy_keyblock_contents(context, 
53                                               pkdcrep->enc_part2->session,
54                                               &(*ppcreds)->keyblock)))
55         goto cleanup;
56
57     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
58         goto cleanup;
59     (*ppcreds)->second_ticket = *pdata;
60     krb5_xfree(pdata);
61
62     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
63     (*ppcreds)->times = pkdcrep->enc_part2->times;
64     (*ppcreds)->magic = KV5M_CREDS;
65
66     (*ppcreds)->authdata = NULL;                        /* not used */
67     (*ppcreds)->is_skey = psectkt->length != 0;
68
69     if (pkdcrep->enc_part2->caddrs) {
70         if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
71                                           &(*ppcreds)->addresses)))
72             goto cleanup_keyblock;
73     } else {
74         /* no addresses in the list means we got what we had */
75         if ((retval = krb5_copy_addresses(context, address,
76                                           &(*ppcreds)->addresses)))
77             goto cleanup_keyblock;
78     }
79
80     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
81         goto cleanup_keyblock;
82
83     (*ppcreds)->ticket = *pdata;
84     free(pdata);
85     return 0;
86
87 cleanup_keyblock:
88     krb5_free_keyblock(context, &(*ppcreds)->keyblock);
89
90 cleanup:
91     free (*ppcreds);
92     *ppcreds = NULL;
93     return retval;
94 }
95  
96 static krb5_error_code
97 check_reply_server(krb5_context context, krb5_flags kdcoptions,
98                    krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
99 {
100
101     if (!krb5_principal_compare(context, dec_rep->ticket->server,
102                                 dec_rep->enc_part2->server))
103         return KRB5_KDCREP_MODIFIED;
104
105     /* Reply is self-consistent. */
106
107     if (krb5_principal_compare(context, dec_rep->ticket->server,
108                                in_cred->server))
109         return 0;
110
111     /* Server in reply differs from what we requested. */
112
113     if (kdcoptions & KDC_OPT_CANONICALIZE) {
114         /* in_cred server differs from ticket returned, but ticket
115            returned is consistent and we requested canonicalization. */
116 #if 0
117 #ifdef DEBUG_REFERRALS
118         printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
119         krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
120         krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
121 #endif
122 #endif
123         return 0;
124     }
125
126     /* We didn't request canonicalization. */
127
128     if (!IS_TGS_PRINC(context, in_cred->server) ||
129         !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
130         /* Canonicalization not requested, and not a TGS referral. */
131         return KRB5_KDCREP_MODIFIED;
132     }
133 #if 0
134     /*
135      * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
136      * effectively checks this.
137      */
138     if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
139         data_eq(*in_cred->server->data[1], *in_cred->client->realm) {
140         /* Attempted to rewrite local TGS. */
141         return KRB5_KDCREP_MODIFIED;
142     }
143 #endif
144     return 0;
145 }
146
147 krb5_error_code
148 krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
149                        krb5_flags kdcoptions, krb5_address *const *address,
150                        krb5_creds *in_cred, krb5_creds **out_cred)
151 {
152     krb5_error_code retval;
153     krb5_kdc_rep *dec_rep;
154     krb5_error *err_reply;
155     krb5_response tgsrep;
156     krb5_enctype *enctypes = 0;
157
158 #ifdef DEBUG_REFERRALS
159     printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
160     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
161     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
162 #endif
163
164     /* tkt->client must be equal to in_cred->client */
165     if (!krb5_principal_compare(context, tkt->client, in_cred->client))
166         return KRB5_PRINC_NOMATCH;
167
168     if (!tkt->ticket.length)
169         return KRB5_NO_TKT_SUPPLIED;
170
171     if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) && 
172         (!in_cred->second_ticket.length))
173         return(KRB5_NO_2ND_TKT);
174
175
176     /* check if we have the right TGT                    */
177     /* tkt->server must be equal to                      */
178     /* krbtgt/realmof(cred->server)@realmof(tgt->server) */
179 /*
180     {
181     krb5_principal tempprinc;
182         if (retval = krb5_tgtname(context, 
183                      krb5_princ_realm(context, in_cred->server),
184                      krb5_princ_realm(context, tkt->server), &tempprinc))
185             return(retval);
186
187         if (!krb5_principal_compare(context, tempprinc, tkt->server)) {
188             krb5_free_principal(context, tempprinc);
189             return (KRB5_PRINC_NOMATCH);
190         }
191     krb5_free_principal(context, tempprinc);
192     }
193 */
194
195     if (in_cred->keyblock.enctype) {
196         enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2);
197         if (!enctypes)
198             return ENOMEM;
199         enctypes[0] = in_cred->keyblock.enctype;
200         enctypes[1] = 0;
201     }
202     
203     retval = krb5_send_tgs(context, kdcoptions, &in_cred->times, enctypes, 
204                            in_cred->server, address, in_cred->authdata,
205                            0,           /* no padata */
206                            (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ? 
207                            &in_cred->second_ticket : NULL,
208                            tkt, &tgsrep);
209     if (enctypes)
210         free(enctypes);
211     if (retval) {
212 #ifdef DEBUG_REFERRALS
213         printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
214                error_message(retval));
215 #endif
216         return retval;
217     }
218
219     switch (tgsrep.message_type) {
220     case KRB5_TGS_REP:
221         break;
222     case KRB5_ERROR:
223     default:
224         if (krb5_is_krb_error(&tgsrep.response))
225             retval = decode_krb5_error(&tgsrep.response, &err_reply);
226         else
227             retval = KRB5KRB_AP_ERR_MSG_TYPE;
228
229         if (retval)                     /* neither proper reply nor error! */
230             goto error_4;
231
232         retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
233         if (err_reply->text.length > 0) {
234 #if 0
235             const char *m;
236 #endif
237             switch (err_reply->error) {
238             case KRB_ERR_GENERIC:
239                 krb5_set_error_message(context, retval,
240                                        "KDC returned error string: %.*s",
241                                        err_reply->text.length,
242                                        err_reply->text.data);
243                 break;
244             case KDC_ERR_S_PRINCIPAL_UNKNOWN:
245                 {
246                     char *s_name;
247                     if (krb5_unparse_name(context, in_cred->server, &s_name) == 0) {
248                         krb5_set_error_message(context, retval,
249                                                "Server %s not found in Kerberos database",
250                                                s_name);
251                         krb5_free_unparsed_name(context, s_name);
252                     } else
253                         /* In case there's a stale S_PRINCIPAL_UNKNOWN
254                            report already noted.  */
255                         krb5_clear_error_message(context);
256                 }
257                 break;
258             default:
259 #if 0 /* We should stop the KDC from sending back this text, because
260          if the local language doesn't match the KDC's language, we'd
261          just wind up printing out the error message in two languages.
262          Well, when we get some localization.  Which is already
263          happening in KfM.  */
264                 m = error_message(retval);
265                 /* Special case: MIT KDC may return this same string
266                    in the e-text field.  */
267                 if (strlen (m) == err_reply->text.length-1
268                     && !strcmp(m, err_reply->text.data))
269                     break;
270                 krb5_set_error_message(context, retval,
271                                        "%s (KDC supplied additional data: %s)",
272                                        m, err_reply->text.data);
273 #endif
274                 break;
275             }
276         }
277
278         krb5_free_error(context, err_reply);
279         goto error_4;
280     }
281
282     if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response,
283                                       &tkt->keyblock, &dec_rep)))
284         goto error_4;
285
286     if (dec_rep->msg_type != KRB5_TGS_REP) {
287         retval = KRB5KRB_AP_ERR_MSG_TYPE;
288         goto error_3;
289     }
290    
291     /* make sure the response hasn't been tampered with..... */
292     retval = 0;
293
294     if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
295         retval = KRB5_KDCREP_MODIFIED;
296
297     if (retval == 0)
298         retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
299
300     if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
301         retval = KRB5_KDCREP_MODIFIED;
302
303     if ((kdcoptions & KDC_OPT_POSTDATED) &&
304         (in_cred->times.starttime != 0) &&
305         (in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
306         retval = KRB5_KDCREP_MODIFIED;
307
308     if ((in_cred->times.endtime != 0) &&
309         (dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
310         retval = KRB5_KDCREP_MODIFIED;
311
312     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
313         (in_cred->times.renew_till != 0) &&
314         (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
315         retval = KRB5_KDCREP_MODIFIED;
316
317     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
318         (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
319         (in_cred->times.endtime != 0) &&
320         (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
321         retval = KRB5_KDCREP_MODIFIED;
322
323     if (retval != 0)
324         goto error_3;
325
326     if (!in_cred->times.starttime &&
327         !in_clock_skew(dec_rep->enc_part2->times.starttime,
328                        tgsrep.request_time)) {
329         retval = KRB5_KDCREP_SKEW;
330         goto error_3;
331     }
332     
333     retval = krb5_kdcrep2creds(context, dec_rep, address, 
334                                &in_cred->second_ticket,  out_cred);
335
336 error_3:;
337     memset(dec_rep->enc_part2->session->contents, 0,
338            dec_rep->enc_part2->session->length);
339     krb5_free_kdc_rep(context, dec_rep);
340
341 error_4:;
342     free(tgsrep.response.data);
343 #ifdef DEBUG_REFERRALS
344     printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
345 #endif
346     return retval;
347 }