libkrb5 support for non-blocking AS requests
[krb5.git] / src / lib / krb5 / krb / get_in_tkt.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * lib/krb5/krb/get_in_tkt.c
4  *
5  * Copyright 1990,1991, 2003, 2008 by the Massachusetts Institute of Technology.
6  * All Rights Reserved.
7  *
8  * Export of this software from the United States of America may
9  *   require a specific license from the United States Government.
10  *   It is the responsibility of any person or organization contemplating
11  *   export to obtain such a license before exporting.
12  *
13  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
14  * distribute this software and its documentation for any purpose and
15  * without fee is hereby granted, provided that the above copyright
16  * notice appear in all copies and that both that copyright notice and
17  * this permission notice appear in supporting documentation, and that
18  * the name of M.I.T. not be used in advertising or publicity pertaining
19  * to distribution of the software without specific, written prior
20  * permission.  Furthermore if you modify this software you must label
21  * your software as modified software and not distribute it in such a
22  * fashion that it might be confused with the original M.I.T. software.
23  * M.I.T. makes no representations about the suitability of
24  * this software for any purpose.  It is provided "as is" without express
25  * or implied warranty.
26  *
27  *
28  * krb5_get_in_tkt()
29  */
30
31 #include <string.h>
32
33 #include "k5-int.h"
34 #include "int-proto.h"
35 #include "os-proto.h"
36 #include "fast.h"
37 #include "init_creds_ctx.h"
38
39 #if APPLE_PKINIT
40 #define     IN_TKT_DEBUG    0
41 #if         IN_TKT_DEBUG
42 #define     inTktDebug(args...)       printf(args)
43 #else
44 #define     inTktDebug(args...)
45 #endif
46 #endif /* APPLE_PKINIT */
47
48 /*
49   All-purpose initial ticket routine, usually called via
50   krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
51
52   Attempts to get an initial ticket for creds->client to use server
53   creds->server, (realm is taken from creds->client), with options
54   options, and using creds->times.starttime, creds->times.endtime,
55   creds->times.renew_till as from, till, and rtime.
56   creds->times.renew_till is ignored unless the RENEWABLE option is requested.
57
58   key_proc is called to fill in the key to be used for decryption.
59   keyseed is passed on to key_proc.
60
61   decrypt_proc is called to perform the decryption of the response (the
62   encrypted part is in dec_rep->enc_part; the decrypted part should be
63   allocated and filled into dec_rep->enc_part2
64   arg is passed on to decrypt_proc.
65
66   If addrs is non-NULL, it is used for the addresses requested.  If it is
67   null, the system standard addresses are used.
68
69   A succesful call will place the ticket in the credentials cache ccache
70   and fill in creds with the ticket information used/returned..
71
72   returns system errors, encryption errors
73
74 */
75
76
77 /* some typedef's for the function args to make things look a bit cleaner */
78
79 typedef krb5_error_code (*git_key_proc) (krb5_context,
80                                          krb5_enctype,
81                                          krb5_data *,
82                                          krb5_const_pointer,
83                                          krb5_keyblock **);
84
85 typedef krb5_error_code (*git_decrypt_proc) (krb5_context,
86                                              const krb5_keyblock *,
87                                              krb5_const_pointer,
88                                              krb5_kdc_rep * );
89
90 static krb5_error_code make_preauth_list (krb5_context,
91                                           krb5_preauthtype *,
92                                           int, krb5_pa_data ***);
93 static krb5_error_code sort_krb5_padata_sequence(krb5_context context,
94                                                  krb5_data *realm,
95                                                  krb5_pa_data **padata);
96
97 /*
98  * This function performs 32 bit bounded addition so we can generate
99  * lifetimes without overflowing krb5_int32
100  */
101 static krb5_int32
102 krb5int_addint32 (krb5_int32 x, krb5_int32 y)
103 {
104     if ((x > 0) && (y > (KRB5_INT32_MAX - x))) {
105         /* sum will be be greater than KRB5_INT32_MAX */
106         return KRB5_INT32_MAX;
107     } else if ((x < 0) && (y < (KRB5_INT32_MIN - x))) {
108         /* sum will be less than KRB5_INT32_MIN */
109         return KRB5_INT32_MIN;
110     }
111
112     return x + y;
113 }
114
115 #if APPLE_PKINIT
116 /*
117  * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this
118  * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this
119  * value is used interchangeably with an explicit now_time throughout this module...
120  */
121 static krb5_error_code
122 gen_nonce(krb5_context  context,
123           krb5_int32    *nonce)
124 {
125     krb5_int32 time_now;
126     krb5_error_code retval = krb5_timeofday(context, &time_now);
127     if(retval) {
128         return retval;
129     }
130     *nonce = time_now;
131     return 0;
132 }
133 #endif /* APPLE_PKINIT */
134
135 /*
136  * This function sends a request to the KDC, and gets back a response;
137  * the response is parsed into ret_err_reply or ret_as_reply if the
138  * reponse is a KRB_ERROR or a KRB_AS_REP packet.  If it is some other
139  * unexpected response, an error is returned.
140  */
141 static krb5_error_code
142 send_as_request(krb5_context            context,
143                 krb5_data *packet, const krb5_data *realm,
144                 krb5_error **           ret_err_reply,
145                 krb5_kdc_rep **         ret_as_reply,
146                 int                         *use_master)
147 {
148     krb5_kdc_rep *as_reply = 0;
149     krb5_error_code retval;
150     krb5_data reply;
151     char k4_version;            /* same type as *(krb5_data::data) */
152     int tcp_only = 0;
153
154     reply.data = 0;
155
156     /* set the nonce if the caller expects us to do it */
157
158     k4_version = packet->data[0];
159 send_again:
160     retval = krb5_sendto_kdc(context, packet,
161                              realm,
162                              &reply, use_master, tcp_only);
163 #if APPLE_PKINIT
164     inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval);
165 #endif /* APPLE_PKINIT */
166
167     if (retval)
168         goto cleanup;
169
170     /* now decode the reply...could be error or as_rep */
171     if (krb5_is_krb_error(&reply)) {
172         krb5_error *err_reply;
173
174         if ((retval = decode_krb5_error(&reply, &err_reply)))
175             /* some other error code--??? */
176             goto cleanup;
177
178         if (ret_err_reply) {
179             if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG
180                 && tcp_only == 0) {
181                 tcp_only = 1;
182                 krb5_free_error(context, err_reply);
183                 free(reply.data);
184                 reply.data = 0;
185                 goto send_again;
186             }
187             *ret_err_reply = err_reply;
188         } else
189             krb5_free_error(context, err_reply);
190         goto cleanup;
191     }
192
193     /*
194      * Check to make sure it isn't a V4 reply.
195      */
196     if (!krb5_is_as_rep(&reply)) {
197 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
198 #define V4_KRB_PROT_VERSION     4
199 #define V4_AUTH_MSG_ERR_REPLY   (5<<1)
200         /* check here for V4 reply */
201         unsigned int t_switch;
202
203         /* From v4 g_in_tkt.c: This used to be
204            switch (pkt_msg_type(rpkt) & ~1) {
205            but SCO 3.2v4 cc compiled that incorrectly.  */
206         t_switch = reply.data[1];
207         t_switch &= ~1;
208
209         if (t_switch == V4_AUTH_MSG_ERR_REPLY
210             && (reply.data[0] == V4_KRB_PROT_VERSION
211                 || reply.data[0] == k4_version)) {
212             retval = KRB5KRB_AP_ERR_V4_REPLY;
213         } else {
214             retval = KRB5KRB_AP_ERR_MSG_TYPE;
215         }
216         goto cleanup;
217     }
218
219     /* It must be a KRB_AS_REP message, or an bad returned packet */
220     if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
221         /* some other error code ??? */
222         goto cleanup;
223
224     if (as_reply->msg_type != KRB5_AS_REP) {
225         retval = KRB5KRB_AP_ERR_MSG_TYPE;
226         krb5_free_kdc_rep(context, as_reply);
227         goto cleanup;
228     }
229
230     if (ret_as_reply)
231         *ret_as_reply = as_reply;
232     else
233         krb5_free_kdc_rep(context, as_reply);
234
235 cleanup:
236     if (reply.data)
237         free(reply.data);
238     return retval;
239 }
240
241 static krb5_error_code
242 decrypt_as_reply(krb5_context           context,
243                  krb5_kdc_req           *request,
244                  krb5_kdc_rep           *as_reply,
245                  git_key_proc           key_proc,
246                  krb5_const_pointer     keyseed,
247                  krb5_keyblock *        key,
248                  git_decrypt_proc       decrypt_proc,
249                  krb5_const_pointer     decryptarg)
250 {
251     krb5_error_code             retval;
252     krb5_keyblock *             decrypt_key = 0;
253     krb5_data                   salt;
254
255     if (as_reply->enc_part2)
256         return 0;
257
258     if (key)
259         decrypt_key = key;
260     else {
261         /*
262          * Use salt corresponding to the client principal supplied by
263          * the KDC, which may differ from the requested principal if
264          * canonicalization is in effect.  We will check
265          * as_reply->client later in verify_as_reply.
266          */
267         if ((retval = krb5_principal2salt(context, as_reply->client, &salt)))
268             return(retval);
269
270         retval = (*key_proc)(context, as_reply->enc_part.enctype,
271                              &salt, keyseed, &decrypt_key);
272         free(salt.data);
273         if (retval)
274             goto cleanup;
275     }
276
277     if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply)))
278         goto cleanup;
279
280 cleanup:
281     if (!key && decrypt_key)
282         krb5_free_keyblock(context, decrypt_key);
283     return (retval);
284 }
285
286 static krb5_error_code
287 verify_as_reply(krb5_context            context,
288                 krb5_timestamp          time_now,
289                 krb5_kdc_req            *request,
290                 krb5_kdc_rep            *as_reply)
291 {
292     krb5_error_code             retval;
293     int                         canon_req;
294     int                         canon_ok;
295
296     /* check the contents for sanity: */
297     if (!as_reply->enc_part2->times.starttime)
298         as_reply->enc_part2->times.starttime =
299             as_reply->enc_part2->times.authtime;
300
301     /*
302      * We only allow the AS-REP server name to be changed if the
303      * caller set the canonicalize flag (or requested an enterprise
304      * principal) and we requested (and received) a TGT.
305      */
306     canon_req = ((request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
307         (krb5_princ_type(context, request->client) == KRB5_NT_ENTERPRISE_PRINCIPAL);
308     if (canon_req) {
309         canon_ok = IS_TGS_PRINC(context, request->server) &&
310             IS_TGS_PRINC(context, as_reply->enc_part2->server);
311     } else
312         canon_ok = 0;
313
314     if ((!canon_ok &&
315          (!krb5_principal_compare(context, as_reply->client, request->client) ||
316           !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)))
317         || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server)
318         || (request->nonce != as_reply->enc_part2->nonce)
319         /* XXX check for extraneous flags */
320         /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
321         || ((request->kdc_options & KDC_OPT_POSTDATED) &&
322             (request->from != 0) &&
323             (request->from != as_reply->enc_part2->times.starttime))
324         || ((request->till != 0) &&
325             (as_reply->enc_part2->times.endtime > request->till))
326         || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
327             (request->rtime != 0) &&
328             (as_reply->enc_part2->times.renew_till > request->rtime))
329         || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
330             !(request->kdc_options & KDC_OPT_RENEWABLE) &&
331             (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
332             (request->till != 0) &&
333             (as_reply->enc_part2->times.renew_till > request->till))
334     ) {
335 #if APPLE_PKINIT
336         inTktDebug("verify_as_reply: KDCREP_MODIFIED\n");
337 #if IN_TKT_DEBUG
338         if(request->client->realm.length && request->client->data->length)
339             inTktDebug("request: name %s realm %s\n",
340                        request->client->realm.data, request->client->data->data);
341         if(as_reply->client->realm.length && as_reply->client->data->length)
342             inTktDebug("reply  : name %s realm %s\n",
343                        as_reply->client->realm.data, as_reply->client->data->data);
344 #endif
345 #endif /* APPLE_PKINIT */
346         return KRB5_KDCREP_MODIFIED;
347     }
348
349     if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
350         retval = krb5_set_real_time(context,
351                                     as_reply->enc_part2->times.authtime, -1);
352         if (retval)
353             return retval;
354     } else {
355         if ((request->from == 0) &&
356             (labs(as_reply->enc_part2->times.starttime - time_now)
357              > context->clockskew))
358             return (KRB5_KDCREP_SKEW);
359     }
360     return 0;
361 }
362
363 static krb5_error_code
364 stash_as_reply(krb5_context             context,
365                krb5_timestamp           time_now,
366                krb5_kdc_req             *request,
367                krb5_kdc_rep             *as_reply,
368                krb5_creds *             creds,
369                krb5_ccache              ccache)
370 {
371     krb5_error_code             retval;
372     krb5_data *                 packet;
373     krb5_principal              client;
374     krb5_principal              server;
375
376     client = NULL;
377     server = NULL;
378
379     if (!creds->client)
380         if ((retval = krb5_copy_principal(context, as_reply->client, &client)))
381             goto cleanup;
382
383     if (!creds->server)
384         if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
385                                           &server)))
386             goto cleanup;
387
388     /* fill in the credentials */
389     if ((retval = krb5_copy_keyblock_contents(context,
390                                               as_reply->enc_part2->session,
391                                               &creds->keyblock)))
392         goto cleanup;
393
394     creds->times = as_reply->enc_part2->times;
395     creds->is_skey = FALSE;             /* this is an AS_REQ, so cannot
396                                            be encrypted in skey */
397     creds->ticket_flags = as_reply->enc_part2->flags;
398     if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
399                                       &creds->addresses)))
400         goto cleanup;
401
402     creds->second_ticket.length = 0;
403     creds->second_ticket.data = 0;
404
405     if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
406         goto cleanup;
407
408     creds->ticket = *packet;
409     free(packet);
410
411     /* store it in the ccache! */
412     if (ccache)
413         if ((retval = krb5_cc_store_cred(context, ccache, creds)))
414             goto cleanup;
415
416     if (!creds->client)
417         creds->client = client;
418     if (!creds->server)
419         creds->server = server;
420
421 cleanup:
422     if (retval) {
423         if (client)
424             krb5_free_principal(context, client);
425         if (server)
426             krb5_free_principal(context, server);
427         if (creds->keyblock.contents) {
428             memset(creds->keyblock.contents, 0,
429                    creds->keyblock.length);
430             free(creds->keyblock.contents);
431             creds->keyblock.contents = 0;
432             creds->keyblock.length = 0;
433         }
434         if (creds->ticket.data) {
435             free(creds->ticket.data);
436             creds->ticket.data = 0;
437         }
438         if (creds->addresses) {
439             krb5_free_addresses(context, creds->addresses);
440             creds->addresses = 0;
441         }
442     }
443     return (retval);
444 }
445
446 static krb5_error_code
447 make_preauth_list(krb5_context  context,
448                   krb5_preauthtype *    ptypes,
449                   int                   nptypes,
450                   krb5_pa_data ***      ret_list)
451 {
452     krb5_preauthtype *          ptypep;
453     krb5_pa_data **             preauthp;
454     int                         i;
455
456     if (nptypes < 0) {
457         for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
458             ;
459     }
460
461     /* allocate space for a NULL to terminate the list */
462
463     if ((preauthp =
464          (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
465         return(ENOMEM);
466
467     for (i=0; i<nptypes; i++) {
468         if ((preauthp[i] =
469              (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
470             for (; i>=0; i--)
471                 free(preauthp[i]);
472             free(preauthp);
473             return (ENOMEM);
474         }
475         preauthp[i]->magic = KV5M_PA_DATA;
476         preauthp[i]->pa_type = ptypes[i];
477         preauthp[i]->length = 0;
478         preauthp[i]->contents = 0;
479     }
480
481     /* fill in the terminating NULL */
482
483     preauthp[nptypes] = NULL;
484
485     *ret_list = preauthp;
486     return 0;
487 }
488
489 #define MAX_IN_TKT_LOOPS 16
490 static const krb5_enctype get_in_tkt_enctypes[] = {
491     ENCTYPE_DES3_CBC_SHA1,
492     ENCTYPE_ARCFOUR_HMAC,
493     ENCTYPE_DES_CBC_MD5,
494     ENCTYPE_DES_CBC_MD4,
495     ENCTYPE_DES_CBC_CRC,
496     0
497 };
498
499 static krb5_error_code
500 rewrite_server_realm(krb5_context context,
501                      krb5_const_principal old_server,
502                      const krb5_data *realm,
503                      krb5_boolean tgs,
504                      krb5_principal *server)
505 {
506     krb5_error_code retval;
507
508     assert(*server == NULL);
509
510     retval = krb5_copy_principal(context, old_server, server);
511     if (retval)
512         return retval;
513
514     krb5_free_data_contents(context, &(*server)->realm);
515     (*server)->realm.data = NULL;
516
517     retval = krb5int_copy_data_contents(context, realm, &(*server)->realm);
518     if (retval)
519         goto cleanup;
520
521     if (tgs) {
522         krb5_free_data_contents(context, &(*server)->data[1]);
523         (*server)->data[1].data = NULL;
524
525         retval = krb5int_copy_data_contents(context, realm, &(*server)->data[1]);
526         if (retval)
527             goto cleanup;
528     }
529
530 cleanup:
531     if (retval) {
532         krb5_free_principal(context, *server);
533         *server = NULL;
534     }
535
536     return retval;
537 }
538
539 static inline int
540 tgt_is_local_realm(krb5_creds *tgt)
541 {
542     return (tgt->server->length == 2
543             && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME)
544             && data_eq(tgt->server->data[1], tgt->client->realm)
545             && data_eq(tgt->server->realm, tgt->client->realm));
546 }
547
548 krb5_error_code KRB5_CALLCONV
549 krb5_get_in_tkt(krb5_context context,
550                 krb5_flags options,
551                 krb5_address * const * addrs,
552                 krb5_enctype * ktypes,
553                 krb5_preauthtype * ptypes,
554                 git_key_proc key_proc,
555                 krb5_const_pointer keyseed,
556                 git_decrypt_proc decrypt_proc,
557                 krb5_const_pointer decryptarg,
558                 krb5_creds * creds,
559                 krb5_ccache ccache,
560                 krb5_kdc_rep ** ret_as_reply)
561 {
562     krb5_error_code     retval;
563     krb5_timestamp      time_now;
564     krb5_keyblock *     decrypt_key = 0;
565     krb5_kdc_req        request;
566     krb5_data *encoded_request;
567     krb5_error *        err_reply;
568     krb5_kdc_rep *      as_reply = 0;
569     krb5_pa_data  **    preauth_to_use = 0;
570     int                 loopcount = 0;
571     krb5_int32          do_more = 0;
572     int                 canon_flag;
573     int             use_master = 0;
574     int                 referral_count = 0;
575     krb5_principal_data referred_client;
576     krb5_principal      referred_server = NULL;
577     krb5_boolean        is_tgt_req;
578
579 #if APPLE_PKINIT
580     inTktDebug("krb5_get_in_tkt top\n");
581 #endif /* APPLE_PKINIT */
582
583     if (! krb5_realm_compare(context, creds->client, creds->server))
584         return KRB5_IN_TKT_REALM_MISMATCH;
585
586     if (ret_as_reply)
587         *ret_as_reply = 0;
588
589     referred_client = *(creds->client);
590     referred_client.realm.data = NULL;
591     referred_client.realm.length = 0;
592
593     /* per referrals draft, enterprise principals imply canonicalization */
594     canon_flag = ((options & KDC_OPT_CANONICALIZE) != 0) ||
595         creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
596
597     /*
598      * Set up the basic request structure
599      */
600     request.magic = KV5M_KDC_REQ;
601     request.msg_type = KRB5_AS_REQ;
602     request.addresses = 0;
603     request.ktype = 0;
604     request.padata = 0;
605     if (addrs)
606         request.addresses = (krb5_address **) addrs;
607     else
608         if ((retval = krb5_os_localaddr(context, &request.addresses)))
609             goto cleanup;
610     request.kdc_options = options;
611     request.client = creds->client;
612     request.server = creds->server;
613     request.nonce = 0;
614     request.from = creds->times.starttime;
615     request.till = creds->times.endtime;
616     request.rtime = creds->times.renew_till;
617 #if APPLE_PKINIT
618     retval = gen_nonce(context, (krb5_int32 *)&time_now);
619     if(retval) {
620         goto cleanup;
621     }
622     request.nonce = time_now;
623 #endif /* APPLE_PKINIT */
624
625     request.ktype = malloc (sizeof(get_in_tkt_enctypes));
626     if (request.ktype == NULL) {
627         retval = ENOMEM;
628         goto cleanup;
629     }
630     memcpy(request.ktype, get_in_tkt_enctypes, sizeof(get_in_tkt_enctypes));
631     for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++);
632     if (ktypes) {
633         int i, req, next = 0;
634         for (req = 0; ktypes[req]; req++) {
635             if (ktypes[req] == request.ktype[next]) {
636                 next++;
637                 continue;
638             }
639             for (i = next + 1; i < request.nktypes; i++)
640                 if (ktypes[req] == request.ktype[i]) {
641                     /* Found the enctype we want, but not in the
642                        position we want.  Move it, but keep the old
643                        one from the desired slot around in case it's
644                        later in our requested-ktypes list.  */
645                     krb5_enctype t;
646                     t = request.ktype[next];
647                     request.ktype[next] = request.ktype[i];
648                     request.ktype[i] = t;
649                     next++;
650                     break;
651                 }
652             /* If we didn't find it, don't do anything special, just
653                drop it.  */
654         }
655         request.ktype[next] = 0;
656         request.nktypes = next;
657     }
658     request.authorization_data.ciphertext.length = 0;
659     request.authorization_data.ciphertext.data = 0;
660     request.unenc_authdata = 0;
661     request.second_ticket = 0;
662
663     /*
664      * If a list of preauth types are passed in, convert it to a
665      * preauth_to_use list.
666      */
667     if (ptypes) {
668         retval = make_preauth_list(context, ptypes, -1, &preauth_to_use);
669         if (retval)
670             goto cleanup;
671     }
672
673     is_tgt_req = tgt_is_local_realm(creds);
674
675     while (1) {
676         if (loopcount++ > MAX_IN_TKT_LOOPS) {
677             retval = KRB5_GET_IN_TKT_LOOP;
678             goto cleanup;
679         }
680
681 #if APPLE_PKINIT
682         inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n");
683 #endif /* APPLE_PKINIT */
684         if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
685                                          keyseed, creds, &request)) != 0)
686             goto cleanup;
687         if (preauth_to_use)
688             krb5_free_pa_data(context, preauth_to_use);
689         preauth_to_use = 0;
690
691         err_reply = 0;
692         as_reply = 0;
693
694         if ((retval = krb5_timeofday(context, &time_now)))
695             goto cleanup;
696
697         /*
698          * XXX we know they are the same size... and we should do
699          * something better than just the current time
700          */
701         request.nonce = (krb5_int32) time_now;
702
703         if ((retval = encode_krb5_as_req(&request, &encoded_request)) != 0)
704             goto cleanup;
705         retval = send_as_request(context, encoded_request,
706                                  krb5_princ_realm(context, request.client), &err_reply,
707                                  &as_reply, &use_master);
708         krb5_free_data(context, encoded_request);
709         if (retval != 0)
710             goto cleanup;
711
712         if (err_reply) {
713             if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
714                 err_reply->e_data.length > 0) {
715                 retval = decode_krb5_padata_sequence(&err_reply->e_data,
716                                                      &preauth_to_use);
717                 krb5_free_error(context, err_reply);
718                 if (retval)
719                     goto cleanup;
720                 retval = sort_krb5_padata_sequence(context,
721                                                    &request.server->realm,
722                                                    preauth_to_use);
723                 if (retval)
724                     goto cleanup;
725                 continue;
726             } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) {
727                 if (++referral_count > KRB5_REFERRAL_MAXHOPS ||
728                     err_reply->client == NULL ||
729                     err_reply->client->realm.length == 0) {
730                     retval = KRB5KDC_ERR_WRONG_REALM;
731                     krb5_free_error(context, err_reply);
732                     goto cleanup;
733                 }
734                 /* Rewrite request.client with realm from error reply */
735                 if (referred_client.realm.data) {
736                     krb5_free_data_contents(context, &referred_client.realm);
737                     referred_client.realm.data = NULL;
738                 }
739                 retval = krb5int_copy_data_contents(context,
740                                                     &err_reply->client->realm,
741                                                     &referred_client.realm);
742                 krb5_free_error(context, err_reply);
743                 if (retval)
744                     goto cleanup;
745                 request.client = &referred_client;
746
747                 if (referred_server != NULL) {
748                     krb5_free_principal(context, referred_server);
749                     referred_server = NULL;
750                 }
751
752                 retval = rewrite_server_realm(context,
753                                               creds->server,
754                                               &referred_client.realm,
755                                               is_tgt_req,
756                                               &referred_server);
757                 if (retval)
758                     goto cleanup;
759                 request.server = referred_server;
760
761                 continue;
762             } else {
763                 retval = (krb5_error_code) err_reply->error
764                     + ERROR_TABLE_BASE_krb5;
765                 krb5_free_error(context, err_reply);
766                 goto cleanup;
767             }
768         } else if (!as_reply) {
769             retval = KRB5KRB_AP_ERR_MSG_TYPE;
770             goto cleanup;
771         }
772         if ((retval = krb5_process_padata(context, &request, as_reply,
773                                           key_proc, keyseed, decrypt_proc,
774                                           &decrypt_key, creds,
775                                           &do_more)) != 0)
776             goto cleanup;
777
778         if (!do_more)
779             break;
780     }
781
782     if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
783                                    keyseed, decrypt_key, decrypt_proc,
784                                    decryptarg)))
785         goto cleanup;
786
787     if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
788         goto cleanup;
789
790     if ((retval = stash_as_reply(context, time_now, &request, as_reply,
791                                  creds, ccache)))
792         goto cleanup;
793
794 cleanup:
795     if (request.ktype)
796         free(request.ktype);
797     if (!addrs && request.addresses)
798         krb5_free_addresses(context, request.addresses);
799     if (request.padata)
800         krb5_free_pa_data(context, request.padata);
801     if (preauth_to_use)
802         krb5_free_pa_data(context, preauth_to_use);
803     if (decrypt_key)
804         krb5_free_keyblock(context, decrypt_key);
805     if (as_reply) {
806         if (ret_as_reply)
807             *ret_as_reply = as_reply;
808         else
809             krb5_free_kdc_rep(context, as_reply);
810     }
811     if (referred_client.realm.data)
812         krb5_free_data_contents(context, &referred_client.realm);
813     if (referred_server)
814         krb5_free_principal(context, referred_server);
815     return (retval);
816 }
817
818 /* begin libdefaults parsing code.  This should almost certainly move
819    somewhere else, but I don't know where the correct somewhere else
820    is yet. */
821
822 /* XXX Duplicating this is annoying; try to work on a better way.*/
823 static const char *const conf_yes[] = {
824     "y", "yes", "true", "t", "1", "on",
825     0,
826 };
827
828 static const char *const conf_no[] = {
829     "n", "no", "false", "nil", "0", "off",
830     0,
831 };
832
833 int
834 _krb5_conf_boolean(const char *s)
835 {
836     const char *const *p;
837
838     for(p=conf_yes; *p; p++) {
839         if (!strcasecmp(*p,s))
840             return 1;
841     }
842
843     for(p=conf_no; *p; p++) {
844         if (!strcasecmp(*p,s))
845             return 0;
846     }
847
848     /* Default to "no" */
849     return 0;
850 }
851
852 static krb5_error_code
853 krb5_libdefault_string(krb5_context context, const krb5_data *realm,
854                        const char *option, char **ret_value)
855 {
856     profile_t profile;
857     const char *names[5];
858     char **nameval = NULL;
859     krb5_error_code retval;
860     char realmstr[1024];
861
862     if (realm->length > sizeof(realmstr)-1)
863         return(EINVAL);
864
865     strncpy(realmstr, realm->data, realm->length);
866     realmstr[realm->length] = '\0';
867
868     if (!context || (context->magic != KV5M_CONTEXT))
869         return KV5M_CONTEXT;
870
871     profile = context->profile;
872
873     names[0] = KRB5_CONF_LIBDEFAULTS;
874
875     /*
876      * Try number one:
877      *
878      * [libdefaults]
879      *          REALM = {
880      *                  option = <boolean>
881      *          }
882      */
883
884     names[1] = realmstr;
885     names[2] = option;
886     names[3] = 0;
887     retval = profile_get_values(profile, names, &nameval);
888     if (retval == 0 && nameval && nameval[0])
889         goto goodbye;
890
891     /*
892      * Try number two:
893      *
894      * [libdefaults]
895      *          option = <boolean>
896      */
897
898     names[1] = option;
899     names[2] = 0;
900     retval = profile_get_values(profile, names, &nameval);
901     if (retval == 0 && nameval && nameval[0])
902         goto goodbye;
903
904 goodbye:
905     if (!nameval)
906         return(ENOENT);
907
908     if (!nameval[0]) {
909         retval = ENOENT;
910     } else {
911         *ret_value = strdup(nameval[0]);
912         if (!*ret_value)
913             retval = ENOMEM;
914     }
915
916     profile_free_list(nameval);
917
918     return retval;
919 }
920
921 /* not static so verify_init_creds() can call it */
922 /* as well as the DNS code */
923
924 krb5_error_code
925 krb5_libdefault_boolean(krb5_context context, const krb5_data *realm,
926                         const char *option, int *ret_value)
927 {
928     char *string = NULL;
929     krb5_error_code retval;
930
931     retval = krb5_libdefault_string(context, realm, option, &string);
932
933     if (retval)
934         return(retval);
935
936     *ret_value = _krb5_conf_boolean(string);
937     free(string);
938
939     return(0);
940 }
941
942 /* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
943  * libdefaults entry are listed before any others. */
944 static krb5_error_code
945 sort_krb5_padata_sequence(krb5_context context, krb5_data *realm,
946                           krb5_pa_data **padata)
947 {
948     int i, j, base;
949     krb5_error_code ret;
950     const char *p;
951     long l;
952     char *q, *preauth_types = NULL;
953     krb5_pa_data *tmp;
954     int need_free_string = 1;
955
956     if ((padata == NULL) || (padata[0] == NULL)) {
957         return 0;
958     }
959
960     ret = krb5_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES,
961                                  &preauth_types);
962     if ((ret != 0) || (preauth_types == NULL)) {
963         /* Try to use PKINIT first. */
964         preauth_types = "17, 16, 15, 14";
965         need_free_string = 0;
966     }
967
968 #ifdef DEBUG
969     fprintf (stderr, "preauth data types before sorting:");
970     for (i = 0; padata[i]; i++) {
971         fprintf (stderr, " %d", padata[i]->pa_type);
972     }
973     fprintf (stderr, "\n");
974 #endif
975
976     base = 0;
977     for (p = preauth_types; *p != '\0';) {
978         /* skip whitespace to find an entry */
979         p += strspn(p, ", ");
980         if (*p != '\0') {
981             /* see if we can extract a number */
982             l = strtol(p, &q, 10);
983             if ((q != NULL) && (q > p)) {
984                 /* got a valid number; search for a matchin entry */
985                 for (i = base; padata[i] != NULL; i++) {
986                     /* bubble the matching entry to the front of the list */
987                     if (padata[i]->pa_type == l) {
988                         tmp = padata[i];
989                         for (j = i; j > base; j--)
990                             padata[j] = padata[j - 1];
991                         padata[base] = tmp;
992                         base++;
993                         break;
994                     }
995                 }
996                 p = q;
997             } else {
998                 break;
999             }
1000         }
1001     }
1002     if (need_free_string)
1003         free(preauth_types);
1004
1005 #ifdef DEBUG
1006     fprintf (stderr, "preauth data types after sorting:");
1007     for (i = 0; padata[i]; i++)
1008         fprintf (stderr, " %d", padata[i]->pa_type);
1009     fprintf (stderr, "\n");
1010 #endif
1011
1012     return 0;
1013 }
1014
1015 static krb5_error_code
1016 build_in_tkt_name(krb5_context context,
1017                   char *in_tkt_service,
1018                   krb5_const_principal client,
1019                   krb5_principal *server)
1020 {
1021     krb5_error_code ret;
1022
1023     *server = NULL;
1024
1025     if (in_tkt_service) {
1026         /* this is ugly, because so are the data structures involved.  I'm
1027            in the library, so I'm going to manipulate the data structures
1028            directly, otherwise, it will be worse. */
1029
1030         if ((ret = krb5_parse_name(context, in_tkt_service, server)))
1031             return ret;
1032
1033         /* stuff the client realm into the server principal.
1034            realloc if necessary */
1035         if ((*server)->realm.length < client->realm.length) {
1036             char *p = realloc((*server)->realm.data,
1037                               client->realm.length);
1038             if (p == NULL) {
1039                 krb5_free_principal(context, *server);
1040                 *server = NULL;
1041                 return ENOMEM;
1042             }
1043             (*server)->realm.data = p;
1044         }
1045
1046         (*server)->realm.length = client->realm.length;
1047         memcpy((*server)->realm.data, client->realm.data, client->realm.length);
1048     } else {
1049         ret = krb5_build_principal_ext(context, server,
1050                                        client->realm.length,
1051                                        client->realm.data,
1052                                        KRB5_TGS_NAME_SIZE,
1053                                        KRB5_TGS_NAME,
1054                                        client->realm.length,
1055                                        client->realm.data,
1056                                        0);
1057     }
1058     return ret;
1059 }
1060
1061 void KRB5_CALLCONV
1062 krb5_init_creds_free(krb5_context context,
1063                      krb5_init_creds_context ctx)
1064 {
1065     if (ctx == NULL)
1066         return;
1067
1068     if (ctx->opte != NULL && krb5_gic_opt_is_shadowed(ctx->opte)) {
1069         krb5_get_init_creds_opt_free(context,
1070                                      (krb5_get_init_creds_opt *)ctx->opte);
1071     }
1072     free(ctx->in_tkt_service);
1073     zap(ctx->password.data, ctx->password.length);
1074     krb5_free_data_contents(context, &ctx->password);
1075     krb5_free_error(context, ctx->err_reply);
1076     krb5_free_cred_contents(context, &ctx->cred);
1077     krb5_free_kdc_req(context, ctx->request);
1078     krb5_free_kdc_rep(context, ctx->reply);
1079     krb5_free_data(context, ctx->encoded_request_body);
1080     krb5_free_data(context, ctx->encoded_previous_request);
1081     krb5int_fast_free_state(context, ctx->fast_state);
1082     krb5_free_pa_data(context, ctx->preauth_to_use);
1083     krb5_free_data_contents(context, &ctx->salt);
1084     krb5_free_data_contents(context, &ctx->s2kparams);
1085     krb5_free_keyblock_contents(context, &ctx->as_key);
1086     free(ctx);
1087 }
1088
1089 static krb5_error_code
1090 init_creds_get(krb5_context context,
1091                krb5_init_creds_context ctx,
1092                int *use_master)
1093 {
1094     krb5_error_code code;
1095     krb5_data request;
1096     krb5_data reply;
1097     krb5_data realm;
1098     unsigned int flags = 0;
1099     int tcp_only = 0;
1100
1101     request.length = 0;
1102     request.data = NULL;
1103     reply.length = 0;
1104     reply.data = NULL;
1105     realm.length = 0;
1106     realm.data = NULL;
1107
1108     for (;;) {
1109         code = krb5_init_creds_step(context,
1110                                     ctx,
1111                                     &reply,
1112                                     &request,
1113                                     &realm,
1114                                     &flags);
1115         if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only)
1116             tcp_only = 1;
1117         else if (code != 0 || (flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE))
1118             break;
1119
1120         krb5_free_data_contents(context, &reply);
1121
1122         code = krb5_sendto_kdc(context, &request, &realm,
1123                                &reply, use_master, tcp_only);
1124         if (code != 0)
1125             break;
1126
1127         krb5_free_data_contents(context, &request);
1128         krb5_free_data_contents(context, &realm);
1129     }
1130
1131     krb5_free_data_contents(context, &request);
1132     krb5_free_data_contents(context, &reply);
1133     krb5_free_data_contents(context, &realm);
1134
1135     return code;
1136 }
1137
1138 /* Heimdal API */
1139 krb5_error_code KRB5_CALLCONV
1140 krb5_init_creds_get(krb5_context context,
1141                     krb5_init_creds_context ctx)
1142 {
1143     int use_master = 0;
1144
1145     return init_creds_get(context, ctx, &use_master);
1146 }
1147
1148 krb5_error_code KRB5_CALLCONV
1149 krb5_init_creds_get_creds(krb5_context context,
1150                           krb5_init_creds_context ctx,
1151                           krb5_creds *creds)
1152 {
1153     if ((ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) == 0)
1154         return KRB5_NO_TKT_SUPPLIED;
1155
1156     return krb5int_copy_creds_contents(context, &ctx->cred, creds);
1157 }
1158
1159 krb5_error_code KRB5_CALLCONV
1160 krb5_init_creds_store_creds(krb5_context context,
1161                             krb5_init_creds_context ctx,
1162                             krb5_ccache ccache)
1163 {
1164     if ((ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) == 0)
1165         return KRB5_NO_TKT_SUPPLIED;
1166
1167     return krb5_cc_store_cred(context, ccache, &ctx->cred);
1168 }
1169
1170 krb5_error_code KRB5_CALLCONV
1171 krb5_init_creds_get_times(krb5_context context,
1172                           krb5_init_creds_context ctx,
1173                           krb5_ticket_times *times)
1174 {
1175     if ((ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) == 0)
1176         return KRB5_NO_TKT_SUPPLIED;
1177
1178     *times = ctx->cred.times;
1179
1180     return 0;
1181 }
1182
1183 krb5_error_code KRB5_CALLCONV
1184 krb5_init_creds_get_error(krb5_context context,
1185                           krb5_init_creds_context ctx,
1186                           krb5_error **error)
1187 {
1188     krb5_error_code code;
1189     krb5_error *ret = NULL;
1190
1191     *error = NULL;
1192
1193     if (ctx->err_reply == NULL)
1194         return 0;
1195
1196     ret = k5alloc(sizeof(*ret), &code);
1197     if (code != 0)
1198         goto cleanup;
1199
1200     ret->magic = KV5M_ERROR;
1201     ret->ctime = ctx->err_reply->ctime;
1202     ret->cusec = ctx->err_reply->cusec;
1203     ret->susec = ctx->err_reply->susec;
1204     ret->stime = ctx->err_reply->stime;
1205     ret->error = ctx->err_reply->error;
1206
1207     if (ctx->err_reply->client != NULL) {
1208         code = krb5_copy_principal(context, ctx->err_reply->client,
1209                                    &ret->client);
1210         if (code != 0)
1211             goto cleanup;
1212     }
1213
1214     code = krb5_copy_principal(context, ctx->err_reply->server, &ret->server);
1215     if (code != 0)
1216         goto cleanup;
1217
1218     code = krb5int_copy_data_contents(context, &ctx->err_reply->text,
1219                                       &ret->text);
1220     if (code != 0)
1221         goto cleanup;
1222
1223     code = krb5int_copy_data_contents(context, &ctx->err_reply->e_data,
1224                                       &ret->e_data);
1225     if (code != 0)
1226         goto cleanup;
1227
1228     *error = ret;
1229
1230 cleanup:
1231     if (code != 0)
1232         krb5_free_error(context, ret);
1233
1234     return code;
1235 }
1236
1237 krb5_error_code KRB5_CALLCONV
1238 krb5_init_creds_init(krb5_context context,
1239                      krb5_principal client,
1240                      krb5_prompter_fct prompter,
1241                      void *data,
1242                      krb5_deltat start_time,
1243                      krb5_get_init_creds_opt *options,
1244                      krb5_init_creds_context *pctx)
1245 {
1246     krb5_error_code code;
1247     krb5_init_creds_context ctx;
1248     int tmp;
1249     char *str = NULL;
1250     krb5_gic_opt_ext *opte;
1251
1252     ctx = k5alloc(sizeof(*ctx), &code);
1253     if (code != 0)
1254         goto cleanup;
1255
1256     ctx->request = k5alloc(sizeof(krb5_kdc_req), &code);
1257     if (code != 0)
1258         goto cleanup;
1259
1260     code = krb5_copy_principal(context, client, &ctx->request->client);
1261     if (code != 0)
1262         goto cleanup;
1263
1264     ctx->prompter = prompter;
1265     ctx->prompter_data = data;
1266     ctx->gak_fct = krb5_get_as_key_password;
1267     ctx->gak_data = &ctx->password;
1268
1269     ctx->request_time = 0; /* filled in later */
1270     ctx->start_time = start_time;
1271
1272     if (options == NULL) {
1273         code = krb5_get_init_creds_opt_alloc(context, &options);
1274         if (code != 0)
1275             goto cleanup;
1276     }
1277
1278     code = krb5int_gic_opt_to_opte(context, options,
1279                                    &ctx->opte, 1, "krb5_init_creds_init");
1280     if (code != 0)
1281         goto cleanup;
1282
1283     opte = ctx->opte;
1284
1285     code = krb5int_fast_make_state(context, &ctx->fast_state);
1286     if (code != 0)
1287         goto cleanup;
1288
1289     ctx->get_data_rock.magic = CLIENT_ROCK_MAGIC;
1290     ctx->get_data_rock.etype = &ctx->etype;
1291     ctx->get_data_rock.fast_state = ctx->fast_state;
1292
1293     /* Initialise request parameters as per krb5_get_init_creds() */
1294     ctx->request->kdc_options = context->kdc_default_options;
1295
1296     /* forwaradble */
1297     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)
1298         tmp = opte->forwardable;
1299     else if (krb5_libdefault_boolean(context, &ctx->request->client->realm,
1300                                      KRB5_CONF_FORWARDABLE, &tmp) == 0)
1301         ;
1302     else
1303         tmp = 0;
1304     if (tmp)
1305         ctx->request->kdc_options |= KDC_OPT_FORWARDABLE;
1306
1307     /* proxiable */
1308     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)
1309         tmp = opte->proxiable;
1310     else if (krb5_libdefault_boolean(context, &ctx->request->client->realm,
1311                                      KRB5_CONF_PROXIABLE, &tmp) == 0)
1312         ;
1313     else
1314         tmp = 0;
1315     if (tmp)
1316         ctx->request->kdc_options |= KDC_OPT_PROXIABLE;
1317
1318     /* canonicalize */
1319     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE)
1320         tmp = 1;
1321     else if (krb5_libdefault_boolean(context, &ctx->request->client->realm,
1322                                      KRB5_CONF_CANONICALIZE, &tmp) == 0)
1323         ;
1324     else
1325         tmp = 0;
1326     if (tmp)
1327         ctx->request->kdc_options |= KDC_OPT_CANONICALIZE;
1328
1329     /* allow_postdate */
1330     if (ctx->start_time > 0)
1331         ctx->request->kdc_options |= KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED;
1332
1333     /* ticket lifetime */
1334     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)
1335         ctx->tkt_life = options->tkt_life;
1336     else if (krb5_libdefault_string(context, &ctx->request->client->realm,
1337                                     KRB5_CONF_TICKET_LIFETIME, &str) == 0) {
1338         code = krb5_string_to_deltat(str, &ctx->tkt_life);
1339         if (code != 0)
1340             goto cleanup;
1341         free(str);
1342         str = NULL;
1343     } else
1344         ctx->tkt_life = 24 * 60 * 60; /* previously hardcoded in kinit */
1345
1346     /* renewable lifetime */
1347     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)
1348         ctx->renew_life = options->renew_life;
1349     else if (krb5_libdefault_string(context, &ctx->request->client->realm,
1350                                     KRB5_CONF_RENEW_LIFETIME, &str) == 0) {
1351         code = krb5_string_to_deltat(str, &ctx->renew_life);
1352         if (code != 0)
1353             goto cleanup;
1354         free(str);
1355         str = NULL;
1356     } else
1357         ctx->renew_life = 0;
1358
1359     if (ctx->renew_life > 0)
1360         ctx->request->kdc_options |= KDC_OPT_RENEWABLE;
1361
1362     /* enctypes */
1363     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST) {
1364         ctx->request->ktype =
1365             k5alloc((opte->etype_list_length * sizeof(krb5_enctype)),
1366                     &code);
1367         if (code != 0)
1368             goto cleanup;
1369         ctx->request->nktypes = opte->etype_list_length;
1370         memcpy(ctx->request->ktype, opte->etype_list,
1371                ctx->request->nktypes * sizeof(krb5_enctype));
1372     } else if (krb5_get_default_in_tkt_ktypes(context,
1373                                               &ctx->request->ktype) == 0) {
1374         for (ctx->request->nktypes = 0;
1375              ctx->request->ktype[ctx->request->nktypes] != ENCTYPE_NULL;
1376              ctx->request->nktypes++)
1377             ;
1378     } else {
1379         /* there isn't any useful default here. */
1380         code = KRB5_CONFIG_ETYPE_NOSUPP;
1381         goto cleanup;
1382     }
1383
1384     /* addresess */
1385     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) {
1386         code = krb5_copy_addresses(context, opte->address_list,
1387                                    &ctx->request->addresses);
1388         if (code != 0)
1389             goto cleanup;
1390     } else if (krb5_libdefault_boolean(context, &ctx->request->client->realm,
1391                                      KRB5_CONF_NOADDRESSES, &tmp) != 0
1392              || tmp) {
1393         ctx->request->addresses = NULL;
1394     } else {
1395         code = krb5_os_localaddr(context, &ctx->request->addresses);
1396         if (code != 0)
1397             goto cleanup;
1398     }
1399
1400     /* initial preauth state */
1401     krb5_preauth_request_context_init(context);
1402
1403     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST) {
1404         code = make_preauth_list(context,
1405                                  opte->preauth_list,
1406                                  opte->preauth_list_length,
1407                                  &ctx->preauth_to_use);
1408         if (code != 0)
1409             goto cleanup;
1410     }
1411
1412     if (opte->flags & KRB5_GET_INIT_CREDS_OPT_SALT) {
1413         code = krb5int_copy_data_contents(context, opte->salt, &ctx->salt);
1414         if (code != 0)
1415             goto cleanup;
1416     } else {
1417         ctx->salt.length = SALT_TYPE_AFS_LENGTH;
1418         ctx->salt.data = NULL;
1419     }
1420
1421     /* nonce */
1422     {
1423         unsigned char random_buf[4];
1424         krb5_data random_data;
1425
1426         random_data.length = sizeof(random_buf);
1427         random_data.data = (char *)random_buf;
1428
1429         /*
1430          * See RT ticket 3196 at MIT.  If we set the high bit, we
1431          * may have compatibility problems with Heimdal, because
1432          * we (incorrectly) encode this value as signed.
1433          */
1434         if (krb5_c_random_make_octets(context, &random_data) == 0)
1435             ctx->request->nonce = 0x7fffffff & load_32_n(random_buf);
1436         else {
1437             krb5_timestamp now;
1438
1439             code = krb5_timeofday(context, &now);
1440             if (code != 0)
1441                 goto cleanup;
1442
1443             ctx->request->nonce = (krb5_int32)now;
1444         }
1445     }
1446
1447     ctx->loopcount = 0;
1448
1449     *pctx = ctx;
1450
1451 cleanup:
1452     if (code != 0)
1453         krb5_init_creds_free(context, ctx);
1454     if (str != NULL)
1455         free(str);
1456
1457     return code;
1458 }
1459
1460 krb5_error_code KRB5_CALLCONV
1461 krb5_init_creds_set_service(krb5_context context,
1462                             krb5_init_creds_context ctx,
1463                             const char *service)
1464 {
1465     char *s;
1466
1467     s = strdup(service);
1468     if (s == NULL)
1469         return ENOMEM;
1470
1471     free(ctx->in_tkt_service);
1472     ctx->in_tkt_service = s;
1473
1474     return 0;
1475 }
1476
1477 static krb5_error_code
1478 init_creds_validate_reply(krb5_context context,
1479                           krb5_init_creds_context ctx,
1480                           krb5_data *reply)
1481 {
1482     krb5_error_code code;
1483     krb5_error *error = NULL;
1484     krb5_kdc_rep *as_reply = NULL;
1485
1486     krb5_free_error(context, ctx->err_reply);
1487     ctx->err_reply = NULL;
1488
1489     krb5_free_kdc_rep(context, ctx->reply);
1490     ctx->reply = NULL;
1491
1492     if (krb5_is_krb_error(reply)) {
1493         code = decode_krb5_error(reply, &error);
1494         if (code != 0)
1495             return code;
1496
1497         assert(error != NULL);
1498
1499         if (error->error == KRB_ERR_RESPONSE_TOO_BIG) {
1500             krb5_free_error(context, error);
1501             return KRB5KRB_ERR_RESPONSE_TOO_BIG;
1502         } else {
1503             ctx->err_reply = error;
1504             return 0;
1505         }
1506     }
1507
1508     /*
1509      * Check to make sure it isn't a V4 reply.
1510      */
1511     if (reply->length != 0 && !krb5_is_as_rep(reply)) {
1512 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
1513 #define V4_KRB_PROT_VERSION     4
1514 #define V4_AUTH_MSG_ERR_REPLY   (5<<1)
1515         /* check here for V4 reply */
1516         unsigned int t_switch;
1517
1518         /* From v4 g_in_tkt.c: This used to be
1519            switch (pkt_msg_type(rpkt) & ~1) {
1520            but SCO 3.2v4 cc compiled that incorrectly.  */
1521         t_switch = reply->data[1];
1522         t_switch &= ~1;
1523
1524         if (t_switch == V4_AUTH_MSG_ERR_REPLY
1525             && reply->data[0] == V4_KRB_PROT_VERSION) {
1526             code = KRB5KRB_AP_ERR_V4_REPLY;
1527         } else {
1528             code = KRB5KRB_AP_ERR_MSG_TYPE;
1529         }
1530         return code;
1531     }
1532
1533     /* It must be a KRB_AS_REP message, or an bad returned packet */
1534     code = decode_krb5_as_rep(reply, &as_reply);
1535     if (code != 0)
1536         return code;
1537
1538     if (as_reply->msg_type != KRB5_AS_REP) {
1539         krb5_free_kdc_rep(context, as_reply);
1540         return KRB5KRB_AP_ERR_MSG_TYPE;
1541     }
1542
1543     ctx->reply = as_reply;
1544
1545     return 0;
1546 }
1547
1548 static krb5_error_code
1549 init_creds_step_request(krb5_context context,
1550                         krb5_init_creds_context ctx,
1551                         krb5_data *out)
1552 {
1553     krb5_error_code code;
1554
1555     krb5_free_principal(context, ctx->request->server);
1556     ctx->request->server = NULL;
1557
1558     code = build_in_tkt_name(context, ctx->in_tkt_service,
1559                              ctx->request->client,
1560                              &ctx->request->server);
1561     if (code != 0)
1562         goto cleanup;
1563
1564     if (ctx->loopcount == 0) {
1565         code = krb5_timeofday(context, &ctx->request_time);
1566         if (code != 0)
1567             goto cleanup;
1568
1569         code = krb5int_fast_as_armor(context, ctx->fast_state,
1570                                      ctx->opte, ctx->request);
1571         if (code != 0)
1572             goto cleanup;
1573
1574         /* give the preauth plugins a chance to prep the request body */
1575         krb5_preauth_prepare_request(context, ctx->opte, ctx->request);
1576         code = krb5int_fast_prep_req_body(context, ctx->fast_state,
1577                                           ctx->request,
1578                                           &ctx->encoded_request_body);
1579         if (code != 0)
1580             goto cleanup;
1581
1582         ctx->request->from = krb5int_addint32(ctx->request_time,
1583                                               ctx->start_time);
1584         ctx->request->till = krb5int_addint32(ctx->request->from,
1585                                               ctx->tkt_life);
1586
1587         if (ctx->renew_life > 0) {
1588             ctx->request->rtime =
1589                 krb5int_addint32(ctx->request->from, ctx->renew_life);
1590             if (ctx->request->rtime < ctx->request->till) {
1591                 /* don't ask for a smaller renewable time than the lifetime */
1592                 ctx->request->rtime = ctx->request->till;
1593             }
1594             ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK);
1595         } else
1596             ctx->request->rtime = 0;
1597     } else if (ctx->loopcount >= MAX_IN_TKT_LOOPS) {
1598         code = KRB5_GET_IN_TKT_LOOP;
1599         goto cleanup;
1600     }
1601
1602     if (ctx->err_reply == NULL) {
1603         /* either our first attempt, or retrying after PREAUTH_NEEDED */
1604         code = krb5_do_preauth(context,
1605                                ctx->request,
1606                                ctx->encoded_request_body,
1607                                ctx->encoded_previous_request,
1608                                ctx->preauth_to_use,
1609                                &ctx->request->padata,
1610                                &ctx->salt,
1611                                &ctx->s2kparams,
1612                                &ctx->etype,
1613                                &ctx->as_key,
1614                                ctx->prompter,
1615                                ctx->prompter_data,
1616                                ctx->gak_fct,
1617                                ctx->gak_data,
1618                                &ctx->get_data_rock,
1619                                ctx->opte);
1620         if (code != 0)
1621             goto cleanup;
1622     } else {
1623         if (ctx->preauth_to_use != NULL) {
1624             /*
1625              * Retry after an error other than PREAUTH_NEEDED,
1626              * using e-data to figure out what to change.
1627              */
1628             code = krb5_do_preauth_tryagain(context,
1629                                             ctx->request,
1630                                             ctx->encoded_request_body,
1631                                             ctx->encoded_previous_request,
1632                                             ctx->preauth_to_use,
1633                                             &ctx->request->padata,
1634                                             ctx->err_reply,
1635                                             &ctx->salt,
1636                                             &ctx->s2kparams,
1637                                             &ctx->etype,
1638                                             &ctx->as_key,
1639                                             ctx->prompter,
1640                                             ctx->prompter_data,
1641                                             ctx->gak_fct,
1642                                             ctx->gak_data,
1643                                             &ctx->get_data_rock,
1644                                             ctx->opte);
1645         } else {
1646             /* No preauth supplied, so can't query the plugins. */
1647             code = KRB5KRB_ERR_GENERIC;
1648         }
1649         if (code != 0) {
1650             /* couldn't come up with anything better */
1651             code = ctx->err_reply->error + ERROR_TABLE_BASE_krb5;
1652             goto cleanup;
1653         }
1654     }
1655
1656     if (ctx->encoded_previous_request != NULL) {
1657         krb5_free_data(context, ctx->encoded_previous_request);
1658         ctx->encoded_previous_request = NULL;
1659     }
1660
1661     code = krb5int_fast_prep_req(context, ctx->fast_state,
1662                                  ctx->request, ctx->encoded_request_body,
1663                                  encode_krb5_as_req,
1664                                  &ctx->encoded_previous_request);
1665     if (code != 0)
1666         goto cleanup;
1667
1668     code = krb5int_copy_data_contents(context,
1669                                       ctx->encoded_previous_request,
1670                                       out);
1671     if (code != 0)
1672         goto cleanup;
1673
1674 cleanup:
1675     return code;
1676 }
1677
1678 static krb5_error_code
1679 init_creds_step_reply(krb5_context context,
1680                       krb5_init_creds_context ctx,
1681                       krb5_data *in)
1682 {
1683     krb5_error_code code;
1684     krb5_pa_data **padata = NULL;
1685     krb5_pa_data **kdc_padata = NULL;
1686     krb5_boolean retry = FALSE;
1687     int canon_flag = 0;
1688     krb5_keyblock *strengthen_key = NULL;
1689     krb5_keyblock encrypting_key;
1690
1691     encrypting_key.length = 0;
1692     encrypting_key.contents = NULL;
1693
1694     /* process previous KDC response */
1695     code = init_creds_validate_reply(context, ctx, in);
1696     if (code != 0)
1697         goto cleanup;
1698
1699     /* per referrals draft, enterprise principals imply canonicalization */
1700     canon_flag = ((ctx->request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
1701         ctx->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
1702
1703     if (ctx->err_reply != NULL) {
1704         code = krb5int_fast_process_error(context, ctx->fast_state,
1705                                           &ctx->err_reply, &padata, &retry);
1706         if (code != 0)
1707             goto cleanup;
1708
1709         if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED && retry) {
1710             /* reset the list of preauth types to try */
1711             krb5_free_pa_data(context, ctx->preauth_to_use);
1712             ctx->preauth_to_use = padata;
1713             padata = NULL;
1714             /* this will trigger a new call to krb5_do_preauth() */
1715             krb5_free_error(context, ctx->err_reply);
1716             ctx->err_reply = NULL;
1717             code = sort_krb5_padata_sequence(context,
1718                                              &ctx->request->client->realm,
1719                                              ctx->preauth_to_use);
1720
1721         } else if (canon_flag && ctx->err_reply->error == KDC_ERR_WRONG_REALM) {
1722             if (ctx->err_reply->client == NULL ||
1723                 !krb5_princ_realm(context, ctx->err_reply->client)->length) {
1724                 code = KRB5KDC_ERR_WRONG_REALM;
1725                 goto cleanup;
1726             }
1727             /* Rewrite request.client with realm from error reply */
1728             krb5_free_data_contents(context, &ctx->request->client->realm);
1729             code = krb5int_copy_data_contents(context,
1730                                               &ctx->err_reply->client->realm,
1731                                               &ctx->request->client->realm);
1732             /* this will trigger a new call to krb5_do_preauth() */
1733             krb5_free_error(context, ctx->err_reply);
1734             ctx->err_reply = NULL;
1735         } else {
1736             if (retry) {
1737                 code = 0;
1738             } else {
1739                 /* error + no hints = give up */
1740                 code = (krb5_error_code)ctx->err_reply->error +
1741                        ERROR_TABLE_BASE_krb5;
1742             }
1743         }
1744
1745         /* Return error code, or continue with next iteration */
1746         goto cleanup;
1747     }
1748
1749     /* We have a response. Process it. */
1750     assert(ctx->reply != NULL);
1751
1752     /* process any preauth data in the as_reply */
1753     krb5_clear_preauth_context_use_counts(context);
1754     code = krb5int_fast_process_response(context, ctx->fast_state,
1755                                          ctx->reply, &strengthen_key);
1756     if (code != 0)
1757         goto cleanup;
1758
1759     code = sort_krb5_padata_sequence(context, &ctx->request->client->realm,
1760                                      ctx->reply->padata);
1761     if (code != 0)
1762         goto cleanup;
1763
1764     ctx->etype = ctx->reply->enc_part.enctype;
1765
1766     code = krb5_do_preauth(context,
1767                            ctx->request,
1768                            ctx->encoded_request_body,
1769                            ctx->encoded_previous_request,
1770                            ctx->reply->padata,
1771                            &kdc_padata,
1772                            &ctx->salt,
1773                            &ctx->s2kparams,
1774                            &ctx->etype,
1775                            &ctx->as_key,
1776                            ctx->prompter,
1777                            ctx->prompter_data,
1778                            ctx->gak_fct,
1779                            ctx->gak_data,
1780                            &ctx->get_data_rock,
1781                            ctx->opte);
1782     if (code != 0)
1783         goto cleanup;
1784
1785     /*
1786      * If we haven't gotten a salt from another source yet, set up one
1787      * corresponding to the client principal returned by the KDC.  We
1788      * could get the same effect by passing local_as_reply->client to
1789      * gak_fct below, but that would put the canonicalized client name
1790      * in the prompt, which raises issues of needing to sanitize
1791      * unprintable characters.  So for now we just let it affect the
1792      * salt.  local_as_reply->client will be checked later on in
1793      * verify_as_reply.
1794      */
1795     if (ctx->salt.length == SALT_TYPE_AFS_LENGTH && ctx->salt.data == NULL) {
1796         code = krb5_principal2salt(context, ctx->reply->client, &ctx->salt);
1797         if (code != 0)
1798             goto cleanup;
1799     }
1800
1801     /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
1802        the AS_REP comes back encrypted in the user's longterm key
1803        instead of in the SAD. If there was a SAM preauth, there
1804        will be an as_key here which will be the SAD. If that fails,
1805        use the gak_fct to get the password, and try again. */
1806
1807     /* XXX because etypes are handled poorly (particularly wrt SAM,
1808        where the etype is fixed by the kdc), we may want to try
1809        decrypt_as_reply twice.  If there's an as_key available, try
1810        it.  If decrypting the as_rep fails, or if there isn't an
1811        as_key at all yet, then use the gak_fct to get one, and try
1812        again.  */
1813     if (ctx->as_key.length) {
1814         code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
1815                                      &encrypting_key);
1816         if (code != 0)
1817             goto cleanup;
1818         code = decrypt_as_reply(context, NULL, ctx->reply, NULL, NULL,
1819                                 &encrypting_key, krb5_kdc_rep_decrypt_proc,
1820                                 NULL);
1821     } else
1822         code = -1;
1823
1824     if (code != 0) {
1825         /* if we haven't get gotten a key, get it now */
1826         code = (*ctx->gak_fct)(context, ctx->request->client,
1827                                ctx->reply->enc_part.enctype,
1828                                ctx->prompter, ctx->prompter_data,
1829                                &ctx->salt, &ctx->s2kparams,
1830                                &ctx->as_key, ctx->gak_data);
1831         if (code != 0)
1832             goto cleanup;
1833
1834         code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
1835                                       &encrypting_key);
1836         if (code != 0)
1837             goto cleanup;
1838
1839         code = decrypt_as_reply(context, NULL, ctx->reply, NULL, NULL,
1840                                 &encrypting_key, krb5_kdc_rep_decrypt_proc,
1841                                 NULL);
1842         if (code != 0)
1843             goto cleanup;
1844     }
1845
1846     code = verify_as_reply(context, ctx->request_time,
1847                            ctx->request, ctx->reply);
1848     if (code != 0)
1849         goto cleanup;
1850
1851     code = stash_as_reply(context, ctx->request_time, ctx->request,
1852                           ctx->reply, &ctx->cred, NULL);
1853     if (code != 0)
1854         goto cleanup;
1855
1856     krb5_preauth_request_context_fini(context);
1857
1858     /* success */
1859     code = 0;
1860     ctx->flags |= KRB5_INIT_CREDS_STEP_FLAG_COMPLETE;
1861
1862 cleanup:
1863     krb5_free_pa_data(context, padata);
1864     krb5_free_pa_data(context, kdc_padata);
1865     krb5_free_keyblock(context, strengthen_key);
1866     krb5_free_keyblock_contents(context, &encrypting_key);
1867
1868     return code;
1869 }
1870
1871 /*
1872  * Do next step of credentials acquisition.
1873  *
1874  * On success returns 0 or KRB5KRB_ERR_RESPONSE_TOO_BIG if the request
1875  * should be sent with TCP.
1876  */
1877 krb5_error_code KRB5_CALLCONV
1878 krb5_init_creds_step(krb5_context context,
1879                      krb5_init_creds_context ctx,
1880                      krb5_data *in,
1881                      krb5_data *out,
1882                      krb5_data *realm,
1883                      unsigned int *flags)
1884 {
1885     krb5_error_code code, code2;
1886
1887     *flags = 0;
1888
1889     out->data = NULL;
1890     out->length = 0;
1891
1892     realm->data = NULL;
1893     realm->length = 0;
1894
1895     if (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE)
1896         goto cleanup;
1897
1898     if (in->length != 0) {
1899         code = init_creds_step_reply(context, ctx, in);
1900         if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
1901             code2 = krb5int_copy_data_contents(context,
1902                                                ctx->encoded_previous_request,
1903                                                out);
1904             if (code2 != 0) {
1905                 code = code2;
1906                 goto cleanup;
1907             }
1908             goto copy_realm;
1909         }
1910         if (code != 0 || (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE))
1911             goto cleanup;
1912     }
1913
1914     code = init_creds_step_request(context, ctx, out);
1915     if (code != 0)
1916         goto cleanup;
1917
1918     /* Only a new request increments the loop count, not a TCP retry */
1919     ctx->loopcount++;
1920
1921 copy_realm:
1922     assert(ctx->request->server != NULL);
1923
1924     code2 = krb5int_copy_data_contents(context,
1925                                        &ctx->request->server->realm,
1926                                        realm);
1927     if (code2 != 0) {
1928         code = code2;
1929         goto cleanup;
1930     }
1931
1932 cleanup:
1933     if (code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) {
1934         char *client_name;
1935
1936         /* See if we can produce a more detailed error message */
1937         code2 = krb5_unparse_name(context, ctx->request->client, &client_name);
1938         if (code2 == 0) {
1939             krb5_set_error_message(context, code,
1940                                    "Client '%s' not found in Kerberos database",
1941                                    client_name);
1942             krb5_free_unparsed_name(context, client_name);
1943         }
1944     }
1945
1946     *flags = (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE);
1947
1948     return code;
1949 }
1950
1951 krb5_error_code KRB5_CALLCONV
1952 krb5int_get_init_creds(krb5_context context,
1953                        krb5_creds *creds,
1954                        krb5_principal client,
1955                        krb5_prompter_fct prompter,
1956                        void *prompter_data,
1957                        krb5_deltat start_time,
1958                        char *in_tkt_service,
1959                        krb5_get_init_creds_opt *options,
1960                        krb5_gic_get_as_key_fct gak_fct,
1961                        void *gak_data,
1962                        int  *use_master,
1963                        krb5_kdc_rep **as_reply)
1964 {
1965     krb5_error_code code;
1966     krb5_init_creds_context ctx = NULL;
1967
1968     code = krb5_init_creds_init(context,
1969                                 client,
1970                                 prompter,
1971                                 prompter_data,
1972                                 start_time,
1973                                 options,
1974                                 &ctx);
1975     if (code != 0)
1976         goto cleanup;
1977
1978     ctx->gak_fct = gak_fct;
1979     ctx->gak_data = gak_data;
1980
1981     if (in_tkt_service) {
1982         code = krb5_init_creds_set_service(context, ctx, in_tkt_service);
1983         if (code != 0)
1984             goto cleanup;
1985     }
1986
1987     code = init_creds_get(context, ctx, use_master);
1988     if (code != 0)
1989         goto cleanup;
1990
1991     code = krb5_init_creds_get_creds(context, ctx, creds);
1992     if (code != 0)
1993         goto cleanup;
1994
1995     if (as_reply != NULL) {
1996         *as_reply = ctx->reply;
1997         ctx->reply = NULL;
1998     }
1999
2000 cleanup:
2001     krb5_init_creds_free(context, ctx);
2002
2003     return code;
2004 }
2005