/* * Copyright (c) 2000-2001 Boris Popov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Boris Popov. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: smb_smb.c,v 1.35.100.2 2005/06/02 00:55:39 lindak Exp $ */ /* * Portions Copyright (C) 2001 - 2014 Apple Inc. All rights reserved. * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * various SMB requests. Most of the routines merely packs data into mbufs. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STYPE_LEN 8 /* share type strings */ struct smb_dialect { int d_id; const char *d_name; }; static struct smb_dialect smb_dialects[3] = { {SMB_DIALECT_NTLM0_12, "NT LANMAN 1.0"}, {SMB_DIALECT_NTLM0_12, "NT LM 0.12"}, #define NDIALECT_SMB1 2 {SMB_DIALECT_SMB2_FF, "SMB 2.???"}, #define NDIALECT_SMB2 3 }; static const uint32_t smb_clnt_caps_mask = SMB_CAP_UNICODE | SMB_CAP_LARGE_FILES | SMB_CAP_NT_SMBS | SMB_CAP_STATUS32 | SMB_CAP_EXT_SECURITY; /* * Default timeout values, all in seconds. * Make these tunable (only via mdb for now). */ int smb_timo_notice = 15; int smb_timo_default = 30; /* was SMB_DEFRQTIMO */ int smb_timo_logon = 45; int smb_timo_open = 45; int smb_timo_read = 45; int smb_timo_write = 60; /* was SMBWRTTIMO */ int smb_timo_append = 90; int smb_smb_negotiate(struct smb_vc *vcp, struct smb_cred *scred) { smb_sopt_t *sv = &vcp->vc_sopt; smbioc_ssn_work_t *wk = &vcp->vc_work; struct smb_rq *rqp = NULL; struct mbchain *mbp = NULL; struct mdchain *mdp = NULL; struct smb_dialect *dp; int err, sblen, tlen; uint8_t wc, eklen; uint16_t dindex, bc; uint16_t ndialects; boolean_t will_sign = B_FALSE; /* * Initialize: vc_hflags and vc_hflags2. * Note: vcp->vc_hflags* are copied into the * (per request) rqp->rq_hflags* by smb_rq_init. * * Like Windows, set FLAGS2_UNICODE in our first request, * even though technically we don't yet know whether the * server supports Unicode. Will clear this flag below * if we find out it doesn't. Need to do this because * some servers reject all non-Unicode requests. */ vcp->vc_hflags = SMB_FLAGS_CASELESS | SMB_FLAGS_CANONICAL_PATHNAMES; vcp->vc_hflags2 = SMB_FLAGS2_KNOWS_LONG_NAMES | SMB_FLAGS2_KNOWS_EAS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_EXT_SEC | SMB_FLAGS2_ERR_STATUS | SMB_FLAGS2_UNICODE; /* * The initial UID needs to be zero, */ vcp->vc_smbuid = 0; /* * (Re)init negotiated values */ bzero(sv, sizeof (*sv)); sv->sv_maxmux = 1; sv->sv_maxvcs = 1; sv->sv_maxtx = 1024; /* * Should we offer the magic SMB2 dialect? */ if (vcp->vc_ssn.ssn_maxver >= SMB2_DIALECT_BASE) ndialects = NDIALECT_SMB2; else ndialects = NDIALECT_SMB1; err = smb_rq_alloc(VCTOCP(vcp), SMB_COM_NEGOTIATE, scred, &rqp); if (err) return (err); /* * Build the SMB request. */ smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); for (dindex = 0; dindex < ndialects; dindex++) { dp = &smb_dialects[dindex]; mb_put_uint8(mbp, SMB_DT_DIALECT); tlen = strlen(dp->d_name) + 1; mb_put_mem(mbp, dp->d_name, tlen, MB_MSYSTEM); } smb_rq_bend(rqp); /* * Do the OTW call. */ err = smb_rq_internal(rqp, smb_timo_default); /* * If it's an SMB1-to-SMB2 negotiate response, * call the special handler and then skip the * whole rest of this function. */ if (err == EPROTO) { err = smb2_parse_smb1nego_resp(rqp); smb_rq_done(rqp); return (err); } if (err) { SMBSDEBUG("smb_rq_internal, err %d", err); goto errout; } /* Should only get status success. */ if (rqp->sr_error != NT_STATUS_SUCCESS) { err = ENOTSUP; goto errout; } /* * Decode the response * * Comments to right show names as described in * The Microsoft SMB Protocol spec. [MS-SMB] * section 2.2.3 */ smb_rq_getreply(rqp, &mdp); (void) md_get_uint8(mdp, &wc); err = md_get_uint16le(mdp, &dindex); if (err != 0) goto errout; if (dindex >= ndialects) { SMBERROR("Invalid dialect index from server: %s\n", vcp->vc_srvname); err = EBADRPC; goto errout; } dp = smb_dialects + dindex; sv->sv_proto = dp->d_id; SMBSDEBUG("Dialect %s", dp->d_name); if (dp->d_id < SMB_DIALECT_NTLM0_12) { SMBSDEBUG("old dialect %s", dp->d_name); goto errout; } if (wc != 17) { SMBSDEBUG("bad wc %d", (int)wc); goto errout; } md_get_uint8(mdp, &sv->sv_sm); /* SecurityMode */ md_get_uint16le(mdp, &sv->sv_maxmux); /* MaxMpxCount */ md_get_uint16le(mdp, &sv->sv_maxvcs); /* MaxCountVCs */ md_get_uint32le(mdp, &sv->sv_maxtx); /* MaxBufferSize */ md_get_uint32le(mdp, &sv->sv_maxraw); /* MaxRawSize */ md_get_uint32le(mdp, &sv->sv_skey); /* SessionKey */ md_get_uint32le(mdp, &sv->sv_caps); /* Capabilities */ md_get_mem(mdp, NULL, 8, MB_MSYSTEM); /* SystemTime(s) */ md_get_uint16le(mdp, (uint16_t *)&sv->sv_tz); md_get_uint8(mdp, &eklen); /* EncryptionKeyLength */ err = md_get_uint16le(mdp, &bc); /* ByteCount */ if (err) goto errout; /* BEGIN CSTYLED */ /* * Will we do SMB signing? Or block the connection? * The table below describes this logic. References: * [Windows Server Protocols: MS-SMB, sec. 3.2.4.2.3] * http://msdn.microsoft.com/en-us/library/cc212511.aspx * http://msdn.microsoft.com/en-us/library/cc212929.aspx * * Srv/Cli | Required | Enabled | If Required | Disabled * ------------+----------+------------+-------------+----------- * Required | Signed | Signed | Signed | Blocked [1] * ------------+----------+------------+-------------+----------- * Enabled | Signed | Signed | Not Signed | Not Signed * ------------+----------+------------+-------------+----------- * If Required | Signed | Not Signed | Not Signed | Not Signed * ------------+----------+------------+-------------+----------- * Disabled | Blocked | Not Signed | Not Signed | Not Signed * * [1] Like Windows 2003 and later, we don't really implement * the "Disabled" setting. Instead we implement "If Required", * so we always sign if the server requires signing. */ /* END CSTYLED */ if (sv->sv_sm & SMB_SM_SIGS_REQUIRE) { /* * Server requires signing. We will sign, * even if local setting is "disabled". */ will_sign = B_TRUE; } else if (sv->sv_sm & SMB_SM_SIGS) { /* * Server enables signing (client's option). * If enabled locally, do signing. */ if (vcp->vc_vopt & SMBVOPT_SIGNING_ENABLED) will_sign = B_TRUE; /* else not signing. */ } else { /* * Server does not support signing. * If we "require" it, bail now. */ if (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED) { SMBERROR("Client requires signing " "but server has it disabled."); err = EBADRPC; goto errout; } } /* * Anonymous sessions can't sign. */ if (vcp->vc_vopt & SMBVOPT_ANONYMOUS) { will_sign = B_FALSE; } SMBSDEBUG("Security signatures: %d", (int)will_sign); if (will_sign) { vcp->vc_flags |= SMBV_SIGNING; vcp->vc_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; /* * MS-SMB 2.2.4.5 says that when SMB signing is enabled, * we should NOT use "large read/write" even though the * server might offer those capabilities. */ sv->sv_caps &= ~(SMB_CAP_LARGE_READX | SMB_CAP_LARGE_WRITEX); } /* See comment above re. FLAGS2_UNICODE */ if ((sv->sv_caps & SMB_CAP_UNICODE) != 0) vcp->vc_flags |= SMBV_UNICODE; else vcp->vc_hflags2 &= ~SMB_FLAGS2_UNICODE; if ((sv->sv_caps & SMB_CAP_STATUS32) == 0) { /* They don't do NT error codes. */ vcp->vc_hflags2 &= ~SMB_FLAGS2_ERR_STATUS; } /* * Warn if they don't support SMB_CAP_NT_SMBS * (We'll try to use NtCreate anyway) */ if ((sv->sv_caps & SMB_CAP_NT_SMBS) == 0) { cmn_err(CE_NOTE, "%s does not support SMB_CAP_NT_SMBS", vcp->vc_srvname); } /* * The rest of the message varies depending on * whether we've negotiated "extended security". * * With extended security, we have: * Server_GUID (length 16) * Security_BLOB * Otherwise we have: * EncryptionKey (length is eklen) * PrimaryDomain */ if (sv->sv_caps & SMB_CAP_EXT_SECURITY) { SMBSDEBUG("Ext.Security: yes"); /* * Skip the server GUID. */ err = md_get_mem(mdp, NULL, SMB_GUIDLEN, MB_MSYSTEM); if (err) goto errout; /* * Remainder is the security blob. * Note: eklen "must be ignored" [MS-SMB] */ sblen = (int)bc - SMB_GUIDLEN; if (sblen < 0) goto errout; /* Security blob (hint) is next */ } else { SMBSDEBUG("Ext.Security: no"); err = ENOTSUP; goto errout; } /* * Copy the security blob out to user space. * Buffer addr,size in vc_auth_rbuf,rlen */ if (wk->wk_u_auth_rlen < sblen) { SMBSDEBUG("vc_auth_rbuf too small"); /* Give caller required size. */ wk->wk_u_auth_rlen = sblen; err = EMSGSIZE; goto errout; } wk->wk_u_auth_rlen = sblen; err = md_get_mem(mdp, wk->wk_u_auth_rbuf.lp_ptr, sblen, MB_MUSER); if (err) goto errout; /* * A few sanity checks on what we received, * becuse we will send these in ssnsetup. * * Maximum outstanding requests (we care), * and Max. VCs (we only use one). Also, * MaxBufferSize lower limit per spec. */ if (sv->sv_maxmux < 1) sv->sv_maxmux = 1; if (sv->sv_maxvcs < 1) sv->sv_maxvcs = 1; if (sv->sv_maxtx < 1024) sv->sv_maxtx = 1024; /* * Maximum transfer size. * Sanity checks: * * Let's be conservative about an upper limit here. * Win2k uses 16644 (and others) so 32k should be a * reasonable sanity limit for this value. * * Note that this limit does NOT affect READX/WRITEX * with CAP_LARGE_..., which we nearly always use. */ vcp->vc_txmax = sv->sv_maxtx; if (vcp->vc_txmax > 0x8000) vcp->vc_txmax = 0x8000; /* * Max read/write sizes, WITHOUT overhead. * This is just the payload size, so we must * leave room for the SMB headers, etc. * This is just the ct_txmax value, but * reduced and rounded down. Tricky bit: * * Servers typically give us a value that's * some nice "round" number, i.e 0x4000 plus * some overhead, i.e. Win2k: 16644==0x4104 * Subtract for the SMB header (32) and the * SMB command word and byte vectors (34?), * then round down to a 512 byte multiple. */ tlen = vcp->vc_txmax - 68; tlen &= 0xFE00; vcp->vc_rwmax = tlen; vcp->vc_rxmax = tlen; vcp->vc_wxmax = tlen; /* * Most of the "capability" bits we offer in session setup * are just copied from those offered by the server. */ sv->sv_caps &= smb_clnt_caps_mask; smb_rq_done(rqp); return (0); errout: smb_rq_done(rqp); if (err == 0) err = EBADRPC; return (err); } static const char NativeOS[] = "illumos"; static const char LanMan[] = "NETSMB"; int smb_smb_ssnsetup(struct smb_vc *vcp, struct smb_cred *scred) { smb_sopt_t *sv = &vcp->vc_sopt; smbioc_ssn_work_t *wk = &vcp->vc_work; struct smb_rq *rqp = NULL; struct mbchain *mbp = NULL; struct mdchain *mdp = NULL; char *sb; int err, ret; uint32_t caps; uint16_t action, bc, sblen; uint8_t wc; caps = sv->sv_caps; sb = wk->wk_u_auth_wbuf.lp_ptr; sblen = (uint16_t)wk->wk_u_auth_wlen; err = smb_rq_alloc(VCTOCP(vcp), SMB_COM_SESSION_SETUP_ANDX, scred, &rqp); if (err != 0) { ret = err; goto out; } /* * Build the SMB Session Setup request. * Always extended security form. */ mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint16le(mbp, 0xff); /* 0: AndXCommand */ mb_put_uint16le(mbp, 0); /* 1: AndXOffset */ mb_put_uint16le(mbp, sv->sv_maxtx); /* 2: MaxBufferSize */ mb_put_uint16le(mbp, sv->sv_maxmux); /* 3: MaxMpxCount */ mb_put_uint16le(mbp, 1); /* 4: VcNumber */ mb_put_uint32le(mbp, sv->sv_skey); /* 5,6: Session Key */ mb_put_uint16le(mbp, sblen); /* 7: Sec. Blob Len */ mb_put_uint32le(mbp, 0); /* 8,9: reserved */ mb_put_uint32le(mbp, caps); /* 10,11: Capabilities */ smb_rq_wend(rqp); /* 12: Byte Count */ smb_rq_bstart(rqp); err = mb_put_mem(mbp, sb, sblen, MB_MUSER); if (err != 0) { ret = err; goto out; } (void) smb_put_dstring(mbp, vcp, NativeOS, SMB_CS_NONE); (void) smb_put_dstring(mbp, vcp, LanMan, SMB_CS_NONE); smb_rq_bend(rqp); /* * Run the request. The return value here should be the * return from this function, unless we fail decoding. * Note: NT_STATUS_MORE_PROCESSING_REQUIRED is OK, and * the caller expects EINPROGRESS for that case. */ ret = smb_rq_internal(rqp, smb_timo_logon); if (ret != 0) goto out; switch (rqp->sr_error) { case NT_STATUS_SUCCESS: break; case NT_STATUS_MORE_PROCESSING_REQUIRED: /* Keep going, but return... */ ret = EINPROGRESS; break; default: ret = EAUTH; goto out; } if (vcp->vc_smbuid == 0) vcp->vc_smbuid = rqp->sr_rpuid; /* * Parse the reply */ smb_rq_getreply(rqp, &mdp); err = md_get_uint8(mdp, &wc); if (err != 0) wc = 0; if (wc != 4) { ret = EBADRPC; goto out; } md_get_uint16le(mdp, NULL); /* secondary cmd */ md_get_uint16le(mdp, NULL); /* andxoffset */ md_get_uint16le(mdp, &action); /* action XXX */ md_get_uint16le(mdp, &sblen); /* sec. blob len */ md_get_uint16le(mdp, &bc); /* byte count */ /* * Get the security blob, after * sanity-checking the length. */ if (sblen == 0 || sblen > bc) { ret = EBADRPC; goto out; } if (sblen > wk->wk_u_auth_rlen) { ret = EBADRPC; goto out; } sb = wk->wk_u_auth_rbuf.lp_ptr; err = md_get_mem(mdp, sb, sblen, MB_MUSER); if (err) { ret = EBADRPC; goto out; } /* * Native OS, LANMGR, & Domain follow here. * We don't need them and don't parse them. */ out: if (err != 0 && err != EINPROGRESS) { /* UID no longer valid. */ vcp->vc_smbuid = 0; } if (rqp) smb_rq_done(rqp); return (ret); } int smb_smb_logoff(struct smb_vc *vcp, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; int error; if (vcp->vc_smbuid == SMB_UID_UNKNOWN) return (0); error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_LOGOFF_ANDX, scred, &rqp); if (error) return (error); mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); mb_put_uint8(mbp, 0); mb_put_uint16le(mbp, 0); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); /* * Run this with a relatively short timeout. (5 sec.) * We don't really care about the result here. * Also, don't reconnect for this, of course! */ rqp->sr_flags |= SMBR_NORECONNECT; error = smb_rq_internal(rqp, 5); smb_rq_done(rqp); return (error); } /* * Get the string representation of a share "use" type, * as needed for the "service" in tree connect. */ static const char * smb_share_typename(uint32_t stype) { const char *p; switch (stype) { case STYPE_DISKTREE: p = "A:"; break; case STYPE_PRINTQ: p = "LPT1:"; break; case STYPE_DEVICE: p = "COMM"; break; case STYPE_IPC: p = "IPC"; break; case STYPE_UNKNOWN: default: p = "?????"; break; } return (p); } /* * Parse a share type name (inverse of above) */ static uint32_t smb_share_parsetype(char *name) { int stype; switch (*name) { case 'A': /* A: */ stype = STYPE_DISKTREE; break; case 'C': /* COMM */ stype = STYPE_DEVICE; break; case 'I': /* IPC */ stype = STYPE_IPC; break; case 'L': /* LPT: */ stype = STYPE_PRINTQ; break; default: stype = STYPE_UNKNOWN; break; } return (stype); } int smb_smb_treeconnect(struct smb_share *ssp, struct smb_cred *scred) { struct smb_vc *vcp; struct smb_rq *rqp = NULL; struct mbchain *mbp; struct mdchain *mdp; const char *tname; char *pbuf, *unc_name = NULL; int error, tlen, plen, unc_len; uint16_t bcnt, options; uint8_t wc; char stype_str[STYPE_LEN]; vcp = SSTOVC(ssp); /* * Make this a "VC-level" request, so it will have * rqp->sr_share == NULL, and smb_iod_sendrq() * will send it with TID = SMB_TID_UNKNOWN * * This also serves to bypass the wait for * share state changes, which this call is * trying to carry out. */ error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_TREE_CONNECT_ANDX, scred, &rqp); if (error) return (error); /* * Build the UNC name, i.e. "//server/share" * but with backslashes of course. * size math: three slashes, one null. */ unc_len = 4 + strlen(vcp->vc_srvname) + strlen(ssp->ss_name); unc_name = kmem_alloc(unc_len, KM_SLEEP); (void) snprintf(unc_name, unc_len, "\\\\%s\\%s", vcp->vc_srvname, ssp->ss_name); SMBSDEBUG("unc_name: \"%s\"", unc_name); /* * Share-level password (pre-computed in user-space) * MS-SMB 2.2.6 says this should be null terminated, * and the pw length includes the null. */ pbuf = ssp->ss_pass; plen = strlen(pbuf) + 1; /* * Build the request. */ mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); mb_put_uint8(mbp, 0); mb_put_uint16le(mbp, 0); mb_put_uint16le(mbp, 0); /* Flags */ mb_put_uint16le(mbp, plen); smb_rq_wend(rqp); smb_rq_bstart(rqp); /* Tree connect password, if any */ error = mb_put_mem(mbp, pbuf, plen, MB_MSYSTEM); if (error) goto out; /* UNC resource name */ error = smb_put_dstring(mbp, vcp, unc_name, SMB_CS_NONE); if (error) goto out; /* * Put the type string (always ASCII), * including the null. */ tname = smb_share_typename(ssp->ss_use); tlen = strlen(tname) + 1; error = mb_put_mem(mbp, tname, tlen, MB_MSYSTEM); if (error) goto out; smb_rq_bend(rqp); /* * Run the request. * * Using NOINTR_RECV because we don't want to risk * missing a successful tree connect response, * which would "leak" Tree IDs. */ rqp->sr_flags |= SMBR_NOINTR_RECV; error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); if (error) { /* * If we get the server name wrong, i.e. due to * mis-configured name services, this will be * NT_STATUS_DUPLICATE_NAME. Log this error. */ SMBERROR("(%s) failed, status=0x%x", unc_name, rqp->sr_error); goto out; } /* * Parse the TCON response */ smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc != 3 && wc != 7) { error = EBADRPC; goto out; } md_get_uint16le(mdp, NULL); /* AndX cmd */ md_get_uint16le(mdp, NULL); /* AndX off */ md_get_uint16le(mdp, &options); /* option bits (DFS, search) */ if (wc == 7) { md_get_uint32le(mdp, NULL); /* MaximalShareAccessRights */ md_get_uint32le(mdp, NULL); /* GuestMaximalShareAcc... */ } error = md_get_uint16le(mdp, &bcnt); /* byte count */ if (error) goto out; /* * Get the returned share type string, i.e. "IPC" or whatever. * (See smb_share_typename, smb_share_parsetype). If we get * an error reading the type, just say STYPE_UNKNOWN. */ tlen = STYPE_LEN; bzero(stype_str, tlen--); if (tlen > bcnt) tlen = bcnt; md_get_mem(mdp, stype_str, tlen, MB_MSYSTEM); stype_str[tlen] = '\0'; ssp->ss_type = smb_share_parsetype(stype_str); /* Success! */ SMB_SS_LOCK(ssp); ssp->ss_tid = rqp->sr_rptid; ssp->ss_vcgenid = vcp->vc_genid; ssp->ss_options = options; ssp->ss_flags |= SMBS_CONNECTED; SMB_SS_UNLOCK(ssp); out: if (unc_name) kmem_free(unc_name, unc_len); smb_rq_done(rqp); return (error); } int smb_smb_treedisconnect(struct smb_share *ssp, struct smb_cred *scred) { struct smb_vc *vcp; struct smb_rq *rqp; int error; if (ssp->ss_tid == SMB_TID_UNKNOWN) return (0); /* * Build this as a "VC-level" request, so it will * avoid testing the _GONE flag on the share, * which has already been set at this point. * Add the share pointer "by hand" below, so * smb_iod_sendrq will plug in the TID. */ vcp = SSTOVC(ssp); error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_TREE_DISCONNECT, scred, &rqp); if (error) return (error); rqp->sr_share = ssp; /* by hand */ smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); /* * Run this with a relatively short timeout. (5 sec.) * We don't really care about the result here, but we * do need to make sure we send this out, or we could * "leak" active tree IDs on interrupt or timeout. * The NOINTR_SEND flag makes this request immune to * interrupt or timeout until the send is done. * Also, don't reconnect for this, of course! */ rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT); error = smb_rq_simple_timed(rqp, 5); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); ssp->ss_tid = SMB_TID_UNKNOWN; return (error); } /* * Modern create/open of file or directory. */ int smb1_smb_ntcreate( struct smb_share *ssp, struct mbchain *name_mb, uint32_t cr_flags, /* create flags */ uint32_t req_acc, /* requested access */ uint32_t efa, /* ext. file attrs (DOS attr +) */ uint32_t share_acc, uint32_t open_disp, /* open disposition */ uint32_t createopt, /* NTCREATEX_OPTIONS_ */ uint32_t impersonate, /* NTCREATEX_IMPERSONATION_... */ struct smb_cred *scrp, uint16_t *fidp, /* returned FID */ uint32_t *cr_act_p, /* optional create action */ struct smbfattr *fap) /* optional attributes */ { struct smb_rq rq, *rqp = &rq; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; struct mdchain *mdp; struct smbfattr fa; uint64_t llongint; uint32_t longint, createact; uint16_t fid; uint8_t wc; int error; bzero(&fa, sizeof (fa)); error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_NT_CREATE_ANDX, scrp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); /* Word parameters */ smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); /* offset to next command (none) */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, name_mb->mb_count); mb_put_uint32le(mbp, cr_flags); /* NTCREATEX_FLAGS_* */ mb_put_uint32le(mbp, 0); /* FID - basis for path if not root */ mb_put_uint32le(mbp, req_acc); mb_put_uint64le(mbp, 0); /* "initial allocation size" */ mb_put_uint32le(mbp, efa); mb_put_uint32le(mbp, share_acc); mb_put_uint32le(mbp, open_disp); mb_put_uint32le(mbp, createopt); mb_put_uint32le(mbp, impersonate); mb_put_uint8(mbp, 0); /* security flags (?) */ smb_rq_wend(rqp); /* * Byte parameters: Just the path name, aligned. * Note: mb_put_mbuf consumes mb_top, so clear it. */ smb_rq_bstart(rqp); if (SMB_UNICODE_STRINGS(vcp)) mb_put_padbyte(mbp); mb_put_mbuf(mbp, name_mb->mb_top); bzero(name_mb, sizeof (*name_mb)); smb_rq_bend(rqp); /* * Don't want to risk missing a successful * open response, or we could "leak" FIDs. */ rqp->sr_flags |= SMBR_NOINTR_RECV; error = smb_rq_simple_timed(rqp, smb_timo_open); if (error) goto done; smb_rq_getreply(rqp, &mdp); /* * spec says 26 for word count, but 34 words are defined * and observed from win2000 */ error = md_get_uint8(mdp, &wc); if (error) goto done; if (wc != 26 && wc < 34) { error = EBADRPC; goto done; } md_get_uint8(mdp, NULL); /* secondary cmd */ md_get_uint8(mdp, NULL); /* mbz */ md_get_uint16le(mdp, NULL); /* andxoffset */ md_get_uint8(mdp, NULL); /* oplock lvl granted */ md_get_uint16le(mdp, &fid); /* file ID */ md_get_uint32le(mdp, &createact); /* create_action */ md_get_uint64le(mdp, &llongint); /* creation time */ smb_time_NT2local(llongint, &fa.fa_createtime); md_get_uint64le(mdp, &llongint); /* access time */ smb_time_NT2local(llongint, &fa.fa_atime); md_get_uint64le(mdp, &llongint); /* write time */ smb_time_NT2local(llongint, &fa.fa_mtime); md_get_uint64le(mdp, &llongint); /* change time */ smb_time_NT2local(llongint, &fa.fa_ctime); md_get_uint32le(mdp, &longint); /* attributes */ fa.fa_attr = longint; md_get_uint64le(mdp, &llongint); /* allocation size */ fa.fa_allocsz = llongint; md_get_uint64le(mdp, &llongint); /* EOF position */ fa.fa_size = llongint; error = md_get_uint16le(mdp, NULL); /* file type */ /* other stuff we don't care about */ done: smb_rq_done(rqp); if (error) return (error); *fidp = fid; if (cr_act_p) *cr_act_p = createact; if (fap) *fap = fa; /* struct copy */ return (0); } int smb1_smb_close(struct smb_share *ssp, uint16_t fid, struct timespec *mtime, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; long time; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_CLOSE, scrp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, fid); if (mtime) { int sv_tz = SSTOVC(ssp)->vc_sopt.sv_tz; smb_time_local2server(mtime, sv_tz, &time); } else { time = 0; } mb_put_uint32le(mbp, time); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); /* Make sure we send, but only if already connected */ rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT); error = smb_rq_simple(rqp); smb_rq_done(rqp); return (error); } int smb_smb_open_prjob( struct smb_share *ssp, char *title, uint16_t setuplen, uint16_t mode, struct smb_cred *scrp, uint16_t *fidp) { struct smb_rq rq, *rqp = &rq; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; struct mdchain *mdp; uint16_t fid; uint8_t wc; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_OPEN_PRINT_FILE, scrp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); /* Word parameters */ smb_rq_wstart(rqp); mb_put_uint16le(mbp, setuplen); mb_put_uint16le(mbp, mode); smb_rq_wend(rqp); /* * Byte parameters: Just the title */ smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); error = smb_put_dstring(mbp, vcp, title, SMB_CS_NONE); smb_rq_bend(rqp); if (error) goto done; /* * Don't want to risk missing a successful * open response, or we could "leak" FIDs. */ rqp->sr_flags |= SMBR_NOINTR_RECV; error = smb_rq_simple_timed(rqp, smb_timo_open); if (error) goto done; smb_rq_getreply(rqp, &mdp); error = md_get_uint8(mdp, &wc); if (error || wc < 1) { error = EBADRPC; goto done; } error = md_get_uint16le(mdp, &fid); done: smb_rq_done(rqp); if (error) return (error); *fidp = fid; return (0); } /* * Like smb_smb_close, but for print shares. */ int smb_smb_close_prjob(struct smb_share *ssp, uint16_t fid, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_CLOSE_PRINT_FILE, scrp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, fid); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); /* Make sure we send but only if already connected */ rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT); error = smb_rq_simple(rqp); smb_rq_done(rqp); return (error); } int smb_smb_readx(smb_fh_t *fhp, uint32_t *lenp, uio_t *uiop, smb_cred_t *scred, int timo) { struct smb_share *ssp = FHTOSS(fhp); struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; int error; uint32_t offlo, offhi, rlen; uint16_t lenhi, lenlo, off, doff; uint8_t wc; lenhi = (uint16_t)(*lenp >> 16); lenlo = (uint16_t)*lenp; offhi = (uint32_t)(uiop->uio_loffset >> 32); offlo = (uint32_t)uiop->uio_loffset; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_READ_ANDX, scred, &rqp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* no secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); /* offset to secondary */ mb_put_uint16le(mbp, fhp->fh_fid1); mb_put_uint32le(mbp, offlo); /* offset (low part) */ mb_put_uint16le(mbp, lenlo); /* MaxCount */ mb_put_uint16le(mbp, 1); /* MinCount */ /* (only indicates blocking) */ mb_put_uint32le(mbp, lenhi); /* MaxCountHigh */ mb_put_uint16le(mbp, lenlo); /* Remaining ("obsolete") */ mb_put_uint32le(mbp, offhi); /* offset (high part) */ smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); if (timo == 0) timo = smb_timo_read; error = smb_rq_simple_timed(rqp, timo); if (error) goto out; smb_rq_getreply(rqp, &mdp); error = md_get_uint8(mdp, &wc); if (error) goto out; if (wc != 12) { error = EBADRPC; goto out; } md_get_uint8(mdp, NULL); md_get_uint8(mdp, NULL); md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, NULL); /* data compaction mode */ md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, &lenlo); /* data len ret. */ md_get_uint16le(mdp, &doff); /* data offset */ md_get_uint16le(mdp, &lenhi); rlen = (lenhi << 16) | lenlo; md_get_mem(mdp, NULL, 4 * 2, MB_MSYSTEM); error = md_get_uint16le(mdp, NULL); /* ByteCount */ if (error) goto out; /* * Does the data offset indicate padding? * The current offset is a constant, found * by counting the md_get_ calls above. */ off = SMB_HDRLEN + 3 + (12 * 2); /* =59 */ if (doff > off) /* pad byte(s)? */ md_get_mem(mdp, NULL, doff - off, MB_MSYSTEM); if (rlen == 0) { *lenp = rlen; goto out; } /* paranoid */ if (rlen > *lenp) { SMBSDEBUG("bad server! rlen %d, len %d\n", rlen, *lenp); rlen = *lenp; } error = md_get_uio(mdp, uiop, rlen); if (error) goto out; /* Success */ *lenp = rlen; out: smb_rq_done(rqp); return (error); } int smb_smb_writex(smb_fh_t *fhp, uint32_t *lenp, uio_t *uiop, smb_cred_t *scred, int timo) { struct smb_share *ssp = FHTOSS(fhp); struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; int error; uint32_t offlo, offhi, rlen; uint16_t lenhi, lenlo; uint8_t wc; lenhi = (uint16_t)(*lenp >> 16); lenlo = (uint16_t)*lenp; offhi = (uint32_t)(uiop->uio_loffset >> 32); offlo = (uint32_t)uiop->uio_loffset; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_WRITE_ANDX, scred, &rqp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* no secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); /* offset to secondary */ mb_put_uint16le(mbp, fhp->fh_fid1); mb_put_uint32le(mbp, offlo); /* offset (low part) */ mb_put_uint32le(mbp, 0); /* MBZ (timeout) */ mb_put_uint16le(mbp, 0); /* !write-thru */ mb_put_uint16le(mbp, 0); mb_put_uint16le(mbp, lenhi); mb_put_uint16le(mbp, lenlo); mb_put_uint16le(mbp, 64); /* data offset from header start */ mb_put_uint32le(mbp, offhi); /* offset (high part) */ smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, 0); /* pad byte */ error = mb_put_uio(mbp, uiop, *lenp); if (error) goto out; smb_rq_bend(rqp); if (timo == 0) timo = smb_timo_write; error = smb_rq_simple_timed(rqp, timo); if (error) goto out; smb_rq_getreply(rqp, &mdp); error = md_get_uint8(mdp, &wc); if (error) goto out; if (wc != 6) { error = EBADRPC; goto out; } md_get_uint8(mdp, NULL); /* andx cmd */ md_get_uint8(mdp, NULL); /* reserved */ md_get_uint16le(mdp, NULL); /* andx offset */ md_get_uint16le(mdp, &lenlo); /* data len ret. */ md_get_uint16le(mdp, NULL); /* remaining */ error = md_get_uint16le(mdp, &lenhi); if (error) goto out; /* Success */ rlen = (lenhi << 16) | lenlo; *lenp = rlen; out: smb_rq_done(rqp); return (error); } static u_int32_t smbechoes = 0; /* * Note: the IOD calls this, so this request must not wait for * connection state changes, etc. (uses smb_rq_internal) */ int smb_smb_echo(struct smb_vc *vcp, struct smb_cred *scred, int timo) { struct smb_rq *rqp; struct mbchain *mbp; int error; error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_ECHO, scred, &rqp); if (error) return (error); mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint16le(mbp, 1); /* echo count */ smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint32le(mbp, atomic_inc_32_nv(&smbechoes)); smb_rq_bend(rqp); rqp->sr_flags |= SMBR_NORECONNECT; error = smb_rq_internal(rqp, timo); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); return (error); }