123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850 |
- /*
- * GSS Proxy upcall module
- *
- * Copyright (C) 2012 Simo Sorce <simo@redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
- #include <linux/sunrpc/svcauth.h>
- #include "gss_rpc_xdr.h"
- static int gssx_enc_bool(struct xdr_stream *xdr, int v)
- {
- __be32 *p;
- p = xdr_reserve_space(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- *p = v ? xdr_one : xdr_zero;
- return 0;
- }
- static int gssx_dec_bool(struct xdr_stream *xdr, u32 *v)
- {
- __be32 *p;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- *v = be32_to_cpu(*p);
- return 0;
- }
- static int gssx_enc_buffer(struct xdr_stream *xdr,
- gssx_buffer *buf)
- {
- __be32 *p;
- p = xdr_reserve_space(xdr, sizeof(u32) + buf->len);
- if (!p)
- return -ENOSPC;
- xdr_encode_opaque(p, buf->data, buf->len);
- return 0;
- }
- static int gssx_enc_in_token(struct xdr_stream *xdr,
- struct gssp_in_token *in)
- {
- __be32 *p;
- p = xdr_reserve_space(xdr, 4);
- if (!p)
- return -ENOSPC;
- *p = cpu_to_be32(in->page_len);
- /* all we need to do is to write pages */
- xdr_write_pages(xdr, in->pages, in->page_base, in->page_len);
- return 0;
- }
- static int gssx_dec_buffer(struct xdr_stream *xdr,
- gssx_buffer *buf)
- {
- u32 length;
- __be32 *p;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- length = be32_to_cpup(p);
- p = xdr_inline_decode(xdr, length);
- if (unlikely(p == NULL))
- return -ENOSPC;
- if (buf->len == 0) {
- /* we intentionally are not interested in this buffer */
- return 0;
- }
- if (length > buf->len)
- return -ENOSPC;
- if (!buf->data) {
- buf->data = kmemdup(p, length, GFP_KERNEL);
- if (!buf->data)
- return -ENOMEM;
- } else {
- memcpy(buf->data, p, length);
- }
- buf->len = length;
- return 0;
- }
- static int gssx_enc_option(struct xdr_stream *xdr,
- struct gssx_option *opt)
- {
- int err;
- err = gssx_enc_buffer(xdr, &opt->option);
- if (err)
- return err;
- err = gssx_enc_buffer(xdr, &opt->value);
- return err;
- }
- static int gssx_dec_option(struct xdr_stream *xdr,
- struct gssx_option *opt)
- {
- int err;
- err = gssx_dec_buffer(xdr, &opt->option);
- if (err)
- return err;
- err = gssx_dec_buffer(xdr, &opt->value);
- return err;
- }
- static int dummy_enc_opt_array(struct xdr_stream *xdr,
- struct gssx_option_array *oa)
- {
- __be32 *p;
- if (oa->count != 0)
- return -EINVAL;
- p = xdr_reserve_space(xdr, 4);
- if (!p)
- return -ENOSPC;
- *p = 0;
- return 0;
- }
- static int dummy_dec_opt_array(struct xdr_stream *xdr,
- struct gssx_option_array *oa)
- {
- struct gssx_option dummy;
- u32 count, i;
- __be32 *p;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- count = be32_to_cpup(p++);
- memset(&dummy, 0, sizeof(dummy));
- for (i = 0; i < count; i++) {
- gssx_dec_option(xdr, &dummy);
- }
- oa->count = 0;
- oa->data = NULL;
- return 0;
- }
- static int get_host_u32(struct xdr_stream *xdr, u32 *res)
- {
- __be32 *p;
- p = xdr_inline_decode(xdr, 4);
- if (!p)
- return -EINVAL;
- /* Contents of linux creds are all host-endian: */
- memcpy(res, p, sizeof(u32));
- return 0;
- }
- static int gssx_dec_linux_creds(struct xdr_stream *xdr,
- struct svc_cred *creds)
- {
- u32 length;
- __be32 *p;
- u32 tmp;
- u32 N;
- int i, err;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- length = be32_to_cpup(p);
- if (length > (3 + NGROUPS_MAX) * sizeof(u32))
- return -ENOSPC;
- /* uid */
- err = get_host_u32(xdr, &tmp);
- if (err)
- return err;
- creds->cr_uid = make_kuid(&init_user_ns, tmp);
- /* gid */
- err = get_host_u32(xdr, &tmp);
- if (err)
- return err;
- creds->cr_gid = make_kgid(&init_user_ns, tmp);
- /* number of additional gid's */
- err = get_host_u32(xdr, &tmp);
- if (err)
- return err;
- N = tmp;
- if ((3 + N) * sizeof(u32) != length)
- return -EINVAL;
- creds->cr_group_info = groups_alloc(N);
- if (creds->cr_group_info == NULL)
- return -ENOMEM;
- /* gid's */
- for (i = 0; i < N; i++) {
- kgid_t kgid;
- err = get_host_u32(xdr, &tmp);
- if (err)
- goto out_free_groups;
- err = -EINVAL;
- kgid = make_kgid(&init_user_ns, tmp);
- if (!gid_valid(kgid))
- goto out_free_groups;
- creds->cr_group_info->gid[i] = kgid;
- }
- groups_sort(creds->cr_group_info);
- return 0;
- out_free_groups:
- groups_free(creds->cr_group_info);
- return err;
- }
- static int gssx_dec_option_array(struct xdr_stream *xdr,
- struct gssx_option_array *oa)
- {
- struct svc_cred *creds;
- u32 count, i;
- __be32 *p;
- int err;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- count = be32_to_cpup(p++);
- if (!count)
- return 0;
- /* we recognize only 1 currently: CREDS_VALUE */
- oa->count = 1;
- oa->data = kmalloc(sizeof(struct gssx_option), GFP_KERNEL);
- if (!oa->data)
- return -ENOMEM;
- creds = kzalloc(sizeof(struct svc_cred), GFP_KERNEL);
- if (!creds) {
- kfree(oa->data);
- return -ENOMEM;
- }
- oa->data[0].option.data = CREDS_VALUE;
- oa->data[0].option.len = sizeof(CREDS_VALUE);
- oa->data[0].value.data = (void *)creds;
- oa->data[0].value.len = 0;
- for (i = 0; i < count; i++) {
- gssx_buffer dummy = { 0, NULL };
- u32 length;
- /* option buffer */
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- length = be32_to_cpup(p);
- p = xdr_inline_decode(xdr, length);
- if (unlikely(p == NULL))
- return -ENOSPC;
- if (length == sizeof(CREDS_VALUE) &&
- memcmp(p, CREDS_VALUE, sizeof(CREDS_VALUE)) == 0) {
- /* We have creds here. parse them */
- err = gssx_dec_linux_creds(xdr, creds);
- if (err)
- return err;
- oa->data[0].value.len = 1; /* presence */
- } else {
- /* consume uninteresting buffer */
- err = gssx_dec_buffer(xdr, &dummy);
- if (err)
- return err;
- }
- }
- return 0;
- }
- static int gssx_dec_status(struct xdr_stream *xdr,
- struct gssx_status *status)
- {
- __be32 *p;
- int err;
- /* status->major_status */
- p = xdr_inline_decode(xdr, 8);
- if (unlikely(p == NULL))
- return -ENOSPC;
- p = xdr_decode_hyper(p, &status->major_status);
- /* status->mech */
- err = gssx_dec_buffer(xdr, &status->mech);
- if (err)
- return err;
- /* status->minor_status */
- p = xdr_inline_decode(xdr, 8);
- if (unlikely(p == NULL))
- return -ENOSPC;
- p = xdr_decode_hyper(p, &status->minor_status);
- /* status->major_status_string */
- err = gssx_dec_buffer(xdr, &status->major_status_string);
- if (err)
- return err;
- /* status->minor_status_string */
- err = gssx_dec_buffer(xdr, &status->minor_status_string);
- if (err)
- return err;
- /* status->server_ctx */
- err = gssx_dec_buffer(xdr, &status->server_ctx);
- if (err)
- return err;
- /* we assume we have no options for now, so simply consume them */
- /* status->options */
- err = dummy_dec_opt_array(xdr, &status->options);
- return err;
- }
- static int gssx_enc_call_ctx(struct xdr_stream *xdr,
- struct gssx_call_ctx *ctx)
- {
- struct gssx_option opt;
- __be32 *p;
- int err;
- /* ctx->locale */
- err = gssx_enc_buffer(xdr, &ctx->locale);
- if (err)
- return err;
- /* ctx->server_ctx */
- err = gssx_enc_buffer(xdr, &ctx->server_ctx);
- if (err)
- return err;
- /* we always want to ask for lucid contexts */
- /* ctx->options */
- p = xdr_reserve_space(xdr, 4);
- *p = cpu_to_be32(2);
- /* we want a lucid_v1 context */
- opt.option.data = LUCID_OPTION;
- opt.option.len = sizeof(LUCID_OPTION);
- opt.value.data = LUCID_VALUE;
- opt.value.len = sizeof(LUCID_VALUE);
- err = gssx_enc_option(xdr, &opt);
- /* ..and user creds */
- opt.option.data = CREDS_OPTION;
- opt.option.len = sizeof(CREDS_OPTION);
- opt.value.data = CREDS_VALUE;
- opt.value.len = sizeof(CREDS_VALUE);
- err = gssx_enc_option(xdr, &opt);
- return err;
- }
- static int gssx_dec_name_attr(struct xdr_stream *xdr,
- struct gssx_name_attr *attr)
- {
- int err;
- /* attr->attr */
- err = gssx_dec_buffer(xdr, &attr->attr);
- if (err)
- return err;
- /* attr->value */
- err = gssx_dec_buffer(xdr, &attr->value);
- if (err)
- return err;
- /* attr->extensions */
- err = dummy_dec_opt_array(xdr, &attr->extensions);
- return err;
- }
- static int dummy_enc_nameattr_array(struct xdr_stream *xdr,
- struct gssx_name_attr_array *naa)
- {
- __be32 *p;
- if (naa->count != 0)
- return -EINVAL;
- p = xdr_reserve_space(xdr, 4);
- if (!p)
- return -ENOSPC;
- *p = 0;
- return 0;
- }
- static int dummy_dec_nameattr_array(struct xdr_stream *xdr,
- struct gssx_name_attr_array *naa)
- {
- struct gssx_name_attr dummy = { .attr = {.len = 0} };
- u32 count, i;
- __be32 *p;
- p = xdr_inline_decode(xdr, 4);
- if (unlikely(p == NULL))
- return -ENOSPC;
- count = be32_to_cpup(p++);
- for (i = 0; i < count; i++) {
- gssx_dec_name_attr(xdr, &dummy);
- }
- naa->count = 0;
- naa->data = NULL;
- return 0;
- }
- static struct xdr_netobj zero_netobj = {};
- static struct gssx_name_attr_array zero_name_attr_array = {};
- static struct gssx_option_array zero_option_array = {};
- static int gssx_enc_name(struct xdr_stream *xdr,
- struct gssx_name *name)
- {
- int err;
- /* name->display_name */
- err = gssx_enc_buffer(xdr, &name->display_name);
- if (err)
- return err;
- /* name->name_type */
- err = gssx_enc_buffer(xdr, &zero_netobj);
- if (err)
- return err;
- /* name->exported_name */
- err = gssx_enc_buffer(xdr, &zero_netobj);
- if (err)
- return err;
- /* name->exported_composite_name */
- err = gssx_enc_buffer(xdr, &zero_netobj);
- if (err)
- return err;
- /* leave name_attributes empty for now, will add once we have any
- * to pass up at all */
- /* name->name_attributes */
- err = dummy_enc_nameattr_array(xdr, &zero_name_attr_array);
- if (err)
- return err;
- /* leave options empty for now, will add once we have any options
- * to pass up at all */
- /* name->extensions */
- err = dummy_enc_opt_array(xdr, &zero_option_array);
- return err;
- }
- static int gssx_dec_name(struct xdr_stream *xdr,
- struct gssx_name *name)
- {
- struct xdr_netobj dummy_netobj = { .len = 0 };
- struct gssx_name_attr_array dummy_name_attr_array = { .count = 0 };
- struct gssx_option_array dummy_option_array = { .count = 0 };
- int err;
- /* name->display_name */
- err = gssx_dec_buffer(xdr, &name->display_name);
- if (err)
- return err;
- /* name->name_type */
- err = gssx_dec_buffer(xdr, &dummy_netobj);
- if (err)
- return err;
- /* name->exported_name */
- err = gssx_dec_buffer(xdr, &dummy_netobj);
- if (err)
- return err;
- /* name->exported_composite_name */
- err = gssx_dec_buffer(xdr, &dummy_netobj);
- if (err)
- return err;
- /* we assume we have no attributes for now, so simply consume them */
- /* name->name_attributes */
- err = dummy_dec_nameattr_array(xdr, &dummy_name_attr_array);
- if (err)
- return err;
- /* we assume we have no options for now, so simply consume them */
- /* name->extensions */
- err = dummy_dec_opt_array(xdr, &dummy_option_array);
- return err;
- }
- static int dummy_enc_credel_array(struct xdr_stream *xdr,
- struct gssx_cred_element_array *cea)
- {
- __be32 *p;
- if (cea->count != 0)
- return -EINVAL;
- p = xdr_reserve_space(xdr, 4);
- if (!p)
- return -ENOSPC;
- *p = 0;
- return 0;
- }
- static int gssx_enc_cred(struct xdr_stream *xdr,
- struct gssx_cred *cred)
- {
- int err;
- /* cred->desired_name */
- err = gssx_enc_name(xdr, &cred->desired_name);
- if (err)
- return err;
- /* cred->elements */
- err = dummy_enc_credel_array(xdr, &cred->elements);
- if (err)
- return err;
- /* cred->cred_handle_reference */
- err = gssx_enc_buffer(xdr, &cred->cred_handle_reference);
- if (err)
- return err;
- /* cred->needs_release */
- err = gssx_enc_bool(xdr, cred->needs_release);
- return err;
- }
- static int gssx_enc_ctx(struct xdr_stream *xdr,
- struct gssx_ctx *ctx)
- {
- __be32 *p;
- int err;
- /* ctx->exported_context_token */
- err = gssx_enc_buffer(xdr, &ctx->exported_context_token);
- if (err)
- return err;
- /* ctx->state */
- err = gssx_enc_buffer(xdr, &ctx->state);
- if (err)
- return err;
- /* ctx->need_release */
- err = gssx_enc_bool(xdr, ctx->need_release);
- if (err)
- return err;
- /* ctx->mech */
- err = gssx_enc_buffer(xdr, &ctx->mech);
- if (err)
- return err;
- /* ctx->src_name */
- err = gssx_enc_name(xdr, &ctx->src_name);
- if (err)
- return err;
- /* ctx->targ_name */
- err = gssx_enc_name(xdr, &ctx->targ_name);
- if (err)
- return err;
- /* ctx->lifetime */
- p = xdr_reserve_space(xdr, 8+8);
- if (!p)
- return -ENOSPC;
- p = xdr_encode_hyper(p, ctx->lifetime);
- /* ctx->ctx_flags */
- p = xdr_encode_hyper(p, ctx->ctx_flags);
- /* ctx->locally_initiated */
- err = gssx_enc_bool(xdr, ctx->locally_initiated);
- if (err)
- return err;
- /* ctx->open */
- err = gssx_enc_bool(xdr, ctx->open);
- if (err)
- return err;
- /* leave options empty for now, will add once we have any options
- * to pass up at all */
- /* ctx->options */
- err = dummy_enc_opt_array(xdr, &ctx->options);
- return err;
- }
- static int gssx_dec_ctx(struct xdr_stream *xdr,
- struct gssx_ctx *ctx)
- {
- __be32 *p;
- int err;
- /* ctx->exported_context_token */
- err = gssx_dec_buffer(xdr, &ctx->exported_context_token);
- if (err)
- return err;
- /* ctx->state */
- err = gssx_dec_buffer(xdr, &ctx->state);
- if (err)
- return err;
- /* ctx->need_release */
- err = gssx_dec_bool(xdr, &ctx->need_release);
- if (err)
- return err;
- /* ctx->mech */
- err = gssx_dec_buffer(xdr, &ctx->mech);
- if (err)
- return err;
- /* ctx->src_name */
- err = gssx_dec_name(xdr, &ctx->src_name);
- if (err)
- return err;
- /* ctx->targ_name */
- err = gssx_dec_name(xdr, &ctx->targ_name);
- if (err)
- return err;
- /* ctx->lifetime */
- p = xdr_inline_decode(xdr, 8+8);
- if (unlikely(p == NULL))
- return -ENOSPC;
- p = xdr_decode_hyper(p, &ctx->lifetime);
- /* ctx->ctx_flags */
- p = xdr_decode_hyper(p, &ctx->ctx_flags);
- /* ctx->locally_initiated */
- err = gssx_dec_bool(xdr, &ctx->locally_initiated);
- if (err)
- return err;
- /* ctx->open */
- err = gssx_dec_bool(xdr, &ctx->open);
- if (err)
- return err;
- /* we assume we have no options for now, so simply consume them */
- /* ctx->options */
- err = dummy_dec_opt_array(xdr, &ctx->options);
- return err;
- }
- static int gssx_enc_cb(struct xdr_stream *xdr, struct gssx_cb *cb)
- {
- __be32 *p;
- int err;
- /* cb->initiator_addrtype */
- p = xdr_reserve_space(xdr, 8);
- if (!p)
- return -ENOSPC;
- p = xdr_encode_hyper(p, cb->initiator_addrtype);
- /* cb->initiator_address */
- err = gssx_enc_buffer(xdr, &cb->initiator_address);
- if (err)
- return err;
- /* cb->acceptor_addrtype */
- p = xdr_reserve_space(xdr, 8);
- if (!p)
- return -ENOSPC;
- p = xdr_encode_hyper(p, cb->acceptor_addrtype);
- /* cb->acceptor_address */
- err = gssx_enc_buffer(xdr, &cb->acceptor_address);
- if (err)
- return err;
- /* cb->application_data */
- err = gssx_enc_buffer(xdr, &cb->application_data);
- return err;
- }
- void gssx_enc_accept_sec_context(struct rpc_rqst *req,
- struct xdr_stream *xdr,
- struct gssx_arg_accept_sec_context *arg)
- {
- int err;
- err = gssx_enc_call_ctx(xdr, &arg->call_ctx);
- if (err)
- goto done;
- /* arg->context_handle */
- if (arg->context_handle)
- err = gssx_enc_ctx(xdr, arg->context_handle);
- else
- err = gssx_enc_bool(xdr, 0);
- if (err)
- goto done;
- /* arg->cred_handle */
- if (arg->cred_handle)
- err = gssx_enc_cred(xdr, arg->cred_handle);
- else
- err = gssx_enc_bool(xdr, 0);
- if (err)
- goto done;
- /* arg->input_token */
- err = gssx_enc_in_token(xdr, &arg->input_token);
- if (err)
- goto done;
- /* arg->input_cb */
- if (arg->input_cb)
- err = gssx_enc_cb(xdr, arg->input_cb);
- else
- err = gssx_enc_bool(xdr, 0);
- if (err)
- goto done;
- err = gssx_enc_bool(xdr, arg->ret_deleg_cred);
- if (err)
- goto done;
- /* leave options empty for now, will add once we have any options
- * to pass up at all */
- /* arg->options */
- err = dummy_enc_opt_array(xdr, &arg->options);
- xdr_inline_pages(&req->rq_rcv_buf,
- PAGE_SIZE/2 /* pretty arbitrary */,
- arg->pages, 0 /* page base */, arg->npages * PAGE_SIZE);
- done:
- if (err)
- dprintk("RPC: gssx_enc_accept_sec_context: %d\n", err);
- }
- int gssx_dec_accept_sec_context(struct rpc_rqst *rqstp,
- struct xdr_stream *xdr,
- struct gssx_res_accept_sec_context *res)
- {
- u32 value_follows;
- int err;
- struct page *scratch;
- scratch = alloc_page(GFP_KERNEL);
- if (!scratch)
- return -ENOMEM;
- xdr_set_scratch_buffer(xdr, page_address(scratch), PAGE_SIZE);
- /* res->status */
- err = gssx_dec_status(xdr, &res->status);
- if (err)
- goto out_free;
- /* res->context_handle */
- err = gssx_dec_bool(xdr, &value_follows);
- if (err)
- goto out_free;
- if (value_follows) {
- err = gssx_dec_ctx(xdr, res->context_handle);
- if (err)
- goto out_free;
- } else {
- res->context_handle = NULL;
- }
- /* res->output_token */
- err = gssx_dec_bool(xdr, &value_follows);
- if (err)
- goto out_free;
- if (value_follows) {
- err = gssx_dec_buffer(xdr, res->output_token);
- if (err)
- goto out_free;
- } else {
- res->output_token = NULL;
- }
- /* res->delegated_cred_handle */
- err = gssx_dec_bool(xdr, &value_follows);
- if (err)
- goto out_free;
- if (value_follows) {
- /* we do not support upcall servers sending this data. */
- err = -EINVAL;
- goto out_free;
- }
- /* res->options */
- err = gssx_dec_option_array(xdr, &res->options);
- out_free:
- __free_page(scratch);
- return err;
- }
|