Unify krb5_get_init_creds_keytab code paths
[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 krb5_error_code KRB5_CALLCONV
81 krb5_init_creds_set_keytab(krb5_context context,
82                            krb5_init_creds_context ctx,
83                            krb5_keytab keytab)
84 {
85     ctx->gak_fct = get_as_key_keytab;
86     ctx->gak_data = keytab;
87
88     return 0;
89 }
90
91 static krb5_error_code
92 get_init_creds_keytab(krb5_context context, krb5_creds *creds,
93                       krb5_principal client, krb5_keytab keytab,
94                       krb5_deltat start_time, char *in_tkt_service,
95                       krb5_get_init_creds_opt *options, int *use_master)
96 {
97     krb5_error_code ret;
98     krb5_init_creds_context ctx = NULL;
99
100     ret = krb5_init_creds_init(context, client, NULL, NULL, start_time,
101                                options, &ctx);
102     if (ret != 0)
103         goto cleanup;
104
105     if (in_tkt_service) {
106         ret = krb5_init_creds_set_service(context, ctx, in_tkt_service);
107         if (ret != 0)
108             goto cleanup;
109     }
110
111     ret = krb5_init_creds_set_keytab(context, ctx, keytab);
112     if (ret != 0)
113         goto cleanup;
114
115     ret = k5_init_creds_get(context, ctx, use_master);
116     if (ret != 0)
117         goto cleanup;
118
119     ret = krb5_init_creds_get_creds(context, ctx, creds);
120     if (ret != 0)
121         goto cleanup;
122
123 cleanup:
124     krb5_init_creds_free(context, ctx);
125
126     return ret;
127 }
128
129 krb5_error_code KRB5_CALLCONV
130 krb5_get_init_creds_keytab(krb5_context context,
131                            krb5_creds *creds,
132                            krb5_principal client,
133                            krb5_keytab arg_keytab,
134                            krb5_deltat start_time,
135                            char *in_tkt_service,
136                            krb5_get_init_creds_opt *options)
137 {
138     krb5_error_code ret, ret2;
139     int use_master;
140     krb5_keytab keytab;
141
142     if (arg_keytab == NULL) {
143         if ((ret = krb5_kt_default(context, &keytab)))
144             return ret;
145     } else {
146         keytab = arg_keytab;
147     }
148
149     use_master = 0;
150
151     /* first try: get the requested tkt from any kdc */
152
153     ret = get_init_creds_keytab(context, creds, client, keytab, start_time,
154                                 in_tkt_service, options, &use_master);
155
156     /* check for success */
157
158     if (ret == 0)
159         goto cleanup;
160
161     /* If all the kdc's are unavailable fail */
162
163     if ((ret == KRB5_KDC_UNREACH) || (ret == KRB5_REALM_CANT_RESOLVE))
164         goto cleanup;
165
166     /* if the reply did not come from the master kdc, try again with
167        the master kdc */
168
169     if (!use_master) {
170         use_master = 1;
171
172         ret2 = get_init_creds_keytab(context, creds, client, keytab,
173                                      start_time, in_tkt_service, options,
174                                      &use_master);
175
176         if (ret2 == 0) {
177             ret = 0;
178             goto cleanup;
179         }
180
181         /* if the master is unreachable, return the error from the
182            slave we were able to contact */
183
184         if ((ret2 == KRB5_KDC_UNREACH) ||
185             (ret2 == KRB5_REALM_CANT_RESOLVE) ||
186             (ret2 == KRB5_REALM_UNKNOWN))
187             goto cleanup;
188
189         ret = ret2;
190     }
191
192     /* at this point, we have a response from the master.  Since we don't
193        do any prompting or changing for keytabs, that's it. */
194
195 cleanup:
196     if (arg_keytab == NULL)
197         krb5_kt_close(context, keytab);
198
199     return(ret);
200 }
201 krb5_error_code KRB5_CALLCONV
202 krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
203                             krb5_address *const *addrs, krb5_enctype *ktypes,
204                             krb5_preauthtype *pre_auth_types,
205                             krb5_keytab arg_keytab, krb5_ccache ccache,
206                             krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
207 {
208     krb5_error_code retval;
209     krb5_get_init_creds_opt *opts;
210     char * server = NULL;
211     krb5_keytab keytab;
212     krb5_principal client_princ, server_princ;
213     int use_master = 0;
214
215     retval = krb5int_populate_gic_opt(context, &opts,
216                                       options, addrs, ktypes,
217                                       pre_auth_types, creds);
218     if (retval)
219         return retval;
220
221     if (arg_keytab == NULL) {
222         retval = krb5_kt_default(context, &keytab);
223         if (retval)
224             goto cleanup;
225     }
226     else keytab = arg_keytab;
227
228     retval = krb5_unparse_name( context, creds->server, &server);
229     if (retval)
230         goto cleanup;
231     server_princ = creds->server;
232     client_princ = creds->client;
233     retval = krb5int_get_init_creds(context, creds, creds->client,
234                                     krb5_prompter_posix,  NULL,
235                                     0, server, opts,
236                                     get_as_key_keytab, (void *)keytab,
237                                     &use_master, ret_as_reply);
238     krb5_free_unparsed_name( context, server);
239     if (retval) {
240         goto cleanup;
241     }
242     krb5_free_principal(context, creds->server);
243     krb5_free_principal(context, creds->client);
244     creds->client = client_princ;
245     creds->server = server_princ;
246
247     /* store it in the ccache! */
248     if (ccache)
249         if ((retval = krb5_cc_store_cred(context, ccache, creds)))
250             goto cleanup;
251 cleanup:
252     krb5_get_init_creds_opt_free(context, opts);
253     if (arg_keytab == NULL)
254         krb5_kt_close(context, keytab);
255     return retval;
256 }
257
258 #endif /* LEAN_CLIENT */