xref: /illumos-gate/usr/src/uts/common/fs/smbclnt/netsmb/smb_usr.c (revision b7b0558ae6cf66a2c72f08f9104c1559d962bc84)
1 /*
2  * Copyright (c) 2000-2001 Boris Popov
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: smb_usr.c,v 1.15 2004/12/13 00:25:18 lindak Exp $
33  */
34 
35 /*
36  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
37  * Use is subject to license terms.
38  */
39 
40 #include <sys/param.h>
41 #include <sys/kmem.h>
42 #include <sys/systm.h>
43 #include <sys/policy.h>
44 #include <sys/conf.h>
45 #include <sys/proc.h>
46 #include <sys/fcntl.h>
47 #include <sys/file.h>
48 #include <sys/socket.h>
49 #include <sys/sunddi.h>
50 #include <sys/cmn_err.h>
51 
52 #include <netsmb/smb_osdep.h>
53 
54 #include <netsmb/smb.h>
55 #include <netsmb/smb_conn.h>
56 #include <netsmb/smb_rq.h>
57 #include <netsmb/smb_subr.h>
58 #include <netsmb/smb_dev.h>
59 
60 static int smb_cpdatain(struct mbchain *mbp, int len, char *data, int seg);
61 
62 /*
63  * Ioctl function for SMBIOC_FLAGS2
64  */
65 int
66 smb_usr_get_flags2(smb_dev_t *sdp, intptr_t arg, int flags)
67 {
68 	struct smb_vc *vcp = NULL;
69 
70 	/* This ioctl requires a session. */
71 	if ((vcp = sdp->sd_vc) == NULL)
72 		return (ENOTCONN);
73 
74 	/*
75 	 * Return the flags2 value.
76 	 */
77 	if (ddi_copyout(&vcp->vc_hflags2, (void *)arg,
78 	    sizeof (u_int16_t), flags))
79 		return (EFAULT);
80 
81 	return (0);
82 }
83 
84 /*
85  * Ioctl function for SMBIOC_GETSSNKEY
86  * Size copied out is SMBIOC_HASH_SZ.
87  *
88  * The RPC library needs this for encrypting things
89  * like "set password" requests.  This is called
90  * with an active RPC binding, so the connection
91  * will already be active (but this checks).
92  */
93 int
94 smb_usr_get_ssnkey(smb_dev_t *sdp, intptr_t arg, int flags)
95 {
96 	struct smb_vc *vcp = NULL;
97 
98 	/* This ioctl requires an active session. */
99 	if ((vcp = sdp->sd_vc) == NULL)
100 		return (ENOTCONN);
101 	if (vcp->vc_state != SMBIOD_ST_VCACTIVE)
102 		return (ENOTCONN);
103 
104 	/*
105 	 * Return the session key.
106 	 */
107 	if (ddi_copyout(vcp->vc_ssn_key, (void *)arg,
108 	    SMBIOC_HASH_SZ, flags))
109 		return (EFAULT);
110 
111 	return (0);
112 }
113 
114 /*
115  * Ioctl function for SMBIOC_REQUEST
116  */
117 int
118 smb_usr_simplerq(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
119 {
120 	struct smb_cred scred;
121 	struct smb_share *ssp;
122 	smbioc_rq_t *ioc = NULL;
123 	struct smb_rq *rqp = NULL;
124 	struct mbchain *mbp;
125 	struct mdchain *mdp;
126 	uint32_t rsz;
127 	int err, mbseg;
128 
129 	/* This ioctl requires a share. */
130 	if ((ssp = sdp->sd_share) == NULL)
131 		return (ENOTCONN);
132 
133 	smb_credinit(&scred, cr);
134 	ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
135 	if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
136 		err = EFAULT;
137 		goto out;
138 	}
139 
140 	/* See ddi_copyin, ddi_copyout */
141 	mbseg = (flags & FKIOCTL) ? MB_MSYSTEM : MB_MUSER;
142 
143 	/*
144 	 * Lots of SMB commands could be safe, but
145 	 * these are the only ones used by libsmbfs.
146 	 */
147 	switch (ioc->ioc_cmd) {
148 		/* These are OK */
149 	case SMB_COM_CLOSE:
150 	case SMB_COM_FLUSH:
151 	case SMB_COM_NT_CREATE_ANDX:
152 	case SMB_COM_OPEN_PRINT_FILE:
153 	case SMB_COM_CLOSE_PRINT_FILE:
154 		break;
155 
156 	default:
157 		err = EPERM;
158 		goto out;
159 	}
160 
161 	err = smb_rq_alloc(SSTOCP(ssp), ioc->ioc_cmd, &scred, &rqp);
162 	if (err)
163 		goto out;
164 
165 	mbp = &rqp->sr_rq;
166 	err = mb_put_mem(mbp, ioc->ioc_tbuf, ioc->ioc_tbufsz, mbseg);
167 
168 	err = smb_rq_simple(rqp);
169 	if (err == 0) {
170 		/*
171 		 * This may have been an open, so save the
172 		 * generation ID of the share, which we
173 		 * check before trying read or write.
174 		 */
175 		sdp->sd_vcgenid = ssp->ss_vcgenid;
176 
177 		/*
178 		 * Have reply data. to copyout.
179 		 * SMB header already parsed.
180 		 */
181 		mdp = &rqp->sr_rp;
182 		rsz = msgdsize(mdp->md_top) - SMB_HDRLEN;
183 		if (ioc->ioc_rbufsz < rsz) {
184 			err = EOVERFLOW;
185 			goto out;
186 		}
187 		ioc->ioc_rbufsz = rsz;
188 		err = md_get_mem(mdp, ioc->ioc_rbuf, rsz, mbseg);
189 		if (err)
190 			goto out;
191 
192 	}
193 
194 	ioc->ioc_errclass = rqp->sr_errclass;
195 	ioc->ioc_serror = rqp->sr_serror;
196 	ioc->ioc_error = rqp->sr_error;
197 	(void) ddi_copyout(ioc, (void *)arg, sizeof (*ioc), flags);
198 
199 out:
200 	if (rqp != NULL)
201 		smb_rq_done(rqp); /* free rqp */
202 	if (ioc != NULL)
203 		kmem_free(ioc, sizeof (*ioc));
204 	smb_credrele(&scred);
205 
206 	return (err);
207 
208 }
209 
210 /*
211  * Ioctl function for SMBIOC_T2RQ
212  */
213 int
214 smb_usr_t2request(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
215 {
216 	struct smb_cred scred;
217 	struct smb_share *ssp;
218 	smbioc_t2rq_t *ioc = NULL;
219 	struct smb_t2rq *t2p = NULL;
220 	struct mdchain *mdp;
221 	int err, len, mbseg;
222 
223 	/* This ioctl requires a share. */
224 	if ((ssp = sdp->sd_share) == NULL)
225 		return (ENOTCONN);
226 
227 	smb_credinit(&scred, cr);
228 	ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
229 	if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
230 		err = EFAULT;
231 		goto out;
232 	}
233 
234 	/* See ddi_copyin, ddi_copyout */
235 	mbseg = (flags & FKIOCTL) ? MB_MSYSTEM : MB_MUSER;
236 
237 	if (ioc->ioc_setupcnt > SMBIOC_T2RQ_MAXSETUP) {
238 		err = EINVAL;
239 		goto out;
240 	}
241 
242 	t2p = kmem_alloc(sizeof (*t2p), KM_SLEEP);
243 	err = smb_t2_init(t2p, SSTOCP(ssp),
244 	    ioc->ioc_setup, ioc->ioc_setupcnt, &scred);
245 	if (err)
246 		goto out;
247 	len = t2p->t2_setupcount = ioc->ioc_setupcnt;
248 	if (len > 1)
249 		t2p->t2_setupdata = ioc->ioc_setup;
250 
251 	/* This ioc member is a fixed-size array. */
252 	if (ioc->ioc_name[0]) {
253 		/* Get the name length - carefully! */
254 		ioc->ioc_name[SMBIOC_T2RQ_MAXNAME-1] = '\0';
255 		t2p->t_name_len = strlen(ioc->ioc_name);
256 		t2p->t_name = ioc->ioc_name;
257 	}
258 	t2p->t2_maxscount = 0;
259 	t2p->t2_maxpcount = ioc->ioc_rparamcnt;
260 	t2p->t2_maxdcount = ioc->ioc_rdatacnt;
261 
262 	/* Transmit parameters */
263 	err = smb_cpdatain(&t2p->t2_tparam,
264 	    ioc->ioc_tparamcnt, ioc->ioc_tparam, mbseg);
265 	if (err)
266 		goto out;
267 
268 	/* Transmit data */
269 	err = smb_cpdatain(&t2p->t2_tdata,
270 	    ioc->ioc_tdatacnt, ioc->ioc_tdata, mbseg);
271 	if (err)
272 		goto out;
273 
274 	err = smb_t2_request(t2p);
275 
276 	/* Copyout returned parameters. */
277 	mdp = &t2p->t2_rparam;
278 	if (err == 0 && mdp->md_top != NULL) {
279 		/* User's buffer large enough? */
280 		len = m_fixhdr(mdp->md_top);
281 		if (len > ioc->ioc_rparamcnt) {
282 			err = EMSGSIZE;
283 			goto out;
284 		}
285 		ioc->ioc_rparamcnt = (ushort_t)len;
286 		err = md_get_mem(mdp, ioc->ioc_rparam, len, mbseg);
287 		if (err)
288 			goto out;
289 	} else
290 		ioc->ioc_rparamcnt = 0;
291 
292 	/* Copyout returned data. */
293 	mdp = &t2p->t2_rdata;
294 	if (err == 0 && mdp->md_top != NULL) {
295 		/* User's buffer large enough? */
296 		len = m_fixhdr(mdp->md_top);
297 		if (len > ioc->ioc_rdatacnt) {
298 			err = EMSGSIZE;
299 			goto out;
300 		}
301 		ioc->ioc_rdatacnt = (ushort_t)len;
302 		err = md_get_mem(mdp, ioc->ioc_rdata, len, mbseg);
303 		if (err)
304 			goto out;
305 	} else
306 		ioc->ioc_rdatacnt = 0;
307 
308 	ioc->ioc_errclass = t2p->t2_sr_errclass;
309 	ioc->ioc_serror = t2p->t2_sr_serror;
310 	ioc->ioc_error = t2p->t2_sr_error;
311 	ioc->ioc_rpflags2 = t2p->t2_sr_rpflags2;
312 
313 	(void) ddi_copyout(ioc, (void *)arg, sizeof (*ioc), flags);
314 
315 
316 out:
317 	if (t2p != NULL) {
318 		/* Note: t2p->t_name no longer allocated */
319 		smb_t2_done(t2p);
320 		kmem_free(t2p, sizeof (*t2p));
321 	}
322 	if (ioc != NULL)
323 		kmem_free(ioc, sizeof (*ioc));
324 	smb_credrele(&scred);
325 
326 	return (err);
327 }
328 
329 /* helper for _t2request */
330 static int
331 smb_cpdatain(struct mbchain *mbp, int len, char *data, int mbseg)
332 {
333 	int error;
334 
335 	if (len == 0)
336 		return (0);
337 	error = mb_init(mbp);
338 	if (error)
339 		return (error);
340 	return (mb_put_mem(mbp, data, len, mbseg));
341 }
342 
343 /*
344  * Helper for nsmb_ioctl cases
345  * SMBIOC_READ, SMBIOC_WRITE
346  */
347 int
348 smb_usr_rw(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
349 {
350 	struct smb_cred scred;
351 	struct smb_share *ssp;
352 	smbioc_rw_t *ioc = NULL;
353 	struct iovec aiov[1];
354 	struct uio  auio;
355 	u_int16_t fh;
356 	int err;
357 	uio_rw_t rw;
358 
359 	/* This ioctl requires a share. */
360 	if ((ssp = sdp->sd_share) == NULL)
361 		return (ENOTCONN);
362 
363 	/* After reconnect, force close+reopen */
364 	if (sdp->sd_vcgenid != ssp->ss_vcgenid)
365 		return (ESTALE);
366 
367 	smb_credinit(&scred, cr);
368 	ioc = kmem_alloc(sizeof (*ioc), KM_SLEEP);
369 	if (ddi_copyin((void *) arg, ioc, sizeof (*ioc), flags)) {
370 		err = EFAULT;
371 		goto out;
372 	}
373 
374 	switch (cmd) {
375 	case SMBIOC_READ:
376 		rw = UIO_READ;
377 		break;
378 	case SMBIOC_WRITE:
379 		rw = UIO_WRITE;
380 		break;
381 	default:
382 		err = ENODEV;
383 		goto out;
384 	}
385 
386 	fh = ioc->ioc_fh;
387 
388 	aiov[0].iov_base = ioc->ioc_base;
389 	aiov[0].iov_len = (size_t)ioc->ioc_cnt;
390 
391 	auio.uio_iov = aiov;
392 	auio.uio_iovcnt = 1;
393 	auio.uio_loffset = ioc->ioc_offset;
394 	auio.uio_segflg = (flags & FKIOCTL) ?
395 	    UIO_SYSSPACE : UIO_USERSPACE;
396 	auio.uio_fmode = 0;
397 	auio.uio_resid = (size_t)ioc->ioc_cnt;
398 
399 	err = smb_rwuio(ssp, fh, rw, &auio, &scred, 0);
400 
401 	/*
402 	 * On return ioc_cnt holds the
403 	 * number of bytes transferred.
404 	 */
405 	ioc->ioc_cnt -= auio.uio_resid;
406 
407 	(void) ddi_copyout(ioc, (void *)arg, sizeof (*ioc), flags);
408 
409 out:
410 	if (ioc != NULL)
411 		kmem_free(ioc, sizeof (*ioc));
412 	smb_credrele(&scred);
413 
414 	return (err);
415 }
416 
417 /*
418  * Ioctl functions: SMBIOC_SSN_FIND, SMBIOC_SSN_CREATE
419  * Find or create a session (a.k.a. "VC" in here)
420  */
421 int
422 smb_usr_get_ssn(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
423 {
424 	struct smb_cred scred;
425 	smbioc_ossn_t *ossn = NULL;
426 	struct smb_vc *vcp = NULL;
427 	int error = 0;
428 	uid_t realuid;
429 
430 	/* Should be no VC */
431 	if (sdp->sd_vc != NULL)
432 		return (EISCONN);
433 
434 	smb_credinit(&scred, cr);
435 	ossn = kmem_alloc(sizeof (*ossn), KM_SLEEP);
436 	if (ddi_copyin((void *)arg, ossn, sizeof (*ossn), flags)) {
437 		error = EFAULT;
438 		goto out;
439 	}
440 
441 	/*
442 	 * Only superuser can specify a UID or GID.
443 	 */
444 	realuid = crgetruid(cr);
445 	if (ossn->ssn_owner == SMBM_ANY_OWNER)
446 		ossn->ssn_owner = realuid;
447 	else {
448 		/*
449 		 * Do we have the privilege to create with the
450 		 * specified uid?  (does uid == cr->cr_uid, etc.)
451 		 */
452 		if (secpolicy_vnode_owner(cr, ossn->ssn_owner)) {
453 			error = EPERM;
454 			goto out;
455 		}
456 		/* ossn->ssn_owner is OK */
457 	}
458 
459 	/*
460 	 * Make sure the strings are null terminated.
461 	 */
462 	ossn->ssn_srvname[SMBIOC_MAX_NAME-1] = '\0';
463 	ossn->ssn_id.id_domain[ SMBIOC_MAX_NAME-1] = '\0';
464 	ossn->ssn_id.id_user[   SMBIOC_MAX_NAME-1] = '\0';
465 
466 	if (cmd == SMBIOC_SSN_CREATE)
467 		ossn->ssn_vopt |= SMBVOPT_CREATE;
468 	else /* FIND */
469 		ossn->ssn_vopt &= ~SMBVOPT_CREATE;
470 
471 	error = smb_vc_findcreate(ossn, &scred, &vcp);
472 	if (error)
473 		goto out;
474 	ASSERT(vcp != NULL);
475 
476 	/*
477 	 * We have a VC, held, but not locked.
478 	 * If we're creating, mark this instance as
479 	 * an open from IOD so close can do cleanup.
480 	 *
481 	 * XXX: Would be nice to have a back pointer
482 	 * from the VC to this (IOD) sdp instance.
483 	 */
484 	if (cmd == SMBIOC_SSN_CREATE) {
485 		if (vcp->iod_thr != NULL) {
486 			error = EEXIST;
487 			goto out;
488 		}
489 		sdp->sd_flags |= NSMBFL_IOD;
490 	} else {
491 		/*
492 		 * Wait for it to finish connecting
493 		 * (or reconnect) if necessary.
494 		 */
495 		if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
496 			error = smb_iod_reconnect(vcp);
497 			if (error != 0)
498 				goto out;
499 		}
500 	}
501 
502 	/*
503 	 * The VC has a hold from _findvc
504 	 * which we keep until _SSN_RELE
505 	 * or nsmb_close().
506 	 */
507 	sdp->sd_level = SMBL_VC;
508 	sdp->sd_vc = vcp;
509 	vcp = NULL;
510 	(void) ddi_copyout(ossn, (void *)arg, sizeof (*ossn), flags);
511 
512 out:
513 	if (vcp) {
514 		/* Error path: rele hold from _findcreate */
515 		smb_vc_rele(vcp);
516 	}
517 	if (ossn != NULL)
518 		kmem_free(ossn, sizeof (*ossn));
519 	smb_credrele(&scred);
520 
521 	return (error);
522 }
523 
524 /*
525  * Ioctl functions: SMBIOC_SSN_RELE, SMBIOC_SSN_KILL
526  * Release or kill the current session.
527  */
528 int
529 smb_usr_drop_ssn(smb_dev_t *sdp, int cmd)
530 {
531 	struct smb_vc *vcp = NULL;
532 
533 	/* Must have a VC. */
534 	if ((vcp = sdp->sd_vc) == NULL)
535 		return (ENOTCONN);
536 
537 	/* If we have a share ref, drop it too. */
538 	if (sdp->sd_share) {
539 		smb_share_rele(sdp->sd_share);
540 		sdp->sd_share = NULL;
541 		sdp->sd_level = SMBL_VC;
542 	}
543 
544 	if (cmd == SMBIOC_SSN_KILL)
545 		smb_vc_kill(vcp);
546 
547 	/* Drop the VC ref. */
548 	smb_vc_rele(vcp);
549 	sdp->sd_vc = NULL;
550 	sdp->sd_level = 0;
551 
552 	return (0);
553 }
554 
555 /*
556  * Find or create a tree (connected share)
557  */
558 int
559 smb_usr_get_tree(smb_dev_t *sdp, int cmd, intptr_t arg, int flags, cred_t *cr)
560 {
561 	struct smb_cred scred;
562 	smbioc_tcon_t *tcon = NULL;
563 	struct smb_vc *vcp = NULL;
564 	struct smb_share *ssp = NULL;
565 	int error = 0;
566 
567 	/* Must have a VC. */
568 	if ((vcp = sdp->sd_vc) == NULL)
569 		return (ENOTCONN);
570 	/* Should not have a share. */
571 	if (sdp->sd_share != NULL)
572 		return (EISCONN);
573 
574 	smb_credinit(&scred, cr);
575 	tcon = kmem_alloc(sizeof (*tcon), KM_SLEEP);
576 	if (ddi_copyin((void *)arg, tcon, sizeof (*tcon), flags)) {
577 		error = EFAULT;
578 		goto out;
579 	}
580 
581 	/*
582 	 * Make sure the strings are null terminated.
583 	 */
584 	tcon->tc_sh.sh_name[SMBIOC_MAX_NAME-1] = '\0';
585 	tcon->tc_sh.sh_pass[SMBIOC_MAX_NAME-1] = '\0';
586 	tcon->tc_sh.sh_type_req[SMBIOC_STYPE_LEN-1] = '\0';
587 	bzero(tcon->tc_sh.sh_type_ret, SMBIOC_STYPE_LEN);
588 
589 	if (cmd == SMBIOC_TREE_CONNECT)
590 		tcon->tc_opt |= SMBSOPT_CREATE;
591 	else /* FIND */
592 		tcon->tc_opt &= ~SMBSOPT_CREATE;
593 
594 	error = smb_share_findcreate(tcon, vcp, &ssp, &scred);
595 	if (error)
596 		goto out;
597 	ASSERT(ssp != NULL);
598 
599 	/*
600 	 * We have a share, held, but not locked.
601 	 * If we're creating, do tree connect now,
602 	 * otherwise let that wait for a request.
603 	 */
604 	if (cmd == SMBIOC_TREE_CONNECT) {
605 		error = smb_share_tcon(ssp, &scred);
606 		if (error)
607 			goto out;
608 	}
609 
610 	/*
611 	 * Give caller the real share type from
612 	 * the tree connect response, so they can
613 	 * see if they got the requested type.
614 	 */
615 	(void) memcpy(tcon->tc_sh.sh_type_ret,
616 	    ssp->ss_type_ret, SMBIOC_STYPE_LEN);
617 
618 	/*
619 	 * The share has a hold from _tcon
620 	 * which we keep until nsmb_close()
621 	 * or the SMBIOC_TDIS below.
622 	 */
623 	sdp->sd_level = SMBL_SHARE;
624 	sdp->sd_share = ssp;
625 	ssp = NULL;
626 	(void) ddi_copyout(tcon, (void *)arg, sizeof (*tcon), flags);
627 
628 out:
629 	if (ssp) {
630 		/* Error path: rele hold from _findcreate */
631 		smb_share_rele(ssp);
632 	}
633 	if (tcon) {
634 		/*
635 		 * This structure may contain a
636 		 * cleartext password, so zap it.
637 		 */
638 		bzero(tcon, sizeof (*tcon));
639 		kmem_free(tcon, sizeof (*tcon));
640 	}
641 	smb_credrele(&scred);
642 
643 	return (error);
644 }
645 
646 /*
647  * Ioctl functions: SMBIOC_TREE_RELE, SMBIOC_TREE_KILL
648  * Release or kill the current tree
649  */
650 int
651 smb_usr_drop_tree(smb_dev_t *sdp, int cmd)
652 {
653 	struct smb_share *ssp = NULL;
654 
655 	/* Must have a VC and a share. */
656 	if (sdp->sd_vc == NULL)
657 		return (ENOTCONN);
658 	if ((ssp = sdp->sd_share) == NULL)
659 		return (ENOTCONN);
660 
661 	if (cmd == SMBIOC_TREE_KILL)
662 		smb_share_kill(ssp);
663 
664 	/* Drop the share ref. */
665 	smb_share_rele(sdp->sd_share);
666 	sdp->sd_share = NULL;
667 	sdp->sd_level = SMBL_VC;
668 
669 	return (0);
670 }
671 
672 
673 /*
674  * Ioctl function: SMBIOC_IOD_WORK
675  *
676  * Become the reader (IOD) thread, until either the connection is
677  * reset by the server, or until the connection is idle longer than
678  * some max time. (max idle time not yet implemented)
679  */
680 int
681 smb_usr_iod_work(smb_dev_t *sdp, intptr_t arg, int flags, cred_t *cr)
682 {
683 	struct smb_vc *vcp = NULL;
684 	int err = 0;
685 
686 	/* Must have a valid session. */
687 	if ((vcp = sdp->sd_vc) == NULL)
688 		return (EINVAL);
689 	if (vcp->vc_flags & SMBV_GONE)
690 		return (EINVAL);
691 
692 	/*
693 	 * Is there already an IOD for this VC?
694 	 * (Should never happen.)
695 	 */
696 	SMB_VC_LOCK(vcp);
697 	if (vcp->iod_thr == NULL)
698 		vcp->iod_thr = curthread;
699 	else
700 		err = EEXIST;
701 	SMB_VC_UNLOCK(vcp);
702 	if (err)
703 		return (err);
704 
705 	/*
706 	 * Copy the "work" state, etc. into the VC
707 	 * The MAC key is copied separately.
708 	 */
709 	if (ddi_copyin((void *)arg, &vcp->vc_work,
710 	    sizeof (smbioc_ssn_work_t), flags)) {
711 		err = EFAULT;
712 		goto out;
713 	}
714 	if (vcp->vc_u_maclen) {
715 		vcp->vc_mackeylen = vcp->vc_u_maclen;
716 		vcp->vc_mackey = kmem_alloc(vcp->vc_mackeylen, KM_SLEEP);
717 		if (ddi_copyin(vcp->vc_u_mackey.lp_ptr, vcp->vc_mackey,
718 		    vcp->vc_mackeylen, flags)) {
719 			err = EFAULT;
720 			goto out;
721 		}
722 	}
723 
724 	err = smb_iod_vc_work(vcp, cr);
725 
726 	/* Caller wants state here. */
727 	vcp->vc_work.wk_out_state = vcp->vc_state;
728 
729 	(void) ddi_copyout(&vcp->vc_work, (void *)arg,
730 	    sizeof (smbioc_ssn_work_t), flags);
731 
732 out:
733 	if (vcp->vc_mackey) {
734 		kmem_free(vcp->vc_mackey, vcp->vc_mackeylen);
735 		vcp->vc_mackey = NULL;
736 		vcp->vc_mackeylen = 0;
737 	}
738 
739 	/*
740 	 * The IOD thread is leaving the driver.  Clear iod_thr,
741 	 * and wake up anybody waiting for us to quit.
742 	 */
743 	SMB_VC_LOCK(vcp);
744 	vcp->iod_thr = NULL;
745 	cv_broadcast(&vcp->vc_statechg);
746 	SMB_VC_UNLOCK(vcp);
747 
748 	return (err);
749 }
750 
751 /*
752  * Ioctl functions: SMBIOC_IOD_IDLE, SMBIOC_IOD_RCFAIL
753  *
754  * Wait for user-level requests to be enqueued on this session,
755  * and then return to the user-space helper, which will then
756  * initiate a reconnect, etc.
757  */
758 int
759 smb_usr_iod_ioctl(smb_dev_t *sdp, int cmd, intptr_t arg, int flags)
760 {
761 	struct smb_vc *vcp = NULL;
762 	int err = 0;
763 
764 	/* Must have a valid session. */
765 	if ((vcp = sdp->sd_vc) == NULL)
766 		return (EINVAL);
767 	if (vcp->vc_flags & SMBV_GONE)
768 		return (EINVAL);
769 
770 	/*
771 	 * Is there already an IOD for this VC?
772 	 * (Should never happen.)
773 	 */
774 	SMB_VC_LOCK(vcp);
775 	if (vcp->iod_thr == NULL)
776 		vcp->iod_thr = curthread;
777 	else
778 		err = EEXIST;
779 	SMB_VC_UNLOCK(vcp);
780 	if (err)
781 		return (err);
782 
783 	/* nothing to copyin */
784 
785 	switch (cmd) {
786 	case SMBIOC_IOD_IDLE:
787 		err = smb_iod_vc_idle(vcp);
788 		break;
789 
790 	case SMBIOC_IOD_RCFAIL:
791 		err = smb_iod_vc_rcfail(vcp);
792 		break;
793 
794 	default:
795 		err = ENOTTY;
796 		goto out;
797 	}
798 
799 	/* Both of these ioctls copy out the new state. */
800 	(void) ddi_copyout(&vcp->vc_state, (void *)arg,
801 	    sizeof (int), flags);
802 
803 out:
804 	/*
805 	 * The IOD thread is leaving the driver.  Clear iod_thr,
806 	 * and wake up anybody waiting for us to quit.
807 	 */
808 	SMB_VC_LOCK(vcp);
809 	vcp->iod_thr = NULL;
810 	cv_broadcast(&vcp->vc_statechg);
811 	SMB_VC_UNLOCK(vcp);
812 
813 	return (err);
814 }
815