| /* |
| * Neil Brown <neilb@cse.unsw.edu.au> |
| * J. Bruce Fields <bfields@umich.edu> |
| * Andy Adamson <andros@umich.edu> |
| * Dug Song <dugsong@monkey.org> |
| * |
| * RPCSEC_GSS server authentication. |
| * This implements RPCSEC_GSS as defined in rfc2203 (rpcsec_gss) and rfc2078 |
| * (gssapi) |
| * |
| * The RPCSEC_GSS involves three stages: |
| * 1/ context creation |
| * 2/ data exchange |
| * 3/ context destruction |
| * |
| * Context creation is handled largely by upcalls to user-space. |
| * In particular, GSS_Accept_sec_context is handled by an upcall |
| * Data exchange is handled entirely within the kernel |
| * In particular, GSS_GetMIC, GSS_VerifyMIC, GSS_Seal, GSS_Unseal are in-kernel. |
| * Context destruction is handled in-kernel |
| * GSS_Delete_sec_context is in-kernel |
| * |
| * Context creation is initiated by a RPCSEC_GSS_INIT request arriving. |
| * The context handle and gss_token are used as a key into the rpcsec_init cache. |
| * The content of this cache includes some of the outputs of GSS_Accept_sec_context, |
| * being major_status, minor_status, context_handle, reply_token. |
| * These are sent back to the client. |
| * Sequence window management is handled by the kernel. The window size if currently |
| * a compile time constant. |
| * |
| * When user-space is happy that a context is established, it places an entry |
| * in the rpcsec_context cache. The key for this cache is the context_handle. |
| * The content includes: |
| * uid/gidlist - for determining access rights |
| * mechanism type |
| * mechanism specific information, such as a key |
| * |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/module.h> |
| #include <linux/pagemap.h> |
| |
| #include <linux/sunrpc/auth_gss.h> |
| #include <linux/sunrpc/svcauth.h> |
| #include <linux/sunrpc/gss_err.h> |
| #include <linux/sunrpc/svcauth.h> |
| #include <linux/sunrpc/svcauth_gss.h> |
| #include <linux/sunrpc/cache.h> |
| |
| #ifdef RPC_DEBUG |
| # define RPCDBG_FACILITY RPCDBG_AUTH |
| #endif |
| |
| /* The rpcsec_init cache is used for mapping RPCSEC_GSS_{,CONT_}INIT requests |
| * into replies. |
| * |
| * Key is context handle (\x if empty) and gss_token. |
| * Content is major_status minor_status (integers) context_handle, reply_token. |
| * |
| */ |
| |
| static int netobj_equal(struct xdr_netobj *a, struct xdr_netobj *b) |
| { |
| return a->len == b->len && 0 == memcmp(a->data, b->data, a->len); |
| } |
| |
| #define RSI_HASHBITS 6 |
| #define RSI_HASHMAX (1<<RSI_HASHBITS) |
| #define RSI_HASHMASK (RSI_HASHMAX-1) |
| |
| struct rsi { |
| struct cache_head h; |
| struct xdr_netobj in_handle, in_token; |
| struct xdr_netobj out_handle, out_token; |
| int major_status, minor_status; |
| }; |
| |
| static struct cache_head *rsi_table[RSI_HASHMAX]; |
| static struct cache_detail rsi_cache; |
| static struct rsi *rsi_lookup(struct rsi *item, int set); |
| |
| static void rsi_free(struct rsi *rsii) |
| { |
| kfree(rsii->in_handle.data); |
| kfree(rsii->in_token.data); |
| kfree(rsii->out_handle.data); |
| kfree(rsii->out_token.data); |
| } |
| |
| static void rsi_put(struct cache_head *item, struct cache_detail *cd) |
| { |
| struct rsi *rsii = container_of(item, struct rsi, h); |
| if (cache_put(item, cd)) { |
| rsi_free(rsii); |
| kfree(rsii); |
| } |
| } |
| |
| static inline int rsi_hash(struct rsi *item) |
| { |
| return hash_mem(item->in_handle.data, item->in_handle.len, RSI_HASHBITS) |
| ^ hash_mem(item->in_token.data, item->in_token.len, RSI_HASHBITS); |
| } |
| |
| static inline int rsi_match(struct rsi *item, struct rsi *tmp) |
| { |
| return netobj_equal(&item->in_handle, &tmp->in_handle) |
| && netobj_equal(&item->in_token, &tmp->in_token); |
| } |
| |
| static int dup_to_netobj(struct xdr_netobj *dst, char *src, int len) |
| { |
| dst->len = len; |
| dst->data = (len ? kmalloc(len, GFP_KERNEL) : NULL); |
| if (dst->data) |
| memcpy(dst->data, src, len); |
| if (len && !dst->data) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| static inline int dup_netobj(struct xdr_netobj *dst, struct xdr_netobj *src) |
| { |
| return dup_to_netobj(dst, src->data, src->len); |
| } |
| |
| static inline void rsi_init(struct rsi *new, struct rsi *item) |
| { |
| new->out_handle.data = NULL; |
| new->out_handle.len = 0; |
| new->out_token.data = NULL; |
| new->out_token.len = 0; |
| new->in_handle.len = item->in_handle.len; |
| item->in_handle.len = 0; |
| new->in_token.len = item->in_token.len; |
| item->in_token.len = 0; |
| new->in_handle.data = item->in_handle.data; |
| item->in_handle.data = NULL; |
| new->in_token.data = item->in_token.data; |
| item->in_token.data = NULL; |
| } |
| |
| static inline void rsi_update(struct rsi *new, struct rsi *item) |
| { |
| BUG_ON(new->out_handle.data || new->out_token.data); |
| new->out_handle.len = item->out_handle.len; |
| item->out_handle.len = 0; |
| new->out_token.len = item->out_token.len; |
| item->out_token.len = 0; |
| new->out_handle.data = item->out_handle.data; |
| item->out_handle.data = NULL; |
| new->out_token.data = item->out_token.data; |
| item->out_token.data = NULL; |
| |
| new->major_status = item->major_status; |
| new->minor_status = item->minor_status; |
| } |
| |
| static void rsi_request(struct cache_detail *cd, |
| struct cache_head *h, |
| char **bpp, int *blen) |
| { |
| struct rsi *rsii = container_of(h, struct rsi, h); |
| |
| qword_addhex(bpp, blen, rsii->in_handle.data, rsii->in_handle.len); |
| qword_addhex(bpp, blen, rsii->in_token.data, rsii->in_token.len); |
| (*bpp)[-1] = '\n'; |
| } |
| |
| |
| static int rsi_parse(struct cache_detail *cd, |
| char *mesg, int mlen) |
| { |
| /* context token expiry major minor context token */ |
| char *buf = mesg; |
| char *ep; |
| int len; |
| struct rsi rsii, *rsip = NULL; |
| time_t expiry; |
| int status = -EINVAL; |
| |
| memset(&rsii, 0, sizeof(rsii)); |
| /* handle */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) |
| goto out; |
| status = -ENOMEM; |
| if (dup_to_netobj(&rsii.in_handle, buf, len)) |
| goto out; |
| |
| /* token */ |
| len = qword_get(&mesg, buf, mlen); |
| status = -EINVAL; |
| if (len < 0) |
| goto out; |
| status = -ENOMEM; |
| if (dup_to_netobj(&rsii.in_token, buf, len)) |
| goto out; |
| |
| rsii.h.flags = 0; |
| /* expiry */ |
| expiry = get_expiry(&mesg); |
| status = -EINVAL; |
| if (expiry == 0) |
| goto out; |
| |
| /* major/minor */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) |
| goto out; |
| if (len == 0) { |
| goto out; |
| } else { |
| rsii.major_status = simple_strtoul(buf, &ep, 10); |
| if (*ep) |
| goto out; |
| len = qword_get(&mesg, buf, mlen); |
| if (len <= 0) |
| goto out; |
| rsii.minor_status = simple_strtoul(buf, &ep, 10); |
| if (*ep) |
| goto out; |
| |
| /* out_handle */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) |
| goto out; |
| status = -ENOMEM; |
| if (dup_to_netobj(&rsii.out_handle, buf, len)) |
| goto out; |
| |
| /* out_token */ |
| len = qword_get(&mesg, buf, mlen); |
| status = -EINVAL; |
| if (len < 0) |
| goto out; |
| status = -ENOMEM; |
| if (dup_to_netobj(&rsii.out_token, buf, len)) |
| goto out; |
| } |
| rsii.h.expiry_time = expiry; |
| rsip = rsi_lookup(&rsii, 1); |
| status = 0; |
| out: |
| rsi_free(&rsii); |
| if (rsip) |
| rsi_put(&rsip->h, &rsi_cache); |
| return status; |
| } |
| |
| static struct cache_detail rsi_cache = { |
| .hash_size = RSI_HASHMAX, |
| .hash_table = rsi_table, |
| .name = "auth.rpcsec.init", |
| .cache_put = rsi_put, |
| .cache_request = rsi_request, |
| .cache_parse = rsi_parse, |
| }; |
| |
| static DefineSimpleCacheLookup(rsi, 0) |
| |
| /* |
| * The rpcsec_context cache is used to store a context that is |
| * used in data exchange. |
| * The key is a context handle. The content is: |
| * uid, gidlist, mechanism, service-set, mech-specific-data |
| */ |
| |
| #define RSC_HASHBITS 10 |
| #define RSC_HASHMAX (1<<RSC_HASHBITS) |
| #define RSC_HASHMASK (RSC_HASHMAX-1) |
| |
| #define GSS_SEQ_WIN 128 |
| |
| struct gss_svc_seq_data { |
| /* highest seq number seen so far: */ |
| int sd_max; |
| /* for i such that sd_max-GSS_SEQ_WIN < i <= sd_max, the i-th bit of |
| * sd_win is nonzero iff sequence number i has been seen already: */ |
| unsigned long sd_win[GSS_SEQ_WIN/BITS_PER_LONG]; |
| spinlock_t sd_lock; |
| }; |
| |
| struct rsc { |
| struct cache_head h; |
| struct xdr_netobj handle; |
| struct svc_cred cred; |
| struct gss_svc_seq_data seqdata; |
| struct gss_ctx *mechctx; |
| }; |
| |
| static struct cache_head *rsc_table[RSC_HASHMAX]; |
| static struct cache_detail rsc_cache; |
| static struct rsc *rsc_lookup(struct rsc *item, int set); |
| |
| static void rsc_free(struct rsc *rsci) |
| { |
| kfree(rsci->handle.data); |
| if (rsci->mechctx) |
| gss_delete_sec_context(&rsci->mechctx); |
| if (rsci->cred.cr_group_info) |
| put_group_info(rsci->cred.cr_group_info); |
| } |
| |
| static void rsc_put(struct cache_head *item, struct cache_detail *cd) |
| { |
| struct rsc *rsci = container_of(item, struct rsc, h); |
| |
| if (cache_put(item, cd)) { |
| rsc_free(rsci); |
| kfree(rsci); |
| } |
| } |
| |
| static inline int |
| rsc_hash(struct rsc *rsci) |
| { |
| return hash_mem(rsci->handle.data, rsci->handle.len, RSC_HASHBITS); |
| } |
| |
| static inline int |
| rsc_match(struct rsc *new, struct rsc *tmp) |
| { |
| return netobj_equal(&new->handle, &tmp->handle); |
| } |
| |
| static inline void |
| rsc_init(struct rsc *new, struct rsc *tmp) |
| { |
| new->handle.len = tmp->handle.len; |
| tmp->handle.len = 0; |
| new->handle.data = tmp->handle.data; |
| tmp->handle.data = NULL; |
| new->mechctx = NULL; |
| new->cred.cr_group_info = NULL; |
| } |
| |
| static inline void |
| rsc_update(struct rsc *new, struct rsc *tmp) |
| { |
| new->mechctx = tmp->mechctx; |
| tmp->mechctx = NULL; |
| memset(&new->seqdata, 0, sizeof(new->seqdata)); |
| spin_lock_init(&new->seqdata.sd_lock); |
| new->cred = tmp->cred; |
| tmp->cred.cr_group_info = NULL; |
| } |
| |
| static int rsc_parse(struct cache_detail *cd, |
| char *mesg, int mlen) |
| { |
| /* contexthandle expiry [ uid gid N <n gids> mechname ...mechdata... ] */ |
| char *buf = mesg; |
| int len, rv; |
| struct rsc rsci, *rscp = NULL; |
| time_t expiry; |
| int status = -EINVAL; |
| |
| memset(&rsci, 0, sizeof(rsci)); |
| /* context handle */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) goto out; |
| status = -ENOMEM; |
| if (dup_to_netobj(&rsci.handle, buf, len)) |
| goto out; |
| |
| rsci.h.flags = 0; |
| /* expiry */ |
| expiry = get_expiry(&mesg); |
| status = -EINVAL; |
| if (expiry == 0) |
| goto out; |
| |
| /* uid, or NEGATIVE */ |
| rv = get_int(&mesg, &rsci.cred.cr_uid); |
| if (rv == -EINVAL) |
| goto out; |
| if (rv == -ENOENT) |
| set_bit(CACHE_NEGATIVE, &rsci.h.flags); |
| else { |
| int N, i; |
| struct gss_api_mech *gm; |
| |
| /* gid */ |
| if (get_int(&mesg, &rsci.cred.cr_gid)) |
| goto out; |
| |
| /* number of additional gid's */ |
| if (get_int(&mesg, &N)) |
| goto out; |
| status = -ENOMEM; |
| rsci.cred.cr_group_info = groups_alloc(N); |
| if (rsci.cred.cr_group_info == NULL) |
| goto out; |
| |
| /* gid's */ |
| status = -EINVAL; |
| for (i=0; i<N; i++) { |
| gid_t gid; |
| if (get_int(&mesg, &gid)) |
| goto out; |
| GROUP_AT(rsci.cred.cr_group_info, i) = gid; |
| } |
| |
| /* mech name */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) |
| goto out; |
| gm = gss_mech_get_by_name(buf); |
| status = -EOPNOTSUPP; |
| if (!gm) |
| goto out; |
| |
| status = -EINVAL; |
| /* mech-specific data: */ |
| len = qword_get(&mesg, buf, mlen); |
| if (len < 0) { |
| gss_mech_put(gm); |
| goto out; |
| } |
| if (gss_import_sec_context(buf, len, gm, &rsci.mechctx)) { |
| gss_mech_put(gm); |
| goto out; |
| } |
| gss_mech_put(gm); |
| } |
| rsci.h.expiry_time = expiry; |
| rscp = rsc_lookup(&rsci, 1); |
| status = 0; |
| out: |
| rsc_free(&rsci); |
| if (rscp) |
| rsc_put(&rscp->h, &rsc_cache); |
| return status; |
| } |
| |
| static struct cache_detail rsc_cache = { |
| .hash_size = RSC_HASHMAX, |
| .hash_table = rsc_table, |
| .name = "auth.rpcsec.context", |
| .cache_put = rsc_put, |
| .cache_parse = rsc_parse, |
| }; |
| |
| static DefineSimpleCacheLookup(rsc, 0); |
| |
| static struct rsc * |
| gss_svc_searchbyctx(struct xdr_netobj *handle) |
| { |
| struct rsc rsci; |
| struct rsc *found; |
| |
| memset(&rsci, 0, sizeof(rsci)); |
| if (dup_to_netobj(&rsci.handle, handle->data, handle->len)) |
| return NULL; |
| found = rsc_lookup(&rsci, 0); |
| rsc_free(&rsci); |
| if (!found) |
| return NULL; |
| if (cache_check(&rsc_cache, &found->h, NULL)) |
| return NULL; |
| return found; |
| } |
| |
| /* Implements sequence number algorithm as specified in RFC 2203. */ |
| static int |
| gss_check_seq_num(struct rsc *rsci, int seq_num) |
| { |
| struct gss_svc_seq_data *sd = &rsci->seqdata; |
| |
| spin_lock(&sd->sd_lock); |
| if (seq_num > sd->sd_max) { |
| if (seq_num >= sd->sd_max + GSS_SEQ_WIN) { |
| memset(sd->sd_win,0,sizeof(sd->sd_win)); |
| sd->sd_max = seq_num; |
| } else while (sd->sd_max < seq_num) { |
| sd->sd_max++; |
| __clear_bit(sd->sd_max % GSS_SEQ_WIN, sd->sd_win); |
| } |
| __set_bit(seq_num % GSS_SEQ_WIN, sd->sd_win); |
| goto ok; |
| } else if (seq_num <= sd->sd_max - GSS_SEQ_WIN) { |
| goto drop; |
| } |
| /* sd_max - GSS_SEQ_WIN < seq_num <= sd_max */ |
| if (__test_and_set_bit(seq_num % GSS_SEQ_WIN, sd->sd_win)) |
| goto drop; |
| ok: |
| spin_unlock(&sd->sd_lock); |
| return 1; |
| drop: |
| spin_unlock(&sd->sd_lock); |
| return 0; |
| } |
| |
| static inline u32 round_up_to_quad(u32 i) |
| { |
| return (i + 3 ) & ~3; |
| } |
| |
| static inline int |
| svc_safe_getnetobj(struct kvec *argv, struct xdr_netobj *o) |
| { |
| int l; |
| |
| if (argv->iov_len < 4) |
| return -1; |
| o->len = ntohl(svc_getu32(argv)); |
| l = round_up_to_quad(o->len); |
| if (argv->iov_len < l) |
| return -1; |
| o->data = argv->iov_base; |
| argv->iov_base += l; |
| argv->iov_len -= l; |
| return 0; |
| } |
| |
| static inline int |
| svc_safe_putnetobj(struct kvec *resv, struct xdr_netobj *o) |
| { |
| u32 *p; |
| |
| if (resv->iov_len + 4 > PAGE_SIZE) |
| return -1; |
| svc_putu32(resv, htonl(o->len)); |
| p = resv->iov_base + resv->iov_len; |
| resv->iov_len += round_up_to_quad(o->len); |
| if (resv->iov_len > PAGE_SIZE) |
| return -1; |
| memcpy(p, o->data, o->len); |
| memset((u8 *)p + o->len, 0, round_up_to_quad(o->len) - o->len); |
| return 0; |
| } |
| |
| /* Verify the checksum on the header and return SVC_OK on success. |
| * Otherwise, return SVC_DROP (in the case of a bad sequence number) |
| * or return SVC_DENIED and indicate error in authp. |
| */ |
| static int |
| gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci, |
| u32 *rpcstart, struct rpc_gss_wire_cred *gc, u32 *authp) |
| { |
| struct gss_ctx *ctx_id = rsci->mechctx; |
| struct xdr_buf rpchdr; |
| struct xdr_netobj checksum; |
| u32 flavor = 0; |
| struct kvec *argv = &rqstp->rq_arg.head[0]; |
| struct kvec iov; |
| |
| /* data to compute the checksum over: */ |
| iov.iov_base = rpcstart; |
| iov.iov_len = (u8 *)argv->iov_base - (u8 *)rpcstart; |
| xdr_buf_from_iov(&iov, &rpchdr); |
| |
| *authp = rpc_autherr_badverf; |
| if (argv->iov_len < 4) |
| return SVC_DENIED; |
| flavor = ntohl(svc_getu32(argv)); |
| if (flavor != RPC_AUTH_GSS) |
| return SVC_DENIED; |
| if (svc_safe_getnetobj(argv, &checksum)) |
| return SVC_DENIED; |
| |
| if (rqstp->rq_deferred) /* skip verification of revisited request */ |
| return SVC_OK; |
| if (gss_verify_mic(ctx_id, &rpchdr, &checksum, NULL) |
| != GSS_S_COMPLETE) { |
| *authp = rpcsec_gsserr_credproblem; |
| return SVC_DENIED; |
| } |
| |
| if (gc->gc_seq > MAXSEQ) { |
| dprintk("RPC: svcauth_gss: discarding request with large sequence number %d\n", |
| gc->gc_seq); |
| *authp = rpcsec_gsserr_ctxproblem; |
| return SVC_DENIED; |
| } |
| if (!gss_check_seq_num(rsci, gc->gc_seq)) { |
| dprintk("RPC: svcauth_gss: discarding request with old sequence number %d\n", |
| gc->gc_seq); |
| return SVC_DROP; |
| } |
| return SVC_OK; |
| } |
| |
| static int |
| gss_write_verf(struct svc_rqst *rqstp, struct gss_ctx *ctx_id, u32 seq) |
| { |
| u32 xdr_seq; |
| u32 maj_stat; |
| struct xdr_buf verf_data; |
| struct xdr_netobj mic; |
| u32 *p; |
| struct kvec iov; |
| |
| svc_putu32(rqstp->rq_res.head, htonl(RPC_AUTH_GSS)); |
| xdr_seq = htonl(seq); |
| |
| iov.iov_base = &xdr_seq; |
| iov.iov_len = sizeof(xdr_seq); |
| xdr_buf_from_iov(&iov, &verf_data); |
| p = rqstp->rq_res.head->iov_base + rqstp->rq_res.head->iov_len; |
| mic.data = (u8 *)(p + 1); |
| maj_stat = gss_get_mic(ctx_id, 0, &verf_data, &mic); |
| if (maj_stat != GSS_S_COMPLETE) |
| return -1; |
| *p++ = htonl(mic.len); |
| memset((u8 *)p + mic.len, 0, round_up_to_quad(mic.len) - mic.len); |
| p += XDR_QUADLEN(mic.len); |
| if (!xdr_ressize_check(rqstp, p)) |
| return -1; |
| return 0; |
| } |
| |
| struct gss_domain { |
| struct auth_domain h; |
| u32 pseudoflavor; |
| }; |
| |
| static struct auth_domain * |
| find_gss_auth_domain(struct gss_ctx *ctx, u32 svc) |
| { |
| char *name; |
| |
| name = gss_service_to_auth_domain_name(ctx->mech_type, svc); |
| if (!name) |
| return NULL; |
| return auth_domain_find(name); |
| } |
| |
| int |
| svcauth_gss_register_pseudoflavor(u32 pseudoflavor, char * name) |
| { |
| struct gss_domain *new; |
| struct auth_domain *test; |
| int stat = -ENOMEM; |
| |
| new = kmalloc(sizeof(*new), GFP_KERNEL); |
| if (!new) |
| goto out; |
| cache_init(&new->h.h); |
| new->h.name = kmalloc(strlen(name) + 1, GFP_KERNEL); |
| if (!new->h.name) |
| goto out_free_dom; |
| strcpy(new->h.name, name); |
| new->h.flavour = RPC_AUTH_GSS; |
| new->pseudoflavor = pseudoflavor; |
| new->h.h.expiry_time = NEVER; |
| |
| test = auth_domain_lookup(&new->h, 1); |
| if (test == &new->h) { |
| BUG_ON(atomic_dec_and_test(&new->h.h.refcnt)); |
| } else { /* XXX Duplicate registration? */ |
| auth_domain_put(&new->h); |
| goto out; |
| } |
| return 0; |
| |
| out_free_dom: |
| kfree(new); |
| out: |
| return stat; |
| } |
| |
| EXPORT_SYMBOL(svcauth_gss_register_pseudoflavor); |
| |
| static inline int |
| read_u32_from_xdr_buf(struct xdr_buf *buf, int base, u32 *obj) |
| { |
| u32 raw; |
| int status; |
| |
| status = read_bytes_from_xdr_buf(buf, base, &raw, sizeof(*obj)); |
| if (status) |
| return status; |
| *obj = ntohl(raw); |
| return 0; |
| } |
| |
| /* It would be nice if this bit of code could be shared with the client. |
| * Obstacles: |
| * The client shouldn't malloc(), would have to pass in own memory. |
| * The server uses base of head iovec as read pointer, while the |
| * client uses separate pointer. */ |
| static int |
| unwrap_integ_data(struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx) |
| { |
| int stat = -EINVAL; |
| u32 integ_len, maj_stat; |
| struct xdr_netobj mic; |
| struct xdr_buf integ_buf; |
| |
| integ_len = ntohl(svc_getu32(&buf->head[0])); |
| if (integ_len & 3) |
| goto out; |
| if (integ_len > buf->len) |
| goto out; |
| if (xdr_buf_subsegment(buf, &integ_buf, 0, integ_len)) |
| BUG(); |
| /* copy out mic... */ |
| if (read_u32_from_xdr_buf(buf, integ_len, &mic.len)) |
| BUG(); |
| if (mic.len > RPC_MAX_AUTH_SIZE) |
| goto out; |
| mic.data = kmalloc(mic.len, GFP_KERNEL); |
| if (!mic.data) |
| goto out; |
| if (read_bytes_from_xdr_buf(buf, integ_len + 4, mic.data, mic.len)) |
| goto out; |
| maj_stat = gss_verify_mic(ctx, &integ_buf, &mic, NULL); |
| if (maj_stat != GSS_S_COMPLETE) |
| goto out; |
| if (ntohl(svc_getu32(&buf->head[0])) != seq) |
| goto out; |
| stat = 0; |
| out: |
| return stat; |
| } |
| |
| struct gss_svc_data { |
| /* decoded gss client cred: */ |
| struct rpc_gss_wire_cred clcred; |
| /* pointer to the beginning of the procedure-specific results, |
| * which may be encrypted/checksummed in svcauth_gss_release: */ |
| u32 *body_start; |
| struct rsc *rsci; |
| }; |
| |
| static int |
| svcauth_gss_set_client(struct svc_rqst *rqstp) |
| { |
| struct gss_svc_data *svcdata = rqstp->rq_auth_data; |
| struct rsc *rsci = svcdata->rsci; |
| struct rpc_gss_wire_cred *gc = &svcdata->clcred; |
| |
| rqstp->rq_client = find_gss_auth_domain(rsci->mechctx, gc->gc_svc); |
| if (rqstp->rq_client == NULL) |
| return SVC_DENIED; |
| return SVC_OK; |
| } |
| |
| /* |
| * Accept an rpcsec packet. |
| * If context establishment, punt to user space |
| * If data exchange, verify/decrypt |
| * If context destruction, handle here |
| * In the context establishment and destruction case we encode |
| * response here and return SVC_COMPLETE. |
| */ |
| static int |
| svcauth_gss_accept(struct svc_rqst *rqstp, u32 *authp) |
| { |
| struct kvec *argv = &rqstp->rq_arg.head[0]; |
| struct kvec *resv = &rqstp->rq_res.head[0]; |
| u32 crlen; |
| struct xdr_netobj tmpobj; |
| struct gss_svc_data *svcdata = rqstp->rq_auth_data; |
| struct rpc_gss_wire_cred *gc; |
| struct rsc *rsci = NULL; |
| struct rsi *rsip, rsikey; |
| u32 *rpcstart; |
| u32 *reject_stat = resv->iov_base + resv->iov_len; |
| int ret; |
| |
| dprintk("RPC: svcauth_gss: argv->iov_len = %zd\n",argv->iov_len); |
| |
| *authp = rpc_autherr_badcred; |
| if (!svcdata) |
| svcdata = kmalloc(sizeof(*svcdata), GFP_KERNEL); |
| if (!svcdata) |
| goto auth_err; |
| rqstp->rq_auth_data = svcdata; |
| svcdata->body_start = NULL; |
| svcdata->rsci = NULL; |
| gc = &svcdata->clcred; |
| |
| /* start of rpc packet is 7 u32's back from here: |
| * xid direction rpcversion prog vers proc flavour |
| */ |
| rpcstart = argv->iov_base; |
| rpcstart -= 7; |
| |
| /* credential is: |
| * version(==1), proc(0,1,2,3), seq, service (1,2,3), handle |
| * at least 5 u32s, and is preceeded by length, so that makes 6. |
| */ |
| |
| if (argv->iov_len < 5 * 4) |
| goto auth_err; |
| crlen = ntohl(svc_getu32(argv)); |
| if (ntohl(svc_getu32(argv)) != RPC_GSS_VERSION) |
| goto auth_err; |
| gc->gc_proc = ntohl(svc_getu32(argv)); |
| gc->gc_seq = ntohl(svc_getu32(argv)); |
| gc->gc_svc = ntohl(svc_getu32(argv)); |
| if (svc_safe_getnetobj(argv, &gc->gc_ctx)) |
| goto auth_err; |
| if (crlen != round_up_to_quad(gc->gc_ctx.len) + 5 * 4) |
| goto auth_err; |
| |
| if ((gc->gc_proc != RPC_GSS_PROC_DATA) && (rqstp->rq_proc != 0)) |
| goto auth_err; |
| |
| /* |
| * We've successfully parsed the credential. Let's check out the |
| * verifier. An AUTH_NULL verifier is allowed (and required) for |
| * INIT and CONTINUE_INIT requests. AUTH_RPCSEC_GSS is required for |
| * PROC_DATA and PROC_DESTROY. |
| * |
| * AUTH_NULL verifier is 0 (AUTH_NULL), 0 (length). |
| * AUTH_RPCSEC_GSS verifier is: |
| * 6 (AUTH_RPCSEC_GSS), length, checksum. |
| * checksum is calculated over rpcheader from xid up to here. |
| */ |
| *authp = rpc_autherr_badverf; |
| switch (gc->gc_proc) { |
| case RPC_GSS_PROC_INIT: |
| case RPC_GSS_PROC_CONTINUE_INIT: |
| if (argv->iov_len < 2 * 4) |
| goto auth_err; |
| if (ntohl(svc_getu32(argv)) != RPC_AUTH_NULL) |
| goto auth_err; |
| if (ntohl(svc_getu32(argv)) != 0) |
| goto auth_err; |
| break; |
| case RPC_GSS_PROC_DATA: |
| case RPC_GSS_PROC_DESTROY: |
| *authp = rpcsec_gsserr_credproblem; |
| rsci = gss_svc_searchbyctx(&gc->gc_ctx); |
| if (!rsci) |
| goto auth_err; |
| switch (gss_verify_header(rqstp, rsci, rpcstart, gc, authp)) { |
| case SVC_OK: |
| break; |
| case SVC_DENIED: |
| goto auth_err; |
| case SVC_DROP: |
| goto drop; |
| } |
| break; |
| default: |
| *authp = rpc_autherr_rejectedcred; |
| goto auth_err; |
| } |
| |
| /* now act upon the command: */ |
| switch (gc->gc_proc) { |
| case RPC_GSS_PROC_INIT: |
| case RPC_GSS_PROC_CONTINUE_INIT: |
| *authp = rpc_autherr_badcred; |
| if (gc->gc_proc == RPC_GSS_PROC_INIT && gc->gc_ctx.len != 0) |
| goto auth_err; |
| memset(&rsikey, 0, sizeof(rsikey)); |
| if (dup_netobj(&rsikey.in_handle, &gc->gc_ctx)) |
| goto drop; |
| *authp = rpc_autherr_badverf; |
| if (svc_safe_getnetobj(argv, &tmpobj)) { |
| kfree(rsikey.in_handle.data); |
| goto auth_err; |
| } |
| if (dup_netobj(&rsikey.in_token, &tmpobj)) { |
| kfree(rsikey.in_handle.data); |
| goto drop; |
| } |
| |
| rsip = rsi_lookup(&rsikey, 0); |
| rsi_free(&rsikey); |
| if (!rsip) { |
| goto drop; |
| } |
| switch(cache_check(&rsi_cache, &rsip->h, &rqstp->rq_chandle)) { |
| case -EAGAIN: |
| goto drop; |
| case -ENOENT: |
| goto drop; |
| case 0: |
| rsci = gss_svc_searchbyctx(&rsip->out_handle); |
| if (!rsci) { |
| goto drop; |
| } |
| if (gss_write_verf(rqstp, rsci->mechctx, GSS_SEQ_WIN)) |
| goto drop; |
| if (resv->iov_len + 4 > PAGE_SIZE) |
| goto drop; |
| svc_putu32(resv, rpc_success); |
| if (svc_safe_putnetobj(resv, &rsip->out_handle)) |
| goto drop; |
| if (resv->iov_len + 3 * 4 > PAGE_SIZE) |
| goto drop; |
| svc_putu32(resv, htonl(rsip->major_status)); |
| svc_putu32(resv, htonl(rsip->minor_status)); |
| svc_putu32(resv, htonl(GSS_SEQ_WIN)); |
| if (svc_safe_putnetobj(resv, &rsip->out_token)) |
| goto drop; |
| rqstp->rq_client = NULL; |
| } |
| goto complete; |
| case RPC_GSS_PROC_DESTROY: |
| set_bit(CACHE_NEGATIVE, &rsci->h.flags); |
| if (resv->iov_len + 4 > PAGE_SIZE) |
| goto drop; |
| svc_putu32(resv, rpc_success); |
| goto complete; |
| case RPC_GSS_PROC_DATA: |
| *authp = rpcsec_gsserr_ctxproblem; |
| if (gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq)) |
| goto auth_err; |
| rqstp->rq_cred = rsci->cred; |
| get_group_info(rsci->cred.cr_group_info); |
| *authp = rpc_autherr_badcred; |
| switch (gc->gc_svc) { |
| case RPC_GSS_SVC_NONE: |
| break; |
| case RPC_GSS_SVC_INTEGRITY: |
| if (unwrap_integ_data(&rqstp->rq_arg, |
| gc->gc_seq, rsci->mechctx)) |
| goto auth_err; |
| /* placeholders for length and seq. number: */ |
| svcdata->body_start = resv->iov_base + resv->iov_len; |
| svc_putu32(resv, 0); |
| svc_putu32(resv, 0); |
| break; |
| case RPC_GSS_SVC_PRIVACY: |
| /* currently unsupported */ |
| default: |
| goto auth_err; |
| } |
| svcdata->rsci = rsci; |
| cache_get(&rsci->h); |
| ret = SVC_OK; |
| goto out; |
| } |
| auth_err: |
| /* Restore write pointer to original value: */ |
| xdr_ressize_check(rqstp, reject_stat); |
| ret = SVC_DENIED; |
| goto out; |
| complete: |
| ret = SVC_COMPLETE; |
| goto out; |
| drop: |
| ret = SVC_DROP; |
| out: |
| if (rsci) |
| rsc_put(&rsci->h, &rsc_cache); |
| return ret; |
| } |
| |
| static int |
| svcauth_gss_release(struct svc_rqst *rqstp) |
| { |
| struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data; |
| struct rpc_gss_wire_cred *gc = &gsd->clcred; |
| struct xdr_buf *resbuf = &rqstp->rq_res; |
| struct xdr_buf integ_buf; |
| struct xdr_netobj mic; |
| struct kvec *resv; |
| u32 *p; |
| int integ_offset, integ_len; |
| int stat = -EINVAL; |
| |
| if (gc->gc_proc != RPC_GSS_PROC_DATA) |
| goto out; |
| /* Release can be called twice, but we only wrap once. */ |
| if (gsd->body_start == NULL) |
| goto out; |
| /* normally not set till svc_send, but we need it here: */ |
| resbuf->len = resbuf->head[0].iov_len |
| + resbuf->page_len + resbuf->tail[0].iov_len; |
| switch (gc->gc_svc) { |
| case RPC_GSS_SVC_NONE: |
| break; |
| case RPC_GSS_SVC_INTEGRITY: |
| p = gsd->body_start; |
| gsd->body_start = NULL; |
| /* move accept_stat to right place: */ |
| memcpy(p, p + 2, 4); |
| /* don't wrap in failure case: */ |
| /* Note: counting on not getting here if call was not even |
| * accepted! */ |
| if (*p != rpc_success) { |
| resbuf->head[0].iov_len -= 2 * 4; |
| goto out; |
| } |
| p++; |
| integ_offset = (u8 *)(p + 1) - (u8 *)resbuf->head[0].iov_base; |
| integ_len = resbuf->len - integ_offset; |
| BUG_ON(integ_len % 4); |
| *p++ = htonl(integ_len); |
| *p++ = htonl(gc->gc_seq); |
| if (xdr_buf_subsegment(resbuf, &integ_buf, integ_offset, |
| integ_len)) |
| BUG(); |
| if (resbuf->page_len == 0 |
| && resbuf->tail[0].iov_len + RPC_MAX_AUTH_SIZE |
| < PAGE_SIZE) { |
| BUG_ON(resbuf->tail[0].iov_len); |
| /* Use head for everything */ |
| resv = &resbuf->head[0]; |
| } else if (resbuf->tail[0].iov_base == NULL) { |
| /* copied from nfsd4_encode_read */ |
| svc_take_page(rqstp); |
| resbuf->tail[0].iov_base = page_address(rqstp |
| ->rq_respages[rqstp->rq_resused-1]); |
| rqstp->rq_restailpage = rqstp->rq_resused-1; |
| resbuf->tail[0].iov_len = 0; |
| resv = &resbuf->tail[0]; |
| } else { |
| resv = &resbuf->tail[0]; |
| } |
| mic.data = (u8 *)resv->iov_base + resv->iov_len + 4; |
| if (gss_get_mic(gsd->rsci->mechctx, 0, &integ_buf, &mic)) |
| goto out_err; |
| svc_putu32(resv, htonl(mic.len)); |
| memset(mic.data + mic.len, 0, |
| round_up_to_quad(mic.len) - mic.len); |
| resv->iov_len += XDR_QUADLEN(mic.len) << 2; |
| /* not strictly required: */ |
| resbuf->len += XDR_QUADLEN(mic.len) << 2; |
| BUG_ON(resv->iov_len > PAGE_SIZE); |
| break; |
| case RPC_GSS_SVC_PRIVACY: |
| default: |
| goto out_err; |
| } |
| |
| out: |
| stat = 0; |
| out_err: |
| if (rqstp->rq_client) |
| auth_domain_put(rqstp->rq_client); |
| rqstp->rq_client = NULL; |
| if (rqstp->rq_cred.cr_group_info) |
| put_group_info(rqstp->rq_cred.cr_group_info); |
| rqstp->rq_cred.cr_group_info = NULL; |
| if (gsd->rsci) |
| rsc_put(&gsd->rsci->h, &rsc_cache); |
| gsd->rsci = NULL; |
| |
| return stat; |
| } |
| |
| static void |
| svcauth_gss_domain_release(struct auth_domain *dom) |
| { |
| struct gss_domain *gd = container_of(dom, struct gss_domain, h); |
| |
| kfree(dom->name); |
| kfree(gd); |
| } |
| |
| static struct auth_ops svcauthops_gss = { |
| .name = "rpcsec_gss", |
| .owner = THIS_MODULE, |
| .flavour = RPC_AUTH_GSS, |
| .accept = svcauth_gss_accept, |
| .release = svcauth_gss_release, |
| .domain_release = svcauth_gss_domain_release, |
| .set_client = svcauth_gss_set_client, |
| }; |
| |
| int |
| gss_svc_init(void) |
| { |
| int rv = svc_auth_register(RPC_AUTH_GSS, &svcauthops_gss); |
| if (rv == 0) { |
| cache_register(&rsc_cache); |
| cache_register(&rsi_cache); |
| } |
| return rv; |
| } |
| |
| void |
| gss_svc_shutdown(void) |
| { |
| cache_unregister(&rsc_cache); |
| cache_unregister(&rsi_cache); |
| svc_auth_unregister(RPC_AUTH_GSS); |
| } |