]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/nfs/nfs4proc.c
Merge tag 'nfs-for-3.12-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[karo-tx-linux.git] / fs / nfs / nfs4proc.c
index 6807e9b8b593f7d4abaff23deb3654f845e227c8..39b6cf2d1683fae228f71d1954383f53550e0439 100644 (file)
@@ -152,6 +152,7 @@ static int nfs4_map_errors(int err)
        case -NFS4ERR_RECALLCONFLICT:
                return -EREMOTEIO;
        case -NFS4ERR_WRONGSEC:
+       case -NFS4ERR_WRONG_CRED:
                return -EPERM;
        case -NFS4ERR_BADOWNER:
        case -NFS4ERR_BADNAME:
@@ -1610,10 +1611,20 @@ int nfs4_open_delegation_recall(struct nfs_open_context *ctx, struct nfs4_state
        return nfs4_handle_delegation_recall_error(server, state, stateid, err);
 }
 
+static void nfs4_open_confirm_prepare(struct rpc_task *task, void *calldata)
+{
+       struct nfs4_opendata *data = calldata;
+
+       nfs40_setup_sequence(data->o_arg.server, &data->o_arg.seq_args,
+                               &data->o_res.seq_res, task);
+}
+
 static void nfs4_open_confirm_done(struct rpc_task *task, void *calldata)
 {
        struct nfs4_opendata *data = calldata;
 
+       nfs40_sequence_done(task, &data->o_res.seq_res);
+
        data->rpc_status = task->tk_status;
        if (data->rpc_status == 0) {
                nfs4_stateid_copy(&data->o_res.stateid, &data->c_res.stateid);
@@ -1642,6 +1653,7 @@ out_free:
 }
 
 static const struct rpc_call_ops nfs4_open_confirm_ops = {
+       .rpc_call_prepare = nfs4_open_confirm_prepare,
        .rpc_call_done = nfs4_open_confirm_done,
        .rpc_release = nfs4_open_confirm_release,
 };
@@ -1669,6 +1681,7 @@ static int _nfs4_proc_open_confirm(struct nfs4_opendata *data)
        };
        int status;
 
+       nfs4_init_sequence(&data->o_arg.seq_args, &data->o_res.seq_res, 1);
        kref_get(&data->kref);
        data->rpc_done = 0;
        data->rpc_status = 0;
@@ -2603,6 +2616,9 @@ int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait)
        };
        int status = -ENOMEM;
 
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_CLEANUP,
+               &task_setup_data.rpc_client, &msg);
+
        calldata = kzalloc(sizeof(*calldata), gfp_mask);
        if (calldata == NULL)
                goto out;
@@ -2868,18 +2884,27 @@ static int nfs4_do_find_root_sec(struct nfs_server *server,
  * @server: initialized nfs_server handle
  * @fhandle: we fill in the pseudo-fs root file handle
  * @info: we fill in an FSINFO struct
+ * @auth_probe: probe the auth flavours
  *
  * Returns zero on success, or a negative errno.
  */
 int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle,
-                        struct nfs_fsinfo *info)
+                        struct nfs_fsinfo *info,
+                        bool auth_probe)
 {
        int status;
 
-       status = nfs4_lookup_root(server, fhandle, info);
-       if ((status == -NFS4ERR_WRONGSEC) &&
-           !(server->flags & NFS_MOUNT_SECFLAVOUR))
+       switch (auth_probe) {
+       case false:
+               status = nfs4_lookup_root(server, fhandle, info);
+               if (status != -NFS4ERR_WRONGSEC)
+                       break;
+               /* Did user force a 'sec=' mount option? */
+               if (server->flags & NFS_MOUNT_SECFLAVOUR)
+                       break;
+       default:
                status = nfs4_do_find_root_sec(server, fhandle, info);
+       }
 
        if (status == 0)
                status = nfs4_server_capabilities(server, fhandle);
@@ -3129,7 +3154,9 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
                        err = -EPERM;
                        if (client != *clnt)
                                goto out;
-
+                       /* No security negotiation if the user specified 'sec=' */
+                       if (NFS_SERVER(dir)->flags & NFS_MOUNT_SECFLAVOUR)
+                               goto out;
                        client = nfs4_create_sec_client(client, dir, name);
                        if (IS_ERR(client))
                                return PTR_ERR(client);
@@ -3169,15 +3196,13 @@ struct rpc_clnt *
 nfs4_proc_lookup_mountpoint(struct inode *dir, struct qstr *name,
                            struct nfs_fh *fhandle, struct nfs_fattr *fattr)
 {
+       struct rpc_clnt *client = NFS_CLIENT(dir);
        int status;
-       struct rpc_clnt *client = rpc_clone_client(NFS_CLIENT(dir));
 
        status = nfs4_proc_lookup_common(&client, dir, name, fhandle, fattr, NULL);
-       if (status < 0) {
-               rpc_shutdown_client(client);
+       if (status < 0)
                return ERR_PTR(status);
-       }
-       return client;
+       return (client == NFS_CLIENT(dir)) ? rpc_clone_client(client) : client;
 }
 
 static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry)
@@ -4016,15 +4041,19 @@ static void nfs4_proc_read_setup(struct nfs_read_data *data, struct rpc_message
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 0);
 }
 
-static void nfs4_proc_read_rpc_prepare(struct rpc_task *task, struct nfs_read_data *data)
+static int nfs4_proc_read_rpc_prepare(struct rpc_task *task, struct nfs_read_data *data)
 {
        if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
                        &data->args.seq_args,
                        &data->res.seq_res,
                        task))
-               return;
-       nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                       data->args.lock_context, FMODE_READ);
+               return 0;
+       if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
+                               data->args.lock_context, FMODE_READ) == -EIO)
+               return -EIO;
+       if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
+               return -EIO;
+       return 0;
 }
 
 static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_write_data *data)
@@ -4100,15 +4129,19 @@ static void nfs4_proc_write_setup(struct nfs_write_data *data, struct rpc_messag
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 1);
 }
 
-static void nfs4_proc_write_rpc_prepare(struct rpc_task *task, struct nfs_write_data *data)
+static int nfs4_proc_write_rpc_prepare(struct rpc_task *task, struct nfs_write_data *data)
 {
        if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
                        &data->args.seq_args,
                        &data->res.seq_res,
                        task))
-               return;
-       nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                       data->args.lock_context, FMODE_WRITE);
+               return 0;
+       if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
+                               data->args.lock_context, FMODE_WRITE) == -EIO)
+               return -EIO;
+       if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
+               return -EIO;
+       return 0;
 }
 
 static void nfs4_proc_commit_rpc_prepare(struct rpc_task *task, struct nfs_commit_data *data)
@@ -5210,6 +5243,9 @@ static struct rpc_task *nfs4_do_unlck(struct file_lock *fl,
                .flags = RPC_TASK_ASYNC,
        };
 
+       nfs4_state_protect(NFS_SERVER(lsp->ls_state->inode)->nfs_client,
+               NFS_SP4_MACH_CRED_CLEANUP, &task_setup_data.rpc_client, &msg);
+
        /* Ensure this is an unlock - when canceling a lock, the
         * canceled lock is passed in, and it won't be an unlock.
         */
@@ -5514,6 +5550,10 @@ static int nfs4_lock_expired(struct nfs4_state *state, struct file_lock *request
        err = nfs4_set_lock_state(state, request);
        if (err != 0)
                return err;
+       if (!recover_lost_locks) {
+               set_bit(NFS_LOCK_LOST, &request->fl_u.nfs4_fl.owner->ls_flags);
+               return 0;
+       }
        do {
                if (test_bit(NFS_DELEGATED_STATE, &state->flags) != 0)
                        return 0;
@@ -5725,8 +5765,23 @@ struct nfs_release_lockowner_data {
        struct nfs4_lock_state *lsp;
        struct nfs_server *server;
        struct nfs_release_lockowner_args args;
+       struct nfs4_sequence_args seq_args;
+       struct nfs4_sequence_res seq_res;
 };
 
+static void nfs4_release_lockowner_prepare(struct rpc_task *task, void *calldata)
+{
+       struct nfs_release_lockowner_data *data = calldata;
+       nfs40_setup_sequence(data->server,
+                               &data->seq_args, &data->seq_res, task);
+}
+
+static void nfs4_release_lockowner_done(struct rpc_task *task, void *calldata)
+{
+       struct nfs_release_lockowner_data *data = calldata;
+       nfs40_sequence_done(task, &data->seq_res);
+}
+
 static void nfs4_release_lockowner_release(void *calldata)
 {
        struct nfs_release_lockowner_data *data = calldata;
@@ -5735,6 +5790,8 @@ static void nfs4_release_lockowner_release(void *calldata)
 }
 
 static const struct rpc_call_ops nfs4_release_lockowner_ops = {
+       .rpc_call_prepare = nfs4_release_lockowner_prepare,
+       .rpc_call_done = nfs4_release_lockowner_done,
        .rpc_release = nfs4_release_lockowner_release,
 };
 
@@ -5747,14 +5804,17 @@ static int nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_st
 
        if (server->nfs_client->cl_mvops->minor_version != 0)
                return -EINVAL;
+
        data = kmalloc(sizeof(*data), GFP_NOFS);
        if (!data)
                return -ENOMEM;
+       nfs4_init_sequence(&data->seq_args, &data->seq_res, 0);
        data->lsp = lsp;
        data->server = server;
        data->args.lock_owner.clientid = server->nfs_client->cl_clientid;
        data->args.lock_owner.id = lsp->ls_seqid.owner_id;
        data->args.lock_owner.s_dev = server->s_dev;
+
        msg.rpc_argp = &data->args;
        rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
        return 0;
@@ -5948,6 +6008,10 @@ static int _nfs4_proc_secinfo(struct inode *dir, const struct qstr *name, struct
        }
 
        dprintk("NFS call  secinfo %s\n", name->name);
+
+       nfs4_state_protect(NFS_SERVER(dir)->nfs_client,
+               NFS_SP4_MACH_CRED_SECINFO, &clnt, &msg);
+
        status = nfs4_call_sync(clnt, NFS_SERVER(dir), &msg, &args.seq_args,
                                &res.seq_res, 0);
        dprintk("NFS reply  secinfo: %d\n", status);
@@ -6073,16 +6137,124 @@ out:
 }
 
 /*
- * nfs4_proc_exchange_id()
+ * Minimum set of SP4_MACH_CRED operations from RFC 5661 in the enforce map
+ * and operations we'd like to see to enable certain features in the allow map
+ */
+static const struct nfs41_state_protection nfs4_sp4_mach_cred_request = {
+       .how = SP4_MACH_CRED,
+       .enforce.u.words = {
+               [1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
+                     1 << (OP_EXCHANGE_ID - 32) |
+                     1 << (OP_CREATE_SESSION - 32) |
+                     1 << (OP_DESTROY_SESSION - 32) |
+                     1 << (OP_DESTROY_CLIENTID - 32)
+       },
+       .allow.u.words = {
+               [0] = 1 << (OP_CLOSE) |
+                     1 << (OP_LOCKU),
+               [1] = 1 << (OP_SECINFO - 32) |
+                     1 << (OP_SECINFO_NO_NAME - 32) |
+                     1 << (OP_TEST_STATEID - 32) |
+                     1 << (OP_FREE_STATEID - 32)
+       }
+};
+
+/*
+ * Select the state protection mode for client `clp' given the server results
+ * from exchange_id in `sp'.
  *
- * Returns zero, a negative errno, or a negative NFS4ERR status code.
+ * Returns 0 on success, negative errno otherwise.
+ */
+static int nfs4_sp4_select_mode(struct nfs_client *clp,
+                                struct nfs41_state_protection *sp)
+{
+       static const u32 supported_enforce[NFS4_OP_MAP_NUM_WORDS] = {
+               [1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
+                     1 << (OP_EXCHANGE_ID - 32) |
+                     1 << (OP_CREATE_SESSION - 32) |
+                     1 << (OP_DESTROY_SESSION - 32) |
+                     1 << (OP_DESTROY_CLIENTID - 32)
+       };
+       unsigned int i;
+
+       if (sp->how == SP4_MACH_CRED) {
+               /* Print state protect result */
+               dfprintk(MOUNT, "Server SP4_MACH_CRED support:\n");
+               for (i = 0; i <= LAST_NFS4_OP; i++) {
+                       if (test_bit(i, sp->enforce.u.longs))
+                               dfprintk(MOUNT, "  enforce op %d\n", i);
+                       if (test_bit(i, sp->allow.u.longs))
+                               dfprintk(MOUNT, "  allow op %d\n", i);
+               }
+
+               /* make sure nothing is on enforce list that isn't supported */
+               for (i = 0; i < NFS4_OP_MAP_NUM_WORDS; i++) {
+                       if (sp->enforce.u.words[i] & ~supported_enforce[i]) {
+                               dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
+                               return -EINVAL;
+                       }
+               }
+
+               /*
+                * Minimal mode - state operations are allowed to use machine
+                * credential.  Note this already happens by default, so the
+                * client doesn't have to do anything more than the negotiation.
+                *
+                * NOTE: we don't care if EXCHANGE_ID is in the list -
+                *       we're already using the machine cred for exchange_id
+                *       and will never use a different cred.
+                */
+               if (test_bit(OP_BIND_CONN_TO_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_CREATE_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_DESTROY_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_DESTROY_CLIENTID, sp->enforce.u.longs)) {
+                       dfprintk(MOUNT, "sp4_mach_cred:\n");
+                       dfprintk(MOUNT, "  minimal mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_MINIMAL, &clp->cl_sp4_flags);
+               } else {
+                       dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
+                       return -EINVAL;
+               }
+
+               if (test_bit(OP_CLOSE, sp->allow.u.longs) &&
+                   test_bit(OP_LOCKU, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  cleanup mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_CLEANUP, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_SECINFO, sp->allow.u.longs) &&
+                   test_bit(OP_SECINFO_NO_NAME, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  secinfo mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_SECINFO, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_TEST_STATEID, sp->allow.u.longs) &&
+                   test_bit(OP_FREE_STATEID, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  stateid mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_STATEID, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_WRITE, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  write mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_WRITE, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_COMMIT, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  commit mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_COMMIT, &clp->cl_sp4_flags);
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * _nfs4_proc_exchange_id()
  *
- * Since the clientid has expired, all compounds using sessions
- * associated with the stale clientid will be returning
- * NFS4ERR_BADSESSION in the sequence operation, and will therefore
- * be in some phase of session reset.
+ * Wrapper for EXCHANGE_ID operation.
  */
-int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
+static int _nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred,
+       u32 sp4_how)
 {
        nfs4_verifier verifier;
        struct nfs41_exchange_id_args args = {
@@ -6129,11 +6301,30 @@ int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
                goto out_server_scope;
        }
 
+       switch (sp4_how) {
+       case SP4_NONE:
+               args.state_protect.how = SP4_NONE;
+               break;
+
+       case SP4_MACH_CRED:
+               args.state_protect = nfs4_sp4_mach_cred_request;
+               break;
+
+       default:
+               /* unsupported! */
+               WARN_ON_ONCE(1);
+               status = -EINVAL;
+               goto out_server_scope;
+       }
+
        status = rpc_call_sync(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT);
        trace_nfs4_exchange_id(clp, status);
        if (status == 0)
                status = nfs4_check_cl_exchange_flags(res.flags);
 
+       if (status == 0)
+               status = nfs4_sp4_select_mode(clp, &res.state_protect);
+
        if (status == 0) {
                clp->cl_clientid = res.clientid;
                clp->cl_exchange_flags = (res.flags & ~EXCHGID4_FLAG_CONFIRMED_R);
@@ -6180,6 +6371,35 @@ out:
        return status;
 }
 
+/*
+ * nfs4_proc_exchange_id()
+ *
+ * Returns zero, a negative errno, or a negative NFS4ERR status code.
+ *
+ * Since the clientid has expired, all compounds using sessions
+ * associated with the stale clientid will be returning
+ * NFS4ERR_BADSESSION in the sequence operation, and will therefore
+ * be in some phase of session reset.
+ *
+ * Will attempt to negotiate SP4_MACH_CRED if krb5i / krb5p auth is used.
+ */
+int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
+{
+       rpc_authflavor_t authflavor = clp->cl_rpcclient->cl_auth->au_flavor;
+       int status;
+
+       /* try SP4_MACH_CRED if krb5i/p */
+       if (authflavor == RPC_AUTH_GSS_KRB5I ||
+           authflavor == RPC_AUTH_GSS_KRB5P) {
+               status = _nfs4_proc_exchange_id(clp, cred, SP4_MACH_CRED);
+               if (!status)
+                       return 0;
+       }
+
+       /* try SP4_NONE */
+       return _nfs4_proc_exchange_id(clp, cred, SP4_NONE);
+}
+
 static int _nfs4_proc_destroy_clientid(struct nfs_client *clp,
                struct rpc_cred *cred)
 {
@@ -7261,7 +7481,8 @@ out:
  */
 static int
 _nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
-                   struct nfs_fsinfo *info, struct nfs4_secinfo_flavors *flavors)
+                   struct nfs_fsinfo *info,
+                   struct nfs4_secinfo_flavors *flavors, bool use_integrity)
 {
        struct nfs41_secinfo_no_name_args args = {
                .style = SECINFO_STYLE_CURRENT_FH,
@@ -7274,8 +7495,23 @@ _nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
                .rpc_argp = &args,
                .rpc_resp = &res,
        };
-       return nfs4_call_sync(server->nfs_client->cl_rpcclient, server, &msg,
-                               &args.seq_args, &res.seq_res, 0);
+       struct rpc_clnt *clnt = server->client;
+       int status;
+
+       if (use_integrity) {
+               clnt = server->nfs_client->cl_rpcclient;
+               msg.rpc_cred = nfs4_get_clid_cred(server->nfs_client);
+       }
+
+       dprintk("--> %s\n", __func__);
+       status = nfs4_call_sync(clnt, server, &msg, &args.seq_args,
+                               &res.seq_res, 0);
+       dprintk("<-- %s status=%d\n", __func__, status);
+
+       if (msg.rpc_cred)
+               put_rpccred(msg.rpc_cred);
+
+       return status;
 }
 
 static int
@@ -7285,7 +7521,24 @@ nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
        struct nfs4_exception exception = { };
        int err;
        do {
-               err = _nfs41_proc_secinfo_no_name(server, fhandle, info, flavors);
+               /* first try using integrity protection */
+               err = -NFS4ERR_WRONGSEC;
+
+               /* try to use integrity protection with machine cred */
+               if (_nfs4_is_integrity_protected(server->nfs_client))
+                       err = _nfs41_proc_secinfo_no_name(server, fhandle, info,
+                                                         flavors, true);
+
+               /*
+                * if unable to use integrity protection, or SECINFO with
+                * integrity protection returns NFS4ERR_WRONGSEC (which is
+                * disallowed by spec, but exists in deployed servers) use
+                * the current filesystem's rpc_client and the user cred.
+                */
+               if (err == -NFS4ERR_WRONGSEC)
+                       err = _nfs41_proc_secinfo_no_name(server, fhandle, info,
+                                                         flavors, false);
+
                switch (err) {
                case 0:
                case -NFS4ERR_WRONGSEC:
@@ -7355,11 +7608,15 @@ static int _nfs41_test_stateid(struct nfs_server *server,
                .rpc_resp = &res,
                .rpc_cred = cred,
        };
+       struct rpc_clnt *rpc_client = server->client;
+
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_STATEID,
+               &rpc_client, &msg);
 
        dprintk("NFS call  test_stateid %p\n", stateid);
        nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
        nfs4_set_sequence_privileged(&args.seq_args);
-       status = nfs4_call_sync_sequence(server->client, server, &msg,
+       status = nfs4_call_sync_sequence(rpc_client, server, &msg,
                        &args.seq_args, &res.seq_res);
        if (status != NFS_OK) {
                dprintk("NFS reply test_stateid: failed, %d\n", status);
@@ -7451,6 +7708,9 @@ static struct rpc_task *_nfs41_free_stateid(struct nfs_server *server,
        };
        struct nfs_free_stateid_data *data;
 
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_STATEID,
+               &task_setup.rpc_client, &msg);
+
        dprintk("NFS call  free_stateid %p\n", stateid);
        data = kmalloc(sizeof(*data), GFP_NOFS);
        if (!data)