Convert DEBUG_REFERRALS to TRACE_* framework
[krb5.git] / src / lib / krb5 / krb / gc_via_tkt.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/gc_via_tkt.c */
3 /*
4  * Copyright 1990,1991,2007-2009 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 /*
28  * Given a tkt, and a target cred, get it.
29  * Assumes that the kdc_rep has been decrypted.
30  */
31
32 #include "k5-int.h"
33 #include "int-proto.h"
34 #include "fast.h"
35
36 static krb5_error_code
37 kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address,
38              krb5_data *psectkt, krb5_creds **ppcreds)
39 {
40     krb5_error_code retval;
41     krb5_data *pdata;
42
43     if ((*ppcreds = (krb5_creds *)calloc(1,sizeof(krb5_creds))) == NULL) {
44         return ENOMEM;
45     }
46
47     if ((retval = krb5_copy_principal(context, pkdcrep->client,
48                                       &(*ppcreds)->client)))
49         goto cleanup;
50
51     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
52                                       &(*ppcreds)->server)))
53         goto cleanup;
54
55     if ((retval = krb5_copy_keyblock_contents(context,
56                                               pkdcrep->enc_part2->session,
57                                               &(*ppcreds)->keyblock)))
58         goto cleanup;
59     TRACE_TGS_REPLY(context, (*ppcreds)->client, (*ppcreds)->server,
60                     &(*ppcreds)->keyblock);
61
62     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
63         goto cleanup_keyblock;
64     (*ppcreds)->second_ticket = *pdata;
65     free(pdata);
66
67     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
68     (*ppcreds)->times = pkdcrep->enc_part2->times;
69     (*ppcreds)->magic = KV5M_CREDS;
70
71     (*ppcreds)->authdata = NULL;                        /* not used */
72     (*ppcreds)->is_skey = psectkt->length != 0;
73
74     if (pkdcrep->enc_part2->caddrs) {
75         if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
76                                           &(*ppcreds)->addresses)))
77             goto cleanup_keyblock;
78     } else {
79         /* no addresses in the list means we got what we had */
80         if ((retval = krb5_copy_addresses(context, address,
81                                           &(*ppcreds)->addresses)))
82             goto cleanup_keyblock;
83     }
84
85     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
86         goto cleanup_keyblock;
87
88     (*ppcreds)->ticket = *pdata;
89     free(pdata);
90     return 0;
91
92 cleanup_keyblock:
93     krb5_free_keyblock_contents(context, &(*ppcreds)->keyblock);
94
95 cleanup:
96     free (*ppcreds);
97     *ppcreds = NULL;
98     return retval;
99 }
100
101 static krb5_error_code
102 check_reply_server(krb5_context context, krb5_flags kdcoptions,
103                    krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
104 {
105
106     if (!krb5_principal_compare(context, dec_rep->ticket->server,
107                                 dec_rep->enc_part2->server))
108         return KRB5_KDCREP_MODIFIED;
109
110     /* Reply is self-consistent. */
111
112     if (krb5_principal_compare(context, dec_rep->ticket->server,
113                                in_cred->server))
114         return 0;
115
116     /* Server in reply differs from what we requested. */
117
118     if (kdcoptions & KDC_OPT_CANONICALIZE) {
119         /* in_cred server differs from ticket returned, but ticket
120            returned is consistent and we requested canonicalization. */
121
122         TRACE_CHECK_REPLY_SERVER_DIFFERS(context, in_cred->server,
123                                          dec_rep->enc_part2->server);
124         return 0;
125     }
126
127     /* We didn't request canonicalization. */
128
129     if (!IS_TGS_PRINC(context, in_cred->server) ||
130         !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
131         /* Canonicalization not requested, and not a TGS referral. */
132         return KRB5_KDCREP_MODIFIED;
133     }
134 #if 0
135     /*
136      * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
137      * effectively checks this.
138      */
139     if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
140         data_eq(*in_cred->server->data[1], *in_cred->client->realm)) {
141         /* Attempted to rewrite local TGS. */
142         return KRB5_KDCREP_MODIFIED;
143     }
144 #endif
145     return 0;
146 }
147
148 /* Return true if a TGS credential is for the client's local realm. */
149 static inline int
150 tgt_is_local_realm(krb5_creds *tgt)
151 {
152     return (tgt->server->length == 2
153             && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME)
154             && data_eq(tgt->server->data[1], tgt->client->realm)
155             && data_eq(tgt->server->realm, tgt->client->realm));
156 }
157
158 krb5_error_code
159 krb5_get_cred_via_tkt(krb5_context context, krb5_creds *tkt,
160                       krb5_flags kdcoptions, krb5_address *const *address,
161                       krb5_creds *in_cred, krb5_creds **out_cred)
162 {
163     return krb5_get_cred_via_tkt_ext (context, tkt,
164                                       kdcoptions, address,
165                                       NULL, in_cred, NULL, NULL,
166                                       NULL, NULL, out_cred, NULL);
167 }
168
169 krb5_error_code
170 krb5int_make_tgs_request(krb5_context context,
171                          struct krb5int_fast_request_state *fast_state,
172                          krb5_creds *tkt,
173                          krb5_flags kdcoptions,
174                          krb5_address *const *address,
175                          krb5_pa_data **in_padata,
176                          krb5_creds *in_cred,
177                          krb5_error_code (*pacb_fct)(krb5_context,
178                                                      krb5_keyblock *,
179                                                      krb5_kdc_req *,
180                                                      void *),
181                          void *pacb_data,
182                          krb5_data *request_data,
183                          krb5_timestamp *timestamp,
184                          krb5_int32 *nonce,
185                          krb5_keyblock **subkey)
186 {
187     krb5_error_code retval;
188     krb5_enctype *enctypes = NULL;
189     krb5_boolean second_tkt;
190
191     request_data->data = NULL;
192     *timestamp = 0;
193     *subkey = NULL;
194
195     /* tkt->client must be equal to in_cred->client */
196     if (!krb5_principal_compare(context, tkt->client, in_cred->client))
197         return KRB5_PRINC_NOMATCH;
198
199     if (!tkt->ticket.length)
200         return KRB5_NO_TKT_SUPPLIED;
201
202     second_tkt = ((kdcoptions & (KDC_OPT_ENC_TKT_IN_SKEY |
203                                  KDC_OPT_CNAME_IN_ADDL_TKT)) != 0);
204     if (second_tkt && !in_cred->second_ticket.length)
205         return KRB5_NO_2ND_TKT;
206
207     if (in_cred->keyblock.enctype) {
208         enctypes = (krb5_enctype *)malloc(sizeof(krb5_enctype)*2);
209         if (enctypes == NULL)
210             return ENOMEM;
211         enctypes[0] = in_cred->keyblock.enctype;
212         enctypes[1] = 0;
213     }
214
215     retval = krb5int_make_tgs_request_ext(context, fast_state, kdcoptions,
216                                           &in_cred->times,
217                                           enctypes, in_cred->server, address,
218                                           in_cred->authdata, in_padata,
219                                           second_tkt ?
220                                           &in_cred->second_ticket : 0,
221                                           tkt, pacb_fct, pacb_data,
222                                           request_data,
223                                           timestamp, nonce, subkey);
224     if (enctypes != NULL)
225         free(enctypes);
226
227     return retval;
228 }
229
230 krb5_error_code
231 krb5int_process_tgs_reply(krb5_context context,
232                           struct krb5int_fast_request_state *fast_state,
233                           krb5_data *response_data,
234                           krb5_creds *tkt,
235                           krb5_flags kdcoptions,
236                           krb5_address *const *address,
237                           krb5_pa_data **in_padata,
238                           krb5_creds *in_cred,
239                           krb5_timestamp timestamp,
240                           krb5_int32 nonce,
241                           krb5_keyblock *subkey,
242                           krb5_pa_data ***out_padata,
243                           krb5_pa_data ***out_enc_padata,
244                           krb5_creds **out_cred)
245 {
246     krb5_error_code retval;
247     krb5_kdc_rep *dec_rep = NULL;
248     krb5_error *err_reply = NULL;
249     krb5_boolean s4u2self;
250
251     s4u2self = krb5int_find_pa_data(context, in_padata,
252                                     KRB5_PADATA_S4U_X509_USER) ||
253         krb5int_find_pa_data(context, in_padata,
254                              KRB5_PADATA_FOR_USER);
255
256     if (krb5_is_krb_error(response_data)) {
257         retval = decode_krb5_error(response_data, &err_reply);
258         if (retval != 0)
259             goto cleanup;
260         retval = krb5int_fast_process_error(context, fast_state,
261                                             &err_reply, NULL, NULL);
262         if (retval)
263             goto cleanup;
264         retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
265         if (err_reply->text.length > 0) {
266             switch (err_reply->error) {
267             case KRB_ERR_GENERIC:
268                 krb5_set_error_message(context, retval,
269                                        _("KDC returned error string: %.*s"),
270                                        err_reply->text.length,
271                                        err_reply->text.data);
272                 break;
273             case KDC_ERR_S_PRINCIPAL_UNKNOWN:
274             {
275                 char *s_name;
276                 if (err_reply->server &&
277                     krb5_unparse_name(context, err_reply->server, &s_name) == 0) {
278                     krb5_set_error_message(context, retval,
279                                            _("Server %s not found in Kerberos "
280                                              "database"), s_name);
281                     krb5_free_unparsed_name(context, s_name);
282                 } else
283                     /* In case there's a stale S_PRINCIPAL_UNKNOWN
284                        report already noted.  */
285                     krb5_clear_error_message(context);
286             }
287             break;
288             }
289         }
290         krb5_free_error(context, err_reply);
291         goto cleanup;
292     } else if (!krb5_is_tgs_rep(response_data)) {
293         retval = KRB5KRB_AP_ERR_MSG_TYPE;
294         goto cleanup;
295     }
296
297     /* Unfortunately, Heimdal at least up through 1.2  encrypts using
298        the session key not the subsession key.  So we try both. */
299     retval = krb5int_decode_tgs_rep(context, fast_state, response_data, subkey,
300                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SUBKEY,
301                                     &dec_rep);
302     if (retval) {
303         TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock);
304         if ((krb5int_decode_tgs_rep(context, fast_state, response_data,
305                                     &tkt->keyblock,
306                                     KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0)
307             retval = 0;
308         else
309             goto cleanup;
310     }
311
312     if (dec_rep->msg_type != KRB5_TGS_REP) {
313         retval = KRB5KRB_AP_ERR_MSG_TYPE;
314         goto cleanup;
315     }
316
317     /*
318      * Don't trust the ok-as-delegate flag from foreign KDCs unless the
319      * cross-realm TGT also had the ok-as-delegate flag set.
320      */
321     if (!tgt_is_local_realm(tkt)
322         && !(tkt->ticket_flags & TKT_FLG_OK_AS_DELEGATE))
323         dec_rep->enc_part2->flags &= ~TKT_FLG_OK_AS_DELEGATE;
324
325     /* make sure the response hasn't been tampered with..... */
326     retval = 0;
327
328     if (s4u2self && !IS_TGS_PRINC(context, dec_rep->ticket->server)) {
329         /* Final hop, check whether KDC supports S4U2Self */
330         if (krb5_principal_compare(context, dec_rep->client, in_cred->server))
331             retval = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
332     } else if ((kdcoptions & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) {
333         /* XXX for constrained delegation this check must be performed by caller
334          * as we don't have access to the key to decrypt the evidence ticket.
335          */
336         if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
337             retval = KRB5_KDCREP_MODIFIED;
338     }
339
340     if (retval == 0)
341         retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
342
343     if (dec_rep->enc_part2->nonce != nonce)
344         retval = KRB5_KDCREP_MODIFIED;
345
346     if ((kdcoptions & KDC_OPT_POSTDATED) &&
347         (in_cred->times.starttime != 0) &&
348         (in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
349         retval = KRB5_KDCREP_MODIFIED;
350
351     if ((in_cred->times.endtime != 0) &&
352         (dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
353         retval = KRB5_KDCREP_MODIFIED;
354
355     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
356         (in_cred->times.renew_till != 0) &&
357         (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
358         retval = KRB5_KDCREP_MODIFIED;
359
360     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
361         (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
362         (in_cred->times.endtime != 0) &&
363         (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
364         retval = KRB5_KDCREP_MODIFIED;
365
366     if (retval != 0)
367         goto cleanup;
368
369     if (!in_cred->times.starttime &&
370         !in_clock_skew(dec_rep->enc_part2->times.starttime,
371                        timestamp)) {
372         retval = KRB5_KDCREP_SKEW;
373         goto cleanup;
374     }
375
376     if (out_padata != NULL) {
377         *out_padata = dec_rep->padata;
378         dec_rep->padata = NULL;
379     }
380     if (out_enc_padata != NULL) {
381         *out_enc_padata = dec_rep->enc_part2->enc_padata;
382         dec_rep->enc_part2->enc_padata = NULL;
383     }
384
385     retval = kdcrep2creds(context, dec_rep, address,
386                           &in_cred->second_ticket, out_cred);
387     if (retval != 0)
388         goto cleanup;
389
390 cleanup:
391     if (dec_rep != NULL) {
392         memset(dec_rep->enc_part2->session->contents, 0,
393                dec_rep->enc_part2->session->length);
394         krb5_free_kdc_rep(context, dec_rep);
395     }
396
397     return retval;
398 }
399
400 krb5_error_code
401 krb5_get_cred_via_tkt_ext(krb5_context context, krb5_creds *tkt,
402                           krb5_flags kdcoptions, krb5_address *const *address,
403                           krb5_pa_data **in_padata,
404                           krb5_creds *in_cred,
405                           krb5_error_code (*pacb_fct)(krb5_context,
406                                                       krb5_keyblock *,
407                                                       krb5_kdc_req *,
408                                                       void *),
409                           void *pacb_data,
410                           krb5_pa_data ***out_padata,
411                           krb5_pa_data ***out_enc_padata,
412                           krb5_creds **out_cred,
413                           krb5_keyblock **out_subkey)
414 {
415     krb5_error_code retval;
416     krb5_data request_data;
417     krb5_data response_data;
418     krb5_timestamp timestamp;
419     krb5_int32 nonce;
420     krb5_keyblock *subkey = NULL;
421     int tcp_only = 0, use_master = 0;
422     struct krb5int_fast_request_state *fast_state = NULL;
423
424     request_data.data = NULL;
425     request_data.length = 0;
426     response_data.data = NULL;
427     response_data.length = 0;
428
429     retval = krb5int_fast_make_state(context, &fast_state);
430     if (retval)
431         goto cleanup;
432
433     TRACE_GET_CRED_VIA_TKT_EXT(context, in_cred->server, tkt->server,
434                                kdcoptions);
435
436     retval = krb5int_make_tgs_request(context, fast_state, tkt, kdcoptions,
437                                       address, in_padata, in_cred,
438                                       pacb_fct, pacb_data,
439                                       &request_data, &timestamp, &nonce,
440                                       &subkey);
441     if (retval != 0)
442         goto cleanup;
443
444 send_again:
445     use_master = 0;
446     retval = krb5_sendto_kdc(context, &request_data,
447                              krb5_princ_realm(context, in_cred->server),
448                              &response_data, &use_master, tcp_only);
449     if (retval == 0) {
450         if (krb5_is_krb_error(&response_data)) {
451             if (!tcp_only) {
452                 krb5_error *err_reply;
453                 retval = decode_krb5_error(&response_data, &err_reply);
454                 if (retval != 0)
455                     goto cleanup;
456                 retval = krb5int_fast_process_error(context, fast_state,
457                                                     &err_reply, NULL, NULL);
458                 if (retval)
459                     goto cleanup;
460                 if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
461                     tcp_only = 1;
462                     krb5_free_error(context, err_reply);
463                     krb5_free_data_contents(context, &response_data);
464                     goto send_again;
465                 }
466                 krb5_free_error(context, err_reply);
467             }
468         }
469     } else
470         goto cleanup;
471
472     retval = krb5int_process_tgs_reply(context, fast_state, &response_data,
473                                        tkt, kdcoptions, address,
474                                        in_padata, in_cred,
475                                        timestamp, nonce, subkey,
476                                        out_padata,
477                                        out_enc_padata, out_cred);
478     if (retval != 0)
479         goto cleanup;
480
481 cleanup:
482     krb5int_fast_free_state(context, fast_state);
483     TRACE_GET_CRED_VIA_TKT_EXT_RETURN(context, retval);
484
485     krb5_free_data_contents(context, &request_data);
486     krb5_free_data_contents(context, &response_data);
487
488     if (subkey != NULL) {
489         if (retval == 0 && out_subkey != NULL)
490             *out_subkey = subkey;
491         else
492             krb5_free_keyblock(context, subkey);
493     }
494
495     return retval;
496 }