1 | // SPDX-License-Identifier: BSD-3-Clause |
2 | /* |
3 | * linux/net/sunrpc/gss_mech_switch.c |
4 | * |
5 | * Copyright (c) 2001 The Regents of the University of Michigan. |
6 | * All rights reserved. |
7 | * |
8 | * J. Bruce Fields <bfields@umich.edu> |
9 | */ |
10 | |
11 | #include <linux/types.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/module.h> |
14 | #include <linux/oid_registry.h> |
15 | #include <linux/sunrpc/msg_prot.h> |
16 | #include <linux/sunrpc/gss_asn1.h> |
17 | #include <linux/sunrpc/auth_gss.h> |
18 | #include <linux/sunrpc/svcauth_gss.h> |
19 | #include <linux/sunrpc/gss_err.h> |
20 | #include <linux/sunrpc/sched.h> |
21 | #include <linux/sunrpc/gss_api.h> |
22 | #include <linux/sunrpc/clnt.h> |
23 | #include <trace/events/rpcgss.h> |
24 | |
25 | #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) |
26 | # define RPCDBG_FACILITY RPCDBG_AUTH |
27 | #endif |
28 | |
29 | static LIST_HEAD(registered_mechs); |
30 | static DEFINE_SPINLOCK(registered_mechs_lock); |
31 | |
32 | static void |
33 | gss_mech_free(struct gss_api_mech *gm) |
34 | { |
35 | struct pf_desc *pf; |
36 | int i; |
37 | |
38 | for (i = 0; i < gm->gm_pf_num; i++) { |
39 | pf = &gm->gm_pfs[i]; |
40 | if (pf->domain) |
41 | auth_domain_put(item: pf->domain); |
42 | kfree(objp: pf->auth_domain_name); |
43 | pf->auth_domain_name = NULL; |
44 | } |
45 | } |
46 | |
47 | static inline char * |
48 | make_auth_domain_name(char *name) |
49 | { |
50 | static char *prefix = "gss/" ; |
51 | char *new; |
52 | |
53 | new = kmalloc(strlen(name) + strlen(prefix) + 1, GFP_KERNEL); |
54 | if (new) { |
55 | strcpy(p: new, q: prefix); |
56 | strcat(p: new, q: name); |
57 | } |
58 | return new; |
59 | } |
60 | |
61 | static int |
62 | gss_mech_svc_setup(struct gss_api_mech *gm) |
63 | { |
64 | struct auth_domain *dom; |
65 | struct pf_desc *pf; |
66 | int i, status; |
67 | |
68 | for (i = 0; i < gm->gm_pf_num; i++) { |
69 | pf = &gm->gm_pfs[i]; |
70 | pf->auth_domain_name = make_auth_domain_name(name: pf->name); |
71 | status = -ENOMEM; |
72 | if (pf->auth_domain_name == NULL) |
73 | goto out; |
74 | dom = svcauth_gss_register_pseudoflavor( |
75 | pseudoflavor: pf->pseudoflavor, name: pf->auth_domain_name); |
76 | if (IS_ERR(ptr: dom)) { |
77 | status = PTR_ERR(ptr: dom); |
78 | goto out; |
79 | } |
80 | pf->domain = dom; |
81 | } |
82 | return 0; |
83 | out: |
84 | gss_mech_free(gm); |
85 | return status; |
86 | } |
87 | |
88 | /** |
89 | * gss_mech_register - register a GSS mechanism |
90 | * @gm: GSS mechanism handle |
91 | * |
92 | * Returns zero if successful, or a negative errno. |
93 | */ |
94 | int gss_mech_register(struct gss_api_mech *gm) |
95 | { |
96 | int status; |
97 | |
98 | status = gss_mech_svc_setup(gm); |
99 | if (status) |
100 | return status; |
101 | spin_lock(lock: ®istered_mechs_lock); |
102 | list_add_rcu(new: &gm->gm_list, head: ®istered_mechs); |
103 | spin_unlock(lock: ®istered_mechs_lock); |
104 | dprintk("RPC: registered gss mechanism %s\n" , gm->gm_name); |
105 | return 0; |
106 | } |
107 | EXPORT_SYMBOL_GPL(gss_mech_register); |
108 | |
109 | /** |
110 | * gss_mech_unregister - release a GSS mechanism |
111 | * @gm: GSS mechanism handle |
112 | * |
113 | */ |
114 | void gss_mech_unregister(struct gss_api_mech *gm) |
115 | { |
116 | spin_lock(lock: ®istered_mechs_lock); |
117 | list_del_rcu(entry: &gm->gm_list); |
118 | spin_unlock(lock: ®istered_mechs_lock); |
119 | dprintk("RPC: unregistered gss mechanism %s\n" , gm->gm_name); |
120 | gss_mech_free(gm); |
121 | } |
122 | EXPORT_SYMBOL_GPL(gss_mech_unregister); |
123 | |
124 | struct gss_api_mech *gss_mech_get(struct gss_api_mech *gm) |
125 | { |
126 | __module_get(module: gm->gm_owner); |
127 | return gm; |
128 | } |
129 | EXPORT_SYMBOL(gss_mech_get); |
130 | |
131 | static struct gss_api_mech * |
132 | _gss_mech_get_by_name(const char *name) |
133 | { |
134 | struct gss_api_mech *pos, *gm = NULL; |
135 | |
136 | rcu_read_lock(); |
137 | list_for_each_entry_rcu(pos, ®istered_mechs, gm_list) { |
138 | if (0 == strcmp(name, pos->gm_name)) { |
139 | if (try_module_get(module: pos->gm_owner)) |
140 | gm = pos; |
141 | break; |
142 | } |
143 | } |
144 | rcu_read_unlock(); |
145 | return gm; |
146 | |
147 | } |
148 | |
149 | struct gss_api_mech * gss_mech_get_by_name(const char *name) |
150 | { |
151 | struct gss_api_mech *gm = NULL; |
152 | |
153 | gm = _gss_mech_get_by_name(name); |
154 | if (!gm) { |
155 | request_module("rpc-auth-gss-%s" , name); |
156 | gm = _gss_mech_get_by_name(name); |
157 | } |
158 | return gm; |
159 | } |
160 | |
161 | struct gss_api_mech *gss_mech_get_by_OID(struct rpcsec_gss_oid *obj) |
162 | { |
163 | struct gss_api_mech *pos, *gm = NULL; |
164 | char buf[32]; |
165 | |
166 | if (sprint_oid(obj->data, obj->len, buf, sizeof(buf)) < 0) |
167 | return NULL; |
168 | request_module("rpc-auth-gss-%s" , buf); |
169 | |
170 | rcu_read_lock(); |
171 | list_for_each_entry_rcu(pos, ®istered_mechs, gm_list) { |
172 | if (obj->len == pos->gm_oid.len) { |
173 | if (0 == memcmp(p: obj->data, q: pos->gm_oid.data, size: obj->len)) { |
174 | if (try_module_get(module: pos->gm_owner)) |
175 | gm = pos; |
176 | break; |
177 | } |
178 | } |
179 | } |
180 | rcu_read_unlock(); |
181 | if (!gm) |
182 | trace_rpcgss_oid_to_mech(oid: buf); |
183 | return gm; |
184 | } |
185 | |
186 | static inline int |
187 | mech_supports_pseudoflavor(struct gss_api_mech *gm, u32 pseudoflavor) |
188 | { |
189 | int i; |
190 | |
191 | for (i = 0; i < gm->gm_pf_num; i++) { |
192 | if (gm->gm_pfs[i].pseudoflavor == pseudoflavor) |
193 | return 1; |
194 | } |
195 | return 0; |
196 | } |
197 | |
198 | static struct gss_api_mech *_gss_mech_get_by_pseudoflavor(u32 pseudoflavor) |
199 | { |
200 | struct gss_api_mech *gm = NULL, *pos; |
201 | |
202 | rcu_read_lock(); |
203 | list_for_each_entry_rcu(pos, ®istered_mechs, gm_list) { |
204 | if (!mech_supports_pseudoflavor(gm: pos, pseudoflavor)) |
205 | continue; |
206 | if (try_module_get(module: pos->gm_owner)) |
207 | gm = pos; |
208 | break; |
209 | } |
210 | rcu_read_unlock(); |
211 | return gm; |
212 | } |
213 | |
214 | struct gss_api_mech * |
215 | gss_mech_get_by_pseudoflavor(u32 pseudoflavor) |
216 | { |
217 | struct gss_api_mech *gm; |
218 | |
219 | gm = _gss_mech_get_by_pseudoflavor(pseudoflavor); |
220 | |
221 | if (!gm) { |
222 | request_module("rpc-auth-gss-%u" , pseudoflavor); |
223 | gm = _gss_mech_get_by_pseudoflavor(pseudoflavor); |
224 | } |
225 | return gm; |
226 | } |
227 | |
228 | /** |
229 | * gss_svc_to_pseudoflavor - map a GSS service number to a pseudoflavor |
230 | * @gm: GSS mechanism handle |
231 | * @qop: GSS quality-of-protection value |
232 | * @service: GSS service value |
233 | * |
234 | * Returns a matching security flavor, or RPC_AUTH_MAXFLAVOR if none is found. |
235 | */ |
236 | rpc_authflavor_t gss_svc_to_pseudoflavor(struct gss_api_mech *gm, u32 qop, |
237 | u32 service) |
238 | { |
239 | int i; |
240 | |
241 | for (i = 0; i < gm->gm_pf_num; i++) { |
242 | if (gm->gm_pfs[i].qop == qop && |
243 | gm->gm_pfs[i].service == service) { |
244 | return gm->gm_pfs[i].pseudoflavor; |
245 | } |
246 | } |
247 | return RPC_AUTH_MAXFLAVOR; |
248 | } |
249 | |
250 | /** |
251 | * gss_mech_info2flavor - look up a pseudoflavor given a GSS tuple |
252 | * @info: a GSS mech OID, quality of protection, and service value |
253 | * |
254 | * Returns a matching pseudoflavor, or RPC_AUTH_MAXFLAVOR if the tuple is |
255 | * not supported. |
256 | */ |
257 | rpc_authflavor_t gss_mech_info2flavor(struct rpcsec_gss_info *info) |
258 | { |
259 | rpc_authflavor_t pseudoflavor; |
260 | struct gss_api_mech *gm; |
261 | |
262 | gm = gss_mech_get_by_OID(obj: &info->oid); |
263 | if (gm == NULL) |
264 | return RPC_AUTH_MAXFLAVOR; |
265 | |
266 | pseudoflavor = gss_svc_to_pseudoflavor(gm, qop: info->qop, service: info->service); |
267 | |
268 | gss_mech_put(gm); |
269 | return pseudoflavor; |
270 | } |
271 | |
272 | /** |
273 | * gss_mech_flavor2info - look up a GSS tuple for a given pseudoflavor |
274 | * @pseudoflavor: GSS pseudoflavor to match |
275 | * @info: rpcsec_gss_info structure to fill in |
276 | * |
277 | * Returns zero and fills in "info" if pseudoflavor matches a |
278 | * supported mechanism. Otherwise a negative errno is returned. |
279 | */ |
280 | int gss_mech_flavor2info(rpc_authflavor_t pseudoflavor, |
281 | struct rpcsec_gss_info *info) |
282 | { |
283 | struct gss_api_mech *gm; |
284 | int i; |
285 | |
286 | gm = gss_mech_get_by_pseudoflavor(pseudoflavor); |
287 | if (gm == NULL) |
288 | return -ENOENT; |
289 | |
290 | for (i = 0; i < gm->gm_pf_num; i++) { |
291 | if (gm->gm_pfs[i].pseudoflavor == pseudoflavor) { |
292 | memcpy(info->oid.data, gm->gm_oid.data, gm->gm_oid.len); |
293 | info->oid.len = gm->gm_oid.len; |
294 | info->qop = gm->gm_pfs[i].qop; |
295 | info->service = gm->gm_pfs[i].service; |
296 | gss_mech_put(gm); |
297 | return 0; |
298 | } |
299 | } |
300 | |
301 | gss_mech_put(gm); |
302 | return -ENOENT; |
303 | } |
304 | |
305 | u32 |
306 | gss_pseudoflavor_to_service(struct gss_api_mech *gm, u32 pseudoflavor) |
307 | { |
308 | int i; |
309 | |
310 | for (i = 0; i < gm->gm_pf_num; i++) { |
311 | if (gm->gm_pfs[i].pseudoflavor == pseudoflavor) |
312 | return gm->gm_pfs[i].service; |
313 | } |
314 | return 0; |
315 | } |
316 | EXPORT_SYMBOL(gss_pseudoflavor_to_service); |
317 | |
318 | bool |
319 | gss_pseudoflavor_to_datatouch(struct gss_api_mech *gm, u32 pseudoflavor) |
320 | { |
321 | int i; |
322 | |
323 | for (i = 0; i < gm->gm_pf_num; i++) { |
324 | if (gm->gm_pfs[i].pseudoflavor == pseudoflavor) |
325 | return gm->gm_pfs[i].datatouch; |
326 | } |
327 | return false; |
328 | } |
329 | |
330 | char * |
331 | gss_service_to_auth_domain_name(struct gss_api_mech *gm, u32 service) |
332 | { |
333 | int i; |
334 | |
335 | for (i = 0; i < gm->gm_pf_num; i++) { |
336 | if (gm->gm_pfs[i].service == service) |
337 | return gm->gm_pfs[i].auth_domain_name; |
338 | } |
339 | return NULL; |
340 | } |
341 | |
342 | void |
343 | gss_mech_put(struct gss_api_mech * gm) |
344 | { |
345 | if (gm) |
346 | module_put(module: gm->gm_owner); |
347 | } |
348 | EXPORT_SYMBOL(gss_mech_put); |
349 | |
350 | /* The mech could probably be determined from the token instead, but it's just |
351 | * as easy for now to pass it in. */ |
352 | int |
353 | gss_import_sec_context(const void *input_token, size_t bufsize, |
354 | struct gss_api_mech *mech, |
355 | struct gss_ctx **ctx_id, |
356 | time64_t *endtime, |
357 | gfp_t gfp_mask) |
358 | { |
359 | if (!(*ctx_id = kzalloc(size: sizeof(**ctx_id), flags: gfp_mask))) |
360 | return -ENOMEM; |
361 | (*ctx_id)->mech_type = gss_mech_get(mech); |
362 | |
363 | return mech->gm_ops->gss_import_sec_context(input_token, bufsize, |
364 | *ctx_id, endtime, gfp_mask); |
365 | } |
366 | |
367 | /* gss_get_mic: compute a mic over message and return mic_token. */ |
368 | |
369 | u32 |
370 | gss_get_mic(struct gss_ctx *context_handle, |
371 | struct xdr_buf *message, |
372 | struct xdr_netobj *mic_token) |
373 | { |
374 | return context_handle->mech_type->gm_ops |
375 | ->gss_get_mic(context_handle, |
376 | message, |
377 | mic_token); |
378 | } |
379 | |
380 | /* gss_verify_mic: check whether the provided mic_token verifies message. */ |
381 | |
382 | u32 |
383 | gss_verify_mic(struct gss_ctx *context_handle, |
384 | struct xdr_buf *message, |
385 | struct xdr_netobj *mic_token) |
386 | { |
387 | return context_handle->mech_type->gm_ops |
388 | ->gss_verify_mic(context_handle, |
389 | message, |
390 | mic_token); |
391 | } |
392 | |
393 | /* |
394 | * This function is called from both the client and server code. |
395 | * Each makes guarantees about how much "slack" space is available |
396 | * for the underlying function in "buf"'s head and tail while |
397 | * performing the wrap. |
398 | * |
399 | * The client and server code allocate RPC_MAX_AUTH_SIZE extra |
400 | * space in both the head and tail which is available for use by |
401 | * the wrap function. |
402 | * |
403 | * Underlying functions should verify they do not use more than |
404 | * RPC_MAX_AUTH_SIZE of extra space in either the head or tail |
405 | * when performing the wrap. |
406 | */ |
407 | u32 |
408 | gss_wrap(struct gss_ctx *ctx_id, |
409 | int offset, |
410 | struct xdr_buf *buf, |
411 | struct page **inpages) |
412 | { |
413 | return ctx_id->mech_type->gm_ops |
414 | ->gss_wrap(ctx_id, offset, buf, inpages); |
415 | } |
416 | |
417 | u32 |
418 | gss_unwrap(struct gss_ctx *ctx_id, |
419 | int offset, |
420 | int len, |
421 | struct xdr_buf *buf) |
422 | { |
423 | return ctx_id->mech_type->gm_ops |
424 | ->gss_unwrap(ctx_id, offset, len, buf); |
425 | } |
426 | |
427 | |
428 | /* gss_delete_sec_context: free all resources associated with context_handle. |
429 | * Note this differs from the RFC 2744-specified prototype in that we don't |
430 | * bother returning an output token, since it would never be used anyway. */ |
431 | |
432 | u32 |
433 | gss_delete_sec_context(struct gss_ctx **context_handle) |
434 | { |
435 | dprintk("RPC: gss_delete_sec_context deleting %p\n" , |
436 | *context_handle); |
437 | |
438 | if (!*context_handle) |
439 | return GSS_S_NO_CONTEXT; |
440 | if ((*context_handle)->internal_ctx_id) |
441 | (*context_handle)->mech_type->gm_ops |
442 | ->gss_delete_sec_context((*context_handle) |
443 | ->internal_ctx_id); |
444 | gss_mech_put((*context_handle)->mech_type); |
445 | kfree(objp: *context_handle); |
446 | *context_handle=NULL; |
447 | return GSS_S_COMPLETE; |
448 | } |
449 | |