Merge commit 'v2.6.30' into for-2.6.31
[safe/jmp/linux-2.6] / fs / nfs / callback.c
index 2e5de77..a886e69 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/mutex.h>
 #include <linux/freezer.h>
 #include <linux/kthread.h>
+#include <linux/sunrpc/svcauth_gss.h>
 
 #include <net/inet_sock.h>
 
@@ -27,7 +28,7 @@
 
 struct nfs_callback_data {
        unsigned int users;
-       struct svc_serv *serv;
+       struct svc_rqst *rqst;
        struct task_struct *task;
 };
 
@@ -37,6 +38,7 @@ static struct svc_program nfs4_callback_program;
 
 unsigned int nfs_callback_set_tcpport;
 unsigned short nfs_callback_tcpport;
+unsigned short nfs_callback_tcpport6;
 static const int nfs_set_port_min = 0;
 static const int nfs_set_port_max = 65535;
 
@@ -59,7 +61,7 @@ module_param_call(callback_tcpport, param_set_port, param_get_int,
 static int
 nfs_callback_svc(void *vrqstp)
 {
-       int err;
+       int err, preverr = 0;
        struct svc_rqst *rqstp = vrqstp;
 
        set_freezable();
@@ -74,32 +76,34 @@ nfs_callback_svc(void *vrqstp)
                 * Listen for a request on the socket
                 */
                err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT);
-               if (err == -EAGAIN || err == -EINTR)
+               if (err == -EAGAIN || err == -EINTR) {
+                       preverr = err;
                        continue;
+               }
                if (err < 0) {
-                       printk(KERN_WARNING
-                                       "%s: terminating on error %d\n",
-                                       __FUNCTION__, -err);
-                       break;
+                       if (err != preverr) {
+                               printk(KERN_WARNING "%s: unexpected error "
+                                       "from svc_recv (%d)\n", __func__, err);
+                               preverr = err;
+                       }
+                       schedule_timeout_uninterruptible(HZ);
+                       continue;
                }
+               preverr = err;
                svc_process(rqstp);
        }
        unlock_kernel();
-       nfs_callback_info.task = NULL;
-       svc_exit_thread(rqstp);
        return 0;
 }
 
 /*
- * Bring up the server process if it is not already up.
+ * Bring up the callback thread if it is not already up.
  */
 int nfs_callback_up(void)
 {
        struct svc_serv *serv = NULL;
-       struct svc_rqst *rqstp;
        int ret = 0;
 
-       lock_kernel();
        mutex_lock(&nfs_callback_mutex);
        if (nfs_callback_info.users++ || nfs_callback_info.task != NULL)
                goto out;
@@ -108,29 +112,42 @@ int nfs_callback_up(void)
        if (!serv)
                goto out_err;
 
-       ret = svc_create_xprt(serv, "tcp", nfs_callback_set_tcpport,
-                             SVC_SOCK_ANONYMOUS);
+       ret = svc_create_xprt(serv, "tcp", PF_INET,
+                               nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
        if (ret <= 0)
                goto out_err;
        nfs_callback_tcpport = ret;
-       dprintk("Callback port = 0x%x\n", nfs_callback_tcpport);
+       dprintk("NFS: Callback listener port = %u (af %u)\n",
+                       nfs_callback_tcpport, PF_INET);
 
-       rqstp = svc_prepare_thread(serv, &serv->sv_pools[0]);
-       if (IS_ERR(rqstp)) {
-               ret = PTR_ERR(rqstp);
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       ret = svc_create_xprt(serv, "tcp", PF_INET6,
+                               nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
+       if (ret > 0) {
+               nfs_callback_tcpport6 = ret;
+               dprintk("NFS: Callback listener port = %u (af %u)\n",
+                               nfs_callback_tcpport6, PF_INET6);
+       } else if (ret != -EAFNOSUPPORT)
+               goto out_err;
+#endif /* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */
+
+       nfs_callback_info.rqst = svc_prepare_thread(serv, &serv->sv_pools[0]);
+       if (IS_ERR(nfs_callback_info.rqst)) {
+               ret = PTR_ERR(nfs_callback_info.rqst);
+               nfs_callback_info.rqst = NULL;
                goto out_err;
        }
 
        svc_sock_update_bufs(serv);
-       nfs_callback_info.serv = serv;
 
-       nfs_callback_info.task = kthread_run(nfs_callback_svc, rqstp,
+       nfs_callback_info.task = kthread_run(nfs_callback_svc,
+                                            nfs_callback_info.rqst,
                                             "nfsv4-svc");
        if (IS_ERR(nfs_callback_info.task)) {
                ret = PTR_ERR(nfs_callback_info.task);
-               nfs_callback_info.serv = NULL;
+               svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.rqst = NULL;
                nfs_callback_info.task = NULL;
-               svc_exit_thread(rqstp);
                goto out_err;
        }
 out:
@@ -143,56 +160,82 @@ out:
        if (serv)
                svc_destroy(serv);
        mutex_unlock(&nfs_callback_mutex);
-       unlock_kernel();
        return ret;
 out_err:
-       dprintk("Couldn't create callback socket or server thread; err = %d\n",
-               ret);
+       dprintk("NFS: Couldn't create callback socket or server thread; "
+               "err = %d\n", ret);
        nfs_callback_info.users--;
        goto out;
 }
 
 /*
- * Kill the server process if it is not already up.
+ * Kill the callback thread if it's no longer being used.
  */
 void nfs_callback_down(void)
 {
-       lock_kernel();
        mutex_lock(&nfs_callback_mutex);
        nfs_callback_info.users--;
-       if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL)
+       if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
                kthread_stop(nfs_callback_info.task);
+               svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.rqst = NULL;
+               nfs_callback_info.task = NULL;
+       }
        mutex_unlock(&nfs_callback_mutex);
-       unlock_kernel();
+}
+
+static int check_gss_callback_principal(struct nfs_client *clp,
+                                       struct svc_rqst *rqstp)
+{
+       struct rpc_clnt *r = clp->cl_rpcclient;
+       char *p = svc_gss_principal(rqstp);
+
+       /*
+        * It might just be a normal user principal, in which case
+        * userspace won't bother to tell us the name at all.
+        */
+       if (p == NULL)
+               return SVC_DENIED;
+
+       /* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */
+
+       if (memcmp(p, "nfs@", 4) != 0)
+               return SVC_DENIED;
+       p += 4;
+       if (strcmp(p, r->cl_server) != 0)
+               return SVC_DENIED;
+       return SVC_OK;
 }
 
 static int nfs_callback_authenticate(struct svc_rqst *rqstp)
 {
        struct nfs_client *clp;
        RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]);
+       int ret = SVC_OK;
 
        /* Don't talk to strangers */
        clp = nfs_find_client(svc_addr(rqstp), 4);
        if (clp == NULL)
                return SVC_DROP;
 
-       dprintk("%s: %s NFSv4 callback!\n", __FUNCTION__,
+       dprintk("%s: %s NFSv4 callback!\n", __func__,
                        svc_print_addr(rqstp, buf, sizeof(buf)));
-       nfs_put_client(clp);
 
        switch (rqstp->rq_authop->flavour) {
                case RPC_AUTH_NULL:
                        if (rqstp->rq_proc != CB_NULL)
-                               return SVC_DENIED;
+                               ret = SVC_DENIED;
                        break;
                case RPC_AUTH_UNIX:
                        break;
                case RPC_AUTH_GSS:
-                       /* FIXME: RPCSEC_GSS handling? */
+                       ret = check_gss_callback_principal(clp, rqstp);
+                       break;
                default:
-                       return SVC_DENIED;
+                       ret = SVC_DENIED;
        }
-       return SVC_OK;
+       nfs_put_client(clp);
+       return ret;
 }
 
 /*