Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[pandora-kernel.git] / net / sunrpc / auth_gss / auth_gss.c
index 55c47ae..4bbc59c 100644 (file)
@@ -57,6 +57,7 @@
 static const struct rpc_authops authgss_ops;
 
 static const struct rpc_credops gss_credops;
+static const struct rpc_credops gss_nullops;
 
 #ifdef RPC_DEBUG
 # define RPCDBG_FACILITY       RPCDBG_AUTH
@@ -64,7 +65,6 @@ static const struct rpc_credops gss_credops;
 
 #define NFS_NGROUPS    16
 
-#define GSS_CRED_EXPIRE                (60 * HZ)       /* XXX: reasonable? */
 #define GSS_CRED_SLACK         1024            /* XXX: unused */
 /* length of a krb5 verifier (48), plus data added before arguments when
  * using integrity (two 4-byte integers): */
@@ -79,9 +79,8 @@ static const struct rpc_credops gss_credops;
 /* dump the buffer in `emacs-hexl' style */
 #define isprint(c)      ((c > 0x1f) && (c < 0x7f))
 
-static DEFINE_RWLOCK(gss_ctx_lock);
-
 struct gss_auth {
+       struct kref kref;
        struct rpc_auth rpc_auth;
        struct gss_api_mech *mech;
        enum rpc_gss_svc service;
@@ -89,7 +88,7 @@ struct gss_auth {
        struct dentry *dentry;
 };
 
-static void gss_destroy_ctx(struct gss_cl_ctx *);
+static void gss_free_ctx(struct gss_cl_ctx *);
 static struct rpc_pipe_ops gss_upcall_ops;
 
 static inline struct gss_cl_ctx *
@@ -103,20 +102,24 @@ static inline void
 gss_put_ctx(struct gss_cl_ctx *ctx)
 {
        if (atomic_dec_and_test(&ctx->count))
-               gss_destroy_ctx(ctx);
+               gss_free_ctx(ctx);
 }
 
+/* gss_cred_set_ctx:
+ * called by gss_upcall_callback and gss_create_upcall in order
+ * to set the gss context. The actual exchange of an old context
+ * and a new one is protected by the inode->i_lock.
+ */
 static void
 gss_cred_set_ctx(struct rpc_cred *cred, struct gss_cl_ctx *ctx)
 {
        struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
        struct gss_cl_ctx *old;
-       write_lock(&gss_ctx_lock);
+
        old = gss_cred->gc_ctx;
-       gss_cred->gc_ctx = ctx;
+       rcu_assign_pointer(gss_cred->gc_ctx, ctx);
        set_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
        clear_bit(RPCAUTH_CRED_NEW, &cred->cr_flags);
-       write_unlock(&gss_ctx_lock);
        if (old)
                gss_put_ctx(old);
 }
@@ -127,10 +130,10 @@ gss_cred_is_uptodate_ctx(struct rpc_cred *cred)
        struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
        int res = 0;
 
-       read_lock(&gss_ctx_lock);
+       rcu_read_lock();
        if (test_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags) && gss_cred->gc_ctx)
                res = 1;
-       read_unlock(&gss_ctx_lock);
+       rcu_read_unlock();
        return res;
 }
 
@@ -169,10 +172,10 @@ gss_cred_get_ctx(struct rpc_cred *cred)
        struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
        struct gss_cl_ctx *ctx = NULL;
 
-       read_lock(&gss_ctx_lock);
+       rcu_read_lock();
        if (gss_cred->gc_ctx)
                ctx = gss_get_ctx(gss_cred->gc_ctx);
-       read_unlock(&gss_ctx_lock);
+       rcu_read_unlock();
        return ctx;
 }
 
@@ -334,11 +337,11 @@ gss_upcall_callback(struct rpc_task *task)
        struct gss_upcall_msg *gss_msg = gss_cred->gc_upcall;
        struct inode *inode = gss_msg->auth->dentry->d_inode;
 
+       spin_lock(&inode->i_lock);
        if (gss_msg->ctx)
                gss_cred_set_ctx(task->tk_msg.rpc_cred, gss_get_ctx(gss_msg->ctx));
        else
                task->tk_status = gss_msg->msg.errno;
-       spin_lock(&inode->i_lock);
        gss_cred->gc_upcall = NULL;
        rpc_wake_up_status(&gss_msg->rpc_waitqueue, gss_msg->msg.errno);
        spin_unlock(&inode->i_lock);
@@ -441,7 +444,6 @@ gss_create_upcall(struct gss_auth *gss_auth, struct gss_cred *gss_cred)
                prepare_to_wait(&gss_msg->waitqueue, &wait, TASK_INTERRUPTIBLE);
                spin_lock(&inode->i_lock);
                if (gss_msg->ctx != NULL || gss_msg->msg.errno < 0) {
-                       spin_unlock(&inode->i_lock);
                        break;
                }
                spin_unlock(&inode->i_lock);
@@ -455,6 +457,7 @@ gss_create_upcall(struct gss_auth *gss_auth, struct gss_cred *gss_cred)
                gss_cred_set_ctx(cred, gss_get_ctx(gss_msg->ctx));
        else
                err = gss_msg->msg.errno;
+       spin_unlock(&inode->i_lock);
 out_intr:
        finish_wait(&gss_msg->waitqueue, &wait);
        gss_release_msg(gss_msg);
@@ -635,6 +638,7 @@ gss_create(struct rpc_clnt *clnt, rpc_authflavor_t flavor)
        auth->au_ops = &authgss_ops;
        auth->au_flavor = flavor;
        atomic_set(&auth->au_count, 1);
+       kref_init(&gss_auth->kref);
 
        gss_auth->dentry = rpc_mkpipe(clnt->cl_dentry, gss_auth->mech->gm_name,
                        clnt, &gss_upcall_ops, RPC_PIPE_WAIT_FOR_OPEN);
@@ -643,7 +647,7 @@ gss_create(struct rpc_clnt *clnt, rpc_authflavor_t flavor)
                goto err_put_mech;
        }
 
-       err = rpcauth_init_credcache(auth, GSS_CRED_EXPIRE);
+       err = rpcauth_init_credcache(auth);
        if (err)
                goto err_unlink_pipe;
 
@@ -659,6 +663,25 @@ out_dec:
        return ERR_PTR(err);
 }
 
+static void
+gss_free(struct gss_auth *gss_auth)
+{
+       rpc_unlink(gss_auth->dentry);
+       gss_auth->dentry = NULL;
+       gss_mech_put(gss_auth->mech);
+
+       kfree(gss_auth);
+       module_put(THIS_MODULE);
+}
+
+static void
+gss_free_callback(struct kref *kref)
+{
+       struct gss_auth *gss_auth = container_of(kref, struct gss_auth, kref);
+
+       gss_free(gss_auth);
+}
+
 static void
 gss_destroy(struct rpc_auth *auth)
 {
@@ -670,21 +693,48 @@ gss_destroy(struct rpc_auth *auth)
        rpcauth_destroy_credcache(auth);
 
        gss_auth = container_of(auth, struct gss_auth, rpc_auth);
-       rpc_unlink(gss_auth->dentry);
-       gss_auth->dentry = NULL;
-       gss_mech_put(gss_auth->mech);
+       kref_put(&gss_auth->kref, gss_free_callback);
+}
 
-       kfree(gss_auth);
-       module_put(THIS_MODULE);
+/*
+ * gss_destroying_context will cause the RPCSEC_GSS to send a NULL RPC call
+ * to the server with the GSS control procedure field set to
+ * RPC_GSS_PROC_DESTROY. This should normally cause the server to release
+ * all RPCSEC_GSS state associated with that context.
+ */
+static int
+gss_destroying_context(struct rpc_cred *cred)
+{
+       struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
+       struct gss_auth *gss_auth = container_of(cred->cr_auth, struct gss_auth, rpc_auth);
+       struct rpc_task *task;
+
+       if (gss_cred->gc_ctx == NULL ||
+                       gss_cred->gc_ctx->gc_proc == RPC_GSS_PROC_DESTROY)
+               return 0;
+
+       gss_cred->gc_ctx->gc_proc = RPC_GSS_PROC_DESTROY;
+       cred->cr_ops = &gss_nullops;
+
+       /* Take a reference to ensure the cred will be destroyed either
+        * by the RPC call or by the put_rpccred() below */
+       get_rpccred(cred);
+
+       task = rpc_call_null(gss_auth->client, cred, RPC_TASK_ASYNC);
+       if (!IS_ERR(task))
+               rpc_put_task(task);
+
+       put_rpccred(cred);
+       return 1;
 }
 
-/* gss_destroy_cred (and gss_destroy_ctx) are used to clean up after failure
+/* gss_destroy_cred (and gss_free_ctx) are used to clean up after failure
  * to create a new cred or context, so they check that things have been
  * allocated before freeing them. */
 static void
-gss_destroy_ctx(struct gss_cl_ctx *ctx)
+gss_do_free_ctx(struct gss_cl_ctx *ctx)
 {
-       dprintk("RPC:       gss_destroy_ctx\n");
+       dprintk("RPC:       gss_free_ctx\n");
 
        if (ctx->gc_gss_ctx)
                gss_delete_sec_context(&ctx->gc_gss_ctx);
@@ -694,15 +744,46 @@ gss_destroy_ctx(struct gss_cl_ctx *ctx)
 }
 
 static void
-gss_destroy_cred(struct rpc_cred *rc)
+gss_free_ctx_callback(struct rcu_head *head)
 {
-       struct gss_cred *cred = container_of(rc, struct gss_cred, gc_base);
+       struct gss_cl_ctx *ctx = container_of(head, struct gss_cl_ctx, gc_rcu);
+       gss_do_free_ctx(ctx);
+}
 
-       dprintk("RPC:       gss_destroy_cred \n");
+static void
+gss_free_ctx(struct gss_cl_ctx *ctx)
+{
+       call_rcu(&ctx->gc_rcu, gss_free_ctx_callback);
+}
+
+static void
+gss_free_cred(struct gss_cred *gss_cred)
+{
+       dprintk("RPC:       gss_free_cred %p\n", gss_cred);
+       kfree(gss_cred);
+}
+
+static void
+gss_free_cred_callback(struct rcu_head *head)
+{
+       struct gss_cred *gss_cred = container_of(head, struct gss_cred, gc_base.cr_rcu);
+       gss_free_cred(gss_cred);
+}
+
+static void
+gss_destroy_cred(struct rpc_cred *cred)
+{
+       struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
+       struct gss_auth *gss_auth = container_of(cred->cr_auth, struct gss_auth, rpc_auth);
+       struct gss_cl_ctx *ctx = gss_cred->gc_ctx;
 
-       if (cred->gc_ctx)
-               gss_put_ctx(cred->gc_ctx);
-       kfree(cred);
+       if (gss_destroying_context(cred))
+               return;
+       rcu_assign_pointer(gss_cred->gc_ctx, NULL);
+       call_rcu(&cred->cr_rcu, gss_free_cred_callback);
+       if (ctx)
+               gss_put_ctx(ctx);
+       kref_put(&gss_auth->kref, gss_free_callback);
 }
 
 /*
@@ -734,6 +815,7 @@ gss_create_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
         */
        cred->gc_base.cr_flags = 1UL << RPCAUTH_CRED_NEW;
        cred->gc_service = gss_auth->service;
+       kref_get(&gss_auth->kref);
        return &cred->gc_base;
 
 out_err:
@@ -845,6 +927,13 @@ gss_refresh(struct rpc_task *task)
        return 0;
 }
 
+/* Dummy refresh routine: used only when destroying the context */
+static int
+gss_refresh_null(struct rpc_task *task)
+{
+       return -EACCES;
+}
+
 static __be32 *
 gss_validate(struct rpc_task *task, __be32 *p)
 {
@@ -874,11 +963,14 @@ gss_validate(struct rpc_task *task, __be32 *p)
        maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &verf_buf, &mic);
        if (maj_stat == GSS_S_CONTEXT_EXPIRED)
                clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
-       if (maj_stat)
+       if (maj_stat) {
+               dprintk("RPC: %5u gss_validate: gss_verify_mic returned"
+                               "error 0x%08x\n", task->tk_pid, maj_stat);
                goto out_bad;
+       }
        /* We leave it to unwrap to calculate au_rslack. For now we just
         * calculate the length of the verifier: */
-       task->tk_auth->au_verfsize = XDR_QUADLEN(len) + 2;
+       cred->cr_auth->au_verfsize = XDR_QUADLEN(len) + 2;
        gss_put_ctx(ctx);
        dprintk("RPC: %5u gss_validate: gss_verify_mic succeeded.\n",
                        task->tk_pid);
@@ -907,7 +999,7 @@ gss_wrap_req_integ(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
        offset = (u8 *)p - (u8 *)snd_buf->head[0].iov_base;
        *p++ = htonl(rqstp->rq_seqno);
 
-       status = encode(rqstp, p, obj);
+       status = rpc_call_xdrproc(encode, rqstp, p, obj);
        if (status)
                return status;
 
@@ -1001,7 +1093,7 @@ gss_wrap_req_priv(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
        offset = (u8 *)p - (u8 *)snd_buf->head[0].iov_base;
        *p++ = htonl(rqstp->rq_seqno);
 
-       status = encode(rqstp, p, obj);
+       status = rpc_call_xdrproc(encode, rqstp, p, obj);
        if (status)
                return status;
 
@@ -1060,12 +1152,12 @@ gss_wrap_req(struct rpc_task *task,
                /* The spec seems a little ambiguous here, but I think that not
                 * wrapping context destruction requests makes the most sense.
                 */
-               status = encode(rqstp, p, obj);
+               status = rpc_call_xdrproc(encode, rqstp, p, obj);
                goto out;
        }
        switch (gss_cred->gc_service) {
                case RPC_GSS_SVC_NONE:
-                       status = encode(rqstp, p, obj);
+                       status = rpc_call_xdrproc(encode, rqstp, p, obj);
                        break;
                case RPC_GSS_SVC_INTEGRITY:
                        status = gss_wrap_req_integ(cred, ctx, encode,
@@ -1178,10 +1270,10 @@ gss_unwrap_resp(struct rpc_task *task,
                        break;
        }
        /* take into account extra slack for integrity and privacy cases: */
-       task->tk_auth->au_rslack = task->tk_auth->au_verfsize + (p - savedp)
+       cred->cr_auth->au_rslack = cred->cr_auth->au_verfsize + (p - savedp)
                                                + (savedlen - head->iov_len);
 out_decode:
-       status = decode(rqstp, p, obj);
+       status = rpc_call_xdrproc(decode, rqstp, p, obj);
 out:
        gss_put_ctx(ctx);
        dprintk("RPC: %5u gss_unwrap_resp returning %d\n", task->tk_pid,
@@ -1213,6 +1305,17 @@ static const struct rpc_credops gss_credops = {
        .crunwrap_resp  = gss_unwrap_resp,
 };
 
+static const struct rpc_credops gss_nullops = {
+       .cr_name        = "AUTH_GSS",
+       .crdestroy      = gss_destroy_cred,
+       .crmatch        = gss_match,
+       .crmarshal      = gss_marshal,
+       .crrefresh      = gss_refresh_null,
+       .crvalidate     = gss_validate,
+       .crwrap_req     = gss_wrap_req,
+       .crunwrap_resp  = gss_unwrap_resp,
+};
+
 static struct rpc_pipe_ops gss_upcall_ops = {
        .upcall         = gss_pipe_upcall,
        .downcall       = gss_pipe_downcall,