xref: /reactos/base/services/nfsd/nfs41_client.c (revision 40462c92)
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 <stdio.h>
24 #include <time.h>
25 #include <winsock2.h>
26 #include <iphlpapi.h> /* for GetAdaptersAddresses() */
27 #include <wincrypt.h> /* for Crypt*() functions */
28 #include <winsock2.h> /* for hostent struct */
29 
30 #include "tree.h"
31 #include "delegation.h"
32 #include "daemon_debug.h"
33 #include "nfs41_ops.h"
34 
35 
36 uint32_t nfs41_exchange_id_flags(
37     IN bool_t is_data)
38 {
39     uint32_t flags = EXCHGID4_FLAG_SUPP_MOVED_REFER;
40     if (is_data)
41         flags |= EXCHGID4_FLAG_USE_PNFS_DS;
42     else
43         flags |= EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS;
44     return flags;
45 }
46 
47 static int pnfs_client_init(
48     IN nfs41_client *client)
49 {
50     enum pnfs_status pnfsstat;
51     int status = NO_ERROR;
52 
53     /* initialize the pnfs layout and device lists for metadata clients */
54     pnfsstat = pnfs_layout_list_create(&client->layouts);
55     if (pnfsstat) {
56         status = ERROR_NOT_ENOUGH_MEMORY;
57         goto out;
58     }
59     pnfsstat = pnfs_file_device_list_create(&client->devices);
60     if (pnfsstat) {
61         status = ERROR_NOT_ENOUGH_MEMORY;
62         goto out_err_layouts;
63     }
64 out:
65     return status;
66 
67 out_err_layouts:
68     pnfs_layout_list_free(client->layouts);
69     client->layouts = NULL;
70     goto out;
71 }
72 
73 static int update_server(
74     IN nfs41_client *client,
75     IN const char *server_scope,
76     IN const server_owner4 *owner)
77 {
78     nfs41_server *server;
79     int status;
80 
81     /* find a server matching the owner.major_id and scope */
82     status = nfs41_server_find_or_create(owner->so_major_id,
83         server_scope, nfs41_rpc_netaddr(client->rpc), &server);
84     if (status)
85         goto out;
86 
87     /* if the server is the same, we now have an extra reference. if
88      * the servers are different, we still need to deref the old server.
89      * so both cases can be treated the same */
90     if (client->server)
91         nfs41_server_deref(client->server);
92     client->server = server;
93 out:
94     return status;
95 }
96 
97 static int update_exchangeid_res(
98     IN nfs41_client *client,
99     IN const nfs41_exchange_id_res *exchangeid)
100 {
101     client->clnt_id = exchangeid->clientid;
102     client->seq_id = exchangeid->sequenceid;
103     client->roles = exchangeid->flags & EXCHGID4_FLAG_MASK_PNFS;
104     return update_server(client, exchangeid->server_scope,
105         &exchangeid->server_owner);
106 }
107 
108 int nfs41_client_create(
109     IN nfs41_rpc_clnt *rpc,
110     IN const client_owner4 *owner,
111     IN bool_t is_data,
112     IN const nfs41_exchange_id_res *exchangeid,
113     OUT nfs41_client **client_out)
114 {
115     int status;
116     nfs41_client *client;
117 
118     client = calloc(1, sizeof(nfs41_client));
119     if (client == NULL) {
120         status = GetLastError();
121         goto out_err_rpc;
122     }
123 
124     memcpy(&client->owner, owner, sizeof(client_owner4));
125     client->rpc = rpc;
126     client->is_data = is_data;
127 
128     status = update_exchangeid_res(client, exchangeid);
129     if (status)
130         goto out_err_client;
131 
132     list_init(&client->state.opens);
133     list_init(&client->state.delegations);
134     InitializeCriticalSection(&client->state.lock);
135 
136     //initialize a lock used to protect access to client id and client id seq#
137     InitializeSRWLock(&client->exid_lock);
138 
139     InitializeConditionVariable(&client->recovery.cond);
140     InitializeCriticalSection(&client->recovery.lock);
141 
142     status = pnfs_client_init(client);
143     if (status) {
144         eprintf("pnfs_client_init() failed with %d\n", status);
145         goto out_err_client;
146     }
147     *client_out = client;
148 out:
149     return status;
150 out_err_client:
151     nfs41_client_free(client); /* also calls nfs41_rpc_clnt_free() */
152     goto out;
153 out_err_rpc:
154     nfs41_rpc_clnt_free(rpc);
155     goto out;
156 }
157 
158 static void dprint_roles(
159     IN int level,
160     IN uint32_t roles)
161 {
162     dprintf(level, "roles: %s%s%s\n",
163         (roles & EXCHGID4_FLAG_USE_NON_PNFS) ? "USE_NON_PNFS " : "",
164         (roles & EXCHGID4_FLAG_USE_PNFS_MDS) ? "USE_PNFS_MDS " : "",
165         (roles & EXCHGID4_FLAG_USE_PNFS_DS) ? "USE_PNFS_DS" : "");
166 }
167 
168 int nfs41_client_renew(
169     IN nfs41_client *client)
170 {
171     nfs41_exchange_id_res exchangeid = { 0 };
172     int status;
173 
174     status = nfs41_exchange_id(client->rpc, &client->owner,
175         nfs41_exchange_id_flags(client->is_data), &exchangeid);
176     if (status) {
177         eprintf("nfs41_exchange_id() failed with %d\n", status);
178         status = ERROR_BAD_NET_RESP;
179         goto out;
180     }
181 
182     if (client->is_data) { /* require USE_PNFS_DS */
183         if ((exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_DS) == 0) {
184             eprintf("client expected USE_PNFS_DS\n");
185             status = ERROR_BAD_NET_RESP;
186             goto out;
187         }
188     } else { /* require USE_NON_PNFS or USE_PNFS_MDS */
189         if ((exchangeid.flags & EXCHGID4_FLAG_USE_NON_PNFS) == 0 &&
190             (exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_MDS) == 0) {
191             eprintf("client expected USE_NON_PNFS OR USE_PNFS_MDS\n");
192             status = ERROR_BAD_NET_RESP;
193             goto out;
194         }
195     }
196 
197     dprint_roles(2, exchangeid.flags);
198 
199     AcquireSRWLockExclusive(&client->exid_lock);
200     status = update_exchangeid_res(client, &exchangeid);
201     ReleaseSRWLockExclusive(&client->exid_lock);
202 out:
203     return status;
204 }
205 
206 void nfs41_client_free(
207     IN nfs41_client *client)
208 {
209     dprintf(2, "nfs41_client_free(%llu)\n", client->clnt_id);
210     nfs41_client_delegation_free(client);
211     if (client->session) nfs41_session_free(client->session);
212     nfs41_destroy_clientid(client->rpc, client->clnt_id);
213     if (client->server) nfs41_server_deref(client->server);
214     nfs41_rpc_clnt_free(client->rpc);
215     if (client->layouts) pnfs_layout_list_free(client->layouts);
216     if (client->devices) pnfs_file_device_list_free(client->devices);
217     DeleteCriticalSection(&client->state.lock);
218     DeleteCriticalSection(&client->recovery.lock);
219     free(client);
220 }
221 
222 
223 /* client_owner generation
224  * we choose to use MAC addresses to generate a client_owner value that
225  * is unique to a machine and persists over restarts.  because the client
226  * can have multiple network adapters/addresses, we take each adapter into
227  * account.  the specification suggests that "for privacy reasons, it is
228  * best to perform some one-way function," so we apply an md5 hash to the
229  * sorted list of MAC addresses */
230 
231 /* References:
232  * RFC 5661: 2.4. Client Identifiers and Client Owners
233  * http://tools.ietf.org/html/rfc5661#section-2.4
234  *
235  * MSDN: GetAdaptersAddresses Function
236  * http://msdn.microsoft.com/en-us/library/aa365915%28VS.85%29.aspx
237  *
238  * MSDN: Example C Program: Creating an MD5 Hash from File Content
239  * http://msdn.microsoft.com/en-us/library/aa382380%28VS.85%29.aspx */
240 
241 
242 /* use an rbtree to sort mac address entries */
243 struct mac_entry {
244     RB_ENTRY(mac_entry)     rbnode;
245     PBYTE                   address;
246     ULONG                   length;
247 };
248 
249 int mac_cmp(struct mac_entry *lhs, struct mac_entry *rhs)
250 {
251     const int diff = rhs->length - lhs->length;
252     return diff ? diff : strncmp((const char*)lhs->address,
253         (const char*)rhs->address, lhs->length);
254 }
255 RB_HEAD(mac_tree, mac_entry);
256 RB_GENERATE(mac_tree, mac_entry, rbnode, mac_cmp)
257 
258 static void mac_entry_insert(
259     IN struct mac_tree *root,
260     IN PBYTE address,
261     IN ULONG length)
262 {
263     struct mac_entry *entry;
264 
265     entry = calloc(1, sizeof(struct mac_entry));
266     if (entry == NULL)
267         return;
268 
269     entry->address = address;
270     entry->length = length;
271 
272     if (RB_INSERT(mac_tree, root, entry))
273         free(entry);
274 }
275 
276 static int adapter_valid(
277     IN const IP_ADAPTER_ADDRESSES *addr)
278 {
279     /* ignore generic interfaces whose address is not unique */
280     switch (addr->IfType) {
281     case IF_TYPE_SOFTWARE_LOOPBACK:
282     case IF_TYPE_TUNNEL:
283         return 0;
284     }
285     /* must have an address */
286     if (addr->PhysicalAddressLength == 0)
287         return 0;
288 #ifndef __REACTOS__
289     /* must support ip */
290     return addr->Ipv4Enabled || addr->Ipv6Enabled;
291 #else
292     return 1;
293 #endif
294 }
295 
296 static DWORD hash_mac_addrs(
297     IN HCRYPTHASH hash)
298 {
299     PIP_ADAPTER_ADDRESSES addr, addrs = NULL;
300     struct mac_tree rbtree = RB_INITIALIZER(rbtree);
301     struct mac_entry *entry, *node;
302     ULONG len;
303     DWORD status;
304 
305     /* start with enough room for DEFAULT_MINIMUM_ENTITIES */
306     len = DEFAULT_MINIMUM_ENTITIES * sizeof(IP_ADAPTER_ADDRESSES);
307 
308     do {
309         PIP_ADAPTER_ADDRESSES tmp;
310         /* reallocate the buffer until we can fit all of it */
311         tmp = realloc(addrs, len);
312         if (tmp == NULL) {
313             status = GetLastError();
314             goto out;
315         }
316         addrs = tmp;
317         status = GetAdaptersAddresses(AF_UNSPEC,
318             GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_SKIP_ANYCAST |
319             GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME |
320             GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_UNICAST,
321             NULL, addrs, &len);
322     } while (status == ERROR_BUFFER_OVERFLOW);
323 
324     if (status) {
325         eprintf("GetAdaptersAddresses() failed with %d\n", status);
326         goto out;
327     }
328 
329     /* get the mac address of each adapter */
330     for (addr = addrs; addr; addr = addr->Next)
331         if (adapter_valid(addr))
332             mac_entry_insert(&rbtree, addr->PhysicalAddress,
333                 addr->PhysicalAddressLength);
334 
335     /* require at least one valid address */
336     if (RB_EMPTY(&rbtree)) {
337         status = ERROR_FILE_NOT_FOUND;
338         eprintf("GetAdaptersAddresses() did not return "
339             "any valid mac addresses, failing with %d.\n", status);
340         goto out;
341     }
342 
343     RB_FOREACH_SAFE(entry, mac_tree, &rbtree, node) {
344         RB_REMOVE(mac_tree, &rbtree, entry);
345 
346         if (!CryptHashData(hash, entry->address, entry->length, 0)) {
347             status = GetLastError();
348             eprintf("CryptHashData() failed with %d\n", status);
349             /* don't break here, we need to free the rest */
350         }
351         free(entry);
352     }
353 out:
354     free(addrs);
355     return status;
356 }
357 
358 int nfs41_client_owner(
359     IN const char *name,
360     IN uint32_t sec_flavor,
361     OUT client_owner4 *owner)
362 {
363     HCRYPTPROV context;
364     HCRYPTHASH hash;
365     PBYTE buffer;
366     DWORD length;
367     const ULONGLONG time_created = GetTickCount64();
368     int status;
369     char username[UNLEN + 1];
370     DWORD len = UNLEN + 1;
371 
372     if (!GetUserNameA(username, &len)) {
373         status = GetLastError();
374         eprintf("GetUserName() failed with %d\n", status);
375         goto out;
376     }
377 
378     /* owner.verifier = "time created" */
379     memcpy(owner->co_verifier, &time_created, sizeof(time_created));
380 
381     /* set up the md5 hash generator */
382     if (!CryptAcquireContext(&context, NULL, NULL,
383         PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
384         status = GetLastError();
385         eprintf("CryptAcquireContext() failed with %d\n", status);
386         goto out;
387     }
388     if (!CryptCreateHash(context, CALG_MD5, 0, 0, &hash)) {
389         status = GetLastError();
390         eprintf("CryptCreateHash() failed with %d\n", status);
391         goto out_context;
392     }
393 
394     if (!CryptHashData(hash, (const BYTE*)&sec_flavor, (DWORD)sizeof(sec_flavor), 0)) {
395         status = GetLastError();
396         eprintf("CryptHashData() failed with %d\n", status);
397         goto out_hash;
398     }
399 
400     if (!CryptHashData(hash, (const BYTE*)username, (DWORD)strlen(username), 0)) {
401         status = GetLastError();
402         eprintf("CryptHashData() failed with %d\n", status);
403         goto out_hash;
404     }
405 
406     if (!CryptHashData(hash, (const BYTE*)name, (DWORD)strlen(name), 0)) {
407         status = GetLastError();
408         eprintf("CryptHashData() failed with %d\n", status);
409         goto out_hash;
410     }
411 
412     /* add the mac address from each applicable adapter to the hash */
413     status = hash_mac_addrs(hash);
414     if (status) {
415         eprintf("hash_mac_addrs() failed with %d\n", status);
416         goto out_hash;
417     }
418 
419     /* extract the hash size (should always be 16 for md5) */
420     buffer = (PBYTE)&owner->co_ownerid_len;
421     length = (DWORD)sizeof(DWORD);
422     if (!CryptGetHashParam(hash, HP_HASHSIZE, buffer, &length, 0)) {
423         status = GetLastError();
424         eprintf("CryptGetHashParam(size) failed with %d\n", status);
425         goto out_hash;
426     }
427     /* extract the hash buffer */
428     buffer = owner->co_ownerid;
429     length = owner->co_ownerid_len;
430     if (!CryptGetHashParam(hash, HP_HASHVAL, buffer, &length, 0)) {
431         status = GetLastError();
432         eprintf("CryptGetHashParam(val) failed with %d\n", status);
433         goto out_hash;
434     }
435 
436 out_hash:
437     CryptDestroyHash(hash);
438 out_context:
439     CryptReleaseContext(context, 0);
440 out:
441     return status;
442 }
443