Use etypes from keytab in krb5_gic_keytab
[krb5.git] / src / lib / krb5 / krb / gic_keytab.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/gic_keytab.c */
3 /*
4  * Copyright (C) 2002, 2003, 2008 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 #ifndef LEAN_CLIENT
27
28 #include "k5-int.h"
29 #include "int-proto.h"
30 #include "init_creds_ctx.h"
31
32 static krb5_error_code
33 get_as_key_keytab(krb5_context context,
34                   krb5_principal client,
35                   krb5_enctype etype,
36                   krb5_prompter_fct prompter,
37                   void *prompter_data,
38                   krb5_data *salt,
39                   krb5_data *params,
40                   krb5_keyblock *as_key,
41                   void *gak_data)
42 {
43     krb5_keytab keytab = (krb5_keytab) gak_data;
44     krb5_error_code ret;
45     krb5_keytab_entry kt_ent;
46     krb5_keyblock *kt_key;
47
48     /* if there's already a key of the correct etype, we're done.
49        if the etype is wrong, free the existing key, and make
50        a new one. */
51
52     if (as_key->length) {
53         if (as_key->enctype == etype)
54             return(0);
55
56         krb5_free_keyblock_contents(context, as_key);
57         as_key->length = 0;
58     }
59
60     if (!krb5_c_valid_enctype(etype))
61         return(KRB5_PROG_ETYPE_NOSUPP);
62
63     if ((ret = krb5_kt_get_entry(context, keytab, client,
64                                  0, /* don't have vno available */
65                                  etype, &kt_ent)))
66         return(ret);
67
68     ret = krb5_copy_keyblock(context, &kt_ent.key, &kt_key);
69
70     /* again, krb5's memory management is lame... */
71
72     *as_key = *kt_key;
73     free(kt_key);
74
75     (void) krb5_kt_free_entry(context, &kt_ent);
76
77     return(ret);
78 }
79
80 /* Return the list of etypes available for client in keytab. */
81 static krb5_error_code
82 lookup_etypes_for_keytab(krb5_context context, krb5_keytab keytab,
83                          krb5_principal client, krb5_enctype **etypes_out)
84 {
85     krb5_kt_cursor cursor;
86     krb5_keytab_entry entry;
87     krb5_enctype *p, *etypes = NULL;
88     krb5_kvno max_kvno = 0;
89     krb5_error_code ret;
90     size_t count = 0;
91
92     *etypes_out = NULL;
93
94     if (keytab->ops->start_seq_get == NULL)
95         return EINVAL;
96     ret = krb5_kt_start_seq_get(context, keytab, &cursor);
97     if (ret != 0)
98         return ret;
99
100     for (;;) {
101         ret = krb5_kt_next_entry(context, keytab, &entry, &cursor);
102         if (ret == KRB5_KT_END)
103             break;
104         if (ret)
105             goto cleanup;
106
107         if (!krb5_c_valid_enctype(entry.key.enctype))
108             continue;
109         if (!krb5_principal_compare(context, entry.principal, client))
110             continue;
111         /* Make sure our list is for the highest kvno found for client. */
112         if (entry.vno > max_kvno) {
113             free(etypes);
114             etypes = NULL;
115             count = 0;
116             max_kvno = entry.vno;
117         } else if (entry.vno != max_kvno)
118             continue;
119
120         /* Leave room for the terminator and possibly a second entry. */
121         p = realloc(etypes, (count + 3) * sizeof(*etypes));
122         if (p == NULL) {
123             ret = ENOMEM;
124             goto cleanup;
125         }
126         etypes = p;
127         etypes[count++] = entry.key.enctype;
128         /* All DES key types work with des-cbc-crc, which is more likely to be
129          * accepted by the KDC (since MIT KDCs refuse des-cbc-md5). */
130         if (entry.key.enctype == ENCTYPE_DES_CBC_MD5 ||
131             entry.key.enctype == ENCTYPE_DES_CBC_MD4)
132             etypes[count++] = ENCTYPE_DES_CBC_CRC;
133         etypes[count] = 0;
134     }
135
136     ret = 0;
137     *etypes_out = etypes;
138     etypes = NULL;
139 cleanup:
140     krb5_kt_end_seq_get(context, keytab, &cursor);
141     free(etypes);
142     return ret;
143 }
144
145 /* Return true if search_for is in etype_list. */
146 static krb5_boolean
147 check_etypes_have(krb5_enctype *etype_list, krb5_enctype search_for)
148 {
149     int i;
150
151     if (!etype_list)
152         return FALSE;
153
154     for (i = 0; etype_list[i] != 0; i++) {
155         if (etype_list[i] == search_for)
156             return TRUE;
157     }
158
159     return FALSE;
160 }
161
162 krb5_error_code KRB5_CALLCONV
163 krb5_init_creds_set_keytab(krb5_context context,
164                            krb5_init_creds_context ctx,
165                            krb5_keytab keytab)
166 {
167     krb5_enctype *etype_list;
168     krb5_error_code ret;
169     int i, j;
170     char *name;
171
172     ctx->gak_fct = get_as_key_keytab;
173     ctx->gak_data = keytab;
174
175     ret = lookup_etypes_for_keytab(context, keytab, ctx->request->client,
176                                    &etype_list);
177     if (ret) {
178         TRACE_INIT_CREDS_KEYTAB_LOOKUP_FAILED(context, ret);
179         return 0;
180     }
181
182     TRACE_INIT_CREDS_KEYTAB_LOOKUP(context, etype_list);
183
184     /* Filter the ktypes list based on what's in the keytab */
185     for (i = 0, j = 0; i < ctx->request->nktypes; i++) {
186         if (check_etypes_have(etype_list, ctx->request->ktype[i])) {
187             ctx->request->ktype[j] = ctx->request->ktype[i];
188             j++;
189         }
190     }
191     ctx->request->nktypes = j;
192     free(etype_list);
193
194     /* Error out now if there's no overlap. */
195     if (ctx->request->nktypes == 0) {
196         ret = krb5_unparse_name(context, ctx->request->client, &name);
197         if (ret == 0) {
198             krb5_set_error_message(context, KRB5_KT_NOTFOUND,
199                                    _("Keytab contains no suitable keys for "
200                                      "%s"), name);
201         }
202         krb5_free_unparsed_name(context, name);
203         return KRB5_KT_NOTFOUND;
204     }
205
206     return 0;
207 }
208
209 static krb5_error_code
210 get_init_creds_keytab(krb5_context context, krb5_creds *creds,
211                       krb5_principal client, krb5_keytab keytab,
212                       krb5_deltat start_time, char *in_tkt_service,
213                       krb5_get_init_creds_opt *options, int *use_master)
214 {
215     krb5_error_code ret;
216     krb5_init_creds_context ctx = NULL;
217
218     ret = krb5_init_creds_init(context, client, NULL, NULL, start_time,
219                                options, &ctx);
220     if (ret != 0)
221         goto cleanup;
222
223     if (in_tkt_service) {
224         ret = krb5_init_creds_set_service(context, ctx, in_tkt_service);
225         if (ret != 0)
226             goto cleanup;
227     }
228
229     ret = krb5_init_creds_set_keytab(context, ctx, keytab);
230     if (ret != 0)
231         goto cleanup;
232
233     ret = k5_init_creds_get(context, ctx, use_master);
234     if (ret != 0)
235         goto cleanup;
236
237     ret = krb5_init_creds_get_creds(context, ctx, creds);
238     if (ret != 0)
239         goto cleanup;
240
241 cleanup:
242     krb5_init_creds_free(context, ctx);
243
244     return ret;
245 }
246
247 krb5_error_code KRB5_CALLCONV
248 krb5_get_init_creds_keytab(krb5_context context,
249                            krb5_creds *creds,
250                            krb5_principal client,
251                            krb5_keytab arg_keytab,
252                            krb5_deltat start_time,
253                            char *in_tkt_service,
254                            krb5_get_init_creds_opt *options)
255 {
256     krb5_error_code ret, ret2;
257     int use_master;
258     krb5_keytab keytab;
259
260     if (arg_keytab == NULL) {
261         if ((ret = krb5_kt_default(context, &keytab)))
262             return ret;
263     } else {
264         keytab = arg_keytab;
265     }
266
267     use_master = 0;
268
269     /* first try: get the requested tkt from any kdc */
270
271     ret = get_init_creds_keytab(context, creds, client, keytab, start_time,
272                                 in_tkt_service, options, &use_master);
273
274     /* check for success */
275
276     if (ret == 0)
277         goto cleanup;
278
279     /* If all the kdc's are unavailable fail */
280
281     if ((ret == KRB5_KDC_UNREACH) || (ret == KRB5_REALM_CANT_RESOLVE))
282         goto cleanup;
283
284     /* if the reply did not come from the master kdc, try again with
285        the master kdc */
286
287     if (!use_master) {
288         use_master = 1;
289
290         ret2 = get_init_creds_keytab(context, creds, client, keytab,
291                                      start_time, in_tkt_service, options,
292                                      &use_master);
293
294         if (ret2 == 0) {
295             ret = 0;
296             goto cleanup;
297         }
298
299         /* if the master is unreachable, return the error from the
300            slave we were able to contact */
301
302         if ((ret2 == KRB5_KDC_UNREACH) ||
303             (ret2 == KRB5_REALM_CANT_RESOLVE) ||
304             (ret2 == KRB5_REALM_UNKNOWN))
305             goto cleanup;
306
307         ret = ret2;
308     }
309
310     /* at this point, we have a response from the master.  Since we don't
311        do any prompting or changing for keytabs, that's it. */
312
313 cleanup:
314     if (arg_keytab == NULL)
315         krb5_kt_close(context, keytab);
316
317     return(ret);
318 }
319 krb5_error_code KRB5_CALLCONV
320 krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
321                             krb5_address *const *addrs, krb5_enctype *ktypes,
322                             krb5_preauthtype *pre_auth_types,
323                             krb5_keytab arg_keytab, krb5_ccache ccache,
324                             krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
325 {
326     krb5_error_code retval;
327     krb5_get_init_creds_opt *opts;
328     char * server = NULL;
329     krb5_keytab keytab;
330     krb5_principal client_princ, server_princ;
331     int use_master = 0;
332
333     retval = krb5int_populate_gic_opt(context, &opts,
334                                       options, addrs, ktypes,
335                                       pre_auth_types, creds);
336     if (retval)
337         return retval;
338
339     if (arg_keytab == NULL) {
340         retval = krb5_kt_default(context, &keytab);
341         if (retval)
342             goto cleanup;
343     }
344     else keytab = arg_keytab;
345
346     retval = krb5_unparse_name( context, creds->server, &server);
347     if (retval)
348         goto cleanup;
349     server_princ = creds->server;
350     client_princ = creds->client;
351     retval = krb5int_get_init_creds(context, creds, creds->client,
352                                     krb5_prompter_posix,  NULL,
353                                     0, server, opts,
354                                     get_as_key_keytab, (void *)keytab,
355                                     &use_master, ret_as_reply);
356     krb5_free_unparsed_name( context, server);
357     if (retval) {
358         goto cleanup;
359     }
360     krb5_free_principal(context, creds->server);
361     krb5_free_principal(context, creds->client);
362     creds->client = client_princ;
363     creds->server = server_princ;
364
365     /* store it in the ccache! */
366     if (ccache)
367         if ((retval = krb5_cc_store_cred(context, ccache, creds)))
368             goto cleanup;
369 cleanup:
370     krb5_get_init_creds_opt_free(context, opts);
371     if (arg_keytab == NULL)
372         krb5_kt_close(context, keytab);
373     return retval;
374 }
375
376 #endif /* LEAN_CLIENT */