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 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 <smbsrv/string.h>
25 #include <smbsrv/smb_kproto.h>
26 #include <smbsrv/smb_fsops.h>
27 #include <smbsrv/smbinfo.h>
28 #include <smbsrv/smb2_kproto.h>
29 
30 /* Windows default values from [MS-SMB2] */
31 /*
32  * (times in seconds)
33  * resilient:
34  * MaxTimeout = 300 (win7+)
35  * if timeout > MaxTimeout, ERROR
36  * if timeout != 0, timeout = req.timeout
37  * if timeout == 0, timeout = (infinity) (Win7/w2k8r2)
38  * if timeout == 0, timeout = 120 (Win8+)
39  * v2:
40  * if timeout != 0, timeout = MIN(timeout, 300) (spec)
41  * if timeout != 0, timeout = timeout (win8/2k12)
42  * if timeout == 0, timeout = Share.CATimeout. \
43  *	if Share.CATimeout == 0, timeout = 60 (win8/w2k12)
44  * if timeout == 0, timeout = 180 (win8.1/w2k12r2)
45  * open.timeout = 60 (win8/w2k12r2) (i.e. we ignore the request)
46  * v1:
47  * open.timeout = 16 minutes
48  */
49 
50 uint32_t smb2_dh_def_timeout = 60 * MILLISEC;	/* mSec. */
51 uint32_t smb2_dh_max_timeout = 300 * MILLISEC;	/* mSec. */
52 
53 uint32_t smb2_res_def_timeout = 120 * MILLISEC;	/* mSec. */
54 uint32_t smb2_res_max_timeout = 300 * MILLISEC;	/* mSec. */
55 
56 /*
57  * smb_dh_should_save
58  *
59  * During session tear-down, decide whether to keep a durable handle.
60  *
61  * There are two cases where we save durable handles:
62  * 1. An SMB2 LOGOFF request was received
63  * 2. An unexpected disconnect from the client
64  *    Note: Specifying a PrevSessionID in session setup
65  *    is considered a disconnect (we just haven't learned about it yet)
66  * In every other case, we close durable handles.
67  *
68  * [MS-SMB2] 3.3.5.6 SMB2_LOGOFF
69  * [MS-SMB2] 3.3.7.1 Handling Loss of a Connection
70  *
71  * If any of the following are true, preserve for reconnect:
72  *
73  * - Open.IsResilient is TRUE.
74  *
75  * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_BATCH and
76  *   Open.OplockState == Held, and Open.IsDurable is TRUE.
77  *
78  * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_LEASE,
79  *   Lease.LeaseState SMB2_LEASE_HANDLE_CACHING,
80  *   Open.OplockState == Held, and Open.IsDurable is TRUE.
81  *
82  * - Open.IsPersistent is TRUE.
83  */
84 boolean_t
85 smb_dh_should_save(smb_ofile_t *of)
86 {
87 	ASSERT(MUTEX_HELD(&of->f_mutex));
88 	ASSERT(of->dh_vers != SMB2_NOT_DURABLE);
89 
90 	if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_NONE)
91 		return (B_FALSE);
92 
93 	if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_ALL)
94 		return (B_TRUE);
95 
96 	switch (of->dh_vers) {
97 	case SMB2_RESILIENT:
98 		return (B_TRUE);
99 
100 	case SMB2_DURABLE_V2:
101 		if (of->dh_persist)
102 			return (B_TRUE);
103 		/* FALLTHROUGH */
104 	case SMB2_DURABLE_V1:
105 		/* IS durable (v1 or v2) */
106 		if ((of->f_oplock.og_state & (OPLOCK_LEVEL_BATCH |
107 		    OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
108 			return (B_TRUE);
109 		/* FALLTHROUGH */
110 	case SMB2_NOT_DURABLE:
111 	default:
112 		break;
113 	}
114 
115 	return (B_FALSE);
116 }
117 
118 /*
119  * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7):
120  * - security descriptor must match provided descriptor
121  *
122  * If file is leased:
123  * - lease must be requested
124  * - client guid must match session guid
125  * - file name must match given name
126  * - lease key must match provided lease key
127  * If file is not leased:
128  * - Lease must not be requested
129  *
130  * dh_v2 only:
131  * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true
132  * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false
133  * - desired access, share access, and create_options must be ignored
134  * - createguid must match
135  */
136 static uint32_t
137 smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of)
138 {
139 	smb_arg_open_t	*op = &sr->sr_open;
140 	char *fname;
141 
142 	if (of->f_lease != NULL) {
143 		if (bcmp(sr->session->clnt_uuid,
144 		    of->f_lease->ls_clnt, 16) != 0)
145 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
146 
147 		if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_LEASE)
148 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
149 		if (bcmp(op->lease_key, of->f_lease->ls_key,
150 		    SMB_LEASE_KEY_SZ) != 0)
151 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
152 
153 		/*
154 		 * We're supposed to check the name is the same.
155 		 * Not really necessary to do this, so just do
156 		 * minimal effort (check last component)
157 		 */
158 		fname = strrchr(op->fqi.fq_path.pn_path, '\\');
159 		if (fname != NULL)
160 			fname++;
161 		else
162 			fname = op->fqi.fq_path.pn_path;
163 		if (smb_strcasecmp(fname, of->f_node->od_name, 0) != 0) {
164 #ifdef	DEBUG
165 			cmn_err(CE_NOTE, "reconnect name <%s> of name <%s>",
166 			    fname, of->f_node->od_name);
167 #endif
168 			return (NT_STATUS_INVALID_PARAMETER);
169 		}
170 	} else {
171 		if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
172 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
173 	}
174 
175 	if (op->dh_vers == SMB2_DURABLE_V2) {
176 		boolean_t op_persist =
177 		    ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0);
178 		if (of->dh_persist != op_persist)
179 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
180 		if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN))
181 			return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
182 	}
183 
184 	if (!smb_is_same_user(sr->user_cr, of->f_cr))
185 		return (NT_STATUS_ACCESS_DENIED);
186 
187 	return (NT_STATUS_SUCCESS);
188 }
189 
190 /*
191  * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2)
192  *
193  * Looks up an ofile on the server's sv_dh_list by the persistid.
194  * If found, it validates the request.
195  * (see smb2_dh_reconnect_checks() for details)
196  * If the checks are passed, add it onto the new tree's list.
197  *
198  * Note that the oplock break code path can get to an ofile via the node
199  * ofile list.  It starts with a ref taken in smb_ofile_hold_olbrk, which
200  * waits if the ofile is found in state RECONNECT.  That wait happens with
201  * the node ofile list lock held as reader, and the oplock mutex held.
202  * Implications of that are: While we're in state RECONNECT, we shoud NOT
203  * block (at least, not for long) and must not try to enter any of the
204  * node ofile list lock or oplock mutex.  Thankfully, we don't need to
205  * enter those while reclaiming an orphaned ofile.
206  */
207 uint32_t
208 smb2_dh_reconnect(smb_request_t *sr)
209 {
210 	smb_arg_open_t	*op = &sr->sr_open;
211 	smb_tree_t *tree = sr->tid_tree;
212 	smb_ofile_t *of;
213 	cred_t *old_cr;
214 	uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
215 	uint16_t fid = 0;
216 
217 	if (smb_idpool_alloc(&tree->t_fid_pool, &fid))
218 		return (NT_STATUS_TOO_MANY_OPENED_FILES);
219 
220 	/* Find orphaned handle. */
221 	of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent);
222 	if (of == NULL)
223 		goto errout;
224 
225 	mutex_enter(&of->f_mutex);
226 	if (of->f_state != SMB_OFILE_STATE_ORPHANED) {
227 		mutex_exit(&of->f_mutex);
228 		goto errout;
229 	}
230 
231 	status = smb2_dh_reconnect_checks(sr, of);
232 	if (status != NT_STATUS_SUCCESS) {
233 		mutex_exit(&of->f_mutex);
234 		goto errout;
235 	}
236 
237 	/*
238 	 * Note: cv_broadcast(&of->f_cv) when we're
239 	 * done messing around in this state.
240 	 * See: smb_ofile_hold_olbrk()
241 	 */
242 	of->f_state = SMB_OFILE_STATE_RECONNECT;
243 	mutex_exit(&of->f_mutex);
244 
245 	/*
246 	 * At this point, we should be the only thread with a ref on the
247 	 * ofile, and the RECONNECT state should prevent new refs from
248 	 * being granted, or other durable threads from observing or
249 	 * reclaiming it. Put this ofile in the new tree, similar to
250 	 * the last part of smb_ofile_open.
251 	 */
252 
253 	old_cr = of->f_cr;
254 	of->f_cr = sr->user_cr;
255 	crhold(of->f_cr);
256 	crfree(old_cr);
257 
258 	of->f_session = sr->session; /* hold is via user and tree */
259 	smb_user_hold_internal(sr->uid_user);
260 	of->f_user = sr->uid_user;
261 	smb_tree_hold_internal(tree);
262 	of->f_tree = tree;
263 	of->f_fid = fid;
264 
265 	smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
266 	smb_llist_insert_tail(&tree->t_ofile_list, of);
267 	smb_llist_exit(&tree->t_ofile_list);
268 	atomic_inc_32(&tree->t_open_files);
269 	atomic_inc_32(&sr->session->s_file_cnt);
270 
271 	/*
272 	 * The ofile is now in the caller's session & tree.
273 	 *
274 	 * In case smb_ofile_hold or smb_oplock_send_brk() are
275 	 * waiting for state RECONNECT to complete, wakeup.
276 	 */
277 	mutex_enter(&of->f_mutex);
278 	of->dh_expire_time = 0;
279 	of->f_state = SMB_OFILE_STATE_OPEN;
280 	cv_broadcast(&of->f_cv);
281 	mutex_exit(&of->f_mutex);
282 
283 	/*
284 	 * The ofile is now visible in the new session.
285 	 * From here, this is similar to the last part of
286 	 * smb_common_open().
287 	 */
288 	op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
289 	(void) smb_node_getattr(sr, of->f_node, zone_kcred(), of,
290 	    &op->fqi.fq_fattr);
291 
292 	/*
293 	 * Set up the fileid and dosattr in open_param for response
294 	 */
295 	op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid;
296 	op->dattr = op->fqi.fq_fattr.sa_dosattr;
297 
298 	/*
299 	 * Set up the file type in open_param for the response
300 	 * The ref. from ofile lookup is "given" to fid_ofile.
301 	 */
302 	op->ftype = SMB_FTYPE_DISK;
303 	sr->smb_fid = of->f_fid;
304 	sr->fid_ofile = of;
305 
306 	if (smb_node_is_file(of->f_node)) {
307 		op->dsize = op->fqi.fq_fattr.sa_vattr.va_size;
308 	} else {
309 		/* directory or symlink */
310 		op->dsize = 0;
311 	}
312 
313 	op->create_options = 0; /* no more modifications wanted */
314 	op->action_taken = SMB_OACT_OPENED;
315 	return (NT_STATUS_SUCCESS);
316 
317 errout:
318 	if (of != NULL)
319 		smb_ofile_release(of);
320 	if (fid != 0)
321 		smb_idpool_free(&tree->t_fid_pool, fid);
322 
323 	return (status);
324 }
325 
326 /*
327  * Durable handle expiration
328  * ofile state is _EXPIRED
329  */
330 static void
331 smb2_dh_expire(void *arg)
332 {
333 	smb_ofile_t *of = (smb_ofile_t *)arg;
334 
335 	smb_ofile_close(of, 0);
336 	smb_ofile_release(of);
337 }
338 
339 void
340 smb2_durable_timers(smb_server_t *sv)
341 {
342 	smb_hash_t *hash;
343 	smb_llist_t *bucket;
344 	smb_ofile_t *of;
345 	hrtime_t now;
346 	int i;
347 
348 	hash = sv->sv_persistid_ht;
349 	now = gethrtime();
350 
351 	for (i = 0; i < hash->num_buckets; i++) {
352 		bucket = &hash->buckets[i].b_list;
353 		smb_llist_enter(bucket, RW_READER);
354 		for (of = smb_llist_head(bucket);
355 		    of != NULL;
356 		    of = smb_llist_next(bucket, of)) {
357 			SMB_OFILE_VALID(of);
358 
359 			/*
360 			 * Check outside the mutex first to avoid some
361 			 * mutex_enter work in this loop.  If the state
362 			 * changes under foot, the worst that happens
363 			 * is we either enter the mutex when we might
364 			 * not have needed to, or we miss some DH in
365 			 * this pass and get it on the next.
366 			 */
367 			if (of->f_state != SMB_OFILE_STATE_ORPHANED)
368 				continue;
369 
370 			mutex_enter(&of->f_mutex);
371 			/* STATE_ORPHANED implies dh_expire_time != 0 */
372 			if (of->f_state == SMB_OFILE_STATE_ORPHANED &&
373 			    of->dh_expire_time <= now) {
374 				of->f_state = SMB_OFILE_STATE_EXPIRED;
375 				/* inline smb_ofile_hold_internal() */
376 				of->f_refcnt++;
377 				smb_llist_post(bucket, of, smb2_dh_expire);
378 			}
379 			mutex_exit(&of->f_mutex);
380 		}
381 		smb_llist_exit(bucket);
382 	}
383 }
384 
385 /*
386  * Clean out durable handles during shutdown.
387  * Like, smb2_durable_timers but expire all,
388  * and make sure the hash buckets are empty.
389  */
390 void
391 smb2_dh_shutdown(smb_server_t *sv)
392 {
393 	smb_hash_t *hash;
394 	smb_llist_t *bucket;
395 	smb_ofile_t *of;
396 	int i;
397 
398 	hash = sv->sv_persistid_ht;
399 
400 	for (i = 0; i < hash->num_buckets; i++) {
401 		bucket = &hash->buckets[i].b_list;
402 		smb_llist_enter(bucket, RW_READER);
403 		of = smb_llist_head(bucket);
404 		while (of != NULL) {
405 			SMB_OFILE_VALID(of);
406 			mutex_enter(&of->f_mutex);
407 
408 			switch (of->f_state) {
409 			case SMB_OFILE_STATE_ORPHANED:
410 				of->f_state = SMB_OFILE_STATE_EXPIRED;
411 				/* inline smb_ofile_hold_internal() */
412 				of->f_refcnt++;
413 				smb_llist_post(bucket, of, smb2_dh_expire);
414 				break;
415 			default:
416 				break;
417 			}
418 			mutex_exit(&of->f_mutex);
419 			of = smb_llist_next(bucket, of);
420 		}
421 		smb_llist_exit(bucket);
422 	}
423 
424 #ifdef	DEBUG
425 	for (i = 0; i < hash->num_buckets; i++) {
426 		bucket = &hash->buckets[i].b_list;
427 		smb_llist_enter(bucket, RW_READER);
428 		of = smb_llist_head(bucket);
429 		while (of != NULL) {
430 			SMB_OFILE_VALID(of);
431 			cmn_err(CE_NOTE, "dh_shutdown leaked of=%p",
432 			    (void *)of);
433 			of = smb_llist_next(bucket, of);
434 		}
435 		smb_llist_exit(bucket);
436 	}
437 #endif	// DEBUG
438 }
439 
440 uint32_t
441 smb2_fsctl_set_resilient(smb_request_t *sr, smb_fsctl_t *fsctl)
442 {
443 	uint32_t timeout;
444 	smb_ofile_t *of = sr->fid_ofile;
445 
446 	/*
447 	 * Note: The spec does not explicitly prohibit resilient directories
448 	 * the same way it prohibits durable directories. We prohibit them
449 	 * anyway as a simplifying assumption, as there doesn't seem to be
450 	 * much use for it. (HYPER-V only seems to use it on files anyway)
451 	 */
452 	if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node))
453 		return (NT_STATUS_INVALID_PARAMETER);
454 
455 	(void) smb_mbc_decodef(fsctl->in_mbc, "l4.",
456 	    &timeout); /* milliseconds */
457 
458 	if (smb2_enable_dh == 0)
459 		return (NT_STATUS_NOT_SUPPORTED);
460 
461 	/*
462 	 * The spec wants us to return INVALID_PARAMETER if the timeout
463 	 * is too large, but we have no way of informing the client
464 	 * what an appropriate timeout is, so just set the timeout to
465 	 * our max and return SUCCESS.
466 	 */
467 	if (timeout == 0)
468 		timeout = smb2_res_def_timeout;
469 	if (timeout > smb2_res_max_timeout)
470 		timeout = smb2_res_max_timeout;
471 
472 	mutex_enter(&of->f_mutex);
473 	of->dh_vers = SMB2_RESILIENT;
474 	of->dh_timeout_offset = MSEC2NSEC(timeout);
475 	mutex_exit(&of->f_mutex);
476 
477 	return (NT_STATUS_SUCCESS);
478 }
479