1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/ccselect.c - krb5_cc_select API and module loader */
3 /*
4 * Copyright (C) 2011 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
27 #include "k5-int.h"
28 #include "cc-int.h"
29 #include <krb5/ccselect_plugin.h>
30 #include "../krb/int-proto.h"
31
32 struct ccselect_module_handle {
33 struct krb5_ccselect_vtable_st vt;
34 krb5_ccselect_moddata data;
35 int priority;
36 };
37
38 static void
free_handles(krb5_context context,struct ccselect_module_handle ** handles)39 free_handles(krb5_context context, struct ccselect_module_handle **handles)
40 {
41 struct ccselect_module_handle *h, **hp;
42
43 if (handles == NULL)
44 return;
45 for (hp = handles; *hp != NULL; hp++) {
46 h = *hp;
47 if (h->vt.fini)
48 h->vt.fini(context, h->data);
49 free(h);
50 }
51 free(handles);
52 }
53
54 static krb5_error_code
load_modules(krb5_context context)55 load_modules(krb5_context context)
56 {
57 krb5_error_code ret;
58 struct ccselect_module_handle **list = NULL, *handle;
59 krb5_plugin_initvt_fn *modules = NULL, *mod;
60 size_t count;
61
62 #ifndef _WIN32
63 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "k5identity",
64 ccselect_k5identity_initvt);
65 if (ret != 0)
66 goto cleanup;
67 #endif
68
69 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "realm",
70 ccselect_realm_initvt);
71 if (ret != 0)
72 goto cleanup;
73
74 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "hostname",
75 ccselect_hostname_initvt);
76 if (ret != 0)
77 goto cleanup;
78
79 ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CCSELECT, &modules);
80 if (ret != 0)
81 goto cleanup;
82
83 /* Allocate a large enough list of handles. */
84 for (count = 0; modules[count] != NULL; count++);
85 list = k5calloc(count + 1, sizeof(*list), &ret);
86 if (list == NULL)
87 goto cleanup;
88
89 /* Initialize each module, ignoring ones that fail. */
90 count = 0;
91 for (mod = modules; *mod != NULL; mod++) {
92 handle = k5alloc(sizeof(*handle), &ret);
93 if (handle == NULL)
94 goto cleanup;
95 ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
96 if (ret != 0) { /* Failed vtable init is non-fatal. */
97 TRACE_CCSELECT_VTINIT_FAIL(context, ret);
98 free(handle);
99 continue;
100 }
101 handle->data = NULL;
102 ret = handle->vt.init(context, &handle->data, &handle->priority);
103 if (ret != 0) { /* Failed initialization is non-fatal. */
104 TRACE_CCSELECT_INIT_FAIL(context, handle->vt.name, ret);
105 free(handle);
106 continue;
107 }
108 list[count++] = handle;
109 list[count] = NULL;
110 }
111 list[count] = NULL;
112
113 ret = 0;
114 context->ccselect_handles = list;
115 list = NULL;
116
117 cleanup:
118 k5_plugin_free_modules(context, modules);
119 free_handles(context, list);
120 return ret;
121 }
122
123 krb5_error_code KRB5_CALLCONV
krb5_cc_select(krb5_context context,krb5_principal server,krb5_ccache * cache_out,krb5_principal * princ_out)124 krb5_cc_select(krb5_context context, krb5_principal server,
125 krb5_ccache *cache_out, krb5_principal *princ_out)
126 {
127 krb5_error_code ret;
128 int priority;
129 struct ccselect_module_handle **hp, *h;
130 krb5_ccache cache;
131 krb5_principal princ;
132 krb5_principal srvcp = NULL;
133 char **fbrealms = NULL;
134
135 *cache_out = NULL;
136 *princ_out = NULL;
137
138 if (context->ccselect_handles == NULL) {
139 ret = load_modules(context);
140 if (ret)
141 goto cleanup;
142 }
143
144 /* Try to use the fallback host realm for the server if there is no
145 * authoritative realm. */
146 if (krb5_is_referral_realm(&server->realm) &&
147 server->type == KRB5_NT_SRV_HST && server->length == 2) {
148 ret = krb5_get_fallback_host_realm(context, &server->data[1],
149 &fbrealms);
150 /* Continue without realm if we failed due to no default realm. */
151 if (ret && ret != KRB5_CONFIG_NODEFREALM)
152 goto cleanup;
153 if (!ret) {
154 /* Make a copy with the first fallback realm. */
155 ret = krb5_copy_principal(context, server, &srvcp);
156 if (ret)
157 goto cleanup;
158 ret = krb5_set_principal_realm(context, srvcp, fbrealms[0]);
159 if (ret)
160 goto cleanup;
161 server = srvcp;
162 }
163 }
164
165 /* Consult authoritative modules first, then heuristic ones. */
166 for (priority = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE;
167 priority >= KRB5_CCSELECT_PRIORITY_HEURISTIC; priority--) {
168 for (hp = context->ccselect_handles; *hp != NULL; hp++) {
169 h = *hp;
170 if (h->priority != priority)
171 continue;
172 ret = h->vt.choose(context, h->data, server, &cache, &princ);
173 if (ret == 0) {
174 TRACE_CCSELECT_MODCHOICE(context, h->vt.name, server, cache,
175 princ);
176 *cache_out = cache;
177 *princ_out = princ;
178 goto cleanup;
179 } else if (ret == KRB5_CC_NOTFOUND) {
180 TRACE_CCSELECT_MODNOTFOUND(context, h->vt.name, server, princ);
181 *princ_out = princ;
182 goto cleanup;
183 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
184 TRACE_CCSELECT_MODFAIL(context, h->vt.name, ret, server);
185 goto cleanup;
186 }
187 }
188 }
189
190 TRACE_CCSELECT_NOTFOUND(context, server);
191 ret = KRB5_CC_NOTFOUND;
192
193 cleanup:
194 krb5_free_principal(context, srvcp);
195 krb5_free_host_realm(context, fbrealms);
196 return ret;
197 }
198
199 void
k5_ccselect_free_context(krb5_context context)200 k5_ccselect_free_context(krb5_context context)
201 {
202 free_handles(context, context->ccselect_handles);
203 context->ccselect_handles = NULL;
204 }
205