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