* krbconfig.c: Removed the krb5_clockskew variable
[krb5.git] / src / lib / krb5 / krb / get_in_tkt.c
1 /*
2  * lib/krb5/krb/get_in_tkt.c
3  *
4  * Copyright 1990,1991 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.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  * 
23  *
24  * krb5_get_in_tkt()
25  */
26
27 #include "k5-int.h"
28
29 /*
30  All-purpose initial ticket routine, usually called via
31  krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
32
33  Attempts to get an initial ticket for creds->client to use server
34  creds->server, (realm is taken from creds->client), with options
35  options, and using creds->times.starttime, creds->times.endtime,
36  creds->times.renew_till as from, till, and rtime.  
37  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
38
39  key_proc is called to fill in the key to be used for decryption.
40  keyseed is passed on to key_proc.
41
42  decrypt_proc is called to perform the decryption of the response (the
43  encrypted part is in dec_rep->enc_part; the decrypted part should be
44  allocated and filled into dec_rep->enc_part2
45  arg is passed on to decrypt_proc.
46
47  If addrs is non-NULL, it is used for the addresses requested.  If it is
48  null, the system standard addresses are used.
49
50  A succesful call will place the ticket in the credentials cache ccache
51  and fill in creds with the ticket information used/returned..
52
53  returns system errors, encryption errors
54
55  */
56
57
58 /* some typedef's for the function args to make things look a bit cleaner */
59
60 typedef krb5_error_code (*git_key_proc) PROTOTYPE((krb5_context,
61                                                    const krb5_enctype,
62                                                    krb5_data *,
63                                                    krb5_const_pointer,
64                                                    krb5_keyblock **));
65
66 typedef krb5_error_code (*git_decrypt_proc) PROTOTYPE((krb5_context,
67                                                        const krb5_keyblock *,
68                                                        krb5_const_pointer,
69                                                        krb5_kdc_rep * ));
70 /*
71  * This function sends a request to the KDC, and gets back a response;
72  * the response is parsed into ret_err_reply or ret_as_reply if the
73  * reponse is a KRB_ERROR or a KRB_AS_REP packet.  If it is some other
74  * unexpected response, an error is returned.
75  */
76 static krb5_error_code
77 send_as_request(context, request, time_now, ret_err_reply, ret_as_reply)
78     krb5_context                context;
79     krb5_kdc_req                *request;
80     krb5_timestamp              *time_now;
81     krb5_error **               ret_err_reply;
82     krb5_kdc_rep **             ret_as_reply;
83 {
84     krb5_kdc_rep *as_reply = 0;
85     krb5_error_code retval;
86     krb5_data *packet;
87     krb5_data reply;
88     char k4_version;            /* same type as *(krb5_data::data) */
89
90     reply.data = 0;
91     
92     if ((retval = krb5_timeofday(context, time_now)))
93         goto cleanup;
94
95     /*
96      * XXX we know they are the same size... and we should do
97      * something better than just the current time
98      */
99     request->nonce = (krb5_int32) *time_now;
100
101     /* encode & send to KDC */
102     if ((retval = encode_krb5_as_req(request, &packet)) != 0)
103         goto cleanup;
104
105     k4_version = packet->data[0];
106     retval = krb5_sendto_kdc(context, packet, 
107                              krb5_princ_realm(context, request->client), &reply);
108     krb5_free_data(context, packet);
109     if (retval)
110         goto cleanup;
111
112     /* now decode the reply...could be error or as_rep */
113     if (krb5_is_krb_error(&reply)) {
114         krb5_error *err_reply;
115
116         if ((retval = decode_krb5_error(&reply, &err_reply)))
117             /* some other error code--??? */        
118             goto cleanup;
119     
120         if (ret_err_reply)
121             *ret_err_reply = err_reply;
122         else
123             krb5_free_error(context, err_reply);
124         goto cleanup;
125     }
126
127     /*
128      * Check to make sure it isn't a V4 reply.
129      */
130     if (!krb5_is_as_rep(&reply)) {
131 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
132 #define V4_KRB_PROT_VERSION     4
133 #define V4_AUTH_MSG_ERR_REPLY   (5<<1)
134         /* check here for V4 reply */
135         unsigned int t_switch;
136
137         /* From v4 g_in_tkt.c: This used to be
138            switch (pkt_msg_type(rpkt) & ~1) {
139            but SCO 3.2v4 cc compiled that incorrectly.  */
140         t_switch = reply.data[1];
141         t_switch &= ~1;
142
143         if (t_switch == V4_AUTH_MSG_ERR_REPLY
144             && (reply.data[0] == V4_KRB_PROT_VERSION
145                 || reply.data[0] == k4_version)) {
146             retval = KRB5KRB_AP_ERR_V4_REPLY;
147         } else {
148             retval = KRB5KRB_AP_ERR_MSG_TYPE;
149         }
150         goto cleanup;
151     }
152
153     /* It must be a KRB_AS_REP message, or an bad returned packet */
154     if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
155         /* some other error code ??? */
156         goto cleanup;
157
158     if (as_reply->msg_type != KRB5_AS_REP) {
159         retval = KRB5KRB_AP_ERR_MSG_TYPE;
160         krb5_free_kdc_rep(context, as_reply);
161         goto cleanup;
162     }
163
164     if (ret_as_reply)
165         *ret_as_reply = as_reply;
166     else
167         krb5_free_kdc_rep(context, as_reply);
168
169 cleanup:
170     if (reply.data)
171         free(reply.data);
172     return retval;
173 }
174
175 static krb5_error_code
176 decrypt_as_reply(context, request, as_reply, key_proc, keyseed,
177                  decrypt_proc, decryptarg)
178     krb5_context                context;
179     krb5_kdc_req                *request;
180     krb5_kdc_rep                *as_reply;
181     git_key_proc                key_proc;
182     krb5_const_pointer          keyseed;
183     git_decrypt_proc            decrypt_proc;
184     krb5_const_pointer          decryptarg;
185 {
186     krb5_error_code             retval;
187     krb5_keyblock *             decrypt_key = 0;
188     krb5_data                   salt;
189     int                         use_salt = 0;
190     int                         f_salt = 0;
191     
192     if (as_reply->enc_part2)
193         return 0;
194     
195     /* Temp kludge to get salt */
196     if (as_reply->padata) {
197         krb5_pa_data **ptr;
198
199         for (ptr = as_reply->padata; *ptr; ptr++) {
200             if ((*ptr)->pa_type == KRB5_PADATA_PW_SALT) {
201                 /* use KDC-supplied salt, instead of default */
202                 salt.data = (char *)(*ptr)->contents;
203                 salt.length = (*ptr)->length;
204                 use_salt = 1;
205                 break;
206             }
207         }
208     }
209     
210     if (!use_salt) {
211         if ((retval = krb5_principal2salt(context, request->client, &salt)))
212             return(retval);
213         f_salt = 1;
214     }
215     
216     if ((retval = (*key_proc)(context, as_reply->ticket->enc_part.enctype,
217                               &salt, keyseed, &decrypt_key)))
218         goto cleanup;
219     
220     if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply)))
221         goto cleanup;
222
223 cleanup:
224     if (f_salt)
225         krb5_xfree(salt.data);
226     if (decrypt_key)
227         krb5_free_keyblock(context, decrypt_key);
228     return (retval);
229 }
230
231 static krb5_error_code
232 verify_as_reply(context, time_now, request, as_reply)
233     krb5_context                context;
234     krb5_timestamp              time_now;
235     krb5_kdc_req                *request;
236     krb5_kdc_rep                *as_reply;
237 {
238     /* check the contents for sanity: */
239     if (!as_reply->enc_part2->times.starttime)
240         as_reply->enc_part2->times.starttime =
241             as_reply->enc_part2->times.authtime;
242     
243     if (!krb5_principal_compare(context, as_reply->client, request->client)
244         || !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)
245         || !krb5_principal_compare(context, as_reply->ticket->server, request->server)
246         || (request->nonce != as_reply->enc_part2->nonce)
247         /* XXX check for extraneous flags */
248         /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
249         || ((request->from != 0) &&
250             (request->from != as_reply->enc_part2->times.starttime))
251         || ((request->till != 0) &&
252             (as_reply->enc_part2->times.endtime > request->till))
253         || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
254             (request->rtime != 0) &&
255             (as_reply->enc_part2->times.renew_till > request->rtime))
256         || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
257             (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
258             (request->till != 0) &&
259             (as_reply->enc_part2->times.renew_till > request->till))
260         )
261         return KRB5_KDCREP_MODIFIED;
262
263     if ((request->from == 0) &&
264         (labs(as_reply->enc_part2->times.starttime - time_now)
265          > context->clockskew))
266         return (KRB5_KDCREP_SKEW);
267
268     return 0;
269 }
270
271 static krb5_error_code
272 stash_as_reply(context, time_now, request, as_reply, creds, ccache)
273     krb5_context                context;
274     krb5_timestamp              time_now;
275     krb5_kdc_req                *request;
276     krb5_kdc_rep                *as_reply;
277     krb5_creds *                creds;
278     krb5_ccache                 ccache;
279 {
280     krb5_error_code             retval;
281     krb5_data *                 packet;
282
283    if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME)
284        krb5_set_time_offsets(context,
285                              (as_reply->enc_part2->times.authtime -
286                               time_now),
287                              0);
288
289     /* XXX issue warning if as_reply->enc_part2->key_exp is nearby */
290         
291     /* fill in the credentials */
292     if ((retval = krb5_copy_keyblock_contents(context, 
293                                               as_reply->enc_part2->session,
294                                               &creds->keyblock)))
295         return (retval);
296
297     creds->times = as_reply->enc_part2->times;
298     creds->is_skey = FALSE;             /* this is an AS_REQ, so cannot
299                                            be encrypted in skey */
300     creds->ticket_flags = as_reply->enc_part2->flags;
301     if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
302                                       &creds->addresses)))
303         goto cleanup;
304
305     creds->second_ticket.length = 0;
306     creds->second_ticket.data = 0;
307
308     if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
309         goto cleanup;
310
311     creds->ticket = *packet;
312     krb5_xfree(packet);
313
314     /* store it in the ccache! */
315     if (ccache) {
316       if ((retval = krb5_cc_store_cred(context, ccache, creds)))
317         goto cleanup;
318     }
319
320 cleanup:
321     if (retval) {
322         if (creds->keyblock.contents) {
323             memset((char *)creds->keyblock.contents, 0,
324                    creds->keyblock.length);
325             krb5_xfree(creds->keyblock.contents);
326             creds->keyblock.contents = 0;
327             creds->keyblock.length = 0;
328         }
329         if (creds->ticket.data) {
330             krb5_xfree(creds->ticket.data);
331             creds->ticket.data = 0;
332         }
333         if (creds->addresses) {
334             krb5_free_addresses(context, creds->addresses);
335             creds->addresses = 0;
336         }
337     }
338     return (retval);
339 }
340
341 static krb5_error_code
342 make_preauth_list(context, ptypes, ret_list)
343     krb5_context        context;
344     krb5_preauthtype *  ptypes;
345     krb5_pa_data ***    ret_list;
346 {
347     krb5_preauthtype *          ptypep;
348     krb5_pa_data **             preauthp;
349     krb5_pa_data **             preauth_to_use;
350     int                         i;
351
352     for (i=1, ptypep = ptypes; *ptypep; ptypep++, i++)
353         ;
354     preauth_to_use = malloc(i * sizeof(krb5_pa_data *));
355     if (preauth_to_use == NULL)
356         return (ENOMEM);
357     for (preauthp = preauth_to_use, ptypep = ptypes;
358          *ptypep;
359          preauthp++, ptypep++) {
360         *preauthp = malloc(sizeof(krb5_pa_data));
361         if (*preauthp == NULL) {
362             krb5_free_pa_data(context, preauth_to_use);
363             return (ENOMEM);
364         }
365         (*preauthp)->magic = KV5M_PA_DATA;
366         (*preauthp)->pa_type = *ptypep;
367         (*preauthp)->length = 0;
368         (*preauthp)->contents = 0;
369     }
370     *ret_list = preauth_to_use;
371     return 0;
372 }
373
374 #define MAX_IN_TKT_LOOPS 16
375
376 krb5_error_code
377 krb5_get_in_tkt(context, options, addrs, ktypes, ptypes, key_proc, keyseed,
378                 decrypt_proc, decryptarg, creds, ccache, ret_as_reply)
379     krb5_context context;
380     const krb5_flags options;
381     krb5_address * const * addrs;
382     krb5_enctype * ktypes;
383     krb5_preauthtype * ptypes;
384     git_key_proc key_proc;
385     krb5_const_pointer keyseed;
386     git_decrypt_proc decrypt_proc;
387     krb5_const_pointer decryptarg;
388     krb5_creds * creds;
389     krb5_ccache ccache;
390     krb5_kdc_rep ** ret_as_reply;
391 {
392     krb5_error_code     retval;
393     krb5_timestamp      time_now;
394     krb5_kdc_req        request;
395     krb5_pa_data        **padata = 0;
396     krb5_error *        err_reply;
397     krb5_kdc_rep *      as_reply;
398     krb5_pa_data  **    preauth_to_use = 0;
399     int                 loopcount = 0;
400     int                 do_more = 0;
401
402     if (! krb5_realm_compare(context, creds->client, creds->server))
403         return KRB5_IN_TKT_REALM_MISMATCH;
404
405     if (ret_as_reply)
406         *ret_as_reply = 0;
407     
408     /*
409      * Set up the basic request structure
410      */
411     request.magic = KV5M_KDC_REQ;
412     request.msg_type = KRB5_AS_REQ;
413     request.addresses = 0;
414     request.ktype = 0;
415     if (addrs)
416         request.addresses = (krb5_address **) addrs;
417     else
418         if ((retval = krb5_os_localaddr(context, &request.addresses)))
419             goto cleanup;
420     request.padata = 0;
421     request.kdc_options = options;
422     request.client = creds->client;
423     request.server = creds->server;
424     request.from = creds->times.starttime;
425     request.till = creds->times.endtime;
426     request.rtime = creds->times.renew_till;
427     if (ktypes)
428         request.ktype = ktypes;
429     else
430         if ((retval = krb5_get_default_in_tkt_ktypes(context, &request.ktype)))
431             goto cleanup;
432     for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++);
433     request.authorization_data.ciphertext.length = 0;
434     request.authorization_data.ciphertext.data = 0;
435     request.unenc_authdata = 0;
436     request.second_ticket = 0;
437
438     /*
439      * If a list of preauth types are passed in, convert it to a
440      * preauth_to_use list.
441      */
442     if (ptypes) {
443         retval = make_preauth_list(context, ptypes, &preauth_to_use);
444         if (retval)
445             goto cleanup;
446     }
447             
448     while (1) {
449         if (loopcount++ > MAX_IN_TKT_LOOPS) {
450             retval = KRB5_GET_IN_TKT_LOOP;
451             goto cleanup;
452         }
453
454         if ((retval = krb5_obtain_padata(context, preauth_to_use, 0, key_proc,
455                                          keyseed, creds, &request)) != 0)
456             goto cleanup;
457         if (preauth_to_use)
458             krb5_free_pa_data(context, preauth_to_use);
459         preauth_to_use = 0;
460         
461         err_reply = 0;
462         as_reply = 0;
463         if ((retval = send_as_request(context, &request, &time_now, &err_reply,
464                                       &as_reply)))
465             goto cleanup;
466
467         if (err_reply) {
468             if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
469                 err_reply->e_data.length > 0) {
470                 retval = decode_krb5_padata_sequence(&err_reply->e_data,
471                                                      &preauth_to_use);
472                 krb5_free_error(context, err_reply);
473                 if (retval)
474                     goto cleanup;
475                 continue;
476             } else {
477                 retval = err_reply->error + ERROR_TABLE_BASE_krb5;
478                 krb5_free_error(context, err_reply);
479                 goto cleanup;
480             }
481         } else if (!as_reply) {
482             retval = KRB5KRB_AP_ERR_MSG_TYPE;
483             goto cleanup;
484         }
485         if ((retval = krb5_process_padata(context, &request, as_reply,
486                                           key_proc, keyseed, creds,
487                                           &do_more)) != 0)
488             goto cleanup;
489
490         if (!do_more)
491             break;
492     }
493     
494     if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
495                                    keyseed, decrypt_proc, decryptarg)))
496         goto cleanup;
497
498     if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
499         goto cleanup;
500
501     if ((retval = stash_as_reply(context, time_now, &request, as_reply,
502                                  creds, ccache)))
503         goto cleanup;
504
505 cleanup:
506     if (!ktypes && request.ktype)
507         free(request.ktype);
508     if (!addrs && request.addresses)
509         krb5_free_addresses(context, request.addresses);
510     if (request.padata)
511         krb5_free_pa_data(context, request.padata);
512     if (padata)
513         krb5_free_pa_data(context, padata);
514     if (preauth_to_use)
515         krb5_free_pa_data(context, preauth_to_use);
516     if (as_reply) {
517         if (ret_as_reply)
518             *ret_as_reply = as_reply;
519         else
520             krb5_free_kdc_rep(context, as_reply);
521     }
522     return (retval);
523 }