* IMplement etype_info in KDC. If the request contains any new
[krb5.git] / src / lib / krb5 / krb / get_in_tkt.c
1 /*
2  * lib/krb5/krb/get_in_tkt.c
3  *
4  * Copyright 1990,1991, 2003 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  * krb5_get_in_tkt()
28  */
29
30 #include <string.h>
31
32 #include "k5-int.h"
33 #include "int-proto.h"
34 #include "os-proto.h"
35
36 /*
37  All-purpose initial ticket routine, usually called via
38  krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
39
40  Attempts to get an initial ticket for creds->client to use server
41  creds->server, (realm is taken from creds->client), with options
42  options, and using creds->times.starttime, creds->times.endtime,
43  creds->times.renew_till as from, till, and rtime.  
44  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
45
46  key_proc is called to fill in the key to be used for decryption.
47  keyseed is passed on to key_proc.
48
49  decrypt_proc is called to perform the decryption of the response (the
50  encrypted part is in dec_rep->enc_part; the decrypted part should be
51  allocated and filled into dec_rep->enc_part2
52  arg is passed on to decrypt_proc.
53
54  If addrs is non-NULL, it is used for the addresses requested.  If it is
55  null, the system standard addresses are used.
56
57  A succesful call will place the ticket in the credentials cache ccache
58  and fill in creds with the ticket information used/returned..
59
60  returns system errors, encryption errors
61
62  */
63
64
65 /* some typedef's for the function args to make things look a bit cleaner */
66
67 typedef krb5_error_code (*git_key_proc) (krb5_context,
68                                                    const krb5_enctype,
69                                                    krb5_data *,
70                                                    krb5_const_pointer,
71                                                    krb5_keyblock **);
72
73 typedef krb5_error_code (*git_decrypt_proc) (krb5_context,
74                                                        const krb5_keyblock *,
75                                                        krb5_const_pointer,
76                                                        krb5_kdc_rep * );
77
78 static krb5_error_code make_preauth_list (krb5_context, 
79                                                     krb5_preauthtype *,
80                                                     int, krb5_pa_data ***);
81 /*
82  * This function sends a request to the KDC, and gets back a response;
83  * the response is parsed into ret_err_reply or ret_as_reply if the
84  * reponse is a KRB_ERROR or a KRB_AS_REP packet.  If it is some other
85  * unexpected response, an error is returned.
86  */
87 static krb5_error_code
88 send_as_request(krb5_context            context,
89                 krb5_kdc_req            *request,
90                 krb5_timestamp          *time_now,
91                 krb5_error **           ret_err_reply,
92                 krb5_kdc_rep **         ret_as_reply,
93                 int                     use_master)
94 {
95     krb5_kdc_rep *as_reply = 0;
96     krb5_error_code retval;
97     krb5_data *packet = 0;
98     krb5_data reply;
99     char k4_version;            /* same type as *(krb5_data::data) */
100     int tcp_only = 0;
101
102     reply.data = 0;
103     
104     if ((retval = krb5_timeofday(context, time_now)))
105         goto cleanup;
106
107     /*
108      * XXX we know they are the same size... and we should do
109      * something better than just the current time
110      */
111     request->nonce = (krb5_int32) *time_now;
112
113     /* encode & send to KDC */
114     if ((retval = encode_krb5_as_req(request, &packet)) != 0)
115         goto cleanup;
116
117     k4_version = packet->data[0];
118 send_again:
119     retval = krb5_sendto_kdc(context, packet, 
120                              krb5_princ_realm(context, request->client),
121                              &reply, use_master, tcp_only);
122     if (retval)
123         goto cleanup;
124
125     /* now decode the reply...could be error or as_rep */
126     if (krb5_is_krb_error(&reply)) {
127         krb5_error *err_reply;
128
129         if ((retval = decode_krb5_error(&reply, &err_reply)))
130             /* some other error code--??? */        
131             goto cleanup;
132     
133         if (ret_err_reply) {
134             if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG
135                 && tcp_only == 0) {
136                 tcp_only = 1;
137                 krb5_free_error(context, err_reply);
138                 free(reply.data);
139                 reply.data = 0;
140                 goto send_again;
141             }
142             *ret_err_reply = err_reply;
143         } else
144             krb5_free_error(context, err_reply);
145         goto cleanup;
146     }
147
148     /*
149      * Check to make sure it isn't a V4 reply.
150      */
151     if (!krb5_is_as_rep(&reply)) {
152 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
153 #define V4_KRB_PROT_VERSION     4
154 #define V4_AUTH_MSG_ERR_REPLY   (5<<1)
155         /* check here for V4 reply */
156         unsigned int t_switch;
157
158         /* From v4 g_in_tkt.c: This used to be
159            switch (pkt_msg_type(rpkt) & ~1) {
160            but SCO 3.2v4 cc compiled that incorrectly.  */
161         t_switch = reply.data[1];
162         t_switch &= ~1;
163
164         if (t_switch == V4_AUTH_MSG_ERR_REPLY
165             && (reply.data[0] == V4_KRB_PROT_VERSION
166                 || reply.data[0] == k4_version)) {
167             retval = KRB5KRB_AP_ERR_V4_REPLY;
168         } else {
169             retval = KRB5KRB_AP_ERR_MSG_TYPE;
170         }
171         goto cleanup;
172     }
173
174     /* It must be a KRB_AS_REP message, or an bad returned packet */
175     if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
176         /* some other error code ??? */
177         goto cleanup;
178
179     if (as_reply->msg_type != KRB5_AS_REP) {
180         retval = KRB5KRB_AP_ERR_MSG_TYPE;
181         krb5_free_kdc_rep(context, as_reply);
182         goto cleanup;
183     }
184
185     if (ret_as_reply)
186         *ret_as_reply = as_reply;
187     else
188         krb5_free_kdc_rep(context, as_reply);
189
190 cleanup:
191     if (packet)
192         krb5_free_data(context, packet);
193     if (reply.data)
194         free(reply.data);
195     return retval;
196 }
197
198 static krb5_error_code
199 decrypt_as_reply(krb5_context           context,
200                  krb5_kdc_req           *request,
201                  krb5_kdc_rep           *as_reply,
202                  git_key_proc           key_proc,
203                  krb5_const_pointer     keyseed,
204                  krb5_keyblock *        key,
205                  git_decrypt_proc       decrypt_proc,
206                  krb5_const_pointer     decryptarg)
207 {
208     krb5_error_code             retval;
209     krb5_keyblock *             decrypt_key = 0;
210     krb5_data                   salt;
211     
212     if (as_reply->enc_part2)
213         return 0;
214
215     if (key)
216             decrypt_key = key;
217     else {
218         if ((retval = krb5_principal2salt(context, request->client, &salt)))
219             return(retval);
220     
221         retval = (*key_proc)(context, as_reply->enc_part.enctype,
222                              &salt, keyseed, &decrypt_key);
223         krb5_xfree(salt.data);
224         if (retval)
225             goto cleanup;
226     }
227     
228     if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply)))
229         goto cleanup;
230
231 cleanup:
232     if (!key && decrypt_key)
233         krb5_free_keyblock(context, decrypt_key);
234     return (retval);
235 }
236
237 static krb5_error_code
238 verify_as_reply(krb5_context            context,
239                 krb5_timestamp          time_now,
240                 krb5_kdc_req            *request,
241                 krb5_kdc_rep            *as_reply)
242 {
243     krb5_error_code             retval;
244     
245     /* check the contents for sanity: */
246     if (!as_reply->enc_part2->times.starttime)
247         as_reply->enc_part2->times.starttime =
248             as_reply->enc_part2->times.authtime;
249     
250     if (!krb5_principal_compare(context, as_reply->client, request->client)
251         || !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)
252         || !krb5_principal_compare(context, as_reply->ticket->server, request->server)
253         || (request->nonce != as_reply->enc_part2->nonce)
254         /* XXX check for extraneous flags */
255         /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
256         || ((request->kdc_options & KDC_OPT_POSTDATED) &&
257             (request->from != 0) &&
258             (request->from != as_reply->enc_part2->times.starttime))
259         || ((request->till != 0) &&
260             (as_reply->enc_part2->times.endtime > request->till))
261         || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
262             (request->rtime != 0) &&
263             (as_reply->enc_part2->times.renew_till > request->rtime))
264         || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
265             (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
266             (request->till != 0) &&
267             (as_reply->enc_part2->times.renew_till > request->till))
268         )
269         return KRB5_KDCREP_MODIFIED;
270
271     if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
272         retval = krb5_set_real_time(context,
273                                     as_reply->enc_part2->times.authtime, 0);
274         if (retval)
275             return retval;
276     } else {
277         if ((request->from == 0) &&
278             (labs(as_reply->enc_part2->times.starttime - time_now)
279              > context->clockskew))
280             return (KRB5_KDCREP_SKEW);
281     }
282     return 0;
283 }
284
285 static krb5_error_code
286 stash_as_reply(krb5_context             context,
287                krb5_timestamp           time_now,
288                krb5_kdc_req             *request,
289                krb5_kdc_rep             *as_reply,
290                krb5_creds *             creds,
291                krb5_ccache              ccache)
292 {
293     krb5_error_code             retval;
294     krb5_data *                 packet;
295     krb5_principal              client;
296     krb5_principal              server;
297
298     client = NULL;
299     server = NULL;
300
301     if (!creds->client)
302         if ((retval = krb5_copy_principal(context, as_reply->client, &client)))
303             goto cleanup;
304
305     if (!creds->server)
306         if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
307                                           &server)))
308             goto cleanup;
309
310     /* fill in the credentials */
311     if ((retval = krb5_copy_keyblock_contents(context, 
312                                               as_reply->enc_part2->session,
313                                               &creds->keyblock)))
314         goto cleanup;
315
316     creds->times = as_reply->enc_part2->times;
317     creds->is_skey = FALSE;             /* this is an AS_REQ, so cannot
318                                            be encrypted in skey */
319     creds->ticket_flags = as_reply->enc_part2->flags;
320     if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
321                                       &creds->addresses)))
322         goto cleanup;
323
324     creds->second_ticket.length = 0;
325     creds->second_ticket.data = 0;
326
327     if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
328         goto cleanup;
329
330     creds->ticket = *packet;
331     krb5_xfree(packet);
332
333     /* store it in the ccache! */
334     if (ccache)
335         if ((retval = krb5_cc_store_cred(context, ccache, creds)))
336             goto cleanup;
337
338     if (!creds->client)
339         creds->client = client;
340     if (!creds->server)
341         creds->server = server;
342
343 cleanup:
344     if (retval) {
345         if (client)
346             krb5_free_principal(context, client);
347         if (server)
348             krb5_free_principal(context, server);
349         if (creds->keyblock.contents) {
350             memset((char *)creds->keyblock.contents, 0,
351                    creds->keyblock.length);
352             krb5_xfree(creds->keyblock.contents);
353             creds->keyblock.contents = 0;
354             creds->keyblock.length = 0;
355         }
356         if (creds->ticket.data) {
357             krb5_xfree(creds->ticket.data);
358             creds->ticket.data = 0;
359         }
360         if (creds->addresses) {
361             krb5_free_addresses(context, creds->addresses);
362             creds->addresses = 0;
363         }
364     }
365     return (retval);
366 }
367
368 static krb5_error_code
369 make_preauth_list(krb5_context  context,
370                   krb5_preauthtype *    ptypes,
371                   int                   nptypes,
372                   krb5_pa_data ***      ret_list)
373 {
374     krb5_preauthtype *          ptypep;
375     krb5_pa_data **             preauthp;
376     int                         i;
377
378     if (nptypes < 0) {
379         for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
380             ;
381     }
382  
383     /* allocate space for a NULL to terminate the list */
384  
385     if ((preauthp =
386          (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
387         return(ENOMEM);
388  
389     for (i=0; i<nptypes; i++) {
390         if ((preauthp[i] =
391              (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
392             for (; i>=0; i++)
393                 free(preauthp[i]);
394             free(preauthp);
395             return (ENOMEM);
396         }
397         preauthp[i]->magic = KV5M_PA_DATA;
398         preauthp[i]->pa_type = ptypes[i];
399         preauthp[i]->length = 0;
400         preauthp[i]->contents = 0;
401     }
402      
403     /* fill in the terminating NULL */
404  
405     preauthp[nptypes] = NULL;
406  
407     *ret_list = preauthp;
408     return 0;
409 }
410
411 #define MAX_IN_TKT_LOOPS 16
412
413 krb5_error_code KRB5_CALLCONV
414 krb5_get_in_tkt(krb5_context context,
415                 const krb5_flags options,
416                 krb5_address * const * addrs,
417                 krb5_enctype * ktypes,
418                 krb5_preauthtype * ptypes,
419                 git_key_proc key_proc,
420                 krb5_const_pointer keyseed,
421                 git_decrypt_proc decrypt_proc,
422                 krb5_const_pointer decryptarg,
423                 krb5_creds * creds,
424                 krb5_ccache ccache,
425                 krb5_kdc_rep ** ret_as_reply)
426 {
427     krb5_error_code     retval;
428     krb5_timestamp      time_now;
429     krb5_keyblock *     decrypt_key = 0;
430     krb5_kdc_req        request;
431     krb5_pa_data        **padata = 0;
432     krb5_error *        err_reply;
433     krb5_kdc_rep *      as_reply = 0;
434     krb5_pa_data  **    preauth_to_use = 0;
435     int                 loopcount = 0;
436     krb5_int32          do_more = 0;
437
438     if (! krb5_realm_compare(context, creds->client, creds->server))
439         return KRB5_IN_TKT_REALM_MISMATCH;
440
441     if (ret_as_reply)
442         *ret_as_reply = 0;
443     
444     /*
445      * Set up the basic request structure
446      */
447     request.magic = KV5M_KDC_REQ;
448     request.msg_type = KRB5_AS_REQ;
449     request.addresses = 0;
450     request.ktype = 0;
451     request.padata = 0;
452     if (addrs)
453         request.addresses = (krb5_address **) addrs;
454     else
455         if ((retval = krb5_os_localaddr(context, &request.addresses)))
456             goto cleanup;
457     request.kdc_options = options;
458     request.client = creds->client;
459     request.server = creds->server;
460     request.from = creds->times.starttime;
461     request.till = creds->times.endtime;
462     request.rtime = creds->times.renew_till;
463     if ((retval = krb5_get_default_in_tkt_ktypes(context, &request.ktype)))
464         goto cleanup;
465     for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++);
466     if (ktypes) {
467         int i, req, next = 0;
468         for (req = 0; ktypes[req]; req++) {
469             if (ktypes[req] == request.ktype[next]) {
470                 next++;
471                 continue;
472             }
473             for (i = next + 1; i < request.nktypes; i++)
474                 if (ktypes[req] == request.ktype[i]) {
475                     /* Found the enctype we want, but not in the
476                        position we want.  Move it, but keep the old
477                        one from the desired slot around in case it's
478                        later in our requested-ktypes list.  */
479                     krb5_enctype t;
480                     t = request.ktype[next];
481                     request.ktype[next] = request.ktype[i];
482                     request.ktype[i] = t;
483                     next++;
484                     break;
485                 }
486             /* If we didn't find it, don't do anything special, just
487                drop it.  */
488         }
489         request.ktype[next] = 0;
490         request.nktypes = next;
491     }
492     request.authorization_data.ciphertext.length = 0;
493     request.authorization_data.ciphertext.data = 0;
494     request.unenc_authdata = 0;
495     request.second_ticket = 0;
496
497     /*
498      * If a list of preauth types are passed in, convert it to a
499      * preauth_to_use list.
500      */
501     if (ptypes) {
502         retval = make_preauth_list(context, ptypes, -1, &preauth_to_use);
503         if (retval)
504             goto cleanup;
505     }
506             
507     while (1) {
508         if (loopcount++ > MAX_IN_TKT_LOOPS) {
509             retval = KRB5_GET_IN_TKT_LOOP;
510             goto cleanup;
511         }
512
513         if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
514                                          keyseed, creds, &request)) != 0)
515             goto cleanup;
516         if (preauth_to_use)
517             krb5_free_pa_data(context, preauth_to_use);
518         preauth_to_use = 0;
519         
520         err_reply = 0;
521         as_reply = 0;
522         if ((retval = send_as_request(context, &request, &time_now, &err_reply,
523                                       &as_reply, 0)))
524             goto cleanup;
525
526         if (err_reply) {
527             if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
528                 err_reply->e_data.length > 0) {
529                 retval = decode_krb5_padata_sequence(&err_reply->e_data,
530                                                      &preauth_to_use);
531                 krb5_free_error(context, err_reply);
532                 if (retval)
533                     goto cleanup;
534                 continue;
535             } else {
536                 retval = (krb5_error_code) err_reply->error 
537                     + ERROR_TABLE_BASE_krb5;
538                 krb5_free_error(context, err_reply);
539                 goto cleanup;
540             }
541         } else if (!as_reply) {
542             retval = KRB5KRB_AP_ERR_MSG_TYPE;
543             goto cleanup;
544         }
545         if ((retval = krb5_process_padata(context, &request, as_reply,
546                                           key_proc, keyseed, decrypt_proc, 
547                                           &decrypt_key, creds,
548                                           &do_more)) != 0)
549             goto cleanup;
550
551         if (!do_more)
552             break;
553     }
554     
555     if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
556                                    keyseed, decrypt_key, decrypt_proc,
557                                    decryptarg)))
558         goto cleanup;
559
560     if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
561         goto cleanup;
562
563     if ((retval = stash_as_reply(context, time_now, &request, as_reply,
564                                  creds, ccache)))
565         goto cleanup;
566
567 cleanup:
568     if (request.ktype)
569         free(request.ktype);
570     if (!addrs && request.addresses)
571         krb5_free_addresses(context, request.addresses);
572     if (request.padata)
573         krb5_free_pa_data(context, request.padata);
574     if (padata)
575         krb5_free_pa_data(context, padata);
576     if (preauth_to_use)
577         krb5_free_pa_data(context, preauth_to_use);
578     if (decrypt_key)
579         krb5_free_keyblock(context, decrypt_key);
580     if (as_reply) {
581         if (ret_as_reply)
582             *ret_as_reply = as_reply;
583         else
584             krb5_free_kdc_rep(context, as_reply);
585     }
586     return (retval);
587 }
588
589 /* begin libdefaults parsing code.  This should almost certainly move
590    somewhere else, but I don't know where the correct somewhere else
591    is yet. */
592
593 /* XXX Duplicating this is annoying; try to work on a better way.*/
594 static const char *const conf_yes[] = {
595     "y", "yes", "true", "t", "1", "on",
596     0,
597 };
598
599 static const char *const conf_no[] = {
600     "n", "no", "false", "nil", "0", "off",
601     0,
602 };
603
604 int
605 _krb5_conf_boolean(const char *s)
606 {
607     const char *const *p;
608
609     for(p=conf_yes; *p; p++) {
610         if (!strcasecmp(*p,s))
611             return 1;
612     }
613
614     for(p=conf_no; *p; p++) {
615         if (!strcasecmp(*p,s))
616             return 0;
617     }
618
619     /* Default to "no" */
620     return 0;
621 }
622
623 static krb5_error_code
624 krb5_libdefault_string(krb5_context context, const krb5_data *realm,
625                        const char *option, char **ret_value)
626 {
627     profile_t profile;
628     const char *names[5];
629     char **nameval = NULL;
630     krb5_error_code retval;
631     char realmstr[1024];
632
633     if (realm->length > sizeof(realmstr)-1)
634         return(EINVAL);
635
636     strncpy(realmstr, realm->data, realm->length);
637     realmstr[realm->length] = '\0';
638
639     if (!context || (context->magic != KV5M_CONTEXT)) 
640         return KV5M_CONTEXT;
641
642     profile = context->profile;
643             
644     names[0] = "libdefaults";
645
646     /*
647      * Try number one:
648      *
649      * [libdefaults]
650      *          REALM = {
651      *                  option = <boolean>
652      *          }
653      */
654
655     names[1] = realmstr;
656     names[2] = option;
657     names[3] = 0;
658     retval = profile_get_values(profile, names, &nameval);
659     if (retval == 0 && nameval && nameval[0])
660         goto goodbye;
661
662     /*
663      * Try number two:
664      *
665      * [libdefaults]
666      *          option = <boolean>
667      */
668     
669     names[1] = option;
670     names[2] = 0;
671     retval = profile_get_values(profile, names, &nameval);
672     if (retval == 0 && nameval && nameval[0])
673         goto goodbye;
674
675 goodbye:
676     if (!nameval) 
677         return(ENOENT);
678
679     if (!nameval[0]) {
680         retval = ENOENT;
681     } else {
682         *ret_value = malloc(strlen(nameval[0]) + 1);
683         if (!*ret_value)
684             retval = ENOMEM;
685         else
686             strcpy(*ret_value, nameval[0]);
687     }
688
689     profile_free_list(nameval);
690
691     return retval;
692 }
693
694 /* not static so verify_init_creds() can call it */
695 /* as well as the DNS code */
696
697 krb5_error_code
698 krb5_libdefault_boolean(krb5_context context, const krb5_data *realm,
699                         const char *option, int *ret_value)
700 {
701     char *string = NULL;
702     krb5_error_code retval;
703
704     retval = krb5_libdefault_string(context, realm, option, &string);
705
706     if (retval)
707         return(retval);
708
709     *ret_value = _krb5_conf_boolean(string);
710     free(string);
711
712     return(0);
713 }
714
715 krb5_error_code KRB5_CALLCONV
716 krb5_get_init_creds(krb5_context context,
717                     krb5_creds *creds,
718                     krb5_principal client,
719                     krb5_prompter_fct prompter,
720                     void *prompter_data,
721                     krb5_deltat start_time,
722                     char *in_tkt_service,
723                     krb5_get_init_creds_opt *options,
724                     krb5_gic_get_as_key_fct gak_fct,
725                     void *gak_data,
726                     int  use_master,
727                     krb5_kdc_rep **as_reply)
728 {
729     krb5_error_code ret;
730     krb5_kdc_req request;
731     krb5_pa_data **padata;
732     int tempint;
733     char *tempstr;
734     krb5_deltat renew_life;
735     int loopcount;
736     krb5_data salt;
737     krb5_data s2kparams;
738     krb5_keyblock as_key;
739     krb5_error *err_reply;
740     krb5_kdc_rep *local_as_reply;
741     krb5_timestamp time_now;
742     krb5_enctype etype = 0;
743
744     /* initialize everything which will be freed at cleanup */
745
746     s2kparams.data = NULL;
747     s2kparams.length = 0;
748     request.server = NULL;
749     request.ktype = NULL;
750     request.addresses = NULL;
751     request.padata = NULL;
752     padata = NULL;
753     as_key.length = 0;
754     salt.length = 0;
755     salt.data = NULL;
756
757         local_as_reply = 0;
758
759     /*
760      * Set up the basic request structure
761      */
762     request.magic = KV5M_KDC_REQ;
763     request.msg_type = KRB5_AS_REQ;
764
765     /* request.padata is filled in later */
766
767     request.kdc_options = 0;
768
769     /* forwardable */
770
771     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE))
772         tempint = options->forwardable;
773     else if ((ret = krb5_libdefault_boolean(context, &client->realm,
774                                             "forwardable", &tempint)) == 0)
775             ;
776     else
777         tempint = 0;
778     if (tempint)
779         request.kdc_options |= KDC_OPT_FORWARDABLE;
780
781     /* proxiable */
782
783     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE))
784         tempint = options->proxiable;
785     else if ((ret = krb5_libdefault_boolean(context, &client->realm,
786                                             "proxiable", &tempint)) == 0)
787             ;
788     else
789         tempint = 0;
790     if (tempint)
791         request.kdc_options |= KDC_OPT_PROXIABLE;
792
793     /* renewable */
794
795     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) {
796         renew_life = options->renew_life;
797     } else if ((ret = krb5_libdefault_string(context, &client->realm,
798                                              "renew_lifetime", &tempstr))
799                == 0) {
800         if ((ret = krb5_string_to_deltat(tempstr, &renew_life))) {
801             free(tempstr);
802             goto cleanup;
803         }
804     } else {
805         renew_life = 0;
806     }
807     if (renew_life > 0)
808         request.kdc_options |= KDC_OPT_RENEWABLE;
809
810     /* allow_postdate */
811
812     if (start_time > 0)
813         request.kdc_options |= (KDC_OPT_ALLOW_POSTDATE|KDC_OPT_POSTDATED);
814
815     /* client */
816
817     request.client = client;
818
819     if (in_tkt_service) {
820         /* this is ugly, because so are the data structures involved.  I'm
821            in the library, so I'm going to manipulate the data structures
822            directly, otherwise, it will be worse. */
823
824         if ((ret = krb5_parse_name(context, in_tkt_service, &request.server)))
825             goto cleanup;
826
827         /* stuff the client realm into the server principal.
828            realloc if necessary */
829         if (request.server->realm.length < request.client->realm.length)
830             if ((request.server->realm.data =
831                  (char *) realloc(request.server->realm.data,
832                                   request.client->realm.length)) == NULL) {
833                 ret = ENOMEM;
834                 goto cleanup;
835             }
836
837         request.server->realm.length = request.client->realm.length;
838         memcpy(request.server->realm.data, request.client->realm.data,
839                request.client->realm.length);
840     } else {
841         if ((ret = krb5_build_principal_ext(context, &request.server,
842                                            request.client->realm.length,
843                                            request.client->realm.data,
844                                            KRB5_TGS_NAME_SIZE,
845                                            KRB5_TGS_NAME,
846                                            request.client->realm.length,
847                                            request.client->realm.data,
848                                            0)))
849             goto cleanup;
850     }
851
852     if ((ret = krb5_timeofday(context, &request.from)))
853         goto cleanup;
854     request.from += start_time;
855
856     request.till = request.from;
857     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE))
858         request.till += options->tkt_life;
859     else
860         request.till += 10*60*60; /* this used to be hardcoded in kinit.c */
861
862     if (renew_life > 0) {
863         request.rtime = request.from;
864         request.rtime += renew_life;
865     } else {
866         request.rtime = 0;
867     }
868
869     /* nonce is filled in by send_as_request */
870
871     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) {
872         request.ktype = options->etype_list;
873         request.nktypes = options->etype_list_length;
874     } else if ((ret = krb5_get_default_in_tkt_ktypes(context,
875                                                      &request.ktype)) == 0) {
876         for (request.nktypes = 0;
877              request.ktype[request.nktypes];
878              request.nktypes++)
879             ;
880     } else {
881         /* there isn't any useful default here.  ret is set from above */
882         goto cleanup;
883     }
884
885     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)) {
886         request.addresses = options->address_list;
887     }
888     /* it would be nice if this parsed out an address list, but
889        that would be work. */
890     else if (((ret = krb5_libdefault_boolean(context, &client->realm,
891                                             "noaddresses", &tempint)) != 0)
892              || (tempint == 1)) {
893             ;
894     } else {
895         if ((ret = krb5_os_localaddr(context, &request.addresses)))
896             goto cleanup;
897     }
898
899     request.authorization_data.ciphertext.length = 0;
900     request.authorization_data.ciphertext.data = 0;
901     request.unenc_authdata = 0;
902     request.second_ticket = 0;
903
904     /* set up the other state.  */
905
906     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) {
907         if ((ret = make_preauth_list(context, options->preauth_list,
908                                     options->preauth_list_length, 
909                                      &padata)))
910             goto cleanup;
911     }
912
913     /* the salt is allocated from somewhere, unless it is from the caller,
914        then it is a reference */
915
916     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)) {
917         salt = *options->salt;
918     } else {
919         salt.length = SALT_TYPE_AFS_LENGTH;
920         salt.data = NULL;
921     }
922
923     /* now, loop processing preauth data and talking to the kdc */
924
925     for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) {
926         if (request.padata) {
927             krb5_free_pa_data(context, request.padata);
928             request.padata = NULL;
929         }
930
931         if ((ret = krb5_do_preauth(context, &request,
932                                   padata, &request.padata,
933                                   &salt, &s2kparams, &etype, &as_key, prompter,
934                                    prompter_data, gak_fct, gak_data)))
935             goto cleanup;
936
937         if (padata) {
938             krb5_free_pa_data(context, padata);
939             padata = 0;
940         }
941
942         err_reply = 0;
943         local_as_reply = 0;
944         if ((ret = send_as_request(context, &request, &time_now, &err_reply,
945                                    &local_as_reply, use_master)))
946             goto cleanup;
947
948         if (err_reply) {
949             if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
950                 err_reply->e_data.length > 0) {
951                 ret = decode_krb5_padata_sequence(&err_reply->e_data,
952                                                   &padata);
953                 krb5_free_error(context, err_reply);
954                 if (ret)
955                     goto cleanup;
956             } else {
957                 ret = (krb5_error_code) err_reply->error 
958                     + ERROR_TABLE_BASE_krb5;
959                 krb5_free_error(context, err_reply);
960                 goto cleanup;
961             }
962         } else if (local_as_reply) {
963             break;
964         } else {
965             ret = KRB5KRB_AP_ERR_MSG_TYPE;
966             goto cleanup;
967         }
968     }
969
970     if (loopcount == MAX_IN_TKT_LOOPS) {
971         ret = KRB5_GET_IN_TKT_LOOP;
972         goto cleanup;
973     }
974
975     /* process any preauth data in the as_reply */
976
977     if ((ret = krb5_do_preauth(context, &request,
978                                local_as_reply->padata, &padata,
979                                &salt, &s2kparams, &etype, &as_key, prompter,
980                                prompter_data, gak_fct, gak_data)))
981         goto cleanup;
982
983     /* XXX if there's padata on output, something is wrong, but it's
984        not obviously an error */
985
986     /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
987        the AS_REP comes back encrypted in the user's longterm key
988        instead of in the SAD. If there was a SAM preauth, there
989        will be an as_key here which will be the SAD. If that fails,
990        use the gak_fct to get the password, and try again. */
991       
992     /* XXX because etypes are handled poorly (particularly wrt SAM,
993        where the etype is fixed by the kdc), we may want to try
994        decrypt_as_reply twice.  If there's an as_key available, try
995        it.  If decrypting the as_rep fails, or if there isn't an
996        as_key at all yet, then use the gak_fct to get one, and try
997        again.  */
998
999     if (as_key.length)
1000         ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
1001                                NULL, &as_key, krb5_kdc_rep_decrypt_proc,
1002                                NULL);
1003     else
1004         ret = -1;
1005            
1006     if (ret) {
1007         /* if we haven't get gotten a key, get it now */
1008
1009         if ((ret = ((*gak_fct)(context, request.client,
1010                                local_as_reply->enc_part.enctype,
1011                                prompter, prompter_data, &salt, &s2kparams,
1012                                &as_key, gak_data))))
1013             goto cleanup;
1014
1015         if ((ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
1016                                     NULL, &as_key, krb5_kdc_rep_decrypt_proc,
1017                                     NULL)))
1018             goto cleanup;
1019     }
1020
1021     if ((ret = verify_as_reply(context, time_now, &request, local_as_reply)))
1022         goto cleanup;
1023
1024     /* XXX this should be inside stash_as_reply, but as long as
1025        get_in_tkt is still around using that arg as an in/out, I can't
1026        do that */
1027     memset(creds, 0, sizeof(*creds));
1028
1029     if ((ret = stash_as_reply(context, time_now, &request, local_as_reply,
1030                               creds, NULL)))
1031         goto cleanup;
1032
1033     /* success */
1034
1035     ret = 0;
1036
1037 cleanup:
1038     if (request.server)
1039         krb5_free_principal(context, request.server);
1040     if (request.ktype &&
1041         (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))))
1042         free(request.ktype);
1043     if (request.addresses &&
1044         (!(options &&
1045            (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST))))
1046         krb5_free_addresses(context, request.addresses);
1047     if (padata)
1048         krb5_free_pa_data(context, padata);
1049     if (request.padata)
1050         krb5_free_pa_data(context, request.padata);
1051     if (as_key.length)
1052         krb5_free_keyblock_contents(context, &as_key);
1053     if (salt.data &&
1054         (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT))))
1055         krb5_xfree(salt.data);
1056     if (as_reply)
1057         *as_reply = local_as_reply;
1058     else if (local_as_reply)
1059         krb5_free_kdc_rep(context, local_as_reply);
1060
1061     return(ret);
1062 }