1 /* NFSv4.1 client for Windows
2  * Copyright � 2012 The Regents of the University of Michigan
3  *
4  * Olga Kornievskaia <aglo@umich.edu>
5  * Casey Bodley <cbodley@umich.edu>
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * without any warranty; without even the implied warranty of merchantability
14  * or fitness for a particular purpose.  See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  */
21 
22 #include <windows.h>
23 #include <strsafe.h>
24 
25 #include "nfs41_ops.h"
26 #include "delegation.h"
27 #include "nfs41_callback.h"
28 #include "daemon_debug.h"
29 
30 
31 #define CBSLVL 2 /* dprintf level for callback server logging */
32 
33 
34 static const char g_server_tag[] = "ms-nfs41-callback";
35 
36 
37 /* callback session */
38 static void replay_cache_write(
39     IN nfs41_cb_session *session,
40     IN struct cb_compound_args *args,
41     IN struct cb_compound_res *res,
42     IN bool_t cachethis);
43 
nfs41_callback_session_init(IN nfs41_session * session)44 void nfs41_callback_session_init(
45     IN nfs41_session *session)
46 {
47     /* initialize the replay cache with status NFS4ERR_SEQ_MISORDERED */
48     struct cb_compound_res res = { 0 };
49     StringCchCopyA(res.tag.str, CB_COMPOUND_MAX_TAG, g_server_tag);
50     res.tag.len = sizeof(g_server_tag);
51     res.status = NFS4ERR_SEQ_MISORDERED;
52 
53     session->cb_session.cb_sessionid = session->session_id;
54 
55     replay_cache_write(&session->cb_session, NULL, &res, FALSE);
56 }
57 
58 
59 /* OP_CB_LAYOUTRECALL */
handle_cb_layoutrecall(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_layoutrecall_args * args,OUT struct cb_layoutrecall_res * res)60 static enum_t handle_cb_layoutrecall(
61     IN nfs41_rpc_clnt *rpc_clnt,
62     IN struct cb_layoutrecall_args *args,
63     OUT struct cb_layoutrecall_res *res)
64 {
65     enum pnfs_status status;
66 
67     status = pnfs_file_layout_recall(rpc_clnt->client, args);
68     switch (status) {
69     case PNFS_PENDING:
70         /* not enough information to process the recall yet */
71         res->status = NFS4ERR_DELAY;
72         break;
73     default:
74         /* forgetful model for layout recalls */
75         res->status = NFS4ERR_NOMATCHING_LAYOUT;
76         break;
77     }
78 
79     dprintf(CBSLVL, "  OP_CB_LAYOUTRECALL { %s, %s, recall %u } %s\n",
80         pnfs_layout_type_string(args->type),
81         pnfs_iomode_string(args->iomode), args->recall.type,
82         nfs_error_string(res->status));
83     return res->status;
84 }
85 
86 /* OP_CB_RECALL_SLOT */
handle_cb_recall_slot(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_recall_slot_args * args,OUT struct cb_recall_slot_res * res)87 static enum_t handle_cb_recall_slot(
88     IN nfs41_rpc_clnt *rpc_clnt,
89     IN struct cb_recall_slot_args *args,
90     OUT struct cb_recall_slot_res *res)
91 {
92     res->status = nfs41_session_recall_slot(rpc_clnt->client->session,
93         args->target_highest_slotid);
94 
95     dprintf(CBSLVL, "  OP_CB_RECALL_SLOT { %u } %s\n",
96         args->target_highest_slotid, nfs_error_string(res->status));
97     return res->status;
98 }
99 
100 /* OP_CB_SEQUENCE */
handle_cb_sequence(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_sequence_args * args,OUT struct cb_sequence_res * res,OUT nfs41_cb_session ** session_out,OUT bool_t * cachethis)101 static enum_t handle_cb_sequence(
102     IN nfs41_rpc_clnt *rpc_clnt,
103     IN struct cb_sequence_args *args,
104     OUT struct cb_sequence_res *res,
105     OUT nfs41_cb_session **session_out,
106     OUT bool_t *cachethis)
107 {
108     nfs41_cb_session *cb_session = &rpc_clnt->client->session->cb_session;
109     uint32_t status = NFS4_OK;
110     res->status = NFS4_OK;
111 
112     *session_out = cb_session;
113 
114     /* validate the sessionid */
115     if (memcmp(cb_session->cb_sessionid, args->sessionid,
116             NFS4_SESSIONID_SIZE)) {
117         eprintf("[cb] received sessionid doesn't match session\n");
118         res->status = NFS4ERR_BADSESSION;
119         goto out;
120     }
121 
122     /* we only support 1 slot for the back channel so slotid MUST be 0 */
123     if (args->slotid != 0) {
124         eprintf("[cb] received unexpected slotid=%d\n", args->slotid);
125         res->status = NFS4ERR_BADSLOT;
126         goto out;
127     }
128     if (args->highest_slotid != 0) {
129         eprintf("[cb] received unexpected highest_slotid=%d\n",
130             args->highest_slotid);
131         res->status = NFS4ERR_BAD_HIGH_SLOT;
132         goto out;
133     }
134 
135     /* check for a retry with the same seqid */
136     if (args->sequenceid == cb_session->cb_seqnum) {
137         if (!cb_session->replay.res.length) {
138             /* return success for sequence, but fail the next operation */
139             res->status = NFS4_OK;
140             status = NFS4ERR_RETRY_UNCACHED_REP;
141         } else {
142             /* return NFS4ERR_SEQ_FALSE_RETRY for all replays; if the retry
143              * turns out to be valid, this response will be replaced anyway */
144             status = res->status = NFS4ERR_SEQ_FALSE_RETRY;
145         }
146         goto out;
147     }
148 
149     /* error on any unexpected seqids */
150     if (args->sequenceid != cb_session->cb_seqnum+1) {
151         eprintf("[cb] bad received seq#=%d, expected=%d\n",
152             args->sequenceid, cb_session->cb_seqnum+1);
153         res->status = NFS4ERR_SEQ_MISORDERED;
154         goto out;
155     }
156 
157     cb_session->cb_seqnum = args->sequenceid;
158     *cachethis = args->cachethis;
159 
160     memcpy(res->ok.sessionid, args->sessionid, NFS4_SESSIONID_SIZE);
161     res->ok.sequenceid = args->sequenceid;
162     res->ok.slotid = args->slotid;
163     res->ok.highest_slotid = args->highest_slotid;
164     res->ok.target_highest_slotid = args->highest_slotid;
165 
166 out:
167     dprintf(CBSLVL, "  OP_CB_SEQUENCE { seqid %u, slot %u, cachethis %d } "
168         "%s\n", args->sequenceid, args->slotid, args->cachethis,
169         nfs_error_string(res->status));
170     return status;
171 }
172 
173 /* OP_CB_GETATTR */
handle_cb_getattr(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_getattr_args * args,OUT struct cb_getattr_res * res)174 static enum_t handle_cb_getattr(
175     IN nfs41_rpc_clnt *rpc_clnt,
176     IN struct cb_getattr_args *args,
177     OUT struct cb_getattr_res *res)
178 {
179     /* look up cached attributes for the given filehandle */
180     res->status = nfs41_delegation_getattr(rpc_clnt->client,
181         &args->fh, &args->attr_request, &res->info);
182     return res->status;
183 }
184 
185 /* OP_CB_RECALL */
handle_cb_recall(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_recall_args * args,OUT struct cb_recall_res * res)186 static enum_t handle_cb_recall(
187     IN nfs41_rpc_clnt *rpc_clnt,
188     IN struct cb_recall_args *args,
189     OUT struct cb_recall_res *res)
190 {
191     /* return the delegation asynchronously */
192     res->status = nfs41_delegation_recall(rpc_clnt->client,
193         &args->fh, &args->stateid, args->truncate);
194     return res->status;
195 }
196 
197 /* OP_CB_NOTIFY_DEVICEID */
handle_cb_notify_deviceid(IN nfs41_rpc_clnt * rpc_clnt,IN struct cb_notify_deviceid_args * args,OUT struct cb_notify_deviceid_res * res)198 static enum_t handle_cb_notify_deviceid(
199     IN nfs41_rpc_clnt *rpc_clnt,
200     IN struct cb_notify_deviceid_args *args,
201     OUT struct cb_notify_deviceid_res *res)
202 {
203     uint32_t i;
204     for (i = 0; i < args->change_count; i++) {
205         pnfs_file_device_notify(rpc_clnt->client->devices,
206             &args->change_list[i]);
207     }
208     res->status = NFS4_OK;
209     return res->status;
210 }
211 
replay_cache_write(IN nfs41_cb_session * session,IN OPTIONAL struct cb_compound_args * args,IN struct cb_compound_res * res,IN bool_t cachethis)212 static void replay_cache_write(
213     IN nfs41_cb_session *session,
214     IN OPTIONAL struct cb_compound_args *args,
215     IN struct cb_compound_res *res,
216     IN bool_t cachethis)
217 {
218     XDR xdr;
219     uint32_t i;
220 
221     session->replay.arg.length = 0;
222     session->replay.res.length = 0;
223 
224     /* encode the reply directly into the replay cache */
225     xdrmem_create(&xdr, (char*)session->replay.res.buffer,
226         NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
227 
228     /* always try to cache the result */
229     if (proc_cb_compound_res(&xdr, res)) {
230         session->replay.res.length = XDR_GETPOS(&xdr);
231 
232         if (args) {
233             /* encode the arguments into the request cache */
234             xdrmem_create(&xdr, (char*)session->replay.arg.buffer,
235                 NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
236 
237             if (proc_cb_compound_args(&xdr, args))
238                 session->replay.arg.length = XDR_GETPOS(&xdr);
239         }
240     } else if (cachethis) {
241         /* on failure, only return errors if caching was requested */
242         res->status = NFS4ERR_REP_TOO_BIG_TO_CACHE;
243 
244         /* find the first operation that failed to encode */
245         for (i = 0; i < res->resarray_count; i++) {
246             if (!res->resarray[i].xdr_ok) {
247                 res->resarray[i].res.status = NFS4ERR_REP_TOO_BIG_TO_CACHE;
248                 res->resarray_count = i + 1;
249                 break;
250             }
251         }
252     }
253 }
254 
replay_validate_args(IN struct cb_compound_args * args,IN const struct replay_cache * cache)255 static bool_t replay_validate_args(
256     IN struct cb_compound_args *args,
257     IN const struct replay_cache *cache)
258 {
259     char buffer[NFS41_MAX_SERVER_CACHE];
260     XDR xdr;
261 
262     /* encode the current arguments into a temporary buffer */
263     xdrmem_create(&xdr, buffer, NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
264 
265     if (!proc_cb_compound_args(&xdr, args))
266         return FALSE;
267 
268     /* must match the cached length */
269     if (XDR_GETPOS(&xdr) != cache->length)
270         return FALSE;
271 
272     /* must match the cached buffer contents */
273     return memcmp(cache->buffer, buffer, cache->length) == 0;
274 }
275 
replay_validate_ops(IN const struct cb_compound_args * args,IN const struct cb_compound_res * res)276 static bool_t replay_validate_ops(
277     IN const struct cb_compound_args *args,
278     IN const struct cb_compound_res *res)
279 {
280     uint32_t i;
281     for (i = 0; i < res->resarray_count; i++) {
282         /* can't have more operations than the request */
283         if (i >= args->argarray_count)
284             return FALSE;
285 
286         /* each opnum must match the request */
287         if (args->argarray[i].opnum != res->resarray[i].opnum)
288             return FALSE;
289 
290         if (res->resarray[i].res.status)
291             break;
292     }
293     return TRUE;
294 }
295 
replay_cache_read(IN nfs41_cb_session * session,IN struct cb_compound_args * args,OUT struct cb_compound_res ** res_out)296 static int replay_cache_read(
297     IN nfs41_cb_session *session,
298     IN struct cb_compound_args *args,
299     OUT struct cb_compound_res **res_out)
300 {
301     XDR xdr;
302     struct cb_compound_res *replay;
303     struct cb_compound_res *res = *res_out;
304     uint32_t status = NFS4_OK;
305 
306     replay = calloc(1, sizeof(struct cb_compound_res));
307     if (replay == NULL) {
308         eprintf("[cb] failed to allocate replay buffer\n");
309         status = NFS4ERR_SERVERFAULT;
310         goto out;
311     }
312 
313     /* decode the response from the replay cache */
314     xdrmem_create(&xdr, (char*)session->replay.res.buffer,
315         NFS41_MAX_SERVER_CACHE, XDR_DECODE);
316     if (!proc_cb_compound_res(&xdr, replay)) {
317         eprintf("[cb] failed to decode replay buffer\n");
318         status = NFS4ERR_SEQ_FALSE_RETRY;
319         goto out_free_replay;
320     }
321 
322     /* if we cached the arguments, use them to validate the retry */
323     if (session->replay.arg.length) {
324         if (!replay_validate_args(args, &session->replay.arg)) {
325             eprintf("[cb] retry attempt with different arguments\n");
326             status = NFS4ERR_SEQ_FALSE_RETRY;
327             goto out_free_replay;
328         }
329     } else { /* otherwise, comparing opnums is the best we can do */
330         if (!replay_validate_ops(args, replay)) {
331             eprintf("[cb] retry attempt with different operations\n");
332             status = NFS4ERR_SEQ_FALSE_RETRY;
333             goto out_free_replay;
334         }
335     }
336 
337     /* free previous response and replace it with the replay */
338     xdr.x_op = XDR_FREE;
339     proc_cb_compound_res(&xdr, res);
340 
341     dprintf(2, "[cb] retry: returning cached response\n");
342 
343     *res_out = replay;
344 out:
345     return status;
346 
347 out_free_replay:
348     xdr.x_op = XDR_FREE;
349     proc_cb_compound_res(&xdr, replay);
350     goto out;
351 }
352 
353 /* CB_COMPOUND */
handle_cb_compound(nfs41_rpc_clnt * rpc_clnt,cb_req * req,struct cb_compound_res ** reply)354 static void handle_cb_compound(nfs41_rpc_clnt *rpc_clnt, cb_req *req, struct cb_compound_res **reply)
355 {
356     struct cb_compound_args args = { 0 };
357     struct cb_compound_res *res = NULL;
358     struct cb_argop *argop;
359     struct cb_resop *resop;
360     XDR *xdr = (XDR*)req->xdr;
361     nfs41_cb_session *session = NULL;
362     bool_t cachethis = FALSE;
363     uint32_t i, status = NFS4_OK;
364 
365     dprintf(CBSLVL, "--> handle_cb_compound()\n");
366 
367     /* decode the arguments */
368     if (!proc_cb_compound_args(xdr, &args)) {
369         status = NFS4ERR_BADXDR;
370         eprintf("failed to decode compound arguments\n");
371     }
372 
373     /* allocate the compound results */
374     res = calloc(1, sizeof(struct cb_compound_res));
375     if (res == NULL) {
376         status = NFS4ERR_SERVERFAULT;
377         goto out;
378     }
379     res->status = status;
380     StringCchCopyA(res->tag.str, CB_COMPOUND_MAX_TAG, g_server_tag);
381     res->tag.str[CB_COMPOUND_MAX_TAG-1] = 0;
382     res->tag.len = (uint32_t)strlen(res->tag.str);
383     res->resarray = calloc(args.argarray_count, sizeof(struct cb_resop));
384     if (res->resarray == NULL) {
385         res->status = NFS4ERR_SERVERFAULT;
386         goto out;
387     }
388 
389     dprintf(CBSLVL, "CB_COMPOUND('%s', %u)\n", args.tag.str, args.argarray_count);
390     if (args.minorversion != 1) {
391         res->status = NFS4ERR_MINOR_VERS_MISMATCH; //XXXXX
392         eprintf("args.minorversion %u != 1\n", args.minorversion);
393         goto out;
394     }
395 
396     /* handle each operation in the compound */
397     for (i = 0; i < args.argarray_count && res->status == NFS4_OK; i++) {
398         argop = &args.argarray[i];
399         resop = &res->resarray[i];
400         resop->opnum = argop->opnum;
401         res->resarray_count++;
402 
403         /* 20.9.3: The error NFS4ERR_SEQUENCE_POS MUST be returned
404          * when CB_SEQUENCE is found in any position in a CB_COMPOUND
405          * beyond the first.  If any other operation is in the first
406          * position of CB_COMPOUND, NFS4ERR_OP_NOT_IN_SESSION MUST
407          * be returned.
408          */
409         if (i == 0 && argop->opnum != OP_CB_SEQUENCE) {
410             res->status = resop->res.status = NFS4ERR_OP_NOT_IN_SESSION;
411             break;
412         }
413         if (i != 0 && argop->opnum == OP_CB_SEQUENCE) {
414             res->status = resop->res.status = NFS4ERR_SEQUENCE_POS;
415             break;
416         }
417         if (status == NFS4ERR_RETRY_UNCACHED_REP) {
418             res->status = resop->res.status = status;
419             break;
420         }
421 
422         switch (argop->opnum) {
423         case OP_CB_LAYOUTRECALL:
424             dprintf(1, "OP_CB_LAYOUTRECALL\n");
425             res->status = handle_cb_layoutrecall(rpc_clnt,
426                 &argop->args.layoutrecall, &resop->res.layoutrecall);
427             break;
428         case OP_CB_RECALL_SLOT:
429             dprintf(1, "OP_CB_RECALL_SLOT\n");
430             res->status = handle_cb_recall_slot(rpc_clnt,
431                 &argop->args.recall_slot, &resop->res.recall_slot);
432             break;
433         case OP_CB_SEQUENCE:
434             dprintf(1, "OP_CB_SEQUENCE\n");
435             status = handle_cb_sequence(rpc_clnt, &argop->args.sequence,
436                 &resop->res.sequence, &session, &cachethis);
437 
438             if (status == NFS4ERR_SEQ_FALSE_RETRY) {
439                 /* replace the current results with the cached response */
440                 status = replay_cache_read(session, &args, &res);
441                 if (status) res->status = status;
442                 goto out;
443             }
444 
445             if (status == NFS4_OK)
446                 res->status = resop->res.sequence.status;
447             break;
448         case OP_CB_GETATTR:
449             dprintf(1, "OP_CB_GETATTR\n");
450             res->status = handle_cb_getattr(rpc_clnt,
451                 &argop->args.getattr, &resop->res.getattr);
452             break;
453         case OP_CB_RECALL:
454             dprintf(1, "OP_CB_RECALL\n");
455             res->status = handle_cb_recall(rpc_clnt,
456                 &argop->args.recall, &resop->res.recall);
457             break;
458         case OP_CB_NOTIFY:
459             dprintf(1, "OP_CB_NOTIFY\n");
460             res->status = NFS4ERR_NOTSUPP;
461             break;
462         case OP_CB_PUSH_DELEG:
463             dprintf(1, "OP_CB_PUSH_DELEG\n");
464             res->status = NFS4ERR_NOTSUPP;
465             break;
466         case OP_CB_RECALL_ANY:
467             dprintf(1, "OP_CB_RECALL_ANY\n");
468             res->status = NFS4ERR_NOTSUPP;
469             break;
470         case OP_CB_RECALLABLE_OBJ_AVAIL:
471             dprintf(1, "OP_CB_RECALLABLE_OBJ_AVAIL\n");
472             res->status = NFS4ERR_NOTSUPP;
473             break;
474         case OP_CB_WANTS_CANCELLED:
475             dprintf(1, "OP_CB_WANTS_CANCELLED\n");
476             res->status = NFS4ERR_NOTSUPP;
477             break;
478         case OP_CB_NOTIFY_LOCK:
479             dprintf(1, "OP_CB_NOTIFY_LOCK\n");
480             res->status = NFS4ERR_NOTSUPP;
481             break;
482         case OP_CB_NOTIFY_DEVICEID:
483             dprintf(1, "OP_CB_NOTIFY_DEVICEID\n");
484             res->status = NFS4_OK;
485             break;
486         case OP_CB_ILLEGAL:
487             dprintf(1, "OP_CB_ILLEGAL\n");
488             res->status = NFS4ERR_NOTSUPP;
489             break;
490         default:
491             eprintf("operation %u not supported\n", argop->opnum);
492             res->status = NFS4ERR_NOTSUPP;
493             break;
494         }
495     }
496 
497     /* always attempt to cache the reply */
498     if (session)
499         replay_cache_write(session, &args, res, cachethis);
500 out:
501     /* free the arguments */
502     xdr->x_op = XDR_FREE;
503     proc_cb_compound_args(xdr, &args);
504 
505     *reply = res;
506     dprintf(CBSLVL, "<-- handle_cb_compound() returning %s (%u results)\n",
507         nfs_error_string(res ? res->status : status),
508         res ? res->resarray_count : 0);
509 }
510 
511 #ifdef __REACTOS__
nfs41_handle_callback(void * rpc_clnt,void * cb,void * dummy)512 int nfs41_handle_callback(void *rpc_clnt, void *cb, void * dummy)
513 {
514     struct cb_compound_res **reply = dummy;
515 #else
516 int nfs41_handle_callback(void *rpc_clnt, void *cb, struct cb_compound_res **reply)
517 {
518 #endif
519     nfs41_rpc_clnt *rpc = (nfs41_rpc_clnt *)rpc_clnt;
520     cb_req *request = (cb_req *)cb;
521     uint32_t status = 0;
522 
523     dprintf(1, "nfs41_handle_callback: received call\n");
524     if (request->rq_prog != NFS41_RPC_CBPROGRAM) {
525         eprintf("invalid rpc program %u\n", request->rq_prog);
526         status = 2;
527         goto out;
528     }
529 
530     switch (request->rq_proc) {
531     case CB_NULL:
532         dprintf(1, "CB_NULL\n");
533         break;
534 
535     case CB_COMPOUND:
536         dprintf(1, "CB_COMPOUND\n");
537         handle_cb_compound(rpc, request, reply);
538         break;
539 
540     default:
541         dprintf(1, "invalid rpc procedure %u\n", request->rq_proc);
542         status = 3;
543         goto out;
544     }
545 out:
546     return status;
547 }
548