1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2017-2022 Tintri by DDN, Inc. All rights reserved. 14 */ 15 16 /* 17 * SMB2 Durable Handle support 18 */ 19 20 #include <sys/types.h> 21 #include <sys/cmn_err.h> 22 #include <sys/fcntl.h> 23 #include <sys/nbmlock.h> 24 #include <sys/sid.h> 25 #include <smbsrv/string.h> 26 #include <smbsrv/smb_kproto.h> 27 #include <smbsrv/smb_fsops.h> 28 #include <smbsrv/smbinfo.h> 29 #include <smbsrv/smb2_kproto.h> 30 31 /* Windows default values from [MS-SMB2] */ 32 /* 33 * (times in seconds) 34 * resilient: 35 * MaxTimeout = 300 (win7+) 36 * if timeout > MaxTimeout, ERROR 37 * if timeout != 0, timeout = req.timeout 38 * if timeout == 0, timeout = (infinity) (Win7/w2k8r2) 39 * if timeout == 0, timeout = 120 (Win8+) 40 * v2: 41 * if timeout != 0, timeout = MIN(timeout, 300) (spec) 42 * if timeout != 0, timeout = timeout (win8/2k12) 43 * if timeout == 0, timeout = Share.CATimeout. \ 44 * if Share.CATimeout == 0, timeout = 60 (win8/w2k12) 45 * if timeout == 0, timeout = 180 (win8.1/w2k12r2) 46 * open.timeout = 60 (win8/w2k12r2) (i.e. we ignore the request) 47 * v1: 48 * open.timeout = 16 minutes 49 */ 50 51 uint32_t smb2_dh_def_timeout = 60 * MILLISEC; /* mSec. */ 52 uint32_t smb2_dh_max_timeout = 300 * MILLISEC; /* mSec. */ 53 54 uint32_t smb2_res_def_timeout = 120 * MILLISEC; /* mSec. */ 55 uint32_t smb2_res_max_timeout = 300 * MILLISEC; /* mSec. */ 56 57 uint32_t smb2_persist_timeout = 300 * MILLISEC; /* mSec. */ 58 59 /* Max. size of the file used to store a CA handle. */ 60 static uint32_t smb2_dh_max_cah_size = 64 * 1024; 61 static uint32_t smb2_ca_info_version = 1; 62 63 /* 64 * Want this to have invariant layout on disk, where the 65 * last two uint32_t values are stored as a uint64_t 66 */ 67 struct nvlk { 68 uint64_t lk_start; 69 uint64_t lk_len; 70 /* (lk_pid << 32) | lk_type */ 71 #ifdef _BIG_ENDIAN 72 uint32_t lk_pid, lk_type; 73 #else 74 uint32_t lk_type, lk_pid; 75 #endif 76 }; 77 78 static void smb2_dh_import_share(void *); 79 static smb_ofile_t *smb2_dh_import_handle(smb_request_t *, smb_node_t *, 80 uint64_t); 81 static int smb2_dh_read_nvlist(smb_request_t *, smb_node_t *, struct nvlist **); 82 static int smb2_dh_import_cred(smb_ofile_t *, char *); 83 84 #define DH_SN_SIZE 24 /* size of DH stream name buffers */ 85 /* 86 * Build the stream name used to store a CA handle. 87 * i.e. ":0123456789abcdef:$CA" 88 * Note: smb_fsop_create adds the SUNWsmb prefix, 89 * so we compose the name without the prefix. 90 */ 91 static inline void 92 smb2_dh_make_stream_name(char *buf, size_t buflen, uint64_t id) 93 { 94 ASSERT(buflen >= DH_SN_SIZE); 95 (void) snprintf(buf, buflen, 96 ":%016" PRIx64 ":$CA", id); 97 } 98 99 /* 100 * smb_dh_should_save 101 * 102 * During session tear-down, decide whether to keep a durable handle. 103 * 104 * There are two cases where we save durable handles: 105 * 1. An SMB2 LOGOFF request was received 106 * 2. An unexpected disconnect from the client 107 * Note: Specifying a PrevSessionID in session setup 108 * is considered a disconnect (we just haven't learned about it yet) 109 * In every other case, we close durable handles. 110 * 111 * [MS-SMB2] 3.3.5.6 SMB2_LOGOFF 112 * [MS-SMB2] 3.3.7.1 Handling Loss of a Connection 113 * 114 * If any of the following are true, preserve for reconnect: 115 * 116 * - Open.IsResilient is TRUE. 117 * 118 * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_BATCH and 119 * Open.OplockState == Held, and Open.IsDurable is TRUE. 120 * 121 * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_LEASE, 122 * Lease.LeaseState SMB2_LEASE_HANDLE_CACHING, 123 * Open.OplockState == Held, and Open.IsDurable is TRUE. 124 * 125 * - Open.IsPersistent is TRUE. 126 * 127 * We also deal with some special cases for shutdown of the 128 * server, session, user, tree (in that order). Other than 129 * the cases above, shutdown (or forced termination) should 130 * destroy durable handles. 131 */ 132 boolean_t 133 smb_dh_should_save(smb_ofile_t *of) 134 { 135 ASSERT(MUTEX_HELD(&of->f_mutex)); 136 ASSERT(of->dh_vers != SMB2_NOT_DURABLE); 137 138 /* SMB service shutting down, destroy DH */ 139 if (of->f_server->sv_state == SMB_SERVER_STATE_STOPPING) 140 return (B_FALSE); 141 142 /* 143 * SMB Session (connection) going away (server up). 144 * If server initiated disconnect, destroy DH 145 * If client initiated disconnect, save all DH. 146 */ 147 if (of->f_session->s_state == SMB_SESSION_STATE_TERMINATED) 148 return (B_FALSE); 149 if (of->f_session->s_state == SMB_SESSION_STATE_DISCONNECTED) 150 return (B_TRUE); 151 152 /* 153 * SMB User logoff, session still "up". 154 * Action depends on why/how this logoff happened, 155 * determined based on user->preserve_opens 156 */ 157 if (of->f_user->u_state == SMB_USER_STATE_LOGGING_OFF) { 158 switch (of->f_user->preserve_opens) { 159 case SMB2_DH_PRESERVE_NONE: 160 /* Server-initiated */ 161 return (B_FALSE); 162 case SMB2_DH_PRESERVE_SOME: 163 /* Previous session logoff. */ 164 goto preserve_some; 165 case SMB2_DH_PRESERVE_ALL: 166 /* Protocol logoff request */ 167 return (B_TRUE); 168 } 169 } 170 171 /* 172 * SMB tree disconnecting (user still logged on) 173 * i.e. when kshare export forces disconnection. 174 */ 175 if (of->f_tree->t_state == SMB_TREE_STATE_DISCONNECTING) 176 return (B_FALSE); 177 178 preserve_some: 179 /* preserve_opens == SMB2_DH_PRESERVE_SOME */ 180 181 switch (of->dh_vers) { 182 uint32_t ol_state; 183 184 case SMB2_RESILIENT: 185 return (B_TRUE); 186 187 case SMB2_DURABLE_V2: 188 if (of->dh_persist) 189 return (B_TRUE); 190 /* FALLTHROUGH */ 191 case SMB2_DURABLE_V1: 192 /* IS durable (v1 or v2) */ 193 if (of->f_lease != NULL) 194 ol_state = of->f_lease->ls_state; 195 else 196 ol_state = of->f_oplock.og_state; 197 if ((ol_state & (OPLOCK_LEVEL_BATCH | 198 OPLOCK_LEVEL_CACHE_HANDLE)) != 0) 199 return (B_TRUE); 200 /* FALLTHROUGH */ 201 case SMB2_NOT_DURABLE: 202 default: 203 break; 204 } 205 206 return (B_FALSE); 207 } 208 209 /* 210 * Is this stream name a CA handle? i.e. 211 * ":0123456789abcdef:$CA" 212 */ 213 static boolean_t 214 smb2_dh_match_ca_name(const char *name, uint64_t *idp) 215 { 216 static const char suffix[] = ":$CA"; 217 u_longlong_t ull; 218 const char *p = name; 219 char *p2 = NULL; 220 int len, rc; 221 222 if (*p++ != ':') 223 return (B_FALSE); 224 225 rc = ddi_strtoull(p, &p2, 16, &ull); 226 if (rc != 0 || p2 != (p + 16)) 227 return (B_FALSE); 228 p += 16; 229 230 len = sizeof (suffix) - 1; 231 if (strncmp(p, suffix, len) != 0) 232 return (B_FALSE); 233 p += len; 234 235 if (*p != '\0') 236 return (B_FALSE); 237 238 *idp = (uint64_t)ull; 239 return (B_TRUE); 240 } 241 242 /* 243 * smb2_dh_new_ca_share 244 * 245 * Called when a new share has ca=true. Find or create the CA dir, 246 * and start a thread to import persistent handles. 247 */ 248 int 249 smb2_dh_new_ca_share(smb_server_t *sv, smb_kshare_t *shr) 250 { 251 smb_kshare_t *shr2; 252 smb_request_t *sr; 253 254 ASSERT(STYPE_ISDSK(shr->shr_type)); 255 256 /* 257 * Need to lookup the kshare again, to get a hold. 258 * Add a function to just get the hold? 259 */ 260 shr2 = smb_kshare_lookup(sv, shr->shr_name); 261 if (shr2 != shr) 262 return (EINVAL); 263 264 sr = smb_request_alloc(sv->sv_session, 0); 265 if (sr == NULL) { 266 /* shutting down? */ 267 smb_kshare_release(sv, shr); 268 return (EINTR); 269 } 270 sr->sr_state = SMB_REQ_STATE_SUBMITTED; 271 272 /* 273 * Mark this share as "busy importing persistent handles" 274 * so we can hold off tree connect until that's done. 275 * Will clear and wakeup below. 276 */ 277 mutex_enter(&shr->shr_mutex); 278 shr->shr_import_busy = sr; 279 mutex_exit(&shr->shr_mutex); 280 281 /* 282 * Start a taskq job to import any CA handles. 283 * The hold on the kshare is given to this job, 284 * which releases it when it's done. 285 */ 286 sr->arg.tcon.si = shr; /* hold from above */ 287 (void) taskq_dispatch( 288 sv->sv_worker_pool, 289 smb2_dh_import_share, sr, TQ_SLEEP); 290 291 return (0); 292 } 293 294 int smb2_dh_import_delay = 0; 295 296 static void 297 smb2_dh_import_share(void *arg) 298 { 299 smb_request_t *sr = arg; 300 smb_kshare_t *shr = sr->arg.tcon.si; 301 smb_node_t *snode; 302 cred_t *kcr = zone_kcred(); 303 smb_streaminfo_t *str_info = NULL; 304 uint64_t id; 305 smb_node_t *str_node; 306 smb_odir_t *od = NULL; 307 smb_ofile_t *of; 308 int rc; 309 boolean_t eof; 310 311 sr->sr_state = SMB_REQ_STATE_ACTIVE; 312 313 if (smb2_dh_import_delay > 0) 314 delay(SEC_TO_TICK(smb2_dh_import_delay)); 315 316 /* 317 * Borrow the server's "root" user. 318 * 319 * This takes the place of smb_session_lookup_ssnid() 320 * that would happen in smb2_dispatch for a normal SR. 321 * As usual, this hold is released in smb_request_free. 322 */ 323 sr->uid_user = sr->sr_server->sv_rootuser; 324 smb_user_hold_internal(sr->uid_user); 325 sr->user_cr = sr->uid_user->u_cred; 326 327 /* 328 * Create a temporary tree connect 329 */ 330 sr->arg.tcon.path = shr->shr_name; 331 sr->tid_tree = smb_tree_alloc(sr, shr, shr->shr_root_node, 332 ACE_ALL_PERMS, 0); 333 if (sr->tid_tree == NULL) { 334 cmn_err(CE_NOTE, "smb2_dh_import_share: " 335 "failed connect share <%s>", shr->shr_name); 336 goto out; 337 } 338 snode = sr->tid_tree->t_snode; 339 340 /* 341 * Get the buffers we'll use to read CA handle data. 342 * Stash in sr_request_buf for smb2_dh_import_handle(). 343 * Also a buffer for the stream name info. 344 */ 345 sr->sr_req_length = smb2_dh_max_cah_size; 346 sr->sr_request_buf = kmem_alloc(sr->sr_req_length, KM_SLEEP); 347 str_info = kmem_alloc(sizeof (smb_streaminfo_t), KM_SLEEP); 348 349 /* 350 * Open the ext. attr dir under the share root and 351 * import CA handles for this share. 352 */ 353 if (smb_odir_openat(sr, snode, &od, B_FALSE) != 0) { 354 cmn_err(CE_NOTE, "!Share [%s] CA import, no xattr dir?", 355 shr->shr_name); 356 goto out; 357 } 358 359 eof = B_FALSE; 360 do { 361 /* 362 * If the kshare gets unshared before we finish, 363 * bail out so we don't hold things up. 364 */ 365 if (shr->shr_flags & SMB_SHRF_REMOVED) 366 break; 367 368 /* 369 * If the server's stopping, no point importing. 370 */ 371 if (smb_server_is_stopping(sr->sr_server)) 372 break; 373 374 /* 375 * Read a stream name and info 376 */ 377 rc = smb_odir_read_streaminfo(sr, od, str_info, &eof); 378 if ((rc != 0) || (eof)) 379 break; 380 381 /* 382 * Skip anything not a CA handle. 383 */ 384 if (!smb2_dh_match_ca_name(str_info->si_name, &id)) { 385 continue; 386 } 387 388 /* 389 * Lookup stream node and import 390 */ 391 str_node = NULL; 392 rc = smb_fsop_lookup_name(sr, kcr, SMB_CASE_SENSITIVE, 393 snode, snode, str_info->si_name, &str_node); 394 if (rc != 0) { 395 cmn_err(CE_NOTE, "Share [%s] CA import, " 396 "lookup <%s> failed rc=%d", 397 shr->shr_name, str_info->si_name, rc); 398 continue; 399 } 400 of = smb2_dh_import_handle(sr, str_node, id); 401 smb_node_release(str_node); 402 if (of != NULL) { 403 smb_ofile_release(of); 404 of = NULL; 405 } 406 sr->fid_ofile = NULL; 407 smb_llist_flush(&sr->tid_tree->t_ofile_list); 408 409 } while (!eof); 410 411 out: 412 if (od != NULL) { 413 smb_odir_close(od); 414 smb_odir_release(od); 415 } 416 417 if (str_info != NULL) 418 kmem_free(str_info, sizeof (smb_streaminfo_t)); 419 /* Let smb_request_free clean up sr->sr_request_buf */ 420 421 /* 422 * We did a (temporary, internal) tree connect above, 423 * which we need to undo before we return. Note that 424 * smb_request_free will do the final release of 425 * sr->tid_tree, sr->uid_user 426 */ 427 if (sr->tid_tree != NULL) 428 smb_tree_disconnect(sr->tid_tree, B_FALSE); 429 430 /* 431 * Wake up any waiting tree connect(s). 432 * See smb_tree_connect_disk(). 433 */ 434 mutex_enter(&shr->shr_mutex); 435 shr->shr_import_busy = NULL; 436 cv_broadcast(&shr->shr_cv); 437 mutex_exit(&shr->shr_mutex); 438 439 smb_kshare_release(sr->sr_server, shr); 440 smb_request_free(sr); 441 } 442 443 /* 444 * This returns the new ofile mostly for dtrace. 445 */ 446 static smb_ofile_t * 447 smb2_dh_import_handle(smb_request_t *sr, smb_node_t *str_node, 448 uint64_t persist_id) 449 { 450 uint8_t client_uuid[UUID_LEN]; 451 smb_tree_t *tree = sr->tid_tree; 452 smb_arg_open_t *op = &sr->arg.open; 453 smb_pathname_t *pn = &op->fqi.fq_path; 454 cred_t *kcr = zone_kcred(); 455 struct nvlist *nvl = NULL; 456 char *sidstr = NULL; 457 smb_ofile_t *of = NULL; 458 smb_attr_t *pa; 459 boolean_t did_open = B_FALSE; 460 boolean_t have_lease = B_FALSE; 461 hrtime_t hrt; 462 uint64_t *u64p; 463 uint64_t u64; 464 uint32_t u32; 465 uint32_t status; 466 char *s; 467 uint8_t *u8p; 468 uint_t alen; 469 int rc; 470 471 /* 472 * While we're called with arg.tcon, we now want to use 473 * smb_arg_open for the rest of import, so clear it. 474 */ 475 bzero(op, sizeof (*op)); 476 op->create_disposition = FILE_OPEN; 477 478 /* 479 * Read and unpack the NVL 480 */ 481 rc = smb2_dh_read_nvlist(sr, str_node, &nvl); 482 if (rc != 0) 483 return (NULL); 484 485 /* 486 * Known CA info version? 487 */ 488 u32 = 0; 489 rc = nvlist_lookup_uint32(nvl, "info_version", &u32); 490 if (rc != 0 || u32 != smb2_ca_info_version) { 491 cmn_err(CE_NOTE, "CA import (%s/%s) bad vers=%d", 492 tree->t_resource, str_node->od_name, u32); 493 goto errout; 494 } 495 496 /* 497 * The persist ID in the nvlist should match the one 498 * encoded in the file name. (not enforced) 499 */ 500 u64 = 0; 501 rc = nvlist_lookup_uint64(nvl, "file_persistid", &u64); 502 if (rc != 0 || u64 != persist_id) { 503 cmn_err(CE_WARN, "CA import (%s/%s) bad id=%016" PRIx64, 504 tree->t_resource, str_node->od_name, u64); 505 /* goto errout? (allow) */ 506 } 507 508 /* 509 * Does it belong in the share being imported? 510 */ 511 s = NULL; 512 rc = nvlist_lookup_string(nvl, "share_name", &s); 513 if (rc != 0) { 514 cmn_err(CE_NOTE, "CA import (%s/%s) no share_name", 515 tree->t_resource, str_node->od_name); 516 goto errout; 517 } 518 if (smb_strcasecmp(s, tree->t_sharename, 0) != 0) { 519 /* Normal (not an error) */ 520 #ifdef DEBUG 521 cmn_err(CE_NOTE, "CA import (%s/%s) other share", 522 tree->t_resource, str_node->od_name); 523 #endif 524 goto errout; 525 } 526 527 /* 528 * Get the path name (for lookup) 529 */ 530 rc = nvlist_lookup_string(nvl, "path_name", &pn->pn_path); 531 if (rc != 0) { 532 cmn_err(CE_NOTE, "CA import (%s/%s) no path_name", 533 tree->t_resource, str_node->od_name); 534 goto errout; 535 } 536 537 /* 538 * owner sid 539 */ 540 rc = nvlist_lookup_string(nvl, "owner_sid", &sidstr); 541 if (rc != 0) { 542 cmn_err(CE_NOTE, "CA import (%s/%s) no owner_sid", 543 tree->t_resource, str_node->od_name); 544 goto errout; 545 } 546 547 /* 548 * granted access 549 */ 550 rc = nvlist_lookup_uint32(nvl, 551 "granted_access", &op->desired_access); 552 if (rc != 0) { 553 cmn_err(CE_NOTE, "CA import (%s/%s) no granted_access", 554 tree->t_resource, str_node->od_name); 555 goto errout; 556 } 557 558 /* 559 * share access 560 */ 561 rc = nvlist_lookup_uint32(nvl, 562 "share_access", &op->share_access); 563 if (rc != 0) { 564 cmn_err(CE_NOTE, "CA import (%s/%s) no share_access", 565 tree->t_resource, str_node->od_name); 566 goto errout; 567 } 568 569 /* 570 * create options 571 */ 572 rc = nvlist_lookup_uint32(nvl, 573 "create_options", &op->create_options); 574 if (rc != 0) { 575 cmn_err(CE_NOTE, "CA import (%s/%s) no create_options", 576 tree->t_resource, str_node->od_name); 577 goto errout; 578 } 579 580 /* 581 * create guid (client-assigned) 582 */ 583 alen = UUID_LEN; 584 u8p = NULL; 585 rc = nvlist_lookup_uint8_array(nvl, "file_guid", &u8p, &alen); 586 if (rc != 0 || alen != UUID_LEN) { 587 cmn_err(CE_NOTE, "CA import (%s/%s) bad file_guid", 588 tree->t_resource, str_node->od_name); 589 goto errout; 590 } 591 bcopy(u8p, op->create_guid, UUID_LEN); 592 593 /* 594 * client uuid (identifies the client) 595 */ 596 alen = UUID_LEN; 597 u8p = NULL; 598 rc = nvlist_lookup_uint8_array(nvl, "client_uuid", &u8p, &alen); 599 if (rc != 0 || alen != UUID_LEN) { 600 cmn_err(CE_NOTE, "CA import (%s/%s) no client_uuid", 601 tree->t_resource, str_node->od_name); 602 goto errout; 603 } 604 bcopy(u8p, client_uuid, UUID_LEN); 605 606 /* 607 * Lease key (optional) 608 */ 609 alen = SMB_LEASE_KEY_SZ; 610 u8p = NULL; 611 rc = nvlist_lookup_uint8_array(nvl, "lease_uuid", &u8p, &alen); 612 if (rc == 0) { 613 bcopy(u8p, op->lease_key, UUID_LEN); 614 (void) nvlist_lookup_uint32(nvl, 615 "lease_state", &op->lease_state); 616 (void) nvlist_lookup_uint16(nvl, 617 "lease_epoch", &op->lease_epoch); 618 (void) nvlist_lookup_uint16(nvl, 619 "lease_version", &op->lease_version); 620 have_lease = B_TRUE; 621 } else { 622 (void) nvlist_lookup_uint32(nvl, 623 "oplock_state", &op->op_oplock_state); 624 } 625 626 /* 627 * Done getting what we need from the NV list. 628 * (re)open the file 629 */ 630 status = smb_common_open(sr); 631 if (status != 0) { 632 cmn_err(CE_NOTE, "CA import (%s/%s) open failed 0x%x", 633 tree->t_resource, str_node->od_name, status); 634 (void) smb_node_set_delete_on_close(str_node, kcr, 0); 635 goto errout; 636 } 637 of = sr->fid_ofile; 638 did_open = B_TRUE; 639 640 /* 641 * Now restore the rest of the SMB2 level state. 642 * See smb2_create after smb_common_open 643 */ 644 645 /* 646 * Setup of->f_cr with owner SID 647 */ 648 rc = smb2_dh_import_cred(of, sidstr); 649 if (rc != 0) { 650 cmn_err(CE_NOTE, "CA import (%s/%s) import cred failed", 651 tree->t_resource, str_node->od_name); 652 goto errout; 653 } 654 655 /* 656 * Use the persist ID we previously assigned. 657 * Like smb_ofile_set_persistid_ph() 658 */ 659 rc = smb_ofile_insert_persistid(of, persist_id); 660 if (rc != 0) { 661 cmn_err(CE_NOTE, "CA import (%s/%s) " 662 "insert_persistid rc=%d", 663 tree->t_resource, str_node->od_name, rc); 664 goto errout; 665 } 666 667 /* 668 * Like smb2_lease_create() 669 * 670 * Lease state is stored in each persistent handle, but 671 * only one handle has the state we want. As we import 672 * each handle, "upgrade" the lease if the handle we're 673 * importing has a "better" lease state (higher epoch or 674 * more cache rights). After all handles are imported, 675 * that will get the lease to the right state. 676 */ 677 if (have_lease) { 678 smb_lease_t *ls; 679 status = smb2_lease_create(sr, client_uuid); 680 if (status != 0) { 681 cmn_err(CE_NOTE, "CA import (%s/%s) get lease 0x%x", 682 tree->t_resource, str_node->od_name, status); 683 goto errout; 684 } 685 ls = of->f_lease; 686 687 /* Use most current "epoch". */ 688 mutex_enter(&ls->ls_mutex); 689 if (ls->ls_epoch < op->lease_epoch) 690 ls->ls_epoch = op->lease_epoch; 691 mutex_exit(&ls->ls_mutex); 692 693 /* 694 * Get the lease (and oplock) 695 * uses op->lease_state 696 */ 697 op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE; 698 smb2_lease_acquire(sr); 699 700 } else { 701 /* 702 * No lease; maybe get an oplock 703 * uses: op->op_oplock_level 704 */ 705 if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) { 706 op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH; 707 } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) { 708 op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; 709 } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) { 710 op->op_oplock_level = SMB2_OPLOCK_LEVEL_II; 711 } else { 712 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE; 713 } 714 smb2_oplock_acquire(sr); 715 } 716 717 /* 718 * Byte range locks 719 */ 720 alen = 0; 721 u64p = NULL; 722 if (nvlist_lookup_uint64_array(nvl, "locks", &u64p, &alen) == 0) { 723 uint_t i; 724 uint_t nlocks = alen / 3; 725 struct nvlk *nlp; 726 727 nlp = (struct nvlk *)u64p; 728 for (i = 0; i < nlocks; i++) { 729 status = smb_lock_range( 730 sr, 731 nlp->lk_start, 732 nlp->lk_len, 733 nlp->lk_pid, 734 nlp->lk_type, 735 0); 736 if (status != 0) { 737 cmn_err(CE_NOTE, "CA import (%s/%s) " 738 "get lock %d failed 0x%x", 739 tree->t_resource, 740 str_node->od_name, 741 i, status); 742 } 743 nlp++; 744 } 745 } 746 alen = SMB_OFILE_LSEQ_MAX; 747 u8p = NULL; 748 if (nvlist_lookup_uint8_array(nvl, "lockseq", &u8p, &alen) == 0) { 749 if (alen != SMB_OFILE_LSEQ_MAX) { 750 cmn_err(CE_NOTE, "CA import (%s/%s) " 751 "get lockseq bad len=%d", 752 tree->t_resource, 753 str_node->od_name, 754 alen); 755 } else { 756 mutex_enter(&of->f_mutex); 757 bcopy(u8p, of->f_lock_seq, alen); 758 mutex_exit(&of->f_mutex); 759 } 760 } 761 762 /* 763 * Optional "sticky" times (set pending attributes) 764 */ 765 mutex_enter(&of->f_mutex); 766 pa = &of->f_pending_attr; 767 if (nvlist_lookup_hrtime(nvl, "atime", &hrt) == 0) { 768 hrt2ts(hrt, &pa->sa_vattr.va_atime); 769 pa->sa_mask |= SMB_AT_ATIME; 770 } 771 if (nvlist_lookup_hrtime(nvl, "mtime", &hrt) == 0) { 772 hrt2ts(hrt, &pa->sa_vattr.va_mtime); 773 pa->sa_mask |= SMB_AT_MTIME; 774 } 775 if (nvlist_lookup_hrtime(nvl, "ctime", &hrt) == 0) { 776 hrt2ts(hrt, &pa->sa_vattr.va_ctime); 777 pa->sa_mask |= SMB_AT_CTIME; 778 } 779 mutex_exit(&of->f_mutex); 780 781 /* 782 * Make durable and persistent. 783 * See smb2_dh_make_persistent() 784 */ 785 of->dh_vers = SMB2_DURABLE_V2; 786 bcopy(op->create_guid, of->dh_create_guid, UUID_LEN); 787 of->dh_persist = B_TRUE; 788 of->dh_nvfile = str_node; 789 smb_node_ref(str_node); 790 of->dh_nvlist = nvl; 791 nvl = NULL; 792 793 /* 794 * Now make it state orphaned... 795 * See smb_ofile_drop(), then 796 * smb_ofile_save_dh() 797 */ 798 mutex_enter(&of->f_mutex); 799 of->f_state = SMB_OFILE_STATE_SAVE_DH; 800 of->dh_timeout_offset = MSEC2NSEC(smb2_persist_timeout); 801 mutex_exit(&of->f_mutex); 802 803 /* 804 * Finished! 805 */ 806 return (of); 807 808 errout: 809 if (did_open) { 810 smb_ofile_close(of, 0); 811 smb_ofile_release(of); 812 } else { 813 ASSERT(of == NULL); 814 } 815 816 if (nvl != NULL) 817 nvlist_free(nvl); 818 819 return (NULL); 820 } 821 822 static int 823 smb2_dh_read_nvlist(smb_request_t *sr, smb_node_t *node, 824 struct nvlist **nvlpp) 825 { 826 smb_attr_t attr; 827 iovec_t iov; 828 uio_t uio; 829 smb_tree_t *tree = sr->tid_tree; 830 cred_t *kcr = zone_kcred(); 831 size_t flen; 832 int rc; 833 834 bzero(&attr, sizeof (attr)); 835 attr.sa_mask = SMB_AT_SIZE; 836 rc = smb_node_getattr(NULL, node, kcr, NULL, &attr); 837 if (rc != 0) { 838 cmn_err(CE_NOTE, "CA import (%s/%s) getattr rc=%d", 839 tree->t_resource, node->od_name, rc); 840 return (rc); 841 } 842 843 if (attr.sa_vattr.va_size < 4 || 844 attr.sa_vattr.va_size > sr->sr_req_length) { 845 cmn_err(CE_NOTE, "CA import (%s/%s) bad size=%" PRIu64, 846 tree->t_resource, node->od_name, 847 (uint64_t)attr.sa_vattr.va_size); 848 return (EINVAL); 849 } 850 flen = (size_t)attr.sa_vattr.va_size; 851 852 bzero(&uio, sizeof (uio)); 853 iov.iov_base = sr->sr_request_buf; 854 iov.iov_len = flen; 855 uio.uio_iov = &iov; 856 uio.uio_iovcnt = 1; 857 uio.uio_resid = flen; 858 uio.uio_segflg = UIO_SYSSPACE; 859 uio.uio_extflg = UIO_COPY_DEFAULT; 860 rc = smb_fsop_read(sr, kcr, node, NULL, &uio, 0); 861 if (rc != 0) { 862 cmn_err(CE_NOTE, "CA import (%s/%s) read, rc=%d", 863 tree->t_resource, node->od_name, rc); 864 return (rc); 865 } 866 if (uio.uio_resid != 0) { 867 cmn_err(CE_NOTE, "CA import (%s/%s) short read", 868 tree->t_resource, node->od_name); 869 return (EIO); 870 } 871 872 rc = nvlist_unpack(sr->sr_request_buf, flen, nvlpp, KM_SLEEP); 873 if (rc != 0) { 874 cmn_err(CE_NOTE, "CA import (%s/%s) unpack, rc=%d", 875 tree->t_resource, node->od_name, rc); 876 return (rc); 877 } 878 879 return (0); 880 } 881 882 /* 883 * Setup a vestigial credential in of->f_cr just good enough for 884 * smb_is_same_user to determine if the caller owned this ofile. 885 * At reconnect, of->f_cr will be replaced with the caller's. 886 */ 887 static int 888 smb2_dh_import_cred(smb_ofile_t *of, char *sidstr) 889 { 890 #ifdef _FAKE_KERNEL 891 _NOTE(ARGUNUSED(sidstr)) 892 /* fksmbd doesn't have real credentials. */ 893 of->f_cr = CRED(); 894 crhold(of->f_cr); 895 #else 896 char tmpstr[SMB_SID_STRSZ]; 897 ksid_t ksid; 898 cred_t *cr, *oldcr; 899 int rc; 900 901 (void) strlcpy(tmpstr, sidstr, sizeof (tmpstr)); 902 bzero(&ksid, sizeof (ksid)); 903 904 rc = smb_sid_splitstr(tmpstr, &ksid.ks_rid); 905 if (rc != 0) 906 return (rc); 907 cr = crget(); 908 909 ksid.ks_domain = ksid_lookupdomain(tmpstr); 910 crsetsid(cr, &ksid, KSID_USER); 911 ksiddomain_hold(ksid.ks_domain); 912 crsetsid(cr, &ksid, KSID_OWNER); 913 914 /* 915 * Just to avoid leaving the KSID_GROUP slot NULL, 916 * put the "everyone" SID there (S-1-1-0). 917 */ 918 ksid.ks_domain = ksid_lookupdomain("S-1-1"); 919 ksid.ks_rid = 0; 920 crsetsid(cr, &ksid, KSID_GROUP); 921 922 oldcr = of->f_cr; 923 of->f_cr = cr; 924 if (oldcr != NULL) 925 crfree(oldcr); 926 #endif 927 928 return (0); 929 } 930 931 /* 932 * Set Delete-on-Close (DoC) on the persistent state file so it will be 933 * removed when the last ref. goes away (in smb2_dh_close_persistent). 934 * 935 * This is called in just two places: 936 * (1) SMB2_close request -- client tells us to destroy the handle. 937 * (2) smb2_dh_expire -- client has forgotten about this handle. 938 * All other (server-initiated) close calls should leave these 939 * persistent state files in the file system. 940 */ 941 void 942 smb2_dh_setdoc_persistent(smb_ofile_t *of) 943 { 944 smb_node_t *strnode; 945 uint32_t status; 946 947 mutex_enter(&of->dh_nvlock); 948 if ((strnode = of->dh_nvfile) != NULL) 949 smb_node_ref(strnode); 950 mutex_exit(&of->dh_nvlock); 951 952 if (strnode != NULL) { 953 status = smb_node_set_delete_on_close(strnode, 954 zone_kcred(), SMB_CASE_SENSITIVE); 955 if (status != 0) { 956 cmn_err(CE_WARN, "Can't set DoC on CA file: %s", 957 strnode->od_name); 958 DTRACE_PROBE1(rm__ca__err, smb_ofile_t *, of); 959 } 960 smb_node_release(strnode); 961 } 962 } 963 964 /* 965 * During ofile close, free the persistent handle state nvlist and 966 * drop our reference to the state file node (which may unlink it 967 * if smb2_dh_setdoc_persistent was called). 968 */ 969 void 970 smb2_dh_close_persistent(smb_ofile_t *of) 971 { 972 smb_node_t *strnode; 973 struct nvlist *nvl; 974 975 /* 976 * Clear out nvlist and stream linkage 977 */ 978 mutex_enter(&of->dh_nvlock); 979 strnode = of->dh_nvfile; 980 of->dh_nvfile = NULL; 981 nvl = of->dh_nvlist; 982 of->dh_nvlist = NULL; 983 mutex_exit(&of->dh_nvlock); 984 985 if (nvl != NULL) 986 nvlist_free(nvl); 987 988 if (strnode != NULL) 989 smb_node_release(strnode); 990 } 991 992 /* 993 * Make this durable handle persistent. 994 * If we succeed, set of->dh_persist = TRUE. 995 */ 996 int 997 smb2_dh_make_persistent(smb_request_t *sr, smb_ofile_t *of) 998 { 999 char fname[DH_SN_SIZE]; 1000 char sidstr[SMB_SID_STRSZ]; 1001 smb_attr_t attr; 1002 smb_arg_open_t *op = &sr->arg.open; 1003 cred_t *kcr = zone_kcred(); 1004 smb_node_t *dnode = of->f_tree->t_snode; 1005 smb_node_t *fnode = NULL; 1006 ksid_t *ksid; 1007 int rc; 1008 1009 ASSERT(of->dh_nvfile == NULL); 1010 1011 /* 1012 * Create the persistent handle nvlist file. 1013 * It's a named stream in the share root. 1014 */ 1015 smb2_dh_make_stream_name(fname, sizeof (fname), of->f_persistid); 1016 1017 bzero(&attr, sizeof (attr)); 1018 attr.sa_mask = SMB_AT_TYPE | SMB_AT_MODE | SMB_AT_SIZE; 1019 attr.sa_vattr.va_type = VREG; 1020 attr.sa_vattr.va_mode = 0640; 1021 attr.sa_vattr.va_size = 4; 1022 rc = smb_fsop_create(sr, kcr, dnode, fname, &attr, &fnode); 1023 if (rc != 0) 1024 return (rc); 1025 1026 mutex_enter(&of->dh_nvlock); 1027 1028 /* fnode is held. rele in smb2_dh_close_persistent */ 1029 of->dh_nvfile = fnode; 1030 (void) nvlist_alloc(&of->dh_nvlist, NV_UNIQUE_NAME, KM_SLEEP); 1031 1032 /* 1033 * Want the ksid as a string 1034 */ 1035 ksid = crgetsid(of->f_user->u_cred, KSID_USER); 1036 (void) snprintf(sidstr, sizeof (sidstr), "%s-%u", 1037 ksid->ks_domain->kd_name, ksid->ks_rid); 1038 1039 /* 1040 * Fill in the fixed parts of the nvlist 1041 */ 1042 (void) nvlist_add_uint32(of->dh_nvlist, 1043 "info_version", smb2_ca_info_version); 1044 (void) nvlist_add_string(of->dh_nvlist, 1045 "owner_sid", sidstr); 1046 (void) nvlist_add_string(of->dh_nvlist, 1047 "share_name", of->f_tree->t_sharename); 1048 (void) nvlist_add_uint64(of->dh_nvlist, 1049 "file_persistid", of->f_persistid); 1050 (void) nvlist_add_uint8_array(of->dh_nvlist, 1051 "file_guid", of->dh_create_guid, UUID_LEN); 1052 (void) nvlist_add_string(of->dh_nvlist, 1053 "client_ipaddr", sr->session->ip_addr_str); 1054 (void) nvlist_add_uint8_array(of->dh_nvlist, 1055 "client_uuid", sr->session->clnt_uuid, UUID_LEN); 1056 (void) nvlist_add_string(of->dh_nvlist, 1057 "path_name", op->fqi.fq_path.pn_path); 1058 (void) nvlist_add_uint32(of->dh_nvlist, 1059 "granted_access", of->f_granted_access); 1060 (void) nvlist_add_uint32(of->dh_nvlist, 1061 "share_access", of->f_share_access); 1062 (void) nvlist_add_uint32(of->dh_nvlist, 1063 "create_options", of->f_create_options); 1064 if (of->f_lease != NULL) { 1065 smb_lease_t *ls = of->f_lease; 1066 (void) nvlist_add_uint8_array(of->dh_nvlist, 1067 "lease_uuid", ls->ls_key, 16); 1068 (void) nvlist_add_uint32(of->dh_nvlist, 1069 "lease_state", ls->ls_state); 1070 (void) nvlist_add_uint16(of->dh_nvlist, 1071 "lease_epoch", ls->ls_epoch); 1072 (void) nvlist_add_uint16(of->dh_nvlist, 1073 "lease_version", ls->ls_version); 1074 } else { 1075 (void) nvlist_add_uint32(of->dh_nvlist, 1076 "oplock_state", of->f_oplock.og_state); 1077 } 1078 mutex_exit(&of->dh_nvlock); 1079 1080 smb2_dh_update_locks(sr, of); 1081 1082 /* Tell sr update nvlist file */ 1083 sr->dh_nvl_dirty = B_TRUE; 1084 1085 return (0); 1086 } 1087 1088 void 1089 smb2_dh_update_nvfile(smb_request_t *sr) 1090 { 1091 smb_attr_t attr; 1092 iovec_t iov; 1093 uio_t uio; 1094 smb_ofile_t *of = sr->fid_ofile; 1095 cred_t *kcr = zone_kcred(); 1096 char *buf = NULL; 1097 size_t buflen = 0; 1098 uint32_t wcnt; 1099 int rc; 1100 1101 if (of == NULL || of->dh_persist == B_FALSE) 1102 return; 1103 1104 mutex_enter(&of->dh_nvlock); 1105 if (of->dh_nvlist == NULL || of->dh_nvfile == NULL) { 1106 mutex_exit(&of->dh_nvlock); 1107 return; 1108 } 1109 1110 rc = nvlist_size(of->dh_nvlist, &buflen, NV_ENCODE_XDR); 1111 if (rc != 0) 1112 goto out; 1113 buf = kmem_zalloc(buflen, KM_SLEEP); 1114 1115 rc = nvlist_pack(of->dh_nvlist, &buf, &buflen, 1116 NV_ENCODE_XDR, KM_SLEEP); 1117 if (rc != 0) 1118 goto out; 1119 1120 bzero(&attr, sizeof (attr)); 1121 attr.sa_mask = SMB_AT_SIZE; 1122 attr.sa_vattr.va_size = buflen; 1123 rc = smb_node_setattr(sr, of->dh_nvfile, kcr, NULL, &attr); 1124 if (rc != 0) 1125 goto out; 1126 1127 bzero(&uio, sizeof (uio)); 1128 iov.iov_base = (void *) buf; 1129 iov.iov_len = buflen; 1130 uio.uio_iov = &iov; 1131 uio.uio_iovcnt = 1; 1132 uio.uio_resid = buflen; 1133 uio.uio_segflg = UIO_SYSSPACE; 1134 uio.uio_extflg = UIO_COPY_DEFAULT; 1135 rc = smb_fsop_write(sr, kcr, of->dh_nvfile, 1136 NULL, &uio, &wcnt, 0); 1137 if (rc == 0 && wcnt != buflen) 1138 rc = EIO; 1139 1140 out: 1141 mutex_exit(&of->dh_nvlock); 1142 1143 if (rc != 0) { 1144 cmn_err(CE_WARN, 1145 "clnt(%s) failed to update persistent handle, rc=%d", 1146 sr->session->ip_addr_str, rc); 1147 } 1148 1149 if (buf != NULL) { 1150 kmem_free(buf, buflen); 1151 } 1152 } 1153 1154 /* 1155 * Called after f_oplock (and lease) changes 1156 * If lease, update: lease_state, lease_epoch 1157 * else (oplock) update: oplock_state 1158 */ 1159 void 1160 smb2_dh_update_oplock(smb_request_t *sr, smb_ofile_t *of) 1161 { 1162 smb_lease_t *ls; 1163 1164 mutex_enter(&of->dh_nvlock); 1165 if (of->dh_nvlist == NULL) { 1166 mutex_exit(&of->dh_nvlock); 1167 return; 1168 } 1169 1170 if (of->f_lease != NULL) { 1171 ls = of->f_lease; 1172 (void) nvlist_add_uint32(of->dh_nvlist, 1173 "lease_state", ls->ls_state); 1174 (void) nvlist_add_uint16(of->dh_nvlist, 1175 "lease_epoch", ls->ls_epoch); 1176 } else { 1177 (void) nvlist_add_uint32(of->dh_nvlist, 1178 "oplock_state", of->f_oplock.og_state); 1179 } 1180 mutex_exit(&of->dh_nvlock); 1181 1182 sr->dh_nvl_dirty = B_TRUE; 1183 } 1184 1185 /* 1186 * Save locks from this ofile as an array of uint64_t, where the 1187 * elements are triplets: (start, length, (pid << 32) | type) 1188 * Note pid should always be zero for SMB2, so we could use 1189 * that 32-bit spot for something else if needed. 1190 */ 1191 void 1192 smb2_dh_update_locks(smb_request_t *sr, smb_ofile_t *of) 1193 { 1194 uint8_t lseq[SMB_OFILE_LSEQ_MAX]; 1195 smb_node_t *node = of->f_node; 1196 smb_llist_t *llist = &node->n_lock_list; 1197 size_t vec_sz; // storage size 1198 uint_t my_cnt = 0; 1199 uint64_t *vec = NULL; 1200 struct nvlk *nlp; 1201 smb_lock_t *lock; 1202 1203 smb_llist_enter(llist, RW_READER); 1204 vec_sz = (llist->ll_count + 1) * sizeof (struct nvlk); 1205 vec = kmem_alloc(vec_sz, KM_SLEEP); 1206 nlp = (struct nvlk *)vec; 1207 for (lock = smb_llist_head(llist); 1208 lock != NULL; 1209 lock = smb_llist_next(llist, lock)) { 1210 if (lock->l_file != of) 1211 continue; 1212 nlp->lk_start = lock->l_start; 1213 nlp->lk_len = lock->l_length; 1214 nlp->lk_pid = lock->l_pid; 1215 nlp->lk_type = lock->l_type; 1216 nlp++; 1217 my_cnt++; 1218 } 1219 smb_llist_exit(llist); 1220 1221 mutex_enter(&of->f_mutex); 1222 bcopy(of->f_lock_seq, lseq, sizeof (lseq)); 1223 mutex_exit(&of->f_mutex); 1224 1225 mutex_enter(&of->dh_nvlock); 1226 if (of->dh_nvlist != NULL) { 1227 1228 (void) nvlist_add_uint64_array(of->dh_nvlist, 1229 "locks", vec, my_cnt * 3); 1230 1231 (void) nvlist_add_uint8_array(of->dh_nvlist, 1232 "lockseq", lseq, sizeof (lseq)); 1233 } 1234 mutex_exit(&of->dh_nvlock); 1235 1236 kmem_free(vec, vec_sz); 1237 1238 sr->dh_nvl_dirty = B_TRUE; 1239 } 1240 1241 /* 1242 * Save "sticky" times 1243 */ 1244 void 1245 smb2_dh_update_times(smb_request_t *sr, smb_ofile_t *of, smb_attr_t *attr) 1246 { 1247 hrtime_t t; 1248 1249 mutex_enter(&of->dh_nvlock); 1250 if (of->dh_nvlist == NULL) { 1251 mutex_exit(&of->dh_nvlock); 1252 return; 1253 } 1254 1255 if (attr->sa_mask & SMB_AT_ATIME) { 1256 t = ts2hrt(&attr->sa_vattr.va_atime); 1257 (void) nvlist_add_hrtime(of->dh_nvlist, "atime", t); 1258 } 1259 if (attr->sa_mask & SMB_AT_MTIME) { 1260 t = ts2hrt(&attr->sa_vattr.va_mtime); 1261 (void) nvlist_add_hrtime(of->dh_nvlist, "mtime", t); 1262 } 1263 if (attr->sa_mask & SMB_AT_CTIME) { 1264 t = ts2hrt(&attr->sa_vattr.va_ctime); 1265 (void) nvlist_add_hrtime(of->dh_nvlist, "ctime", t); 1266 } 1267 mutex_exit(&of->dh_nvlock); 1268 1269 sr->dh_nvl_dirty = B_TRUE; 1270 } 1271 1272 1273 /* 1274 * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7): 1275 * - security descriptor must match provided descriptor 1276 * 1277 * If file is leased: 1278 * - lease must be requested 1279 * - client guid must match session guid 1280 * - file name must match given name 1281 * - lease key must match provided lease key 1282 * If file is not leased: 1283 * - Lease must not be requested 1284 * 1285 * dh_v2 only: 1286 * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true 1287 * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false 1288 * - desired access, share access, and create_options must be ignored 1289 * - createguid must match 1290 */ 1291 static uint32_t 1292 smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of) 1293 { 1294 smb_arg_open_t *op = &sr->sr_open; 1295 char *fname; 1296 1297 if (of->f_lease != NULL) { 1298 if (bcmp(sr->session->clnt_uuid, 1299 of->f_lease->ls_clnt, 16) != 0) 1300 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1301 1302 if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_LEASE) 1303 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1304 if (bcmp(op->lease_key, of->f_lease->ls_key, 1305 SMB_LEASE_KEY_SZ) != 0) 1306 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1307 1308 /* 1309 * We're supposed to check the name is the same. 1310 * Not really necessary to do this, so just do 1311 * minimal effort (check last component) 1312 */ 1313 fname = strrchr(op->fqi.fq_path.pn_path, '\\'); 1314 if (fname != NULL) 1315 fname++; 1316 else 1317 fname = op->fqi.fq_path.pn_path; 1318 if (smb_strcasecmp(fname, of->f_node->od_name, 0) != 0) { 1319 #ifdef DEBUG 1320 cmn_err(CE_NOTE, "reconnect name <%s> of name <%s>", 1321 fname, of->f_node->od_name); 1322 #endif 1323 return (NT_STATUS_INVALID_PARAMETER); 1324 } 1325 } else { 1326 if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) 1327 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1328 } 1329 1330 if (op->dh_vers == SMB2_DURABLE_V2) { 1331 boolean_t op_persist = 1332 ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0); 1333 if (of->dh_persist != op_persist) 1334 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1335 if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN)) 1336 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 1337 } 1338 1339 if (!smb_is_same_user(sr->user_cr, of->f_cr)) 1340 return (NT_STATUS_ACCESS_DENIED); 1341 1342 return (NT_STATUS_SUCCESS); 1343 } 1344 1345 /* 1346 * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2) 1347 * 1348 * Looks up an ofile on the server's sv_dh_list by the persistid. 1349 * If found, it validates the request. 1350 * (see smb2_dh_reconnect_checks() for details) 1351 * If the checks are passed, add it onto the new tree's list. 1352 * 1353 * Note that the oplock break code path can get to an ofile via the node 1354 * ofile list. It starts with a ref taken in smb_ofile_hold_olbrk, which 1355 * waits if the ofile is found in state RECONNECT. That wait happens with 1356 * the node ofile list lock held as reader, and the oplock mutex held. 1357 * Implications of that are: While we're in state RECONNECT, we shoud NOT 1358 * block (at least, not for long) and must not try to enter any of the 1359 * node ofile list lock or oplock mutex. Thankfully, we don't need to 1360 * enter those while reclaiming an orphaned ofile. 1361 */ 1362 uint32_t 1363 smb2_dh_reconnect(smb_request_t *sr) 1364 { 1365 smb_arg_open_t *op = &sr->sr_open; 1366 smb_tree_t *tree = sr->tid_tree; 1367 smb_ofile_t *of; 1368 cred_t *old_cr; 1369 uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND; 1370 uint16_t fid = 0; 1371 1372 if (smb_idpool_alloc(&tree->t_fid_pool, &fid)) 1373 return (NT_STATUS_TOO_MANY_OPENED_FILES); 1374 1375 /* Find orphaned handle. */ 1376 of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent); 1377 if (of == NULL) 1378 goto errout; 1379 1380 mutex_enter(&of->f_mutex); 1381 if (of->f_state != SMB_OFILE_STATE_ORPHANED) { 1382 mutex_exit(&of->f_mutex); 1383 goto errout; 1384 } 1385 1386 status = smb2_dh_reconnect_checks(sr, of); 1387 if (status != NT_STATUS_SUCCESS) { 1388 mutex_exit(&of->f_mutex); 1389 goto errout; 1390 } 1391 1392 /* 1393 * Note: cv_broadcast(&of->f_cv) when we're 1394 * done messing around in this state. 1395 * See: smb_ofile_hold_olbrk() 1396 */ 1397 of->f_state = SMB_OFILE_STATE_RECONNECT; 1398 mutex_exit(&of->f_mutex); 1399 1400 /* 1401 * At this point, we should be the only thread with a ref on the 1402 * ofile, and the RECONNECT state should prevent new refs from 1403 * being granted, or other durable threads from observing or 1404 * reclaiming it. Put this ofile in the new tree, similar to 1405 * the last part of smb_ofile_open. 1406 */ 1407 1408 old_cr = of->f_cr; 1409 of->f_cr = sr->user_cr; 1410 crhold(of->f_cr); 1411 crfree(old_cr); 1412 1413 of->f_session = sr->session; /* hold is via user and tree */ 1414 smb_user_hold_internal(sr->uid_user); 1415 of->f_user = sr->uid_user; 1416 smb_tree_hold_internal(tree); 1417 of->f_tree = tree; 1418 of->f_fid = fid; 1419 1420 smb_llist_enter(&tree->t_ofile_list, RW_WRITER); 1421 smb_llist_insert_tail(&tree->t_ofile_list, of); 1422 smb_llist_exit(&tree->t_ofile_list); 1423 atomic_inc_32(&tree->t_open_files); 1424 atomic_inc_32(&sr->session->s_file_cnt); 1425 1426 /* 1427 * The ofile is now in the caller's session & tree. 1428 * 1429 * In case smb_ofile_hold or smb_oplock_send_brk() are 1430 * waiting for state RECONNECT to complete, wakeup. 1431 */ 1432 mutex_enter(&of->f_mutex); 1433 of->dh_expire_time = 0; 1434 of->f_state = SMB_OFILE_STATE_OPEN; 1435 cv_broadcast(&of->f_cv); 1436 mutex_exit(&of->f_mutex); 1437 1438 /* 1439 * The ofile is now visible in the new session. 1440 * From here, this is similar to the last part of 1441 * smb_common_open(). 1442 */ 1443 op->fqi.fq_fattr.sa_mask = SMB_AT_ALL; 1444 (void) smb_node_getattr(sr, of->f_node, zone_kcred(), of, 1445 &op->fqi.fq_fattr); 1446 1447 /* 1448 * Set up the fileid and dosattr in open_param for response 1449 */ 1450 op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid; 1451 op->dattr = op->fqi.fq_fattr.sa_dosattr; 1452 1453 /* 1454 * Set up the file type in open_param for the response 1455 * The ref. from ofile lookup is "given" to fid_ofile. 1456 */ 1457 op->ftype = SMB_FTYPE_DISK; 1458 sr->smb_fid = of->f_fid; 1459 sr->fid_ofile = of; 1460 1461 if (smb_node_is_file(of->f_node)) { 1462 op->dsize = op->fqi.fq_fattr.sa_vattr.va_size; 1463 } else { 1464 /* directory or symlink */ 1465 op->dsize = 0; 1466 } 1467 1468 op->create_options = 0; /* no more modifications wanted */ 1469 op->action_taken = SMB_OACT_OPENED; 1470 return (NT_STATUS_SUCCESS); 1471 1472 errout: 1473 if (of != NULL) 1474 smb_ofile_release(of); 1475 if (fid != 0) 1476 smb_idpool_free(&tree->t_fid_pool, fid); 1477 1478 return (status); 1479 } 1480 1481 /* 1482 * Durable handle expiration 1483 * ofile state is _EXPIRED 1484 */ 1485 static void 1486 smb2_dh_expire(void *arg) 1487 { 1488 smb_ofile_t *of = (smb_ofile_t *)arg; 1489 1490 if (of->dh_persist) 1491 smb2_dh_setdoc_persistent(of); 1492 smb_ofile_close(of, 0); 1493 smb_ofile_release(of); 1494 } 1495 1496 void 1497 smb2_durable_timers(smb_server_t *sv) 1498 { 1499 smb_hash_t *hash; 1500 smb_llist_t *bucket; 1501 smb_ofile_t *of; 1502 hrtime_t now; 1503 int i; 1504 1505 hash = sv->sv_persistid_ht; 1506 now = gethrtime(); 1507 1508 for (i = 0; i < hash->num_buckets; i++) { 1509 bucket = &hash->buckets[i].b_list; 1510 smb_llist_enter(bucket, RW_READER); 1511 for (of = smb_llist_head(bucket); 1512 of != NULL; 1513 of = smb_llist_next(bucket, of)) { 1514 SMB_OFILE_VALID(of); 1515 1516 /* 1517 * Check outside the mutex first to avoid some 1518 * mutex_enter work in this loop. If the state 1519 * changes under foot, the worst that happens 1520 * is we either enter the mutex when we might 1521 * not have needed to, or we miss some DH in 1522 * this pass and get it on the next. 1523 */ 1524 if (of->f_state != SMB_OFILE_STATE_ORPHANED) 1525 continue; 1526 1527 mutex_enter(&of->f_mutex); 1528 /* STATE_ORPHANED implies dh_expire_time != 0 */ 1529 if (of->f_state == SMB_OFILE_STATE_ORPHANED && 1530 of->dh_expire_time <= now) { 1531 of->f_state = SMB_OFILE_STATE_EXPIRED; 1532 /* inline smb_ofile_hold_internal() */ 1533 of->f_refcnt++; 1534 smb_llist_post(bucket, of, smb2_dh_expire); 1535 } 1536 mutex_exit(&of->f_mutex); 1537 } 1538 smb_llist_exit(bucket); 1539 } 1540 } 1541 1542 /* 1543 * This is called when we're about to add a new open to some node. 1544 * If we still have orphaned durable handles on this node, let's 1545 * assume the client has lost interest in those and close them, 1546 * otherwise we might conflict with our own orphaned handles. 1547 * 1548 * We need this because we import persistent handles "speculatively" 1549 * during share import (before the client ever asks for reconnect). 1550 * That allows us to avoid any need for a "create blackout" (or 1551 * "grace period") because the imported handles prevent unwanted 1552 * conflicting opens from other clients. However, if some client 1553 * "forgets" about a persistent handle (*cough* Hyper-V) and tries 1554 * a new (conflicting) open instead of a reconnect, that might 1555 * fail unless we expire our orphaned durables handle first. 1556 * 1557 * Logic similar to smb_node_open_check() 1558 */ 1559 void 1560 smb2_dh_close_my_orphans(smb_request_t *sr, smb_ofile_t *new_of) 1561 { 1562 smb_node_t *node = new_of->f_node; 1563 smb_ofile_t *of; 1564 1565 SMB_NODE_VALID(node); 1566 1567 smb_llist_enter(&node->n_ofile_list, RW_READER); 1568 for (of = smb_llist_head(&node->n_ofile_list); 1569 of != NULL; 1570 of = smb_llist_next(&node->n_ofile_list, of)) { 1571 1572 /* Same client? */ 1573 if (of->f_lease != NULL && 1574 bcmp(sr->session->clnt_uuid, 1575 of->f_lease->ls_clnt, 16) != 0) 1576 continue; 1577 1578 if (!smb_is_same_user(sr->user_cr, of->f_cr)) 1579 continue; 1580 1581 mutex_enter(&of->f_mutex); 1582 if (of->f_state == SMB_OFILE_STATE_ORPHANED) { 1583 of->f_state = SMB_OFILE_STATE_EXPIRED; 1584 /* inline smb_ofile_hold_internal() */ 1585 of->f_refcnt++; 1586 smb_llist_post(&node->n_ofile_list, 1587 of, smb2_dh_expire); 1588 } 1589 mutex_exit(&of->f_mutex); 1590 } 1591 1592 smb_llist_exit(&node->n_ofile_list); 1593 } 1594 1595 /* 1596 * Called for each orphaned DH during shutdown. 1597 * Clean out any in-memory state, but leave any 1598 * on-disk persistent handle state in place. 1599 */ 1600 static void 1601 smb2_dh_cleanup(void *arg) 1602 { 1603 smb_ofile_t *of = (smb_ofile_t *)arg; 1604 smb_node_t *strnode; 1605 struct nvlist *nvl; 1606 1607 /* 1608 * Intentionally skip smb2_dh_close_persistent by 1609 * clearing dh_nvfile before smb_ofile_close(). 1610 */ 1611 mutex_enter(&of->dh_nvlock); 1612 strnode = of->dh_nvfile; 1613 of->dh_nvfile = NULL; 1614 nvl = of->dh_nvlist; 1615 of->dh_nvlist = NULL; 1616 mutex_exit(&of->dh_nvlock); 1617 1618 if (nvl != NULL) 1619 nvlist_free(nvl); 1620 1621 if (strnode != NULL) 1622 smb_node_release(strnode); 1623 1624 smb_ofile_close(of, 0); 1625 smb_ofile_release(of); 1626 } 1627 1628 /* 1629 * Clean out durable handles during shutdown. 1630 * 1631 * Like, smb2_durable_timers but cleanup only in-memory state, 1632 * and leave any persistent state there for later reconnect. 1633 */ 1634 void 1635 smb2_dh_shutdown(smb_server_t *sv) 1636 { 1637 smb_hash_t *hash; 1638 smb_llist_t *bucket; 1639 smb_ofile_t *of; 1640 int i; 1641 1642 hash = sv->sv_persistid_ht; 1643 1644 for (i = 0; i < hash->num_buckets; i++) { 1645 bucket = &hash->buckets[i].b_list; 1646 smb_llist_enter(bucket, RW_READER); 1647 of = smb_llist_head(bucket); 1648 while (of != NULL) { 1649 SMB_OFILE_VALID(of); 1650 mutex_enter(&of->f_mutex); 1651 1652 switch (of->f_state) { 1653 case SMB_OFILE_STATE_ORPHANED: 1654 of->f_state = SMB_OFILE_STATE_EXPIRED; 1655 /* inline smb_ofile_hold_internal() */ 1656 of->f_refcnt++; 1657 smb_llist_post(bucket, of, smb2_dh_cleanup); 1658 break; 1659 default: 1660 break; 1661 } 1662 mutex_exit(&of->f_mutex); 1663 of = smb_llist_next(bucket, of); 1664 } 1665 smb_llist_exit(bucket); 1666 } 1667 1668 #ifdef DEBUG 1669 for (i = 0; i < hash->num_buckets; i++) { 1670 bucket = &hash->buckets[i].b_list; 1671 smb_llist_enter(bucket, RW_READER); 1672 of = smb_llist_head(bucket); 1673 while (of != NULL) { 1674 SMB_OFILE_VALID(of); 1675 cmn_err(CE_NOTE, "dh_shutdown leaked of=%p", 1676 (void *)of); 1677 of = smb_llist_next(bucket, of); 1678 } 1679 smb_llist_exit(bucket); 1680 } 1681 #endif // DEBUG 1682 } 1683 1684 uint32_t 1685 smb2_fsctl_set_resilient(smb_request_t *sr, smb_fsctl_t *fsctl) 1686 { 1687 uint32_t timeout; 1688 smb_ofile_t *of = sr->fid_ofile; 1689 1690 /* 1691 * Note: The spec does not explicitly prohibit resilient directories 1692 * the same way it prohibits durable directories. We prohibit them 1693 * anyway as a simplifying assumption, as there doesn't seem to be 1694 * much use for it. (HYPER-V only seems to use it on files anyway) 1695 */ 1696 if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node)) 1697 return (NT_STATUS_INVALID_PARAMETER); 1698 1699 (void) smb_mbc_decodef(fsctl->in_mbc, "l4.", 1700 &timeout); /* milliseconds */ 1701 1702 if (smb2_enable_dh == 0) 1703 return (NT_STATUS_NOT_SUPPORTED); 1704 1705 /* 1706 * The spec wants us to return INVALID_PARAMETER if the timeout 1707 * is too large, but we have no way of informing the client 1708 * what an appropriate timeout is, so just set the timeout to 1709 * our max and return SUCCESS. 1710 */ 1711 if (timeout == 0) 1712 timeout = smb2_res_def_timeout; 1713 if (timeout > smb2_res_max_timeout) 1714 timeout = smb2_res_max_timeout; 1715 1716 mutex_enter(&of->f_mutex); 1717 of->dh_vers = SMB2_RESILIENT; 1718 of->dh_timeout_offset = MSEC2NSEC(timeout); 1719 mutex_exit(&of->f_mutex); 1720 1721 return (NT_STATUS_SUCCESS); 1722 } 1723