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