xref: /reactos/base/services/nfsd/nfs41_rpc.c (revision 8a978a17)
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 "nfs41_ops.h"
23 #include "daemon_debug.h"
24 #include "nfs41_xdr.h"
25 #include "nfs41_callback.h"
26 #include "nfs41_driver.h" /* for AUTH_SYS, AUTHGSS_KRB5s defines */
27 
28 #include "rpc/rpc.h"
29 #define SECURITY_WIN32
30 #include <security.h>
31 #include "rpc/auth_sspi.h"
32 
33 static enum clnt_stat send_null(CLIENT *client)
34 {
35     struct timeval timeout = {10, 100};
36 
37     return clnt_call(client, 0,
38                      (xdrproc_t)xdr_void, NULL,
39                      (xdrproc_t)xdr_void, NULL, timeout);
40 }
41 
42 static int get_client_for_netaddr(
43     IN const netaddr4 *netaddr,
44     IN uint32_t wsize,
45     IN uint32_t rsize,
46     IN nfs41_rpc_clnt *rpc,
47     OUT OPTIONAL char *server_name,
48     OUT CLIENT **client_out)
49 {
50     int status = ERROR_NETWORK_UNREACHABLE;
51     struct netconfig *nconf;
52     struct netbuf *addr;
53     CLIENT *client;
54 
55     nconf = getnetconfigent(netaddr->netid);
56     if (nconf == NULL)
57         goto out;
58 
59     addr = uaddr2taddr(nconf, netaddr->uaddr);
60     if (addr == NULL)
61         goto out_free_conf;
62 
63     if (server_name) {
64         getnameinfo(addr->buf, addr->len, server_name, NI_MAXHOST, NULL, 0, 0);
65         dprintf(1, "servername is %s\n", server_name);
66     }
67     dprintf(1, "callback function %p args %p\n", nfs41_handle_callback, rpc);
68     client = clnt_tli_create(RPC_ANYFD, nconf, addr, NFS41_RPC_PROGRAM,
69         NFS41_RPC_VERSION, wsize, rsize, rpc ? proc_cb_compound_res : NULL,
70         rpc ? nfs41_handle_callback : NULL, rpc ? rpc : NULL);
71     if (client) {
72         *client_out = client;
73         status = NO_ERROR;
74     }
75 
76     freenetbuf(addr);
77 out_free_conf:
78     freenetconfigent(nconf);
79 out:
80     return status;
81 }
82 
83 static int get_client_for_multi_addr(
84     IN const multi_addr4 *addrs,
85     IN uint32_t wsize,
86     IN uint32_t rsize,
87     IN nfs41_rpc_clnt *rpc,
88     OUT OPTIONAL char *server_name,
89     OUT CLIENT **client_out,
90     OUT uint32_t *addr_index)
91 {
92     int status = ERROR_NETWORK_UNREACHABLE;
93     uint32_t i;
94     for (i = 0; i < addrs->count; i++) {
95         status = get_client_for_netaddr(&addrs->arr[i],
96             wsize, rsize, rpc, server_name, client_out);
97         if (status == NO_ERROR) {
98             *addr_index = i;
99             break;
100         }
101     }
102     return status;
103 }
104 
105 int create_rpcsec_auth_client(
106     IN uint32_t sec_flavor,
107     IN char *server_name,
108     CLIENT	*client
109     )
110 {
111     int status = ERROR_NETWORK_UNREACHABLE;
112 
113     switch (sec_flavor) {
114     case RPCSEC_AUTHGSS_KRB5:
115         client->cl_auth = authsspi_create_default(client, server_name,
116             RPCSEC_SSPI_SVC_NONE);
117         break;
118     case RPCSEC_AUTHGSS_KRB5I:
119         client->cl_auth = authsspi_create_default(client, server_name,
120             RPCSEC_SSPI_SVC_INTEGRITY);
121         break;
122     case RPCSEC_AUTHGSS_KRB5P:
123         client->cl_auth = authsspi_create_default(client, server_name,
124             RPCSEC_SSPI_SVC_PRIVACY);
125         break;
126     default:
127         eprintf("create_rpc_auth_client: unknown rpcsec flavor %d\n",
128                 sec_flavor);
129         client->cl_auth = NULL;
130     }
131 
132     if (client->cl_auth == NULL) {
133         eprintf("nfs41_rpc_clnt_create: failed to create %s\n",
134                 secflavorop2name(sec_flavor));
135         goto out;
136     } else
137         dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n",
138             secflavorop2name(sec_flavor));
139     status = 0;
140 out:
141     return status;
142 }
143 
144 /* Returns a client structure and an associated lock */
145 int nfs41_rpc_clnt_create(
146     IN const multi_addr4 *addrs,
147     IN uint32_t wsize,
148     IN uint32_t rsize,
149     IN uint32_t uid,
150     IN uint32_t gid,
151     IN uint32_t sec_flavor,
152     OUT nfs41_rpc_clnt **rpc_out)
153 {
154     CLIENT *client;
155     nfs41_rpc_clnt *rpc;
156     uint32_t addr_index;
157     int status;
158     char machname[MAXHOSTNAMELEN + 1];
159     gid_t gids[1];
160     bool_t needcb = 1;
161 
162     rpc = calloc(1, sizeof(nfs41_rpc_clnt));
163     if (rpc == NULL) {
164         status = GetLastError();
165         goto out;
166     }
167 #ifdef NO_CB_4_KRB5P
168     if (sec_flavor == RPCSEC_AUTHGSS_KRB5P)
169         needcb = 0;
170 #endif
171     rpc->needcb = needcb;
172     rpc->cond = CreateEvent(NULL, TRUE, FALSE, NULL);
173     if (rpc->cond == NULL) {
174         status = GetLastError();
175         eprintf("CreateEvent failed %d\n", status);
176         goto out_free_rpc_clnt;
177     }
178     status = get_client_for_multi_addr(addrs, wsize, rsize, needcb?rpc:NULL,
179                 rpc->server_name, &client, &addr_index);
180     if (status) {
181         clnt_pcreateerror("connecting failed");
182         goto out_free_rpc_cond;
183     }
184     if (send_null(client) != RPC_SUCCESS) {
185         // XXX Do what here?
186         eprintf("nfs41_rpc_clnt_create: send_null failed\n");
187         status = ERROR_NETWORK_UNREACHABLE;
188         goto out_err_client;
189     }
190 
191     rpc->sec_flavor = sec_flavor;
192     if (sec_flavor == RPCSEC_AUTH_SYS) {
193         if (gethostname(machname, sizeof(machname)) == -1) {
194             eprintf("nfs41_rpc_clnt_create: gethostname failed\n");
195             goto out_err_client;
196         }
197         machname[sizeof(machname) - 1] = '\0';
198         client->cl_auth = authsys_create(machname, uid, gid, 0, gids);
199         if (client->cl_auth == NULL) {
200             eprintf("nfs41_rpc_clnt_create: failed to create rpc authsys\n");
201             status = ERROR_NETWORK_UNREACHABLE;
202             goto out_err_client;
203         }
204     } else {
205         status = create_rpcsec_auth_client(sec_flavor, rpc->server_name, client);
206         if (status) {
207             eprintf("nfs41_rpc_clnt_create: failed to establish security "
208                     "context with %s\n", rpc->server_name);
209             status = ERROR_NETWORK_UNREACHABLE;
210             goto out_err_client;
211         } else
212             dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n",
213                 secflavorop2name(sec_flavor));
214     }
215     rpc->rpc = client;
216 
217     /* keep a copy of the address and buffer sizes for reconnect */
218     memcpy(&rpc->addrs, addrs, sizeof(multi_addr4));
219     /* save the index of the address we connected to */
220     rpc->addr_index = addr_index;
221     rpc->wsize = wsize;
222     rpc->rsize = rsize;
223     rpc->is_valid_session = TRUE;
224     rpc->uid = uid;
225     rpc->gid = gid;
226 
227     //initialize rpc client lock
228     InitializeSRWLock(&rpc->lock);
229 
230     *rpc_out = rpc;
231 out:
232     return status;
233 out_err_client:
234     clnt_destroy(client);
235 out_free_rpc_cond:
236     CloseHandle(rpc->cond);
237 out_free_rpc_clnt:
238     free(rpc);
239     goto out;
240 }
241 
242 /* Frees resources allocated in clnt_create */
243 void nfs41_rpc_clnt_free(
244     IN nfs41_rpc_clnt *rpc)
245 {
246     auth_destroy(rpc->rpc->cl_auth);
247     clnt_destroy(rpc->rpc);
248     CloseHandle(rpc->cond);
249     free(rpc);
250 }
251 
252 static bool_t rpc_renew_in_progress(nfs41_rpc_clnt *rpc, int *value)
253 {
254     bool_t status = FALSE;
255     AcquireSRWLockExclusive(&rpc->lock);
256     if (value) {
257         dprintf(1, "nfs41_rpc_renew_in_progress: setting value %d\n", *value);
258         rpc->in_recovery = *value;
259         if (!rpc->in_recovery)
260             SetEvent(rpc->cond);
261     } else {
262         status = rpc->in_recovery;
263         dprintf(1, "nfs41_rpc_renew_in_progress: returning value %d\n", status);
264     }
265     ReleaseSRWLockExclusive(&rpc->lock);
266     return status;
267 }
268 
269 static bool_t rpc_should_retry(nfs41_rpc_clnt *rpc, uint32_t version)
270 {
271     bool_t status = 0;
272     AcquireSRWLockExclusive(&rpc->lock);
273     if (rpc->version > version)
274         status = 1;
275     ReleaseSRWLockExclusive(&rpc->lock);
276     return status;
277 }
278 
279 static int rpc_reconnect(
280     IN nfs41_rpc_clnt *rpc)
281 {
282     CLIENT *client = NULL;
283     uint32_t addr_index;
284     int status;
285 
286     AcquireSRWLockExclusive(&rpc->lock);
287 
288     status = get_client_for_multi_addr(&rpc->addrs, rpc->wsize, rpc->rsize,
289                 rpc->needcb?rpc:NULL, NULL, &client, &addr_index);
290     if (status)
291         goto out_unlock;
292 
293     if(rpc->sec_flavor == RPCSEC_AUTH_SYS)
294         client->cl_auth = rpc->rpc->cl_auth;
295     else {
296         auth_destroy(rpc->rpc->cl_auth);
297         status = create_rpcsec_auth_client(rpc->sec_flavor, rpc->server_name, client);
298         if (status) {
299             eprintf("Failed to reestablish security context\n");
300             status = ERROR_NETWORK_UNREACHABLE;
301             goto out_err_client;
302         }
303     }
304     if (send_null(client) != RPC_SUCCESS) {
305         eprintf("rpc_reconnect: send_null failed\n");
306         status = ERROR_NETWORK_UNREACHABLE;
307         goto out_err_client;
308     }
309 
310     clnt_destroy(rpc->rpc);
311     rpc->rpc = client;
312     rpc->addr_index = addr_index;
313     rpc->version++;
314     dprintf(1, "nfs41_send_compound: reestablished RPC connection\n");
315 
316 out_unlock:
317     ReleaseSRWLockExclusive(&rpc->lock);
318 
319     /* after releasing the rpc lock, send a BIND_CONN_TO_SESSION if
320      * we need to associate the connection with the backchannel */
321     if (status == NO_ERROR && rpc->needcb &&
322             rpc->client && rpc->client->session) {
323         status = nfs41_bind_conn_to_session(rpc,
324             rpc->client->session->session_id, CDFC4_BACK_OR_BOTH);
325         if (status)
326             eprintf("nfs41_bind_conn_to_session() failed with %s\n",
327                 nfs_error_string(status));
328         status = NFS4_OK;
329     }
330     return status;
331 
332 out_err_client:
333     clnt_destroy(client);
334     goto out_unlock;
335 }
336 
337 int nfs41_send_compound(
338     IN nfs41_rpc_clnt *rpc,
339     IN char *inbuf,
340     OUT char *outbuf)
341 {
342     struct timeval timeout = {90, 100};
343     enum clnt_stat rpc_status;
344     int status, count = 0, one = 1, zero = 0;
345     uint32_t version;
346 
347  try_again:
348     AcquireSRWLockShared(&rpc->lock);
349     version = rpc->version;
350     rpc_status = clnt_call(rpc->rpc, 1,
351                            (xdrproc_t)nfs_encode_compound, inbuf,
352                            (xdrproc_t)nfs_decode_compound, outbuf,
353                            timeout);
354     ReleaseSRWLockShared(&rpc->lock);
355 
356     if (rpc_status != RPC_SUCCESS) {
357         eprintf("clnt_call returned rpc_status = %s\n",
358             rpc_error_string(rpc_status));
359         switch(rpc_status) {
360         case RPC_CANTRECV:
361         case RPC_CANTSEND:
362         case RPC_TIMEDOUT:
363         case RPC_AUTHERROR:
364             if (++count > 3 || !rpc->is_valid_session) {
365                 status = ERROR_NETWORK_UNREACHABLE;
366                 break;
367             }
368             if (rpc_should_retry(rpc, version))
369                 goto try_again;
370             while (rpc_renew_in_progress(rpc, NULL)) {
371                 status = WaitForSingleObject(rpc->cond, INFINITE);
372                 if (status != WAIT_OBJECT_0) {
373                     dprintf(1, "rpc_renew_in_progress: WaitForSingleObject failed\n");
374                     print_condwait_status(1, status);
375                     status = ERROR_LOCK_VIOLATION;
376                     goto out;
377                 }
378                 rpc_renew_in_progress(rpc, &zero);
379                 goto try_again;
380             }
381             rpc_renew_in_progress(rpc, &one);
382             if (rpc_status == RPC_AUTHERROR && rpc->sec_flavor != RPCSEC_AUTH_SYS) {
383                 AcquireSRWLockExclusive(&rpc->lock);
384                 auth_destroy(rpc->rpc->cl_auth);
385                 status = create_rpcsec_auth_client(rpc->sec_flavor,
386                             rpc->server_name, rpc->rpc);
387                 ReleaseSRWLockExclusive(&rpc->lock);
388                 if (status) {
389                     eprintf("Failed to reestablish security context\n");
390                     status = ERROR_NETWORK_UNREACHABLE;
391                     goto out;
392                 }
393             } else
394                 if (rpc_reconnect(rpc))
395                     eprintf("rpc_reconnect: Failed to reconnect!\n");
396             rpc_renew_in_progress(rpc, &zero);
397             goto try_again;
398         default:
399             eprintf("UNHANDLED RPC_ERROR: %d\n", rpc_status);
400 			status = ERROR_NETWORK_UNREACHABLE;
401             goto out;
402         }
403         goto out;
404     }
405 
406     status = 0;
407 out:
408     return status;
409 }
410