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