Merge branch 'for-3.10' of git://linux-nfs.org/~bfields/linux
[firefly-linux-kernel-4.4.55.git] / net / sunrpc / auth_gss / svcauth_gss.c
index c3ba570222dcbdeff872dc099ac3e6c38ea17ab5..871c73c921654b14b14eface6b4f2cfe0f27d5d2 100644 (file)
@@ -48,8 +48,8 @@
 #include <linux/sunrpc/svcauth.h>
 #include <linux/sunrpc/svcauth_gss.h>
 #include <linux/sunrpc/cache.h>
+#include "gss_rpc_upcall.h"
 
-#include "../netns.h"
 
 #ifdef RPC_DEBUG
 # define RPCDBG_FACILITY       RPCDBG_AUTH
@@ -497,7 +497,8 @@ static int rsc_parse(struct cache_detail *cd,
                len = qword_get(&mesg, buf, mlen);
                if (len < 0)
                        goto out;
-               status = gss_import_sec_context(buf, len, gm, &rsci.mechctx, GFP_KERNEL);
+               status = gss_import_sec_context(buf, len, gm, &rsci.mechctx,
+                                               NULL, GFP_KERNEL);
                if (status)
                        goto out;
 
@@ -505,8 +506,10 @@ static int rsc_parse(struct cache_detail *cd,
                len = qword_get(&mesg, buf, mlen);
                if (len > 0) {
                        rsci.cred.cr_principal = kstrdup(buf, GFP_KERNEL);
-                       if (!rsci.cred.cr_principal)
+                       if (!rsci.cred.cr_principal) {
+                               status = -ENOMEM;
                                goto out;
+                       }
                }
 
        }
@@ -987,13 +990,10 @@ gss_write_init_verf(struct cache_detail *cd, struct svc_rqst *rqstp,
 }
 
 static inline int
-gss_read_verf(struct rpc_gss_wire_cred *gc,
-             struct kvec *argv, __be32 *authp,
-             struct xdr_netobj *in_handle,
-             struct xdr_netobj *in_token)
+gss_read_common_verf(struct rpc_gss_wire_cred *gc,
+                    struct kvec *argv, __be32 *authp,
+                    struct xdr_netobj *in_handle)
 {
-       struct xdr_netobj tmpobj;
-
        /* Read the verifier; should be NULL: */
        *authp = rpc_autherr_badverf;
        if (argv->iov_len < 2 * 4)
@@ -1009,6 +1009,23 @@ gss_read_verf(struct rpc_gss_wire_cred *gc,
        if (dup_netobj(in_handle, &gc->gc_ctx))
                return SVC_CLOSE;
        *authp = rpc_autherr_badverf;
+
+       return 0;
+}
+
+static inline int
+gss_read_verf(struct rpc_gss_wire_cred *gc,
+             struct kvec *argv, __be32 *authp,
+             struct xdr_netobj *in_handle,
+             struct xdr_netobj *in_token)
+{
+       struct xdr_netobj tmpobj;
+       int res;
+
+       res = gss_read_common_verf(gc, argv, authp, in_handle);
+       if (res)
+               return res;
+
        if (svc_safe_getnetobj(argv, &tmpobj)) {
                kfree(in_handle->data);
                return SVC_DENIED;
@@ -1021,6 +1038,40 @@ gss_read_verf(struct rpc_gss_wire_cred *gc,
        return 0;
 }
 
+/* Ok this is really heavily depending on a set of semantics in
+ * how rqstp is set up by svc_recv and pages laid down by the
+ * server when reading a request. We are basically guaranteed that
+ * the token lays all down linearly across a set of pages, starting
+ * at iov_base in rq_arg.head[0] which happens to be the first of a
+ * set of pages stored in rq_pages[].
+ * rq_arg.head[0].iov_base will provide us the page_base to pass
+ * to the upcall.
+ */
+static inline int
+gss_read_proxy_verf(struct svc_rqst *rqstp,
+                   struct rpc_gss_wire_cred *gc, __be32 *authp,
+                   struct xdr_netobj *in_handle,
+                   struct gssp_in_token *in_token)
+{
+       struct kvec *argv = &rqstp->rq_arg.head[0];
+       u32 inlen;
+       int res;
+
+       res = gss_read_common_verf(gc, argv, authp, in_handle);
+       if (res)
+               return res;
+
+       inlen = svc_getnl(argv);
+       if (inlen > (argv->iov_len + rqstp->rq_arg.page_len))
+               return SVC_DENIED;
+
+       in_token->pages = rqstp->rq_pages;
+       in_token->page_base = (ulong)argv->iov_base & ~PAGE_MASK;
+       in_token->page_len = inlen;
+
+       return 0;
+}
+
 static inline int
 gss_write_resv(struct kvec *resv, size_t size_limit,
               struct xdr_netobj *out_handle, struct xdr_netobj *out_token,
@@ -1048,7 +1099,7 @@ gss_write_resv(struct kvec *resv, size_t size_limit,
  * the upcall results are available, write the verifier and result.
  * Otherwise, drop the request pending an answer to the upcall.
  */
-static int svcauth_gss_handle_init(struct svc_rqst *rqstp,
+static int svcauth_gss_legacy_init(struct svc_rqst *rqstp,
                        struct rpc_gss_wire_cred *gc, __be32 *authp)
 {
        struct kvec *argv = &rqstp->rq_arg.head[0];
@@ -1088,6 +1139,287 @@ out:
        return ret;
 }
 
+static int gss_proxy_save_rsc(struct cache_detail *cd,
+                               struct gssp_upcall_data *ud,
+                               uint64_t *handle)
+{
+       struct rsc rsci, *rscp = NULL;
+       static atomic64_t ctxhctr;
+       long long ctxh;
+       struct gss_api_mech *gm = NULL;
+       time_t expiry;
+       int status = -EINVAL;
+
+       memset(&rsci, 0, sizeof(rsci));
+       /* context handle */
+       status = -ENOMEM;
+       /* the handle needs to be just a unique id,
+        * use a static counter */
+       ctxh = atomic64_inc_return(&ctxhctr);
+
+       /* make a copy for the caller */
+       *handle = ctxh;
+
+       /* make a copy for the rsc cache */
+       if (dup_to_netobj(&rsci.handle, (char *)handle, sizeof(uint64_t)))
+               goto out;
+       rscp = rsc_lookup(cd, &rsci);
+       if (!rscp)
+               goto out;
+
+       /* creds */
+       if (!ud->found_creds) {
+               /* userspace seem buggy, we should always get at least a
+                * mapping to nobody */
+               dprintk("RPC:       No creds found, marking Negative!\n");
+               set_bit(CACHE_NEGATIVE, &rsci.h.flags);
+       } else {
+
+               /* steal creds */
+               rsci.cred = ud->creds;
+               memset(&ud->creds, 0, sizeof(struct svc_cred));
+
+               status = -EOPNOTSUPP;
+               /* get mech handle from OID */
+               gm = gss_mech_get_by_OID(&ud->mech_oid);
+               if (!gm)
+                       goto out;
+
+               status = -EINVAL;
+               /* mech-specific data: */
+               status = gss_import_sec_context(ud->out_handle.data,
+                                               ud->out_handle.len,
+                                               gm, &rsci.mechctx,
+                                               &expiry, GFP_KERNEL);
+               if (status)
+                       goto out;
+       }
+
+       rsci.h.expiry_time = expiry;
+       rscp = rsc_update(cd, &rsci, rscp);
+       status = 0;
+out:
+       gss_mech_put(gm);
+       rsc_free(&rsci);
+       if (rscp)
+               cache_put(&rscp->h, cd);
+       else
+               status = -ENOMEM;
+       return status;
+}
+
+static int svcauth_gss_proxy_init(struct svc_rqst *rqstp,
+                       struct rpc_gss_wire_cred *gc, __be32 *authp)
+{
+       struct kvec *resv = &rqstp->rq_res.head[0];
+       struct xdr_netobj cli_handle;
+       struct gssp_upcall_data ud;
+       uint64_t handle;
+       int status;
+       int ret;
+       struct net *net = rqstp->rq_xprt->xpt_net;
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+       memset(&ud, 0, sizeof(ud));
+       ret = gss_read_proxy_verf(rqstp, gc, authp,
+                                 &ud.in_handle, &ud.in_token);
+       if (ret)
+               return ret;
+
+       ret = SVC_CLOSE;
+
+       /* Perform synchronous upcall to gss-proxy */
+       status = gssp_accept_sec_context_upcall(net, &ud);
+       if (status)
+               goto out;
+
+       dprintk("RPC:       svcauth_gss: gss major status = %d\n",
+                       ud.major_status);
+
+       switch (ud.major_status) {
+       case GSS_S_CONTINUE_NEEDED:
+               cli_handle = ud.out_handle;
+               break;
+       case GSS_S_COMPLETE:
+               status = gss_proxy_save_rsc(sn->rsc_cache, &ud, &handle);
+               if (status)
+                       goto out;
+               cli_handle.data = (u8 *)&handle;
+               cli_handle.len = sizeof(handle);
+               break;
+       default:
+               ret = SVC_CLOSE;
+               goto out;
+       }
+
+       /* Got an answer to the upcall; use it: */
+       if (gss_write_init_verf(sn->rsc_cache, rqstp,
+                               &cli_handle, &ud.major_status))
+               goto out;
+       if (gss_write_resv(resv, PAGE_SIZE,
+                          &cli_handle, &ud.out_token,
+                          ud.major_status, ud.minor_status))
+               goto out;
+
+       ret = SVC_COMPLETE;
+out:
+       gssp_free_upcall_data(&ud);
+       return ret;
+}
+
+DEFINE_SPINLOCK(use_gssp_lock);
+
+static bool use_gss_proxy(struct net *net)
+{
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+       if (sn->use_gss_proxy != -1)
+               return sn->use_gss_proxy;
+       spin_lock(&use_gssp_lock);
+       /*
+        * If you wanted gss-proxy, you should have said so before
+        * starting to accept requests:
+        */
+       sn->use_gss_proxy = 0;
+       spin_unlock(&use_gssp_lock);
+       return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static bool set_gss_proxy(struct net *net, int type)
+{
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+       int ret = 0;
+
+       WARN_ON_ONCE(type != 0 && type != 1);
+       spin_lock(&use_gssp_lock);
+       if (sn->use_gss_proxy == -1 || sn->use_gss_proxy == type)
+               sn->use_gss_proxy = type;
+       else
+               ret = -EBUSY;
+       spin_unlock(&use_gssp_lock);
+       wake_up(&sn->gssp_wq);
+       return ret;
+}
+
+static inline bool gssp_ready(struct sunrpc_net *sn)
+{
+       switch (sn->use_gss_proxy) {
+               case -1:
+                       return false;
+               case 0:
+                       return true;
+               case 1:
+                       return sn->gssp_clnt;
+       }
+       WARN_ON_ONCE(1);
+       return false;
+}
+
+static int wait_for_gss_proxy(struct net *net)
+{
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+       return wait_event_interruptible(sn->gssp_wq, gssp_ready(sn));
+}
+
+
+static ssize_t write_gssp(struct file *file, const char __user *buf,
+                        size_t count, loff_t *ppos)
+{
+       struct net *net = PDE_DATA(file->f_path.dentry->d_inode);
+       char tbuf[20];
+       unsigned long i;
+       int res;
+
+       if (*ppos || count > sizeof(tbuf)-1)
+               return -EINVAL;
+       if (copy_from_user(tbuf, buf, count))
+               return -EFAULT;
+
+       tbuf[count] = 0;
+       res = kstrtoul(tbuf, 0, &i);
+       if (res)
+               return res;
+       if (i != 1)
+               return -EINVAL;
+       res = set_gss_proxy(net, 1);
+       if (res)
+               return res;
+       res = set_gssp_clnt(net);
+       if (res)
+               return res;
+       return count;
+}
+
+static ssize_t read_gssp(struct file *file, char __user *buf,
+                        size_t count, loff_t *ppos)
+{
+       struct net *net = PDE_DATA(file->f_path.dentry->d_inode);
+       unsigned long p = *ppos;
+       char tbuf[10];
+       size_t len;
+       int ret;
+
+       ret = wait_for_gss_proxy(net);
+       if (ret)
+               return ret;
+
+       snprintf(tbuf, sizeof(tbuf), "%d\n", use_gss_proxy(net));
+       len = strlen(tbuf);
+       if (p >= len)
+               return 0;
+       len -= p;
+       if (len > count)
+               len = count;
+       if (copy_to_user(buf, (void *)(tbuf+p), len))
+               return -EFAULT;
+       *ppos += len;
+       return len;
+}
+
+static const struct file_operations use_gss_proxy_ops = {
+       .open = nonseekable_open,
+       .write = write_gssp,
+       .read = read_gssp,
+};
+
+static int create_use_gss_proxy_proc_entry(struct net *net)
+{
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+       struct proc_dir_entry **p = &sn->use_gssp_proc;
+
+       sn->use_gss_proxy = -1;
+       *p = proc_create_data("use-gss-proxy", S_IFREG|S_IRUSR|S_IWUSR,
+                             sn->proc_net_rpc,
+                             &use_gss_proxy_ops, net);
+       if (!*p)
+               return -ENOMEM;
+       init_gssp_clnt(sn);
+       return 0;
+}
+
+static void destroy_use_gss_proxy_proc_entry(struct net *net)
+{
+       struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+       if (sn->use_gssp_proc) {
+               remove_proc_entry("use-gss-proxy", sn->proc_net_rpc); 
+               clear_gssp_clnt(sn);
+       }
+}
+#else /* CONFIG_PROC_FS */
+
+static int create_use_gss_proxy_proc_entry(struct net *net)
+{
+       return 0;
+}
+
+static void destroy_use_gss_proxy_proc_entry(struct net *net) {}
+
+#endif /* CONFIG_PROC_FS */
+
 /*
  * Accept an rpcsec packet.
  * If context establishment, punt to user space
@@ -1154,7 +1486,10 @@ svcauth_gss_accept(struct svc_rqst *rqstp, __be32 *authp)
        switch (gc->gc_proc) {
        case RPC_GSS_PROC_INIT:
        case RPC_GSS_PROC_CONTINUE_INIT:
-               return svcauth_gss_handle_init(rqstp, gc, authp);
+               if (use_gss_proxy(SVC_NET(rqstp)))
+                       return svcauth_gss_proxy_init(rqstp, gc, authp);
+               else
+                       return svcauth_gss_legacy_init(rqstp, gc, authp);
        case RPC_GSS_PROC_DATA:
        case RPC_GSS_PROC_DESTROY:
                /* Look up the context, and check the verifier: */
@@ -1531,7 +1866,12 @@ gss_svc_init_net(struct net *net)
        rv = rsi_cache_create_net(net);
        if (rv)
                goto out1;
+       rv = create_use_gss_proxy_proc_entry(net);
+       if (rv)
+               goto out2;
        return 0;
+out2:
+       destroy_use_gss_proxy_proc_entry(net);
 out1:
        rsc_cache_destroy_net(net);
        return rv;
@@ -1540,6 +1880,7 @@ out1:
 void
 gss_svc_shutdown_net(struct net *net)
 {
+       destroy_use_gss_proxy_proc_entry(net);
        rsi_cache_destroy_net(net);
        rsc_cache_destroy_net(net);
 }