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