xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_durable.c (revision 2a295025ab352ac2f6469a947d5b5e2f9379f943)
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