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