1 /*
2  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
4  */
5 /*
6  * lib/krb5/krb/get_in_tkt.c
7  *
8  * Copyright 1990,1991, 2003 by the Massachusetts Institute of Technology.
9  * All Rights Reserved.
10  *
11  * Export of this software from the United States of America may
12  *   require a specific license from the United States Government.
13  *   It is the responsibility of any person or organization contemplating
14  *   export to obtain such a license before exporting.
15  *
16  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
17  * distribute this software and its documentation for any purpose and
18  * without fee is hereby granted, provided that the above copyright
19  * notice appear in all copies and that both that copyright notice and
20  * this permission notice appear in supporting documentation, and that
21  * the name of M.I.T. not be used in advertising or publicity pertaining
22  * to distribution of the software without specific, written prior
23  * permission.  Furthermore if you modify this software you must label
24  * your software as modified software and not distribute it in such a
25  * fashion that it might be confused with the original M.I.T. software.
26  * M.I.T. makes no representations about the suitability of
27  * this software for any purpose.  It is provided "as is" without express
28  * or implied warranty.
29  *
30  *
31  * krb5_get_in_tkt()
32  */
33 
34 #include <string.h>
35 #include <ctype.h>
36 #include "k5-int.h"
37 #include "int-proto.h"
38 #include "os-proto.h"
39 #include <locale.h>
40 #include <syslog.h>
41 
42 /*
43  All-purpose initial ticket routine, usually called via
44  krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
45 
46  Attempts to get an initial ticket for creds->client to use server
47  creds->server, (realm is taken from creds->client), with options
48  options, and using creds->times.starttime, creds->times.endtime,
49  creds->times.renew_till as from, till, and rtime.
50  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
51 
52  key_proc is called to fill in the key to be used for decryption.
53  keyseed is passed on to key_proc.
54 
55  decrypt_proc is called to perform the decryption of the response (the
56  encrypted part is in dec_rep->enc_part; the decrypted part should be
57  allocated and filled into dec_rep->enc_part2
58  arg is passed on to decrypt_proc.
59 
60  If addrs is non-NULL, it is used for the addresses requested.  If it is
61  null, the system standard addresses are used.
62 
63  A succesful call will place the ticket in the credentials cache ccache
64  and fill in creds with the ticket information used/returned..
65 
66  returns system errors, encryption errors
67 
68  */
69 
70 /* Solaris Kerberos */
71 #define	max(a, b)	((a) > (b) ? (a) : (b))
72 
73 /* some typedef's for the function args to make things look a bit cleaner */
74 
75 typedef krb5_error_code (*git_key_proc) (krb5_context,
76 						   const krb5_enctype,
77 						   krb5_data *,
78 						   krb5_const_pointer,
79 						   krb5_keyblock **);
80 
81 typedef krb5_error_code (*git_decrypt_proc) (krb5_context,
82 						       const krb5_keyblock *,
83 						       krb5_const_pointer,
84 						       krb5_kdc_rep * );
85 
86 static krb5_error_code make_preauth_list (krb5_context,
87 						    krb5_preauthtype *,
88 						    int, krb5_pa_data ***);
89 static krb5_error_code sort_krb5_padata_sequence(krb5_context context,
90 						 krb5_data *realm,
91 						 krb5_pa_data **padata);
92 
93 /*
94  * This function performs 32 bit bounded addition so we can generate
95  * lifetimes without overflowing krb5_int32
96  */
krb5int_addint32(krb5_int32 x,krb5_int32 y)97 static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y)
98 {
99     if ((x > 0) && (y > (KRB5_INT32_MAX - x))) {
100         /* sum will be be greater than KRB5_INT32_MAX */
101         return KRB5_INT32_MAX;
102     } else if ((x < 0) && (y < (KRB5_INT32_MIN - x))) {
103         /* sum will be less than KRB5_INT32_MIN */
104         return KRB5_INT32_MIN;
105     }
106 
107     return x + y;
108 }
109 
110 /*
111  * This function sends a request to the KDC, and gets back a response;
112  * the response is parsed into ret_err_reply or ret_as_reply if the
113  * reponse is a KRB_ERROR or a KRB_AS_REP packet.  If it is some other
114  * unexpected response, an error is returned.
115  */
116 static krb5_error_code
send_as_request2(krb5_context context,krb5_kdc_req * request,krb5_error ** ret_err_reply,krb5_kdc_rep ** ret_as_reply,int * use_master,char ** hostname_used)117 send_as_request2(krb5_context 		context,
118 		krb5_kdc_req		*request,
119 		krb5_error ** 		ret_err_reply,
120 		krb5_kdc_rep ** 	ret_as_reply,
121 		int 			*use_master,
122 		char			**hostname_used)
123 
124 {
125     krb5_kdc_rep *as_reply = 0;
126     krb5_error_code retval;
127     krb5_data *packet = 0;
128     krb5_data reply;
129     char k4_version;		/* same type as *(krb5_data::data) */
130     int tcp_only = 0;
131     krb5_timestamp time_now;
132 
133     reply.data = 0;
134 
135     /* Solaris Kerberos (illumos) */
136     if (krb5_getenv("MS_INTEROP")) {
137         /* Don't bother with UDP. */
138         tcp_only = 1;
139     }
140 
141     /* set the nonce if the caller expects us to do it */
142     if (request->nonce == 0) {
143         if ((retval = krb5_timeofday(context, &time_now)))
144 	    goto cleanup;
145         request->nonce = (krb5_int32) time_now;
146     }
147 
148     /* encode & send to KDC */
149     if ((retval = encode_krb5_as_req(request, &packet)) != 0)
150 	goto cleanup;
151 
152     k4_version = packet->data[0];
153 send_again:
154     retval = krb5_sendto_kdc2(context, packet,
155 			    krb5_princ_realm(context, request->client),
156 			    &reply, use_master, tcp_only, hostname_used);
157     if (retval)
158 	goto cleanup;
159 
160     /* now decode the reply...could be error or as_rep */
161     if (krb5_is_krb_error(&reply)) {
162 	krb5_error *err_reply;
163 
164 	if ((retval = decode_krb5_error(&reply, &err_reply)))
165 	    /* some other error code--??? */
166 	    goto cleanup;
167 
168 	if (ret_err_reply) {
169 	    if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG
170 		&& tcp_only == 0) {
171 		tcp_only = 1;
172 		krb5_free_error(context, err_reply);
173 		free(reply.data);
174 		reply.data = 0;
175 		goto send_again;
176 	    }
177 	    *ret_err_reply = err_reply;
178 	} else {
179 	    krb5_free_error(context, err_reply);
180 	    err_reply = NULL;
181 	}
182 	goto cleanup;
183     }
184 
185     /*
186      * Check to make sure it isn't a V4 reply.
187      */
188     if (!krb5_is_as_rep(&reply)) {
189 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
190 #define V4_KRB_PROT_VERSION	4
191 #define V4_AUTH_MSG_ERR_REPLY	(5<<1)
192 	/* check here for V4 reply */
193 	unsigned int t_switch;
194 
195 	/* From v4 g_in_tkt.c: This used to be
196 	   switch (pkt_msg_type(rpkt) & ~1) {
197 	   but SCO 3.2v4 cc compiled that incorrectly.  */
198 	t_switch = reply.data[1];
199 	t_switch &= ~1;
200 
201 	if (t_switch == V4_AUTH_MSG_ERR_REPLY
202 	    && (reply.data[0] == V4_KRB_PROT_VERSION
203 		|| reply.data[0] == k4_version)) {
204 	    retval = KRB5KRB_AP_ERR_V4_REPLY;
205 	} else {
206 	    retval = KRB5KRB_AP_ERR_MSG_TYPE;
207 	}
208 	goto cleanup;
209     }
210 
211     /* It must be a KRB_AS_REP message, or an bad returned packet */
212     if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
213 	/* some other error code ??? */
214 	goto cleanup;
215 
216     if (as_reply->msg_type != KRB5_AS_REP) {
217 	retval = KRB5KRB_AP_ERR_MSG_TYPE;
218 	krb5_free_kdc_rep(context, as_reply);
219 	goto cleanup;
220     }
221 
222     if (ret_as_reply)
223 	*ret_as_reply = as_reply;
224     else
225 	krb5_free_kdc_rep(context, as_reply);
226 
227 cleanup:
228     if (packet)
229 	krb5_free_data(context, packet);
230     if (reply.data)
231 	free(reply.data);
232     return retval;
233 }
234 
235 static krb5_error_code
send_as_request(krb5_context context,krb5_kdc_req * request,krb5_error ** ret_err_reply,krb5_kdc_rep ** ret_as_reply,int * use_master)236 send_as_request(krb5_context 		context,
237 		krb5_kdc_req		*request,
238 		krb5_error ** 		ret_err_reply,
239 		krb5_kdc_rep ** 	ret_as_reply,
240 		int 			    *use_master)
241 {
242 	return send_as_request2(context,
243 			    request,
244 			    ret_err_reply,
245 			    ret_as_reply,
246 			    use_master,
247 			    NULL);
248 }
249 
250 static krb5_error_code
decrypt_as_reply(krb5_context context,krb5_kdc_req * request,krb5_kdc_rep * as_reply,git_key_proc key_proc,krb5_const_pointer keyseed,krb5_keyblock * key,git_decrypt_proc decrypt_proc,krb5_const_pointer decryptarg)251 decrypt_as_reply(krb5_context 		context,
252 		 krb5_kdc_req		*request,
253 		 krb5_kdc_rep		*as_reply,
254 		 git_key_proc 		key_proc,
255 		 krb5_const_pointer 	keyseed,
256 		 krb5_keyblock *	key,
257 		 git_decrypt_proc 	decrypt_proc,
258 		 krb5_const_pointer 	decryptarg)
259 {
260     krb5_error_code		retval;
261     krb5_keyblock *		decrypt_key = 0;
262     krb5_data 			salt;
263 
264     if (as_reply->enc_part2)
265 	return 0;
266 
267     if (key)
268 	    decrypt_key = key;
269     /* Solaris Kerberos */
270     else if (request != NULL) {
271 	if ((retval = krb5_principal2salt(context, request->client, &salt)))
272 	    return(retval);
273 
274 	retval = (*key_proc)(context, as_reply->enc_part.enctype,
275 			     &salt, keyseed, &decrypt_key);
276 	krb5_xfree(salt.data);
277 	if (retval)
278 	    goto cleanup;
279     } else {
280 	KRB5_LOG0(KRB5_ERR, "decrypt_as_reply() end, "
281 		"error key == NULL and request == NULL");
282 	return (EINVAL);
283     }
284 
285     /*
286      * Solaris kerberos: Overwriting the decrypt_key->enctype because the
287      * decrypt key's enctype may not be an exact match with the enctype that the
288      * KDC used to encrypt this part of the AS reply.  This assumes the
289      * as_reply->enc_part.enctype has been validated which is done by checking
290      * to see if the enctype that the KDC sent back in the as_reply is one of
291      * the enctypes originally requested.  Note, if request is NULL then the
292      * as_reply->enc_part.enctype could not be validated.
293      */
294 
295     if (request != NULL) {
296         if (is_in_keytype(request->ktype, request->nktypes,
297                 as_reply->enc_part.enctype)) {
298 
299 	    decrypt_key->enctype = as_reply->enc_part.enctype;
300 
301 	} else {
302 	    KRB5_LOG0(KRB5_ERR, "decrypt_as_reply() end, "
303 		    "error is_in_keytype() returned false");
304 	    retval = KRB5_BAD_ENCTYPE;
305 	    goto cleanup;
306 	}
307     }
308 
309     if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply))){
310 	KRB5_LOG(KRB5_ERR, "decrypt_as_reply() error (*decrypt_proc)() retval "
311 			    "= %d", retval);
312 	goto cleanup;
313     }
314 
315 cleanup:
316     if (!key && decrypt_key)
317 	krb5_free_keyblock(context, decrypt_key);
318     return (retval);
319 }
320 
321 static krb5_error_code
verify_as_reply(krb5_context context,krb5_timestamp time_now,krb5_kdc_req * request,krb5_kdc_rep * as_reply)322 verify_as_reply(krb5_context 		context,
323 		krb5_timestamp 		time_now,
324 		krb5_kdc_req		*request,
325 		krb5_kdc_rep		*as_reply)
326 {
327     krb5_error_code		retval;
328 
329     /* check the contents for sanity: */
330     if (!as_reply->enc_part2->times.starttime)
331 	as_reply->enc_part2->times.starttime =
332 	    as_reply->enc_part2->times.authtime;
333 
334     if (!krb5_principal_compare(context, as_reply->client, request->client)
335 	|| !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)
336 	|| !krb5_principal_compare(context, as_reply->ticket->server, request->server)
337 	|| (request->nonce != as_reply->enc_part2->nonce)
338 	/* XXX check for extraneous flags */
339 	/* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
340 	|| ((request->kdc_options & KDC_OPT_POSTDATED) &&
341 	    (request->from != 0) &&
342 	    (request->from != as_reply->enc_part2->times.starttime))
343 	|| ((request->till != 0) &&
344 	    (as_reply->enc_part2->times.endtime > request->till))
345 	|| ((request->kdc_options & KDC_OPT_RENEWABLE) &&
346 	    /*
347 	     * Solaris Kerberos: Here we error only if renewable_ok was not set.
348 	     */
349 	    !(request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
350 	    (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
351 	    (request->rtime != 0) &&
352 	    (as_reply->enc_part2->times.renew_till > request->rtime))
353 	|| ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
354 	    !(request->kdc_options & KDC_OPT_RENEWABLE) &&
355 	    (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
356 	    (request->till != 0) &&
357 	    (as_reply->enc_part2->times.renew_till > request->till))
358 	    /*
359 	     * Solaris Kerberos: renew_till should never be greater than till or
360 	     * rtime.
361 	     */
362 	|| ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
363 	    (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
364 	    (request->till != 0) &&
365 	    (request->rtime != 0) &&
366 	    (as_reply->enc_part2->times.renew_till > max(request->till,
367 	     request->rtime)))
368 	)
369 	return KRB5_KDCREP_MODIFIED;
370 
371     if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
372 	retval = krb5_set_real_time(context,
373 				    as_reply->enc_part2->times.authtime, 0);
374 	if (retval)
375 	    return retval;
376     } else {
377 	if ((request->from == 0) &&
378 	    (labs(as_reply->enc_part2->times.starttime - time_now)
379 	     > context->clockskew))
380 	    return (KRB5_KDCREP_SKEW);
381     }
382     return 0;
383 }
384 
385 /*ARGSUSED*/
386 static krb5_error_code
stash_as_reply(krb5_context context,krb5_timestamp time_now,krb5_kdc_req * request,krb5_kdc_rep * as_reply,krb5_creds * creds,krb5_ccache ccache)387 stash_as_reply(krb5_context 		context,
388 	       krb5_timestamp 		time_now,
389 	       krb5_kdc_req		*request,
390 	       krb5_kdc_rep		*as_reply,
391 	       krb5_creds * 		creds,
392 	       krb5_ccache 		ccache)
393 {
394     krb5_error_code 		retval;
395     krb5_data *			packet;
396     krb5_principal		client;
397     krb5_principal		server;
398 
399     client = NULL;
400     server = NULL;
401 
402     if (!creds->client)
403         if ((retval = krb5_copy_principal(context, as_reply->client, &client)))
404 	    goto cleanup;
405 
406     if (!creds->server)
407 	if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
408 					  &server)))
409 	    goto cleanup;
410 
411     /* fill in the credentials */
412     if ((retval = krb5_copy_keyblock_contents(context,
413 					      as_reply->enc_part2->session,
414 					      &creds->keyblock)))
415 	goto cleanup;
416 
417     creds->times = as_reply->enc_part2->times;
418     creds->is_skey = FALSE;		/* this is an AS_REQ, so cannot
419 					   be encrypted in skey */
420     creds->ticket_flags = as_reply->enc_part2->flags;
421     if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
422 				      &creds->addresses)))
423 	goto cleanup;
424 
425     creds->second_ticket.length = 0;
426     creds->second_ticket.data = 0;
427 
428     if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
429 	goto cleanup;
430 
431     creds->ticket = *packet;
432     krb5_xfree(packet);
433 
434     /* store it in the ccache! */
435     if (ccache) /* Solaris Kerberos */
436 	if ((retval = krb5_cc_store_cred(context, ccache, creds)) !=0)
437 	    goto cleanup;
438 
439     if (!creds->client)
440 	creds->client = client;
441     if (!creds->server)
442 	creds->server = server;
443 
444 cleanup:
445     if (retval) {
446 	if (client)
447 	    krb5_free_principal(context, client);
448 	if (server)
449 	    krb5_free_principal(context, server);
450 	if (creds->keyblock.contents) {
451 	    memset((char *)creds->keyblock.contents, 0,
452 		   creds->keyblock.length);
453 	    krb5_xfree(creds->keyblock.contents);
454 	    creds->keyblock.contents = 0;
455 	    creds->keyblock.length = 0;
456 	}
457 	if (creds->ticket.data) {
458 	    krb5_xfree(creds->ticket.data);
459 	    creds->ticket.data = 0;
460 	}
461 	if (creds->addresses) {
462 	    krb5_free_addresses(context, creds->addresses);
463 	    creds->addresses = 0;
464 	}
465     }
466     return (retval);
467 }
468 
469 /*ARGSUSED*/
470 static krb5_error_code
make_preauth_list(krb5_context context,krb5_preauthtype * ptypes,int nptypes,krb5_pa_data *** ret_list)471 make_preauth_list(krb5_context	context,
472 		  krb5_preauthtype *	ptypes,
473 		  int			nptypes,
474 		  krb5_pa_data ***	ret_list)
475 {
476     krb5_preauthtype *		ptypep;
477     krb5_pa_data **		preauthp;
478     int				i;
479 
480     if (nptypes < 0) {
481  	for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
482  	    ;
483     }
484 
485     /* allocate space for a NULL to terminate the list */
486 
487     if ((preauthp =
488  	 (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
489  	return(ENOMEM);
490 
491     for (i=0; i<nptypes; i++) {
492  	if ((preauthp[i] =
493  	     (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
494  	    for (; i>=0; i++)
495  		free(preauthp[i]);
496  	    free(preauthp);
497 	    return (ENOMEM);
498 	}
499  	preauthp[i]->magic = KV5M_PA_DATA;
500  	preauthp[i]->pa_type = ptypes[i];
501  	preauthp[i]->length = 0;
502  	preauthp[i]->contents = 0;
503     }
504 
505     /* fill in the terminating NULL */
506 
507     preauthp[nptypes] = NULL;
508 
509     *ret_list = preauthp;
510     return 0;
511 }
512 
513 #define MAX_IN_TKT_LOOPS 16
514 static const krb5_enctype get_in_tkt_enctypes[] = {
515     ENCTYPE_DES3_CBC_SHA1,
516     ENCTYPE_ARCFOUR_HMAC,
517     ENCTYPE_DES_CBC_MD5,
518     ENCTYPE_DES_CBC_MD4,
519     ENCTYPE_DES_CBC_CRC,
520     0
521 };
522 
523 krb5_error_code KRB5_CALLCONV
krb5_get_in_tkt(krb5_context context,const krb5_flags options,krb5_address * const * addrs,krb5_enctype * ktypes,krb5_preauthtype * ptypes,git_key_proc key_proc,krb5_const_pointer keyseed,git_decrypt_proc decrypt_proc,krb5_const_pointer decryptarg,krb5_creds * creds,krb5_ccache ccache,krb5_kdc_rep ** ret_as_reply)524 krb5_get_in_tkt(krb5_context context,
525 		const krb5_flags options,
526 		krb5_address * const * addrs,
527 		krb5_enctype * ktypes,
528 		krb5_preauthtype * ptypes,
529 		git_key_proc key_proc,
530 		krb5_const_pointer keyseed,
531 		git_decrypt_proc decrypt_proc,
532 		krb5_const_pointer decryptarg,
533 		krb5_creds * creds,
534 		krb5_ccache ccache,
535 		krb5_kdc_rep ** ret_as_reply)
536 {
537     krb5_error_code	retval;
538     krb5_timestamp	time_now;
539     krb5_keyblock *	decrypt_key = 0;
540     krb5_kdc_req	request;
541     krb5_pa_data	**padata = 0;
542     krb5_error *	err_reply;
543     krb5_kdc_rep *	as_reply = 0;
544     krb5_pa_data  **	preauth_to_use = 0;
545     int			loopcount = 0;
546     krb5_int32		do_more = 0;
547     int             use_master = 0;
548     char *hostname_used = NULL;
549 
550     if (! krb5_realm_compare(context, creds->client, creds->server)) {
551 	/* Solaris Kerberos */
552 	char *s_name = NULL;
553 	char *c_name = NULL;
554 	krb5_error_code serr, cerr;
555 	serr = krb5_unparse_name(context, creds->server, &s_name);
556 	cerr = krb5_unparse_name(context, creds->client, &c_name);
557 	krb5_set_error_message(context, KRB5_IN_TKT_REALM_MISMATCH,
558 			    dgettext(TEXT_DOMAIN,
559 				    "Client/server realm mismatch in initial ticket request: '%s' requesting ticket '%s'"),
560 			    cerr ? "unknown" : c_name,
561 			    serr ? "unknown" : s_name);
562 	if (s_name)
563 	    krb5_free_unparsed_name(context, s_name);
564 	if (c_name)
565 	    krb5_free_unparsed_name(context, c_name);
566 	return KRB5_IN_TKT_REALM_MISMATCH;
567     }
568 
569     if (ret_as_reply)
570 	*ret_as_reply = 0;
571 
572     /*
573      * Set up the basic request structure
574      */
575     request.magic = KV5M_KDC_REQ;
576     request.msg_type = KRB5_AS_REQ;
577     request.addresses = 0;
578     request.ktype = 0;
579     request.padata = 0;
580     if (addrs)
581 	request.addresses = (krb5_address **) addrs;
582     else
583 	if ((retval = krb5_os_localaddr(context, &request.addresses)))
584 	    goto cleanup;
585     request.kdc_options = options;
586     request.client = creds->client;
587     request.server = creds->server;
588     request.nonce = 0;
589     request.from = creds->times.starttime;
590     request.till = creds->times.endtime;
591     request.rtime = creds->times.renew_till;
592 
593     request.ktype = malloc (sizeof(get_in_tkt_enctypes));
594     if (request.ktype == NULL) {
595 	retval = ENOMEM;
596 	goto cleanup;
597     }
598     memcpy(request.ktype, get_in_tkt_enctypes, sizeof(get_in_tkt_enctypes));
599     for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++);
600     if (ktypes) {
601 	int i, req, next = 0;
602 	for (req = 0; ktypes[req]; req++) {
603 	    if (ktypes[req] == request.ktype[next]) {
604 		next++;
605 		continue;
606 	    }
607 	    for (i = next + 1; i < request.nktypes; i++)
608 		if (ktypes[req] == request.ktype[i]) {
609 		    /* Found the enctype we want, but not in the
610 		       position we want.  Move it, but keep the old
611 		       one from the desired slot around in case it's
612 		       later in our requested-ktypes list.  */
613 		    krb5_enctype t;
614 		    t = request.ktype[next];
615 		    request.ktype[next] = request.ktype[i];
616 		    request.ktype[i] = t;
617 		    next++;
618 		    break;
619 		}
620 	    /* If we didn't find it, don't do anything special, just
621 	       drop it.  */
622 	}
623 	request.ktype[next] = 0;
624 	request.nktypes = next;
625     }
626     request.authorization_data.ciphertext.length = 0;
627     request.authorization_data.ciphertext.data = 0;
628     request.unenc_authdata = 0;
629     request.second_ticket = 0;
630 
631     /*
632      * If a list of preauth types are passed in, convert it to a
633      * preauth_to_use list.
634      */
635     if (ptypes) {
636 	retval = make_preauth_list(context, ptypes, -1, &preauth_to_use);
637 	if (retval)
638 	    goto cleanup;
639     }
640 
641     while (1) {
642 	if (loopcount++ > MAX_IN_TKT_LOOPS) {
643 	    retval = KRB5_GET_IN_TKT_LOOP;
644 	    /* Solaris Kerberos */
645 	    {
646                 char *s_name = NULL;
647 		char *c_name = NULL;
648 		krb5_error_code serr, cerr;
649 		serr = krb5_unparse_name(context, creds->server, &s_name);
650 		cerr = krb5_unparse_name(context, creds->client, &c_name);
651 		krb5_set_error_message(context, retval,
652 				    dgettext(TEXT_DOMAIN,
653 					    "Looping detected getting ticket: '%s' requesting ticket '%s'. Max loops is %d.  Make sure a KDC is available"),
654 				    cerr ? "unknown" : c_name,
655 				    serr ? "unknown" : s_name,
656 				    MAX_IN_TKT_LOOPS);
657 		if (s_name)
658 		    krb5_free_unparsed_name(context, s_name);
659 		if (c_name)
660 		    krb5_free_unparsed_name(context, c_name);
661 	    }
662 	    goto cleanup;
663 	}
664 
665 	if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
666 					 keyseed, creds, &request)) != 0)
667 	    goto cleanup;
668 	if (preauth_to_use)
669 	    krb5_free_pa_data(context, preauth_to_use);
670 	preauth_to_use = 0;
671 
672 	err_reply = 0;
673 	as_reply = 0;
674 
675         if ((retval = krb5_timeofday(context, &time_now)))
676 	    goto cleanup;
677 
678         /*
679          * XXX we know they are the same size... and we should do
680          * something better than just the current time
681          */
682 	request.nonce = (krb5_int32) time_now;
683 
684 	if ((retval = send_as_request2(context, &request, &err_reply,
685 				    &as_reply, &use_master,
686 				    &hostname_used)))
687 	    goto cleanup;
688 
689 	if (err_reply) {
690 	    if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
691 		err_reply->e_data.length > 0) {
692 		retval = decode_krb5_padata_sequence(&err_reply->e_data,
693 						     &preauth_to_use);
694 		krb5_free_error(context, err_reply);
695                 err_reply = NULL;
696 		if (retval)
697 		    goto cleanup;
698                 retval = sort_krb5_padata_sequence(context,
699 						   &request.server->realm,
700 						   padata);
701 		if (retval)
702 		    goto cleanup;
703 		continue;
704 	    } else {
705 		retval = (krb5_error_code) err_reply->error
706 		    + ERROR_TABLE_BASE_krb5;
707 		krb5_free_error(context, err_reply);
708                 err_reply = NULL;
709 		goto cleanup;
710 	    }
711 	} else if (!as_reply) {
712 	    retval = KRB5KRB_AP_ERR_MSG_TYPE;
713 	    goto cleanup;
714 	}
715 	if ((retval = krb5_process_padata(context, &request, as_reply,
716 					  key_proc, keyseed, decrypt_proc,
717 					  &decrypt_key, creds,
718 					  &do_more)) != 0)
719 	    goto cleanup;
720 
721 	if (!do_more)
722 	    break;
723     }
724 
725     if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
726 				   keyseed, decrypt_key, decrypt_proc,
727 				   decryptarg)))
728 	goto cleanup;
729 
730     if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
731 	goto cleanup;
732 
733     if ((retval = stash_as_reply(context, time_now, &request, as_reply,
734 				 creds, ccache)))
735 	goto cleanup;
736 
737 cleanup:
738     if (request.ktype)
739 	free(request.ktype);
740     if (!addrs && request.addresses)
741 	krb5_free_addresses(context, request.addresses);
742     if (request.padata)
743 	krb5_free_pa_data(context, request.padata);
744     if (padata)
745 	krb5_free_pa_data(context, padata);
746     if (preauth_to_use)
747 	krb5_free_pa_data(context, preauth_to_use);
748     if (decrypt_key)
749     	krb5_free_keyblock(context, decrypt_key);
750     if (as_reply) {
751 	if (ret_as_reply)
752 	    *ret_as_reply = as_reply;
753 	else
754 	    krb5_free_kdc_rep(context, as_reply);
755     }
756     if (hostname_used)
757         free(hostname_used);
758 
759     return (retval);
760 }
761 
762 /* begin libdefaults parsing code.  This should almost certainly move
763    somewhere else, but I don't know where the correct somewhere else
764    is yet. */
765 
766 /* XXX Duplicating this is annoying; try to work on a better way.*/
767 static const char *const conf_yes[] = {
768     "y", "yes", "true", "t", "1", "on",
769     0,
770 };
771 
772 static const char *const conf_no[] = {
773     "n", "no", "false", "nil", "0", "off",
774     0,
775 };
776 
777 int
_krb5_conf_boolean(const char * s)778 _krb5_conf_boolean(const char *s)
779 {
780     const char *const *p;
781 
782     for(p=conf_yes; *p; p++) {
783 	if (!strcasecmp(*p,s))
784 	    return 1;
785     }
786 
787     for(p=conf_no; *p; p++) {
788 	if (!strcasecmp(*p,s))
789 	    return 0;
790     }
791 
792     /* Default to "no" */
793     return 0;
794 }
795 
796 static krb5_error_code
krb5_libdefault_string(krb5_context context,const krb5_data * realm,const char * option,char ** ret_value)797 krb5_libdefault_string(krb5_context context, const krb5_data *realm,
798 		       const char *option, char **ret_value)
799 {
800     profile_t profile;
801     const char *names[5];
802     char **nameval = NULL;
803     krb5_error_code retval;
804     char realmstr[1024];
805 
806     if (realm->length > sizeof(realmstr)-1)
807 	return(EINVAL);
808 
809     strncpy(realmstr, realm->data, realm->length);
810     realmstr[realm->length] = '\0';
811 
812     if (!context || (context->magic != KV5M_CONTEXT))
813 	return KV5M_CONTEXT;
814 
815     profile = context->profile;
816 
817     /* Solaris Kerberos */
818     names[0] = "realms";
819 
820     /*
821      * Try number one:
822      *
823      * [realms]
824      *		REALM = {
825      *			option = <boolean>
826      *		}
827      */
828 
829     names[1] = realmstr;
830     names[2] = option;
831     names[3] = 0;
832     retval = profile_get_values(profile, names, &nameval);
833     if (retval == 0 && nameval && nameval[0])
834 	goto goodbye;
835 
836     /*
837      * Try number two:
838      *
839      * [libdefaults]
840      *		option = <boolean>
841      */
842 
843     names[0] = "libdefaults";
844     names[1] = option;
845     names[2] = 0;
846     retval = profile_get_values(profile, names, &nameval);
847     if (retval == 0 && nameval && nameval[0])
848 	goto goodbye;
849 
850 goodbye:
851     if (!nameval)
852 	return(ENOENT);
853 
854     if (!nameval[0]) {
855         retval = ENOENT;
856     } else {
857         *ret_value = malloc(strlen(nameval[0]) + 1);
858         if (!*ret_value)
859             retval = ENOMEM;
860         else
861             strcpy(*ret_value, nameval[0]);
862     }
863 
864     profile_free_list(nameval);
865 
866     return retval;
867 }
868 
869 /* not static so verify_init_creds() can call it */
870 /* as well as the DNS code */
871 
872 krb5_error_code
krb5_libdefault_boolean(krb5_context context,const krb5_data * realm,const char * option,int * ret_value)873 krb5_libdefault_boolean(krb5_context context, const krb5_data *realm,
874 			const char *option, int *ret_value)
875 {
876     char *string = NULL;
877     krb5_error_code retval;
878 
879     retval = krb5_libdefault_string(context, realm, option, &string);
880 
881     if (retval)
882 	return(retval);
883 
884     *ret_value = _krb5_conf_boolean(string);
885     free(string);
886 
887     return(0);
888 }
889 
890 /* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
891  * libdefaults entry are listed before any others. */
892 static krb5_error_code
sort_krb5_padata_sequence(krb5_context context,krb5_data * realm,krb5_pa_data ** padata)893 sort_krb5_padata_sequence(krb5_context context, krb5_data *realm,
894 			  krb5_pa_data **padata)
895 {
896     int i, j, base;
897     krb5_error_code ret;
898     const char *p;
899     long l;
900     char *q, *preauth_types = NULL;
901     krb5_pa_data *tmp;
902     int need_free_string = 1;
903 
904     if ((padata == NULL) || (padata[0] == NULL)) {
905 	return 0;
906     }
907 
908     ret = krb5_libdefault_string(context, realm, "preferred_preauth_types",
909 				 &preauth_types);
910     if ((ret != 0) || (preauth_types == NULL)) {
911 	/* Try to use PKINIT first. */
912 	preauth_types = "17, 16, 15, 14";
913 	need_free_string = 0;
914     }
915 
916 #ifdef DEBUG
917     fprintf (stderr, "preauth data types before sorting:");
918     for (i = 0; padata[i]; i++) {
919 	fprintf (stderr, " %d", padata[i]->pa_type);
920     }
921     fprintf (stderr, "\n");
922 #endif
923 
924     base = 0;
925     for (p = preauth_types; *p != '\0';) {
926 	/* skip whitespace to find an entry */
927 	p += strspn(p, ", ");
928 	if (*p != '\0') {
929 	    /* see if we can extract a number */
930 	    l = strtol(p, &q, 10);
931 	    if ((q != NULL) && (q > p)) {
932 		/* got a valid number; search for a matchin entry */
933 		for (i = base; padata[i] != NULL; i++) {
934 		    /* bubble the matching entry to the front of the list */
935 		    if (padata[i]->pa_type == l) {
936 			tmp = padata[i];
937 			for (j = i; j > base; j--)
938 			    padata[j] = padata[j - 1];
939 			padata[base] = tmp;
940 			base++;
941 			break;
942 		    }
943 		}
944 		p = q;
945 	    } else {
946 		break;
947 	    }
948 	}
949     }
950     if (need_free_string)
951 	free(preauth_types);
952 
953 #ifdef DEBUG
954     fprintf (stderr, "preauth data types after sorting:");
955     for (i = 0; padata[i]; i++)
956 	fprintf (stderr, " %d", padata[i]->pa_type);
957     fprintf (stderr, "\n");
958 #endif
959 
960     return 0;
961 }
962 
963 /*
964  * Solaris Kerberos
965  * Return 1 if any char in string is lower-case.
966  */
967 static int
is_lower_case(char * s)968 is_lower_case(char *s)
969 {
970     if (!s)
971 	return 0;
972 
973     while (*s) {
974 	if (islower((int)*s))
975 	    return 1;
976 	s++;
977     }
978     return 0;
979 }
980 
981 krb5_error_code KRB5_CALLCONV
krb5_get_init_creds(krb5_context context,krb5_creds * creds,krb5_principal client,krb5_prompter_fct prompter,void * prompter_data,krb5_deltat start_time,char * in_tkt_service,krb5_gic_opt_ext * options,krb5_gic_get_as_key_fct gak_fct,void * gak_data,int * use_master,krb5_kdc_rep ** as_reply)982 krb5_get_init_creds(krb5_context context,
983 		    krb5_creds *creds,
984 		    krb5_principal client,
985 		    krb5_prompter_fct prompter,
986 		    void *prompter_data,
987 		    krb5_deltat start_time,
988 		    char *in_tkt_service,
989 		    krb5_gic_opt_ext *options,
990 		    krb5_gic_get_as_key_fct gak_fct,
991 		    void *gak_data,
992 		    int  *use_master,
993 		    krb5_kdc_rep **as_reply)
994 {
995     krb5_error_code ret;
996     krb5_kdc_req request;
997     krb5_data *encoded_request_body, *encoded_previous_request;
998     krb5_pa_data **preauth_to_use, **kdc_padata;
999     int tempint;
1000     char *tempstr = NULL;
1001     krb5_deltat tkt_life;
1002     krb5_deltat renew_life;
1003     int loopcount;
1004     krb5_data salt;
1005     krb5_data s2kparams;
1006     krb5_keyblock as_key;
1007     krb5_error *err_reply = NULL;
1008     krb5_kdc_rep *local_as_reply;
1009     krb5_timestamp time_now;
1010     krb5_enctype etype = 0;
1011     krb5_preauth_client_rock get_data_rock;
1012     char *hostname_used = NULL;
1013 
1014     /* initialize everything which will be freed at cleanup */
1015 
1016     s2kparams.data = NULL;
1017     s2kparams.length = 0;
1018     request.server = NULL;
1019     request.ktype = NULL;
1020     request.addresses = NULL;
1021     request.padata = NULL;
1022     encoded_request_body = NULL;
1023     encoded_previous_request = NULL;
1024     preauth_to_use = NULL;
1025     kdc_padata = NULL;
1026     as_key.length = 0;
1027     salt.length = 0;
1028     salt.data = NULL;
1029 
1030     (void) memset(&as_key, 0, sizeof(as_key));
1031 
1032     local_as_reply = 0;
1033 
1034     /*
1035      * Set up the basic request structure
1036      */
1037     request.magic = KV5M_KDC_REQ;
1038     request.msg_type = KRB5_AS_REQ;
1039 
1040     /* request.nonce is filled in when we send a request to the kdc */
1041     request.nonce = 0;
1042 
1043     /* request.padata is filled in later */
1044 
1045     request.kdc_options = context->kdc_default_options;
1046 
1047     /* forwardable */
1048 
1049     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE))
1050 	tempint = options->forwardable;
1051     else if ((ret = krb5_libdefault_boolean(context, &client->realm,
1052 					    "forwardable", &tempint)) == 0)
1053 	/*EMPTY*/
1054 	;
1055     else
1056 	tempint = 0;
1057     if (tempint)
1058 	request.kdc_options |= KDC_OPT_FORWARDABLE;
1059 
1060     /* proxiable */
1061 
1062     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE))
1063 	tempint = options->proxiable;
1064     else if ((ret = krb5_libdefault_boolean(context, &client->realm,
1065 					    "proxiable", &tempint)) == 0)
1066 	/*EMPTY*/
1067 	;
1068     else
1069 	tempint = 0;
1070     if (tempint)
1071 	request.kdc_options |= KDC_OPT_PROXIABLE;
1072 
1073     /* allow_postdate */
1074 
1075     if (start_time > 0)
1076 	request.kdc_options |= (KDC_OPT_ALLOW_POSTDATE|KDC_OPT_POSTDATED);
1077 
1078     /* ticket lifetime */
1079 
1080     if ((ret = krb5_timeofday(context, &request.from)))
1081 	goto cleanup;
1082     request.from = krb5int_addint32(request.from, start_time);
1083 
1084     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)) {
1085         tkt_life = options->tkt_life;
1086     } else if ((ret = krb5_libdefault_string(context, &client->realm,
1087 					     "ticket_lifetime", &tempstr))
1088 	       == 0) {
1089 	ret = krb5_string_to_deltat(tempstr, &tkt_life);
1090 	free(tempstr);
1091 	if (ret) {
1092 	    goto cleanup;
1093 	}
1094     } else {
1095 	/* this used to be hardcoded in kinit.c */
1096 	tkt_life = 24*60*60;
1097     }
1098     request.till = krb5int_addint32(request.from, tkt_life);
1099 
1100     /* renewable lifetime */
1101 
1102     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) {
1103 	renew_life = options->renew_life;
1104     } else if ((ret = krb5_libdefault_string(context, &client->realm,
1105 					     "renew_lifetime", &tempstr))
1106 	       == 0) {
1107 	ret = krb5_string_to_deltat(tempstr, &renew_life);
1108 	free(tempstr);
1109 	if (ret) {
1110 	    goto cleanup;
1111 	}
1112     } else {
1113 	renew_life = 0;
1114     }
1115     if (renew_life > 0)
1116 	request.kdc_options |= KDC_OPT_RENEWABLE;
1117 
1118     if (renew_life > 0) {
1119 	request.rtime = krb5int_addint32(request.from, renew_life);
1120         if (request.rtime < request.till) {
1121             /* don't ask for a smaller renewable time than the lifetime */
1122             request.rtime = request.till;
1123         }
1124         /* we are already asking for renewable tickets so strip this option */
1125 	request.kdc_options &= ~(KDC_OPT_RENEWABLE_OK);
1126     } else {
1127 	request.rtime = 0;
1128     }
1129 
1130     /* client */
1131 
1132     request.client = client;
1133 
1134     /* service */
1135 
1136     if (in_tkt_service) {
1137 	/* this is ugly, because so are the data structures involved.  I'm
1138 	   in the library, so I'm going to manipulate the data structures
1139 	   directly, otherwise, it will be worse. */
1140 
1141         if ((ret = krb5_parse_name(context, in_tkt_service, &request.server)))
1142 	    goto cleanup;
1143 
1144 	/* stuff the client realm into the server principal.
1145 	   realloc if necessary */
1146 	if (request.server->realm.length < request.client->realm.length)
1147 	    if ((request.server->realm.data =
1148 		 (char *) realloc(request.server->realm.data,
1149 				  request.client->realm.length)) == NULL) {
1150 		ret = ENOMEM;
1151 		goto cleanup;
1152 	    }
1153 
1154 	request.server->realm.length = request.client->realm.length;
1155 	memcpy(request.server->realm.data, request.client->realm.data,
1156 	       request.client->realm.length);
1157     } else {
1158 	if ((ret = krb5_build_principal_ext(context, &request.server,
1159 					   request.client->realm.length,
1160 					   request.client->realm.data,
1161 					   KRB5_TGS_NAME_SIZE,
1162 					   KRB5_TGS_NAME,
1163 					   request.client->realm.length,
1164 					   request.client->realm.data,
1165 					   0)))
1166 	    goto cleanup;
1167     }
1168 
1169     krb5_preauth_request_context_init(context);
1170 
1171     /* nonce is filled in by send_as_request if we don't take care of it */
1172 
1173     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) {
1174 	request.ktype = options->etype_list;
1175 	request.nktypes = options->etype_list_length;
1176     } else if ((ret = krb5_get_default_in_tkt_ktypes(context,
1177 						     &request.ktype)) == 0) {
1178 	for (request.nktypes = 0;
1179 	     request.ktype[request.nktypes];
1180 	     request.nktypes++)
1181 	    ;
1182     } else {
1183 	/* there isn't any useful default here.  ret is set from above */
1184 	goto cleanup;
1185     }
1186 
1187     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)) {
1188 	request.addresses = options->address_list;
1189     }
1190     /* it would be nice if this parsed out an address list, but
1191        that would be work. */
1192     else if (((ret = krb5_libdefault_boolean(context, &client->realm,
1193 					    "no_addresses", &tempint)) == 0)
1194 	     || (tempint == 1)) {
1195 	    /*EMPTY*/
1196 	    ;
1197     } else if (((ret = krb5_libdefault_boolean(context, &client->realm,
1198 					    "noaddresses", &tempint)) == 0)
1199 	     || (tempint == 1)) {
1200 	    /*EMPTY*/
1201 	    ;
1202     } else {
1203 	if ((ret = krb5_os_localaddr(context, &request.addresses)))
1204 	    goto cleanup;
1205     }
1206 
1207     request.authorization_data.ciphertext.length = 0;
1208     request.authorization_data.ciphertext.data = 0;
1209     request.unenc_authdata = 0;
1210     request.second_ticket = 0;
1211 
1212     /* set up the other state.  */
1213 
1214     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) {
1215 	if ((ret = make_preauth_list(context, options->preauth_list,
1216 				     options->preauth_list_length,
1217 				     &preauth_to_use)))
1218 	    goto cleanup;
1219     }
1220 
1221     /* the salt is allocated from somewhere, unless it is from the caller,
1222        then it is a reference */
1223 
1224     if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)) {
1225 	salt = *options->salt;
1226     } else {
1227 	salt.length = SALT_TYPE_AFS_LENGTH;
1228 	salt.data = NULL;
1229     }
1230 
1231 
1232     /* set the request nonce */
1233     if ((ret = krb5_timeofday(context, &time_now)))
1234 	goto cleanup;
1235     /*
1236      * XXX we know they are the same size... and we should do
1237      * something better than just the current time
1238      */
1239     request.nonce = (krb5_int32) time_now;
1240 
1241     /* give the preauth plugins a chance to prep the request body */
1242     krb5_preauth_prepare_request(context, options, &request);
1243     ret = encode_krb5_kdc_req_body(&request, &encoded_request_body);
1244     if (ret)
1245         goto cleanup;
1246 
1247     get_data_rock.magic = CLIENT_ROCK_MAGIC;
1248     get_data_rock.as_reply = NULL;
1249 
1250     /* now, loop processing preauth data and talking to the kdc */
1251     for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) {
1252 	if (request.padata) {
1253 	    krb5_free_pa_data(context, request.padata);
1254 	    request.padata = NULL;
1255 	}
1256 	if (!err_reply) {
1257             /* either our first attempt, or retrying after PREAUTH_NEEDED */
1258 	    if ((ret = krb5_do_preauth(context,
1259 				       &request,
1260 				       encoded_request_body,
1261 				       encoded_previous_request,
1262 				       preauth_to_use, &request.padata,
1263 				       &salt, &s2kparams, &etype, &as_key,
1264 				       prompter, prompter_data,
1265 				       gak_fct, gak_data,
1266 				       &get_data_rock, options)))
1267 	        goto cleanup;
1268 	} else {
1269 	    if (preauth_to_use != NULL) {
1270 		/*
1271 		 * Retry after an error other than PREAUTH_NEEDED,
1272 		 * using e-data to figure out what to change.
1273 		 */
1274 		ret = krb5_do_preauth_tryagain(context,
1275 					       &request,
1276 					       encoded_request_body,
1277 					       encoded_previous_request,
1278 					       preauth_to_use, &request.padata,
1279 					       err_reply,
1280 					       &salt, &s2kparams, &etype,
1281 					       &as_key,
1282 					       prompter, prompter_data,
1283 					       gak_fct, gak_data,
1284 					       &get_data_rock, options);
1285 	    } else {
1286 		/* No preauth supplied, so can't query the plug-ins. */
1287 		ret = KRB5KRB_ERR_GENERIC;
1288 	    }
1289 	    if (ret) {
1290 		/* couldn't come up with anything better */
1291 		ret = err_reply->error + ERROR_TABLE_BASE_krb5;
1292 	    }
1293 	    krb5_free_error(context, err_reply);
1294 	    err_reply = NULL;
1295 	    if (ret)
1296 		goto cleanup;
1297 	}
1298 
1299         if (encoded_previous_request != NULL) {
1300 	    krb5_free_data(context, encoded_previous_request);
1301 	    encoded_previous_request = NULL;
1302         }
1303         ret = encode_krb5_as_req(&request, &encoded_previous_request);
1304 	if (ret)
1305 	    goto cleanup;
1306 
1307 	err_reply = NULL;
1308 	local_as_reply = 0;
1309 	if ((ret = send_as_request2(context, &request, &err_reply,
1310 				    &local_as_reply, use_master,
1311 				    &hostname_used)))
1312 	    goto cleanup;
1313 
1314 	if (err_reply) {
1315 	    if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
1316 		err_reply->e_data.length > 0) {
1317 		/* reset the list of preauth types to try */
1318 		if (preauth_to_use) {
1319 		    krb5_free_pa_data(context, preauth_to_use);
1320 		    preauth_to_use = NULL;
1321 		}
1322 		ret = decode_krb5_padata_sequence(&err_reply->e_data,
1323 						  &preauth_to_use);
1324  		krb5_free_error(context, err_reply);
1325  		err_reply = NULL;
1326 		if (ret)
1327 		    goto cleanup;
1328 		ret = sort_krb5_padata_sequence(context,
1329 						&request.server->realm,
1330 						preauth_to_use);
1331 		if (ret)
1332 		    goto cleanup;
1333 		/* continue to next iteration */
1334 	    } else {
1335 		if (err_reply->e_data.length > 0) {
1336 		    /* continue to next iteration */
1337 		} else {
1338 		    /* error + no hints = give up */
1339 		    ret = (krb5_error_code) err_reply->error
1340 		          + ERROR_TABLE_BASE_krb5;
1341 		    goto cleanup;
1342 		}
1343 	    }
1344 	} else if (local_as_reply) {
1345 	    break;
1346 	} else {
1347 	    ret = KRB5KRB_AP_ERR_MSG_TYPE;
1348 	    goto cleanup;
1349 	}
1350     }
1351 
1352     if (loopcount == MAX_IN_TKT_LOOPS) {
1353 	ret = KRB5_GET_IN_TKT_LOOP;
1354 	/* Solaris Kerberos */
1355 	{
1356             char *s_name = NULL;
1357 	    char *c_name = NULL;
1358 	    krb5_error_code serr, cerr;
1359 	    serr = krb5_unparse_name(context, creds->server, &s_name);
1360 	    cerr = krb5_unparse_name(context, creds->client, &c_name);
1361 	    krb5_set_error_message(context, ret,
1362 				dgettext(TEXT_DOMAIN,
1363 					"Looping detected getting initial creds: '%s' requesting ticket '%s'. Max loops is %d.  Make sure a KDC is available"),
1364 				cerr ? "unknown" : c_name,
1365 				serr ? "unknown" : s_name,
1366 				MAX_IN_TKT_LOOPS);
1367 	    if (s_name)
1368 		krb5_free_unparsed_name(context, s_name);
1369 	    if (c_name)
1370 		krb5_free_unparsed_name(context, c_name);
1371 	}
1372 	goto cleanup;
1373     }
1374 
1375     /* process any preauth data in the as_reply */
1376     krb5_clear_preauth_context_use_counts(context);
1377     if ((ret = sort_krb5_padata_sequence(context, &request.server->realm,
1378 					 local_as_reply->padata)))
1379 	goto cleanup;
1380     get_data_rock.as_reply = local_as_reply;
1381     if ((ret = krb5_do_preauth(context,
1382 			       &request,
1383 			       encoded_request_body, encoded_previous_request,
1384 			       local_as_reply->padata, &kdc_padata,
1385 			       &salt, &s2kparams, &etype, &as_key, prompter,
1386 			       prompter_data, gak_fct, gak_data,
1387 			       &get_data_rock, options)))
1388 	goto cleanup;
1389 
1390     /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
1391        the AS_REP comes back encrypted in the user's longterm key
1392        instead of in the SAD. If there was a SAM preauth, there
1393        will be an as_key here which will be the SAD. If that fails,
1394        use the gak_fct to get the password, and try again. */
1395 
1396     /* XXX because etypes are handled poorly (particularly wrt SAM,
1397        where the etype is fixed by the kdc), we may want to try
1398        decrypt_as_reply twice.  If there's an as_key available, try
1399        it.  If decrypting the as_rep fails, or if there isn't an
1400        as_key at all yet, then use the gak_fct to get one, and try
1401        again.  */
1402 
1403     if (as_key.length)
1404 	ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
1405 			       NULL, &as_key, krb5_kdc_rep_decrypt_proc,
1406 			       NULL);
1407     else
1408 	ret = -1;
1409 
1410     if (ret) {
1411 	/* if we haven't get gotten a key, get it now */
1412 
1413 	if ((ret = ((*gak_fct)(context, request.client,
1414 			       local_as_reply->enc_part.enctype,
1415 			       prompter, prompter_data, &salt, &s2kparams,
1416 			       &as_key, gak_data))))
1417 	    goto cleanup;
1418 
1419 	if ((ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
1420 				    NULL, &as_key, krb5_kdc_rep_decrypt_proc,
1421 				    NULL)))
1422 	    goto cleanup;
1423     }
1424 
1425     if ((ret = verify_as_reply(context, time_now, &request, local_as_reply)))
1426 	goto cleanup;
1427 
1428     /* XXX this should be inside stash_as_reply, but as long as
1429        get_in_tkt is still around using that arg as an in/out, I can't
1430        do that */
1431 	/* Solaris Kerberos */
1432 	(void) memset(creds, 0, sizeof(*creds));
1433 
1434     /* Solaris Kerberos */
1435     if ((ret = stash_as_reply(context, time_now, &request, local_as_reply,
1436 			      creds, (krb5_ccache)NULL)))
1437 	goto cleanup;
1438 
1439     /* success */
1440 
1441     ret = 0;
1442 
1443 cleanup:
1444     if (ret != 0) {
1445         char *client_name = NULL;
1446         /* See if we can produce a more detailed error message.  */
1447         switch (ret) {
1448         case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
1449             if (krb5_unparse_name(context, client, &client_name) == 0) {
1450                 krb5_set_error_message(context, ret,
1451                                        dgettext(TEXT_DOMAIN,
1452 						"Client '%s' not found in Kerberos database"),
1453                                        client_name);
1454                 free(client_name);
1455             }
1456             break;
1457         /* Solaris Kerberos: spruce-up the err msg */
1458 	case KRB5_PREAUTH_FAILED:
1459 	case KRB5KDC_ERR_PREAUTH_FAILED:
1460             if (krb5_unparse_name(context, client, &client_name) == 0) {
1461                 krb5_set_error_message(context, ret,
1462 				    dgettext(TEXT_DOMAIN,
1463 				      "Client '%s' pre-authentication failed"),
1464                                        client_name);
1465                 free(client_name);
1466             }
1467             break;
1468 	/* Solaris Kerberos: spruce-up the err msg */
1469 	case KRB5KRB_AP_ERR_SKEW: /* KRB_AP_ERR_SKEW + ERROR_TABLE_BASE_krb5 */
1470 	    {
1471                 char *s_name = NULL;
1472 		char *c_name = NULL;
1473 		char stimestring[17];
1474 		char fill = ' ';
1475 		krb5_error_code c_err, s_err, s_time;
1476 
1477 		s_err = krb5_unparse_name(context,
1478 					err_reply->server, &s_name);
1479 		s_time = krb5_timestamp_to_sfstring(err_reply->stime,
1480 						    stimestring,
1481 						    sizeof (stimestring),
1482 						    &fill);
1483 		c_err = krb5_unparse_name(context, client, &c_name);
1484 		krb5_set_error_message(context, ret,
1485 				    dgettext(TEXT_DOMAIN,
1486 					    "Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm"),
1487 				    c_err == 0 ? c_name : "unknown",
1488 				    s_err == 0 ? s_name : "unknown",
1489 				    hostname_used ? hostname_used : "unknown",
1490 				    s_time == 0 ? stimestring : "unknown",
1491 				    (s_time != 0) ? 0 :
1492 				      (abs(err_reply->stime - time_now) / 60));
1493 		if (s_name)
1494 			krb5_free_unparsed_name(context, s_name);
1495 		if (c_name)
1496 			krb5_free_unparsed_name(context, c_name);
1497 	    }
1498 	    break;
1499 	case KRB5_KDCREP_MODIFIED:
1500             if (krb5_unparse_name(context, client, &client_name) == 0) {
1501 		/*
1502 		 * Solaris Kerberos
1503 		 * Extra err msg for common(?) case of
1504 		 * 'kinit user@lower-case-def-realm'.
1505 		 * DNS SRV recs will match (case insensitive) and trigger sendto
1506 		 * KDC and result in this error (at least w/MSFT AD KDC).
1507 		 */
1508 		char *realm = strpbrk(client_name, "@");
1509 		int set = 0;
1510 		if (realm++) {
1511 		    if (realm && realm[0] && is_lower_case(realm)) {
1512 			krb5_set_error_message(context, ret,
1513 					    dgettext(TEXT_DOMAIN,
1514 						    "KDC reply did not match expectations for client '%s': lower-case detected in realm '%s'"),
1515 					    client_name, realm);
1516 			set = 1;
1517 		    }
1518 		}
1519 		if (!set)
1520 		    krb5_set_error_message(context, ret,
1521 					dgettext(TEXT_DOMAIN,
1522 						"KDC reply did not match expectations for client '%s'"),
1523 					client_name);
1524                 free(client_name);
1525             }
1526 	    break;
1527         default:
1528             break;
1529         }
1530     }
1531     if (err_reply)
1532 	    krb5_free_error(context, err_reply);
1533     krb5_preauth_request_context_fini(context);
1534     if (encoded_previous_request != NULL) {
1535 	krb5_free_data(context, encoded_previous_request);
1536 	encoded_previous_request = NULL;
1537     }
1538     if (encoded_request_body != NULL) {
1539 	krb5_free_data(context, encoded_request_body);
1540 	encoded_request_body = NULL;
1541     }
1542     if (request.server)
1543 	krb5_free_principal(context, request.server);
1544     if (request.ktype &&
1545 	(!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))))
1546 	free(request.ktype);
1547     if (request.addresses &&
1548 	(!(options &&
1549 	   (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST))))
1550 	krb5_free_addresses(context, request.addresses);
1551     if (preauth_to_use)
1552 	krb5_free_pa_data(context, preauth_to_use);
1553     if (kdc_padata)
1554 	krb5_free_pa_data(context, kdc_padata);
1555     if (request.padata)
1556 	krb5_free_pa_data(context, request.padata);
1557     if (as_key.length)
1558 	krb5_free_keyblock_contents(context, &as_key);
1559     if (salt.data &&
1560 	(!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT))))
1561 	krb5_xfree(salt.data);
1562     krb5_free_data_contents(context, &s2kparams);
1563     if (as_reply)
1564 	*as_reply = local_as_reply;
1565     else if (local_as_reply)
1566 	krb5_free_kdc_rep(context, local_as_reply);
1567     if (hostname_used)
1568         free(hostname_used);
1569     return(ret);
1570 }
1571