]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Neil Brown <neilb@cse.unsw.edu.au> | |
3 | * J. Bruce Fields <bfields@umich.edu> | |
4 | * Andy Adamson <andros@umich.edu> | |
5 | * Dug Song <dugsong@monkey.org> | |
6 | * | |
7 | * RPCSEC_GSS server authentication. | |
8 | * This implements RPCSEC_GSS as defined in rfc2203 (rpcsec_gss) and rfc2078 | |
9 | * (gssapi) | |
10 | * | |
11 | * The RPCSEC_GSS involves three stages: | |
12 | * 1/ context creation | |
13 | * 2/ data exchange | |
14 | * 3/ context destruction | |
15 | * | |
16 | * Context creation is handled largely by upcalls to user-space. | |
17 | * In particular, GSS_Accept_sec_context is handled by an upcall | |
18 | * Data exchange is handled entirely within the kernel | |
19 | * In particular, GSS_GetMIC, GSS_VerifyMIC, GSS_Seal, GSS_Unseal are in-kernel. | |
20 | * Context destruction is handled in-kernel | |
21 | * GSS_Delete_sec_context is in-kernel | |
22 | * | |
23 | * Context creation is initiated by a RPCSEC_GSS_INIT request arriving. | |
24 | * The context handle and gss_token are used as a key into the rpcsec_init cache. | |
25 | * The content of this cache includes some of the outputs of GSS_Accept_sec_context, | |
26 | * being major_status, minor_status, context_handle, reply_token. | |
27 | * These are sent back to the client. | |
28 | * Sequence window management is handled by the kernel. The window size if currently | |
29 | * a compile time constant. | |
30 | * | |
31 | * When user-space is happy that a context is established, it places an entry | |
32 | * in the rpcsec_context cache. The key for this cache is the context_handle. | |
33 | * The content includes: | |
34 | * uid/gidlist - for determining access rights | |
35 | * mechanism type | |
36 | * mechanism specific information, such as a key | |
37 | * | |
38 | */ | |
39 | ||
40 | #include <linux/types.h> | |
41 | #include <linux/module.h> | |
42 | #include <linux/pagemap.h> | |
43 | ||
44 | #include <linux/sunrpc/auth_gss.h> | |
45 | #include <linux/sunrpc/svcauth.h> | |
46 | #include <linux/sunrpc/gss_err.h> | |
47 | #include <linux/sunrpc/svcauth.h> | |
48 | #include <linux/sunrpc/svcauth_gss.h> | |
49 | #include <linux/sunrpc/cache.h> | |
50 | ||
51 | #ifdef RPC_DEBUG | |
52 | # define RPCDBG_FACILITY RPCDBG_AUTH | |
53 | #endif | |
54 | ||
55 | /* The rpcsec_init cache is used for mapping RPCSEC_GSS_{,CONT_}INIT requests | |
56 | * into replies. | |
57 | * | |
58 | * Key is context handle (\x if empty) and gss_token. | |
59 | * Content is major_status minor_status (integers) context_handle, reply_token. | |
60 | * | |
61 | */ | |
62 | ||
63 | static int netobj_equal(struct xdr_netobj *a, struct xdr_netobj *b) | |
64 | { | |
65 | return a->len == b->len && 0 == memcmp(a->data, b->data, a->len); | |
66 | } | |
67 | ||
68 | #define RSI_HASHBITS 6 | |
69 | #define RSI_HASHMAX (1<<RSI_HASHBITS) | |
70 | #define RSI_HASHMASK (RSI_HASHMAX-1) | |
71 | ||
72 | struct rsi { | |
73 | struct cache_head h; | |
74 | struct xdr_netobj in_handle, in_token; | |
75 | struct xdr_netobj out_handle, out_token; | |
76 | int major_status, minor_status; | |
77 | }; | |
78 | ||
79 | static struct cache_head *rsi_table[RSI_HASHMAX]; | |
80 | static struct cache_detail rsi_cache; | |
81 | static struct rsi *rsi_lookup(struct rsi *item, int set); | |
82 | ||
83 | static void rsi_free(struct rsi *rsii) | |
84 | { | |
85 | kfree(rsii->in_handle.data); | |
86 | kfree(rsii->in_token.data); | |
87 | kfree(rsii->out_handle.data); | |
88 | kfree(rsii->out_token.data); | |
89 | } | |
90 | ||
91 | static void rsi_put(struct cache_head *item, struct cache_detail *cd) | |
92 | { | |
93 | struct rsi *rsii = container_of(item, struct rsi, h); | |
94 | if (cache_put(item, cd)) { | |
95 | rsi_free(rsii); | |
96 | kfree(rsii); | |
97 | } | |
98 | } | |
99 | ||
100 | static inline int rsi_hash(struct rsi *item) | |
101 | { | |
102 | return hash_mem(item->in_handle.data, item->in_handle.len, RSI_HASHBITS) | |
103 | ^ hash_mem(item->in_token.data, item->in_token.len, RSI_HASHBITS); | |
104 | } | |
105 | ||
106 | static inline int rsi_match(struct rsi *item, struct rsi *tmp) | |
107 | { | |
108 | return netobj_equal(&item->in_handle, &tmp->in_handle) | |
109 | && netobj_equal(&item->in_token, &tmp->in_token); | |
110 | } | |
111 | ||
112 | static int dup_to_netobj(struct xdr_netobj *dst, char *src, int len) | |
113 | { | |
114 | dst->len = len; | |
115 | dst->data = (len ? kmalloc(len, GFP_KERNEL) : NULL); | |
116 | if (dst->data) | |
117 | memcpy(dst->data, src, len); | |
118 | if (len && !dst->data) | |
119 | return -ENOMEM; | |
120 | return 0; | |
121 | } | |
122 | ||
123 | static inline int dup_netobj(struct xdr_netobj *dst, struct xdr_netobj *src) | |
124 | { | |
125 | return dup_to_netobj(dst, src->data, src->len); | |
126 | } | |
127 | ||
128 | static inline void rsi_init(struct rsi *new, struct rsi *item) | |
129 | { | |
130 | new->out_handle.data = NULL; | |
131 | new->out_handle.len = 0; | |
132 | new->out_token.data = NULL; | |
133 | new->out_token.len = 0; | |
134 | new->in_handle.len = item->in_handle.len; | |
135 | item->in_handle.len = 0; | |
136 | new->in_token.len = item->in_token.len; | |
137 | item->in_token.len = 0; | |
138 | new->in_handle.data = item->in_handle.data; | |
139 | item->in_handle.data = NULL; | |
140 | new->in_token.data = item->in_token.data; | |
141 | item->in_token.data = NULL; | |
142 | } | |
143 | ||
144 | static inline void rsi_update(struct rsi *new, struct rsi *item) | |
145 | { | |
146 | BUG_ON(new->out_handle.data || new->out_token.data); | |
147 | new->out_handle.len = item->out_handle.len; | |
148 | item->out_handle.len = 0; | |
149 | new->out_token.len = item->out_token.len; | |
150 | item->out_token.len = 0; | |
151 | new->out_handle.data = item->out_handle.data; | |
152 | item->out_handle.data = NULL; | |
153 | new->out_token.data = item->out_token.data; | |
154 | item->out_token.data = NULL; | |
155 | ||
156 | new->major_status = item->major_status; | |
157 | new->minor_status = item->minor_status; | |
158 | } | |
159 | ||
160 | static void rsi_request(struct cache_detail *cd, | |
161 | struct cache_head *h, | |
162 | char **bpp, int *blen) | |
163 | { | |
164 | struct rsi *rsii = container_of(h, struct rsi, h); | |
165 | ||
166 | qword_addhex(bpp, blen, rsii->in_handle.data, rsii->in_handle.len); | |
167 | qword_addhex(bpp, blen, rsii->in_token.data, rsii->in_token.len); | |
168 | (*bpp)[-1] = '\n'; | |
169 | } | |
170 | ||
171 | ||
172 | static int rsi_parse(struct cache_detail *cd, | |
173 | char *mesg, int mlen) | |
174 | { | |
175 | /* context token expiry major minor context token */ | |
176 | char *buf = mesg; | |
177 | char *ep; | |
178 | int len; | |
179 | struct rsi rsii, *rsip = NULL; | |
180 | time_t expiry; | |
181 | int status = -EINVAL; | |
182 | ||
183 | memset(&rsii, 0, sizeof(rsii)); | |
184 | /* handle */ | |
185 | len = qword_get(&mesg, buf, mlen); | |
186 | if (len < 0) | |
187 | goto out; | |
188 | status = -ENOMEM; | |
189 | if (dup_to_netobj(&rsii.in_handle, buf, len)) | |
190 | goto out; | |
191 | ||
192 | /* token */ | |
193 | len = qword_get(&mesg, buf, mlen); | |
194 | status = -EINVAL; | |
195 | if (len < 0) | |
196 | goto out; | |
197 | status = -ENOMEM; | |
198 | if (dup_to_netobj(&rsii.in_token, buf, len)) | |
199 | goto out; | |
200 | ||
201 | rsii.h.flags = 0; | |
202 | /* expiry */ | |
203 | expiry = get_expiry(&mesg); | |
204 | status = -EINVAL; | |
205 | if (expiry == 0) | |
206 | goto out; | |
207 | ||
208 | /* major/minor */ | |
209 | len = qword_get(&mesg, buf, mlen); | |
210 | if (len < 0) | |
211 | goto out; | |
212 | if (len == 0) { | |
213 | goto out; | |
214 | } else { | |
215 | rsii.major_status = simple_strtoul(buf, &ep, 10); | |
216 | if (*ep) | |
217 | goto out; | |
218 | len = qword_get(&mesg, buf, mlen); | |
219 | if (len <= 0) | |
220 | goto out; | |
221 | rsii.minor_status = simple_strtoul(buf, &ep, 10); | |
222 | if (*ep) | |
223 | goto out; | |
224 | ||
225 | /* out_handle */ | |
226 | len = qword_get(&mesg, buf, mlen); | |
227 | if (len < 0) | |
228 | goto out; | |
229 | status = -ENOMEM; | |
230 | if (dup_to_netobj(&rsii.out_handle, buf, len)) | |
231 | goto out; | |
232 | ||
233 | /* out_token */ | |
234 | len = qword_get(&mesg, buf, mlen); | |
235 | status = -EINVAL; | |
236 | if (len < 0) | |
237 | goto out; | |
238 | status = -ENOMEM; | |
239 | if (dup_to_netobj(&rsii.out_token, buf, len)) | |
240 | goto out; | |
241 | } | |
242 | rsii.h.expiry_time = expiry; | |
243 | rsip = rsi_lookup(&rsii, 1); | |
244 | status = 0; | |
245 | out: | |
246 | rsi_free(&rsii); | |
247 | if (rsip) | |
248 | rsi_put(&rsip->h, &rsi_cache); | |
249 | return status; | |
250 | } | |
251 | ||
252 | static struct cache_detail rsi_cache = { | |
253 | .hash_size = RSI_HASHMAX, | |
254 | .hash_table = rsi_table, | |
255 | .name = "auth.rpcsec.init", | |
256 | .cache_put = rsi_put, | |
257 | .cache_request = rsi_request, | |
258 | .cache_parse = rsi_parse, | |
259 | }; | |
260 | ||
261 | static DefineSimpleCacheLookup(rsi, 0) | |
262 | ||
263 | /* | |
264 | * The rpcsec_context cache is used to store a context that is | |
265 | * used in data exchange. | |
266 | * The key is a context handle. The content is: | |
267 | * uid, gidlist, mechanism, service-set, mech-specific-data | |
268 | */ | |
269 | ||
270 | #define RSC_HASHBITS 10 | |
271 | #define RSC_HASHMAX (1<<RSC_HASHBITS) | |
272 | #define RSC_HASHMASK (RSC_HASHMAX-1) | |
273 | ||
274 | #define GSS_SEQ_WIN 128 | |
275 | ||
276 | struct gss_svc_seq_data { | |
277 | /* highest seq number seen so far: */ | |
278 | int sd_max; | |
279 | /* for i such that sd_max-GSS_SEQ_WIN < i <= sd_max, the i-th bit of | |
280 | * sd_win is nonzero iff sequence number i has been seen already: */ | |
281 | unsigned long sd_win[GSS_SEQ_WIN/BITS_PER_LONG]; | |
282 | spinlock_t sd_lock; | |
283 | }; | |
284 | ||
285 | struct rsc { | |
286 | struct cache_head h; | |
287 | struct xdr_netobj handle; | |
288 | struct svc_cred cred; | |
289 | struct gss_svc_seq_data seqdata; | |
290 | struct gss_ctx *mechctx; | |
291 | }; | |
292 | ||
293 | static struct cache_head *rsc_table[RSC_HASHMAX]; | |
294 | static struct cache_detail rsc_cache; | |
295 | static struct rsc *rsc_lookup(struct rsc *item, int set); | |
296 | ||
297 | static void rsc_free(struct rsc *rsci) | |
298 | { | |
299 | kfree(rsci->handle.data); | |
300 | if (rsci->mechctx) | |
301 | gss_delete_sec_context(&rsci->mechctx); | |
302 | if (rsci->cred.cr_group_info) | |
303 | put_group_info(rsci->cred.cr_group_info); | |
304 | } | |
305 | ||
306 | static void rsc_put(struct cache_head *item, struct cache_detail *cd) | |
307 | { | |
308 | struct rsc *rsci = container_of(item, struct rsc, h); | |
309 | ||
310 | if (cache_put(item, cd)) { | |
311 | rsc_free(rsci); | |
312 | kfree(rsci); | |
313 | } | |
314 | } | |
315 | ||
316 | static inline int | |
317 | rsc_hash(struct rsc *rsci) | |
318 | { | |
319 | return hash_mem(rsci->handle.data, rsci->handle.len, RSC_HASHBITS); | |
320 | } | |
321 | ||
322 | static inline int | |
323 | rsc_match(struct rsc *new, struct rsc *tmp) | |
324 | { | |
325 | return netobj_equal(&new->handle, &tmp->handle); | |
326 | } | |
327 | ||
328 | static inline void | |
329 | rsc_init(struct rsc *new, struct rsc *tmp) | |
330 | { | |
331 | new->handle.len = tmp->handle.len; | |
332 | tmp->handle.len = 0; | |
333 | new->handle.data = tmp->handle.data; | |
334 | tmp->handle.data = NULL; | |
335 | new->mechctx = NULL; | |
336 | new->cred.cr_group_info = NULL; | |
337 | } | |
338 | ||
339 | static inline void | |
340 | rsc_update(struct rsc *new, struct rsc *tmp) | |
341 | { | |
342 | new->mechctx = tmp->mechctx; | |
343 | tmp->mechctx = NULL; | |
344 | memset(&new->seqdata, 0, sizeof(new->seqdata)); | |
345 | spin_lock_init(&new->seqdata.sd_lock); | |
346 | new->cred = tmp->cred; | |
347 | tmp->cred.cr_group_info = NULL; | |
348 | } | |
349 | ||
350 | static int rsc_parse(struct cache_detail *cd, | |
351 | char *mesg, int mlen) | |
352 | { | |
353 | /* contexthandle expiry [ uid gid N <n gids> mechname ...mechdata... ] */ | |
354 | char *buf = mesg; | |
355 | int len, rv; | |
356 | struct rsc rsci, *rscp = NULL; | |
357 | time_t expiry; | |
358 | int status = -EINVAL; | |
359 | ||
360 | memset(&rsci, 0, sizeof(rsci)); | |
361 | /* context handle */ | |
362 | len = qword_get(&mesg, buf, mlen); | |
363 | if (len < 0) goto out; | |
364 | status = -ENOMEM; | |
365 | if (dup_to_netobj(&rsci.handle, buf, len)) | |
366 | goto out; | |
367 | ||
368 | rsci.h.flags = 0; | |
369 | /* expiry */ | |
370 | expiry = get_expiry(&mesg); | |
371 | status = -EINVAL; | |
372 | if (expiry == 0) | |
373 | goto out; | |
374 | ||
375 | /* uid, or NEGATIVE */ | |
376 | rv = get_int(&mesg, &rsci.cred.cr_uid); | |
377 | if (rv == -EINVAL) | |
378 | goto out; | |
379 | if (rv == -ENOENT) | |
380 | set_bit(CACHE_NEGATIVE, &rsci.h.flags); | |
381 | else { | |
382 | int N, i; | |
383 | struct gss_api_mech *gm; | |
384 | ||
385 | /* gid */ | |
386 | if (get_int(&mesg, &rsci.cred.cr_gid)) | |
387 | goto out; | |
388 | ||
389 | /* number of additional gid's */ | |
390 | if (get_int(&mesg, &N)) | |
391 | goto out; | |
392 | status = -ENOMEM; | |
393 | rsci.cred.cr_group_info = groups_alloc(N); | |
394 | if (rsci.cred.cr_group_info == NULL) | |
395 | goto out; | |
396 | ||
397 | /* gid's */ | |
398 | status = -EINVAL; | |
399 | for (i=0; i<N; i++) { | |
400 | gid_t gid; | |
401 | if (get_int(&mesg, &gid)) | |
402 | goto out; | |
403 | GROUP_AT(rsci.cred.cr_group_info, i) = gid; | |
404 | } | |
405 | ||
406 | /* mech name */ | |
407 | len = qword_get(&mesg, buf, mlen); | |
408 | if (len < 0) | |
409 | goto out; | |
410 | gm = gss_mech_get_by_name(buf); | |
411 | status = -EOPNOTSUPP; | |
412 | if (!gm) | |
413 | goto out; | |
414 | ||
415 | status = -EINVAL; | |
416 | /* mech-specific data: */ | |
417 | len = qword_get(&mesg, buf, mlen); | |
418 | if (len < 0) { | |
419 | gss_mech_put(gm); | |
420 | goto out; | |
421 | } | |
422 | if (gss_import_sec_context(buf, len, gm, &rsci.mechctx)) { | |
423 | gss_mech_put(gm); | |
424 | goto out; | |
425 | } | |
426 | gss_mech_put(gm); | |
427 | } | |
428 | rsci.h.expiry_time = expiry; | |
429 | rscp = rsc_lookup(&rsci, 1); | |
430 | status = 0; | |
431 | out: | |
432 | rsc_free(&rsci); | |
433 | if (rscp) | |
434 | rsc_put(&rscp->h, &rsc_cache); | |
435 | return status; | |
436 | } | |
437 | ||
438 | static struct cache_detail rsc_cache = { | |
439 | .hash_size = RSC_HASHMAX, | |
440 | .hash_table = rsc_table, | |
441 | .name = "auth.rpcsec.context", | |
442 | .cache_put = rsc_put, | |
443 | .cache_parse = rsc_parse, | |
444 | }; | |
445 | ||
446 | static DefineSimpleCacheLookup(rsc, 0); | |
447 | ||
448 | static struct rsc * | |
449 | gss_svc_searchbyctx(struct xdr_netobj *handle) | |
450 | { | |
451 | struct rsc rsci; | |
452 | struct rsc *found; | |
453 | ||
454 | memset(&rsci, 0, sizeof(rsci)); | |
455 | if (dup_to_netobj(&rsci.handle, handle->data, handle->len)) | |
456 | return NULL; | |
457 | found = rsc_lookup(&rsci, 0); | |
458 | rsc_free(&rsci); | |
459 | if (!found) | |
460 | return NULL; | |
461 | if (cache_check(&rsc_cache, &found->h, NULL)) | |
462 | return NULL; | |
463 | return found; | |
464 | } | |
465 | ||
466 | /* Implements sequence number algorithm as specified in RFC 2203. */ | |
467 | static int | |
468 | gss_check_seq_num(struct rsc *rsci, int seq_num) | |
469 | { | |
470 | struct gss_svc_seq_data *sd = &rsci->seqdata; | |
471 | ||
472 | spin_lock(&sd->sd_lock); | |
473 | if (seq_num > sd->sd_max) { | |
474 | if (seq_num >= sd->sd_max + GSS_SEQ_WIN) { | |
475 | memset(sd->sd_win,0,sizeof(sd->sd_win)); | |
476 | sd->sd_max = seq_num; | |
477 | } else while (sd->sd_max < seq_num) { | |
478 | sd->sd_max++; | |
479 | __clear_bit(sd->sd_max % GSS_SEQ_WIN, sd->sd_win); | |
480 | } | |
481 | __set_bit(seq_num % GSS_SEQ_WIN, sd->sd_win); | |
482 | goto ok; | |
483 | } else if (seq_num <= sd->sd_max - GSS_SEQ_WIN) { | |
484 | goto drop; | |
485 | } | |
486 | /* sd_max - GSS_SEQ_WIN < seq_num <= sd_max */ | |
487 | if (__test_and_set_bit(seq_num % GSS_SEQ_WIN, sd->sd_win)) | |
488 | goto drop; | |
489 | ok: | |
490 | spin_unlock(&sd->sd_lock); | |
491 | return 1; | |
492 | drop: | |
493 | spin_unlock(&sd->sd_lock); | |
494 | return 0; | |
495 | } | |
496 | ||
497 | static inline u32 round_up_to_quad(u32 i) | |
498 | { | |
499 | return (i + 3 ) & ~3; | |
500 | } | |
501 | ||
502 | static inline int | |
503 | svc_safe_getnetobj(struct kvec *argv, struct xdr_netobj *o) | |
504 | { | |
505 | int l; | |
506 | ||
507 | if (argv->iov_len < 4) | |
508 | return -1; | |
509 | o->len = ntohl(svc_getu32(argv)); | |
510 | l = round_up_to_quad(o->len); | |
511 | if (argv->iov_len < l) | |
512 | return -1; | |
513 | o->data = argv->iov_base; | |
514 | argv->iov_base += l; | |
515 | argv->iov_len -= l; | |
516 | return 0; | |
517 | } | |
518 | ||
519 | static inline int | |
520 | svc_safe_putnetobj(struct kvec *resv, struct xdr_netobj *o) | |
521 | { | |
522 | u32 *p; | |
523 | ||
524 | if (resv->iov_len + 4 > PAGE_SIZE) | |
525 | return -1; | |
526 | svc_putu32(resv, htonl(o->len)); | |
527 | p = resv->iov_base + resv->iov_len; | |
528 | resv->iov_len += round_up_to_quad(o->len); | |
529 | if (resv->iov_len > PAGE_SIZE) | |
530 | return -1; | |
531 | memcpy(p, o->data, o->len); | |
532 | memset((u8 *)p + o->len, 0, round_up_to_quad(o->len) - o->len); | |
533 | return 0; | |
534 | } | |
535 | ||
536 | /* Verify the checksum on the header and return SVC_OK on success. | |
537 | * Otherwise, return SVC_DROP (in the case of a bad sequence number) | |
538 | * or return SVC_DENIED and indicate error in authp. | |
539 | */ | |
540 | static int | |
541 | gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci, | |
542 | u32 *rpcstart, struct rpc_gss_wire_cred *gc, u32 *authp) | |
543 | { | |
544 | struct gss_ctx *ctx_id = rsci->mechctx; | |
545 | struct xdr_buf rpchdr; | |
546 | struct xdr_netobj checksum; | |
547 | u32 flavor = 0; | |
548 | struct kvec *argv = &rqstp->rq_arg.head[0]; | |
549 | struct kvec iov; | |
550 | ||
551 | /* data to compute the checksum over: */ | |
552 | iov.iov_base = rpcstart; | |
553 | iov.iov_len = (u8 *)argv->iov_base - (u8 *)rpcstart; | |
554 | xdr_buf_from_iov(&iov, &rpchdr); | |
555 | ||
556 | *authp = rpc_autherr_badverf; | |
557 | if (argv->iov_len < 4) | |
558 | return SVC_DENIED; | |
559 | flavor = ntohl(svc_getu32(argv)); | |
560 | if (flavor != RPC_AUTH_GSS) | |
561 | return SVC_DENIED; | |
562 | if (svc_safe_getnetobj(argv, &checksum)) | |
563 | return SVC_DENIED; | |
564 | ||
565 | if (rqstp->rq_deferred) /* skip verification of revisited request */ | |
566 | return SVC_OK; | |
567 | if (gss_verify_mic(ctx_id, &rpchdr, &checksum, NULL) | |
568 | != GSS_S_COMPLETE) { | |
569 | *authp = rpcsec_gsserr_credproblem; | |
570 | return SVC_DENIED; | |
571 | } | |
572 | ||
573 | if (gc->gc_seq > MAXSEQ) { | |
574 | dprintk("RPC: svcauth_gss: discarding request with large sequence number %d\n", | |
575 | gc->gc_seq); | |
576 | *authp = rpcsec_gsserr_ctxproblem; | |
577 | return SVC_DENIED; | |
578 | } | |
579 | if (!gss_check_seq_num(rsci, gc->gc_seq)) { | |
580 | dprintk("RPC: svcauth_gss: discarding request with old sequence number %d\n", | |
581 | gc->gc_seq); | |
582 | return SVC_DROP; | |
583 | } | |
584 | return SVC_OK; | |
585 | } | |
586 | ||
587 | static int | |
588 | gss_write_verf(struct svc_rqst *rqstp, struct gss_ctx *ctx_id, u32 seq) | |
589 | { | |
590 | u32 xdr_seq; | |
591 | u32 maj_stat; | |
592 | struct xdr_buf verf_data; | |
593 | struct xdr_netobj mic; | |
594 | u32 *p; | |
595 | struct kvec iov; | |
596 | ||
597 | svc_putu32(rqstp->rq_res.head, htonl(RPC_AUTH_GSS)); | |
598 | xdr_seq = htonl(seq); | |
599 | ||
600 | iov.iov_base = &xdr_seq; | |
601 | iov.iov_len = sizeof(xdr_seq); | |
602 | xdr_buf_from_iov(&iov, &verf_data); | |
603 | p = rqstp->rq_res.head->iov_base + rqstp->rq_res.head->iov_len; | |
604 | mic.data = (u8 *)(p + 1); | |
605 | maj_stat = gss_get_mic(ctx_id, 0, &verf_data, &mic); | |
606 | if (maj_stat != GSS_S_COMPLETE) | |
607 | return -1; | |
608 | *p++ = htonl(mic.len); | |
609 | memset((u8 *)p + mic.len, 0, round_up_to_quad(mic.len) - mic.len); | |
610 | p += XDR_QUADLEN(mic.len); | |
611 | if (!xdr_ressize_check(rqstp, p)) | |
612 | return -1; | |
613 | return 0; | |
614 | } | |
615 | ||
616 | struct gss_domain { | |
617 | struct auth_domain h; | |
618 | u32 pseudoflavor; | |
619 | }; | |
620 | ||
621 | static struct auth_domain * | |
622 | find_gss_auth_domain(struct gss_ctx *ctx, u32 svc) | |
623 | { | |
624 | char *name; | |
625 | ||
626 | name = gss_service_to_auth_domain_name(ctx->mech_type, svc); | |
627 | if (!name) | |
628 | return NULL; | |
629 | return auth_domain_find(name); | |
630 | } | |
631 | ||
632 | int | |
633 | svcauth_gss_register_pseudoflavor(u32 pseudoflavor, char * name) | |
634 | { | |
635 | struct gss_domain *new; | |
636 | struct auth_domain *test; | |
637 | int stat = -ENOMEM; | |
638 | ||
639 | new = kmalloc(sizeof(*new), GFP_KERNEL); | |
640 | if (!new) | |
641 | goto out; | |
642 | cache_init(&new->h.h); | |
643 | new->h.name = kmalloc(strlen(name) + 1, GFP_KERNEL); | |
644 | if (!new->h.name) | |
645 | goto out_free_dom; | |
646 | strcpy(new->h.name, name); | |
647 | new->h.flavour = RPC_AUTH_GSS; | |
648 | new->pseudoflavor = pseudoflavor; | |
649 | new->h.h.expiry_time = NEVER; | |
650 | ||
651 | test = auth_domain_lookup(&new->h, 1); | |
652 | if (test == &new->h) { | |
653 | BUG_ON(atomic_dec_and_test(&new->h.h.refcnt)); | |
654 | } else { /* XXX Duplicate registration? */ | |
655 | auth_domain_put(&new->h); | |
656 | goto out; | |
657 | } | |
658 | return 0; | |
659 | ||
660 | out_free_dom: | |
661 | kfree(new); | |
662 | out: | |
663 | return stat; | |
664 | } | |
665 | ||
666 | EXPORT_SYMBOL(svcauth_gss_register_pseudoflavor); | |
667 | ||
668 | static inline int | |
669 | read_u32_from_xdr_buf(struct xdr_buf *buf, int base, u32 *obj) | |
670 | { | |
671 | u32 raw; | |
672 | int status; | |
673 | ||
674 | status = read_bytes_from_xdr_buf(buf, base, &raw, sizeof(*obj)); | |
675 | if (status) | |
676 | return status; | |
677 | *obj = ntohl(raw); | |
678 | return 0; | |
679 | } | |
680 | ||
681 | /* It would be nice if this bit of code could be shared with the client. | |
682 | * Obstacles: | |
683 | * The client shouldn't malloc(), would have to pass in own memory. | |
684 | * The server uses base of head iovec as read pointer, while the | |
685 | * client uses separate pointer. */ | |
686 | static int | |
687 | unwrap_integ_data(struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx) | |
688 | { | |
689 | int stat = -EINVAL; | |
690 | u32 integ_len, maj_stat; | |
691 | struct xdr_netobj mic; | |
692 | struct xdr_buf integ_buf; | |
693 | ||
694 | integ_len = ntohl(svc_getu32(&buf->head[0])); | |
695 | if (integ_len & 3) | |
696 | goto out; | |
697 | if (integ_len > buf->len) | |
698 | goto out; | |
699 | if (xdr_buf_subsegment(buf, &integ_buf, 0, integ_len)) | |
700 | BUG(); | |
701 | /* copy out mic... */ | |
702 | if (read_u32_from_xdr_buf(buf, integ_len, &mic.len)) | |
703 | BUG(); | |
704 | if (mic.len > RPC_MAX_AUTH_SIZE) | |
705 | goto out; | |
706 | mic.data = kmalloc(mic.len, GFP_KERNEL); | |
707 | if (!mic.data) | |
708 | goto out; | |
709 | if (read_bytes_from_xdr_buf(buf, integ_len + 4, mic.data, mic.len)) | |
710 | goto out; | |
711 | maj_stat = gss_verify_mic(ctx, &integ_buf, &mic, NULL); | |
712 | if (maj_stat != GSS_S_COMPLETE) | |
713 | goto out; | |
714 | if (ntohl(svc_getu32(&buf->head[0])) != seq) | |
715 | goto out; | |
716 | stat = 0; | |
717 | out: | |
718 | return stat; | |
719 | } | |
720 | ||
721 | struct gss_svc_data { | |
722 | /* decoded gss client cred: */ | |
723 | struct rpc_gss_wire_cred clcred; | |
724 | /* pointer to the beginning of the procedure-specific results, | |
725 | * which may be encrypted/checksummed in svcauth_gss_release: */ | |
726 | u32 *body_start; | |
727 | struct rsc *rsci; | |
728 | }; | |
729 | ||
730 | static int | |
731 | svcauth_gss_set_client(struct svc_rqst *rqstp) | |
732 | { | |
733 | struct gss_svc_data *svcdata = rqstp->rq_auth_data; | |
734 | struct rsc *rsci = svcdata->rsci; | |
735 | struct rpc_gss_wire_cred *gc = &svcdata->clcred; | |
736 | ||
737 | rqstp->rq_client = find_gss_auth_domain(rsci->mechctx, gc->gc_svc); | |
738 | if (rqstp->rq_client == NULL) | |
739 | return SVC_DENIED; | |
740 | return SVC_OK; | |
741 | } | |
742 | ||
743 | /* | |
744 | * Accept an rpcsec packet. | |
745 | * If context establishment, punt to user space | |
746 | * If data exchange, verify/decrypt | |
747 | * If context destruction, handle here | |
748 | * In the context establishment and destruction case we encode | |
749 | * response here and return SVC_COMPLETE. | |
750 | */ | |
751 | static int | |
752 | svcauth_gss_accept(struct svc_rqst *rqstp, u32 *authp) | |
753 | { | |
754 | struct kvec *argv = &rqstp->rq_arg.head[0]; | |
755 | struct kvec *resv = &rqstp->rq_res.head[0]; | |
756 | u32 crlen; | |
757 | struct xdr_netobj tmpobj; | |
758 | struct gss_svc_data *svcdata = rqstp->rq_auth_data; | |
759 | struct rpc_gss_wire_cred *gc; | |
760 | struct rsc *rsci = NULL; | |
761 | struct rsi *rsip, rsikey; | |
762 | u32 *rpcstart; | |
763 | u32 *reject_stat = resv->iov_base + resv->iov_len; | |
764 | int ret; | |
765 | ||
766 | dprintk("RPC: svcauth_gss: argv->iov_len = %zd\n",argv->iov_len); | |
767 | ||
768 | *authp = rpc_autherr_badcred; | |
769 | if (!svcdata) | |
770 | svcdata = kmalloc(sizeof(*svcdata), GFP_KERNEL); | |
771 | if (!svcdata) | |
772 | goto auth_err; | |
773 | rqstp->rq_auth_data = svcdata; | |
774 | svcdata->body_start = NULL; | |
775 | svcdata->rsci = NULL; | |
776 | gc = &svcdata->clcred; | |
777 | ||
778 | /* start of rpc packet is 7 u32's back from here: | |
779 | * xid direction rpcversion prog vers proc flavour | |
780 | */ | |
781 | rpcstart = argv->iov_base; | |
782 | rpcstart -= 7; | |
783 | ||
784 | /* credential is: | |
785 | * version(==1), proc(0,1,2,3), seq, service (1,2,3), handle | |
786 | * at least 5 u32s, and is preceeded by length, so that makes 6. | |
787 | */ | |
788 | ||
789 | if (argv->iov_len < 5 * 4) | |
790 | goto auth_err; | |
791 | crlen = ntohl(svc_getu32(argv)); | |
792 | if (ntohl(svc_getu32(argv)) != RPC_GSS_VERSION) | |
793 | goto auth_err; | |
794 | gc->gc_proc = ntohl(svc_getu32(argv)); | |
795 | gc->gc_seq = ntohl(svc_getu32(argv)); | |
796 | gc->gc_svc = ntohl(svc_getu32(argv)); | |
797 | if (svc_safe_getnetobj(argv, &gc->gc_ctx)) | |
798 | goto auth_err; | |
799 | if (crlen != round_up_to_quad(gc->gc_ctx.len) + 5 * 4) | |
800 | goto auth_err; | |
801 | ||
802 | if ((gc->gc_proc != RPC_GSS_PROC_DATA) && (rqstp->rq_proc != 0)) | |
803 | goto auth_err; | |
804 | ||
805 | /* | |
806 | * We've successfully parsed the credential. Let's check out the | |
807 | * verifier. An AUTH_NULL verifier is allowed (and required) for | |
808 | * INIT and CONTINUE_INIT requests. AUTH_RPCSEC_GSS is required for | |
809 | * PROC_DATA and PROC_DESTROY. | |
810 | * | |
811 | * AUTH_NULL verifier is 0 (AUTH_NULL), 0 (length). | |
812 | * AUTH_RPCSEC_GSS verifier is: | |
813 | * 6 (AUTH_RPCSEC_GSS), length, checksum. | |
814 | * checksum is calculated over rpcheader from xid up to here. | |
815 | */ | |
816 | *authp = rpc_autherr_badverf; | |
817 | switch (gc->gc_proc) { | |
818 | case RPC_GSS_PROC_INIT: | |
819 | case RPC_GSS_PROC_CONTINUE_INIT: | |
820 | if (argv->iov_len < 2 * 4) | |
821 | goto auth_err; | |
822 | if (ntohl(svc_getu32(argv)) != RPC_AUTH_NULL) | |
823 | goto auth_err; | |
824 | if (ntohl(svc_getu32(argv)) != 0) | |
825 | goto auth_err; | |
826 | break; | |
827 | case RPC_GSS_PROC_DATA: | |
828 | case RPC_GSS_PROC_DESTROY: | |
829 | *authp = rpcsec_gsserr_credproblem; | |
830 | rsci = gss_svc_searchbyctx(&gc->gc_ctx); | |
831 | if (!rsci) | |
832 | goto auth_err; | |
833 | switch (gss_verify_header(rqstp, rsci, rpcstart, gc, authp)) { | |
834 | case SVC_OK: | |
835 | break; | |
836 | case SVC_DENIED: | |
837 | goto auth_err; | |
838 | case SVC_DROP: | |
839 | goto drop; | |
840 | } | |
841 | break; | |
842 | default: | |
843 | *authp = rpc_autherr_rejectedcred; | |
844 | goto auth_err; | |
845 | } | |
846 | ||
847 | /* now act upon the command: */ | |
848 | switch (gc->gc_proc) { | |
849 | case RPC_GSS_PROC_INIT: | |
850 | case RPC_GSS_PROC_CONTINUE_INIT: | |
851 | *authp = rpc_autherr_badcred; | |
852 | if (gc->gc_proc == RPC_GSS_PROC_INIT && gc->gc_ctx.len != 0) | |
853 | goto auth_err; | |
854 | memset(&rsikey, 0, sizeof(rsikey)); | |
855 | if (dup_netobj(&rsikey.in_handle, &gc->gc_ctx)) | |
856 | goto drop; | |
857 | *authp = rpc_autherr_badverf; | |
858 | if (svc_safe_getnetobj(argv, &tmpobj)) { | |
859 | kfree(rsikey.in_handle.data); | |
860 | goto auth_err; | |
861 | } | |
862 | if (dup_netobj(&rsikey.in_token, &tmpobj)) { | |
863 | kfree(rsikey.in_handle.data); | |
864 | goto drop; | |
865 | } | |
866 | ||
867 | rsip = rsi_lookup(&rsikey, 0); | |
868 | rsi_free(&rsikey); | |
869 | if (!rsip) { | |
870 | goto drop; | |
871 | } | |
872 | switch(cache_check(&rsi_cache, &rsip->h, &rqstp->rq_chandle)) { | |
873 | case -EAGAIN: | |
874 | goto drop; | |
875 | case -ENOENT: | |
876 | goto drop; | |
877 | case 0: | |
878 | rsci = gss_svc_searchbyctx(&rsip->out_handle); | |
879 | if (!rsci) { | |
880 | goto drop; | |
881 | } | |
882 | if (gss_write_verf(rqstp, rsci->mechctx, GSS_SEQ_WIN)) | |
883 | goto drop; | |
884 | if (resv->iov_len + 4 > PAGE_SIZE) | |
885 | goto drop; | |
886 | svc_putu32(resv, rpc_success); | |
887 | if (svc_safe_putnetobj(resv, &rsip->out_handle)) | |
888 | goto drop; | |
889 | if (resv->iov_len + 3 * 4 > PAGE_SIZE) | |
890 | goto drop; | |
891 | svc_putu32(resv, htonl(rsip->major_status)); | |
892 | svc_putu32(resv, htonl(rsip->minor_status)); | |
893 | svc_putu32(resv, htonl(GSS_SEQ_WIN)); | |
894 | if (svc_safe_putnetobj(resv, &rsip->out_token)) | |
895 | goto drop; | |
896 | rqstp->rq_client = NULL; | |
897 | } | |
898 | goto complete; | |
899 | case RPC_GSS_PROC_DESTROY: | |
900 | set_bit(CACHE_NEGATIVE, &rsci->h.flags); | |
901 | if (resv->iov_len + 4 > PAGE_SIZE) | |
902 | goto drop; | |
903 | svc_putu32(resv, rpc_success); | |
904 | goto complete; | |
905 | case RPC_GSS_PROC_DATA: | |
906 | *authp = rpcsec_gsserr_ctxproblem; | |
907 | if (gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq)) | |
908 | goto auth_err; | |
909 | rqstp->rq_cred = rsci->cred; | |
910 | get_group_info(rsci->cred.cr_group_info); | |
911 | *authp = rpc_autherr_badcred; | |
912 | switch (gc->gc_svc) { | |
913 | case RPC_GSS_SVC_NONE: | |
914 | break; | |
915 | case RPC_GSS_SVC_INTEGRITY: | |
916 | if (unwrap_integ_data(&rqstp->rq_arg, | |
917 | gc->gc_seq, rsci->mechctx)) | |
918 | goto auth_err; | |
919 | /* placeholders for length and seq. number: */ | |
920 | svcdata->body_start = resv->iov_base + resv->iov_len; | |
921 | svc_putu32(resv, 0); | |
922 | svc_putu32(resv, 0); | |
923 | break; | |
924 | case RPC_GSS_SVC_PRIVACY: | |
925 | /* currently unsupported */ | |
926 | default: | |
927 | goto auth_err; | |
928 | } | |
929 | svcdata->rsci = rsci; | |
930 | cache_get(&rsci->h); | |
931 | ret = SVC_OK; | |
932 | goto out; | |
933 | } | |
934 | auth_err: | |
935 | /* Restore write pointer to original value: */ | |
936 | xdr_ressize_check(rqstp, reject_stat); | |
937 | ret = SVC_DENIED; | |
938 | goto out; | |
939 | complete: | |
940 | ret = SVC_COMPLETE; | |
941 | goto out; | |
942 | drop: | |
943 | ret = SVC_DROP; | |
944 | out: | |
945 | if (rsci) | |
946 | rsc_put(&rsci->h, &rsc_cache); | |
947 | return ret; | |
948 | } | |
949 | ||
950 | static int | |
951 | svcauth_gss_release(struct svc_rqst *rqstp) | |
952 | { | |
953 | struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data; | |
954 | struct rpc_gss_wire_cred *gc = &gsd->clcred; | |
955 | struct xdr_buf *resbuf = &rqstp->rq_res; | |
956 | struct xdr_buf integ_buf; | |
957 | struct xdr_netobj mic; | |
958 | struct kvec *resv; | |
959 | u32 *p; | |
960 | int integ_offset, integ_len; | |
961 | int stat = -EINVAL; | |
962 | ||
963 | if (gc->gc_proc != RPC_GSS_PROC_DATA) | |
964 | goto out; | |
965 | /* Release can be called twice, but we only wrap once. */ | |
966 | if (gsd->body_start == NULL) | |
967 | goto out; | |
968 | /* normally not set till svc_send, but we need it here: */ | |
969 | resbuf->len = resbuf->head[0].iov_len | |
970 | + resbuf->page_len + resbuf->tail[0].iov_len; | |
971 | switch (gc->gc_svc) { | |
972 | case RPC_GSS_SVC_NONE: | |
973 | break; | |
974 | case RPC_GSS_SVC_INTEGRITY: | |
975 | p = gsd->body_start; | |
976 | gsd->body_start = NULL; | |
977 | /* move accept_stat to right place: */ | |
978 | memcpy(p, p + 2, 4); | |
979 | /* don't wrap in failure case: */ | |
980 | /* Note: counting on not getting here if call was not even | |
981 | * accepted! */ | |
982 | if (*p != rpc_success) { | |
983 | resbuf->head[0].iov_len -= 2 * 4; | |
984 | goto out; | |
985 | } | |
986 | p++; | |
987 | integ_offset = (u8 *)(p + 1) - (u8 *)resbuf->head[0].iov_base; | |
988 | integ_len = resbuf->len - integ_offset; | |
989 | BUG_ON(integ_len % 4); | |
990 | *p++ = htonl(integ_len); | |
991 | *p++ = htonl(gc->gc_seq); | |
992 | if (xdr_buf_subsegment(resbuf, &integ_buf, integ_offset, | |
993 | integ_len)) | |
994 | BUG(); | |
995 | if (resbuf->page_len == 0 | |
996 | && resbuf->tail[0].iov_len + RPC_MAX_AUTH_SIZE | |
997 | < PAGE_SIZE) { | |
998 | BUG_ON(resbuf->tail[0].iov_len); | |
999 | /* Use head for everything */ | |
1000 | resv = &resbuf->head[0]; | |
1001 | } else if (resbuf->tail[0].iov_base == NULL) { | |
1002 | /* copied from nfsd4_encode_read */ | |
1003 | svc_take_page(rqstp); | |
1004 | resbuf->tail[0].iov_base = page_address(rqstp | |
1005 | ->rq_respages[rqstp->rq_resused-1]); | |
1006 | rqstp->rq_restailpage = rqstp->rq_resused-1; | |
1007 | resbuf->tail[0].iov_len = 0; | |
1008 | resv = &resbuf->tail[0]; | |
1009 | } else { | |
1010 | resv = &resbuf->tail[0]; | |
1011 | } | |
1012 | mic.data = (u8 *)resv->iov_base + resv->iov_len + 4; | |
1013 | if (gss_get_mic(gsd->rsci->mechctx, 0, &integ_buf, &mic)) | |
1014 | goto out_err; | |
1015 | svc_putu32(resv, htonl(mic.len)); | |
1016 | memset(mic.data + mic.len, 0, | |
1017 | round_up_to_quad(mic.len) - mic.len); | |
1018 | resv->iov_len += XDR_QUADLEN(mic.len) << 2; | |
1019 | /* not strictly required: */ | |
1020 | resbuf->len += XDR_QUADLEN(mic.len) << 2; | |
1021 | BUG_ON(resv->iov_len > PAGE_SIZE); | |
1022 | break; | |
1023 | case RPC_GSS_SVC_PRIVACY: | |
1024 | default: | |
1025 | goto out_err; | |
1026 | } | |
1027 | ||
1028 | out: | |
1029 | stat = 0; | |
1030 | out_err: | |
1031 | if (rqstp->rq_client) | |
1032 | auth_domain_put(rqstp->rq_client); | |
1033 | rqstp->rq_client = NULL; | |
1034 | if (rqstp->rq_cred.cr_group_info) | |
1035 | put_group_info(rqstp->rq_cred.cr_group_info); | |
1036 | rqstp->rq_cred.cr_group_info = NULL; | |
1037 | if (gsd->rsci) | |
1038 | rsc_put(&gsd->rsci->h, &rsc_cache); | |
1039 | gsd->rsci = NULL; | |
1040 | ||
1041 | return stat; | |
1042 | } | |
1043 | ||
1044 | static void | |
1045 | svcauth_gss_domain_release(struct auth_domain *dom) | |
1046 | { | |
1047 | struct gss_domain *gd = container_of(dom, struct gss_domain, h); | |
1048 | ||
1049 | kfree(dom->name); | |
1050 | kfree(gd); | |
1051 | } | |
1052 | ||
1053 | static struct auth_ops svcauthops_gss = { | |
1054 | .name = "rpcsec_gss", | |
1055 | .owner = THIS_MODULE, | |
1056 | .flavour = RPC_AUTH_GSS, | |
1057 | .accept = svcauth_gss_accept, | |
1058 | .release = svcauth_gss_release, | |
1059 | .domain_release = svcauth_gss_domain_release, | |
1060 | .set_client = svcauth_gss_set_client, | |
1061 | }; | |
1062 | ||
1063 | int | |
1064 | gss_svc_init(void) | |
1065 | { | |
1066 | int rv = svc_auth_register(RPC_AUTH_GSS, &svcauthops_gss); | |
1067 | if (rv == 0) { | |
1068 | cache_register(&rsc_cache); | |
1069 | cache_register(&rsi_cache); | |
1070 | } | |
1071 | return rv; | |
1072 | } | |
1073 | ||
1074 | void | |
1075 | gss_svc_shutdown(void) | |
1076 | { | |
1077 | cache_unregister(&rsc_cache); | |
1078 | cache_unregister(&rsi_cache); | |
1079 | svc_auth_unregister(RPC_AUTH_GSS); | |
1080 | } |