1 /*
2  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 /*
8  * lib/krb5/krb/gic_keytab.c
9  *
10  * Copyright (C) 2002, 2003 by the Massachusetts Institute of Technology.
11  * All rights reserved.
12  *
13  * Export of this software from the United States of America may
14  *   require a specific license from the United States Government.
15  *   It is the responsibility of any person or organization contemplating
16  *   export to obtain such a license before exporting.
17  *
18  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
19  * distribute this software and its documentation for any purpose and
20  * without fee is hereby granted, provided that the above copyright
21  * notice appear in all copies and that both that copyright notice and
22  * this permission notice appear in supporting documentation, and that
23  * the name of M.I.T. not be used in advertising or publicity pertaining
24  * to distribution of the software without specific, written prior
25  * permission.  Furthermore if you modify this software you must label
26  * your software as modified software and not distribute it in such a
27  * fashion that it might be confused with the original M.I.T. software.
28  * M.I.T. makes no representations about the suitability of
29  * this software for any purpose.  It is provided "as is" without express
30  * or implied warranty.
31  */
32 
33 /* Solaris Kerberos */
34 #include <libintl.h>
35 #include <locale.h>
36 
37 #include "k5-int.h"
38 
39 /*ARGSUSED*/
40 static krb5_error_code
41 krb5_get_as_key_keytab(
42     krb5_context context,
43     krb5_principal client,
44     krb5_enctype etype,
45     krb5_prompter_fct prompter,
46     void *prompter_data,
47     krb5_data *salt,
48     krb5_data *params,
49     krb5_keyblock *as_key,
50     void *gak_data)
51 {
52     krb5_keytab keytab = (krb5_keytab) gak_data;
53     krb5_error_code ret;
54     krb5_keytab_entry kt_ent;
55     krb5_keyblock *kt_key;
56 
57     /* if there's already a key of the correct etype, we're done.
58        if the etype is wrong, free the existing key, and make
59        a new one. */
60 
61     if (as_key->length) {
62 	if (as_key->enctype == etype)
63 	    return(0);
64 
65 	krb5_free_keyblock_contents(context, as_key);
66 	as_key->length = 0;
67     }
68 
69     if (!krb5_c_valid_enctype(etype))
70 	return(KRB5_PROG_ETYPE_NOSUPP);
71 
72     /* Solaris Kerberos */
73     if ((ret = krb5_kt_get_entry(context, keytab, client,
74 				 0, /* don't have vno available */
75 				 etype, &kt_ent)) != 0)
76 	return(ret);
77 
78     ret = krb5_copy_keyblock(context, &kt_ent.key, &kt_key);
79 
80     /* again, krb5's memory management is lame... */
81 
82     *as_key = *kt_key;
83     krb5_xfree(kt_key);
84 
85     (void) krb5_kt_free_entry(context, &kt_ent);
86 
87     return(ret);
88 }
89 
90 krb5_error_code KRB5_CALLCONV
91 krb5_get_init_creds_keytab(krb5_context context,
92 			   krb5_creds *creds,
93 			   krb5_principal client,
94 			   krb5_keytab arg_keytab,
95 			   krb5_deltat start_time,
96 			   char *in_tkt_service,
97 			   krb5_get_init_creds_opt *options)
98 {
99    krb5_error_code ret, ret2;
100    int use_master;
101    krb5_keytab keytab;
102    krb5_gic_opt_ext *opte = NULL;
103 
104    if (arg_keytab == NULL) {
105        if ((ret = krb5_kt_default(context, &keytab)))
106 	    return ret;
107    } else {
108        keytab = arg_keytab;
109    }
110 
111    ret = krb5int_gic_opt_to_opte(context, options, &opte, 1,
112 				 "krb5_get_init_creds_keytab");
113    if (ret)
114       return ret;
115 
116    /*
117     * Solaris Kerberos:
118     * If "client" was constructed from krb5_sname_to_princ() it may
119     * have a referral realm. This happens when there is no applicable
120     * domain-to-realm mapping in the Kerberos configuration file.
121     * If that is the case then the realm of the first principal found
122     * in the keytab which matches the client can be used for the client's
123     * realm.
124     */
125    if (krb5_is_referral_realm(&client->realm)) {
126 	krb5_data realm;
127 	ret = krb5_kt_find_realm(context, keytab, client, &realm);
128 	if (ret == 0) {
129 		krb5_free_data_contents(context, &client->realm);
130 		client->realm.length = realm.length;
131 		client->realm.data = realm.data;
132 	} else {
133 		/* Try to set a useful error message */
134 		char *princ = NULL;
135 		krb5_unparse_name(context, client, &princ);
136 
137 		krb5_set_error_message(context, ret,
138 		    gettext("Failed to find realm for %s in keytab"),
139 		    princ ? princ : "<unknown>");
140 		if (princ)
141 			krb5_free_unparsed_name(context, princ);
142 	}
143    }
144 
145    if (ret != 0)
146 	goto cleanup;
147 
148 
149    use_master = 0;
150 
151    /* first try: get the requested tkt from any kdc */
152 
153    ret = krb5_get_init_creds(context, creds, client, NULL, NULL,
154 			     start_time, in_tkt_service, opte,
155 			     krb5_get_as_key_keytab, (void *) keytab,
156 			     &use_master,NULL);
157 
158    /* check for success */
159 
160    if (ret == 0)
161       goto cleanup;
162 
163    /* If all the kdc's are unavailable fail */
164 
165    if ((ret == KRB5_KDC_UNREACH) || (ret == KRB5_REALM_CANT_RESOLVE))
166       goto cleanup;
167 
168    /* if the reply did not come from the master kdc, try again with
169       the master kdc */
170 
171    if (!use_master) {
172       use_master = 1;
173 
174       ret2 = krb5_get_init_creds(context, creds, client, NULL, NULL,
175 				 start_time, in_tkt_service, opte,
176 				 krb5_get_as_key_keytab, (void *) keytab,
177 				 &use_master, NULL);
178 
179       if (ret2 == 0) {
180 	 ret = 0;
181 	 goto cleanup;
182       }
183 
184       /* if the master is unreachable, return the error from the
185 	 slave we were able to contact */
186 
187       if ((ret2 == KRB5_KDC_UNREACH) ||
188 	  (ret2 == KRB5_REALM_CANT_RESOLVE) ||
189 	  (ret2 == KRB5_REALM_UNKNOWN))
190 	 goto cleanup;
191 
192       ret = ret2;
193    }
194 
195    /* at this point, we have a response from the master.  Since we don't
196       do any prompting or changing for keytabs, that's it. */
197 
198 cleanup:
199    if (opte && krb5_gic_opt_is_shadowed(opte))
200        krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
201    if (arg_keytab == NULL)
202        (void) krb5_kt_close(context, keytab); /* Solaris Kerberos */
203 
204    return(ret);
205 }
206 krb5_error_code KRB5_CALLCONV
207 krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
208 			      krb5_address *const *addrs, krb5_enctype *ktypes,
209 			      krb5_preauthtype *pre_auth_types,
210 			      krb5_keytab arg_keytab, krb5_ccache ccache,
211 			      krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
212 {
213     krb5_error_code retval;
214     krb5_gic_opt_ext *opte;
215     char * server = NULL;
216     krb5_keytab keytab;
217     krb5_principal client_princ, server_princ;
218     int use_master = 0;
219 
220     retval = krb5int_populate_gic_opt(context, &opte,
221 				      options, addrs, ktypes,
222 				      pre_auth_types, creds);
223     if (retval)
224 	return retval;
225 
226     if (arg_keytab == NULL) {
227 	retval = krb5_kt_default(context, &keytab);
228 	if (retval)
229 	    return retval;
230     }
231     else keytab = arg_keytab;
232 
233     retval = krb5_unparse_name( context, creds->server, &server);
234     if (retval)
235 	goto cleanup;
236     server_princ = creds->server;
237     client_princ = creds->client;
238     retval = krb5_get_init_creds (context,
239 				  creds, creds->client,
240 				  krb5_prompter_posix,  NULL,
241 				  0, server, opte,
242 				  krb5_get_as_key_keytab, (void *)keytab,
243 				  &use_master, ret_as_reply);
244     krb5_free_unparsed_name( context, server);
245     krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
246     if (retval) {
247 	goto cleanup;
248     }
249 	if (creds->server)
250 	    krb5_free_principal( context, creds->server);
251 	if (creds->client)
252 	    krb5_free_principal( context, creds->client);
253 	creds->client = client_princ;
254 	creds->server = server_princ;
255 
256     /* store it in the ccache! */
257     if (ccache)
258 	if ((retval = krb5_cc_store_cred(context, ccache, creds)))
259 	    goto cleanup;
260  cleanup:    if (arg_keytab == NULL)
261      krb5_kt_close(context, keytab);
262     return retval;
263 }
264 
265