/* * Copyright (c) 2011 - 2013 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NDIALECTS 1 static const uint16_t smb2_dialects[1] = { SMB2_DIALECT_0210 }; /* Optional capabilities we advertise (none yet). */ uint32_t smb2_clnt_caps = 0; /* How many credits to ask for during ssn. setup. */ uint16_t smb2_ss_req_credits = 64; /* * Default timeout values, all in seconds. * Make these tunable (only via mdb for now). */ int smb2_timo_notice = 15; int smb2_timo_default = 30; int smb2_timo_logon = 45; int smb2_timo_open = 45; int smb2_timo_read = 45; int smb2_timo_write = 60; int smb2_timo_append = 90; /* * This is a special handler for the odd SMB1-to-SMB2 negotiate * response, where an SMB1 request gets an SMB2 response. * * Unlike most parse functions here, this needs to parse both * the SMB2 header and the nego. response body. Note that * the only "SMB2" dialect our SMB1 negotiate offered was * { SMB_DIALECT_SMB2_FF, "SMB 2.???"} so the only valid * SMB2 dialect we should get is: SMB2_DIALECT_02ff */ int smb2_parse_smb1nego_resp(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; struct smb_sopt *sp = &vcp->vc_sopt; struct mdchain *mdp; uint16_t length = 0; int error; /* Get pointer to response data */ smb_rq_getreply(rqp, &mdp); error = smb2_rq_parsehdr(rqp); if (error != 0) return (error); /* * Parse SMB 2/3 Negotiate Response * We are already pointing to begining of Response data */ /* Check structure size is 65 */ md_get_uint16le(mdp, &length); if (length != 65) return (EBADRPC); /* Get Security Mode */ md_get_uint16le(mdp, &sp->sv2_security_mode); /* Get Dialect. */ error = md_get_uint16le(mdp, &sp->sv2_dialect); if (error != 0) return (error); /* What dialect did we get? */ if (sp->sv2_dialect != SMB2_DIALECT_02ff) { SMBERROR("Unknown dialect 0x%x\n", sp->sv2_dialect); return (EINVAL); } /* Set our (internal) SMB1 dialect also. */ sp->sv_proto = SMB_DIALECT_SMB2_FF; /* * This request did not go through smb2_iod_addrq and * smb2_iod_process() so the SMB2 message ID state is * behind what we need it to be. Fix that. */ vcp->vc2_next_message_id = 1; vcp->vc2_limit_message_id = 2; /* * Skip parsing the rest. We'll get a normal * SMB2 negotiate next and do negotiate then. */ return (0); } int smb2_smb_negotiate(struct smb_vc *vcp, struct smb_cred *scred) { smb_sopt_t *sp = &vcp->vc_sopt; smbioc_ssn_work_t *wk = &vcp->vc_work; struct smb_rq *rqp = NULL; struct mbchain *mbp = NULL; struct mdchain *mdp = NULL; uint16_t ndialects = NDIALECTS; boolean_t will_sign = B_FALSE; uint16_t length = 0; uint16_t security_mode; uint16_t sec_buf_off; uint16_t sec_buf_len; int err, i; /* * Compute security mode */ if (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED) { security_mode = SMB2_NEGOTIATE_SIGNING_REQUIRED; } else { security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; } err = smb_rq_alloc(VCTOCP(vcp), SMB2_NEGOTIATE, scred, &rqp); if (err) return (err); /* * Build the SMB2 negotiate request. */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 36); /* Struct Size */ mb_put_uint16le(mbp, ndialects); /* Dialect Count */ mb_put_uint16le(mbp, security_mode); mb_put_uint16le(mbp, 0); /* Reserved */ mb_put_uint32le(mbp, smb2_clnt_caps); mb_put_mem(mbp, vcp->vc_cl_guid, 16, MB_MSYSTEM); mb_put_uint64le(mbp, 0); /* Start Time */ for (i = 0; i < ndialects; i++) { /* Dialects */ mb_put_uint16le(mbp, smb2_dialects[i]); } /* * Do the OTW call. */ err = smb2_rq_internal(rqp, smb2_timo_default); if (err) { goto errout; } /* Should only get status success. */ if (rqp->sr_error != NT_STATUS_SUCCESS) { err = ENOTSUP; goto errout; } /* * Decode the negotiate response */ smb_rq_getreply(rqp, &mdp); md_get_uint16le(mdp, &length); /* Struct size */ if (length != 65) { err = EBADRPC; goto errout; } md_get_uint16le(mdp, &sp->sv2_security_mode); md_get_uint16le(mdp, &sp->sv2_dialect); md_get_uint16le(mdp, NULL); /* reserved */ md_get_mem(mdp, sp->sv2_guid, 16, MB_MSYSTEM); md_get_uint32le(mdp, &sp->sv2_capabilities); md_get_uint32le(mdp, &sp->sv2_maxtransact); md_get_uint32le(mdp, &sp->sv2_maxread); md_get_uint32le(mdp, &sp->sv2_maxwrite); md_get_uint64le(mdp, NULL); /* curr_time */ md_get_uint64le(mdp, NULL); /* boot_time */ /* Get Security Blob offset and length */ md_get_uint16le(mdp, &sec_buf_off); err = md_get_uint16le(mdp, &sec_buf_len); if (err != 0) goto errout; md_get_uint32le(mdp, NULL); /* reserved */ /* * Security buffer offset is from the beginning of SMB 2 Header * Calculate how much further we have to go to get to it. * Current offset is: SMB2_HDRLEN + 64 */ if (sec_buf_len != 0) { int skip = (int)sec_buf_off - (SMB2_HDRLEN + 64); if (skip < 0) { err = EBADRPC; goto errout; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } /* * Copy the security blob out to user space. * Buffer addr,size in vc_auth_rbuf,rlen */ if (wk->wk_u_auth_rlen < sec_buf_len) { SMBSDEBUG("vc_auth_rbuf too small"); /* Give caller required size. */ wk->wk_u_auth_rlen = sec_buf_len; err = EMSGSIZE; goto errout; } wk->wk_u_auth_rlen = sec_buf_len; err = md_get_mem(mdp, wk->wk_u_auth_rbuf.lp_ptr, sec_buf_len, MB_MUSER); if (err) { goto errout; } } /* * Decoded everything. Now decisions. */ /* * Turn on signing if either Server or client requires it, * except: anonymous sessions can't sign. */ if ((sp->sv2_security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) || (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED)) will_sign = B_TRUE; 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; /* * ToDo - too many places are looking at sv_caps, so for now * set the SMB1 capabilities too. Later we should use the * sv2_capabilities for SMB 2+. */ sp->sv_caps = (SMB_CAP_UNICODE | SMB_CAP_LARGE_FILES | SMB_CAP_STATUS32 | SMB_CAP_LARGE_READX | SMB_CAP_LARGE_WRITEX | SMB_CAP_EXT_SECURITY); if (sp->sv2_capabilities & SMB2_CAP_DFS) sp->sv_caps |= SMB_CAP_DFS; /* * 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 (sp->sv2_maxread < 0x8000) { SMBSDEBUG("maxread too small\n"); err = ENOTSUP; goto errout; } if (sp->sv2_maxwrite < 0x8000) { SMBSDEBUG("maxwrite too small\n"); err = ENOTSUP; goto errout; } if (sp->sv2_maxtransact < 0x4000) { SMBSDEBUG("maxtransact too small\n"); err = ENOTSUP; goto errout; } /* Here too, fill SMB1 fields */ vcp->vc_rxmax = sp->sv2_maxread; vcp->vc_wxmax = sp->sv2_maxwrite; vcp->vc_txmax = sp->sv2_maxtransact; smb_rq_done(rqp); return (0); errout: smb_rq_done(rqp); if (err == 0) err = EBADRPC; return (err); } int smb2_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; uint16_t sblen; uint16_t length = 0; uint16_t session_flags; uint16_t sec_buf_off; uint16_t sec_buf_len; uint8_t security_mode; /* * Compute security mode */ if (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED) { security_mode = SMB2_NEGOTIATE_SIGNING_REQUIRED; } else { security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; } sb = wk->wk_u_auth_wbuf.lp_ptr; sblen = (uint16_t)wk->wk_u_auth_wlen; err = smb_rq_alloc(VCTOCP(vcp), SMB2_SESSION_SETUP, scred, &rqp); if (err != 0) { ret = err; goto out; } /* * Always ask for some credits. The server usually will * only grant these credits once we've authenticated. */ rqp->sr2_creditsrequested = smb2_ss_req_credits; /* * Build the SMB Session Setup request. */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 25); /* Struct size */ mb_put_uint8(mbp, 0); /* VcNumber */ mb_put_uint8(mbp, security_mode); mb_put_uint32le(mbp, smb2_clnt_caps); /* Capabilities */ mb_put_uint32le(mbp, 0); /* Channel - always 0 */ /* * Security buffer offset and length. Normally would use * ptr = mb_reserve() and fill in later, but since only a * small amount of fixed-size stuff follows (12 bytes) * we can just compute the offset now. */ mb_put_uint16le(mbp, mbp->mb_count + 12); mb_put_uint16le(mbp, sblen); mb_put_uint64le(mbp, vcp->vc2_prev_session_id); err = mb_put_mem(mbp, sb, sblen, MB_MUSER); if (err != 0) { ret = err; goto out; } /* * 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 = smb2_rq_internal(rqp, smb2_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; } /* * After the first Session Setup Response, * save the session ID. */ if (vcp->vc2_session_id == 0) vcp->vc2_session_id = rqp->sr2_rspsessionid; /* * Decode the session setup response */ smb_rq_getreply(rqp, &mdp); md_get_uint16le(mdp, &length); /* Struct size */ if (length != 9) { ret = EBADRPC; goto out; } md_get_uint16le(mdp, &session_flags); md_get_uint16le(mdp, &sec_buf_off); err = md_get_uint16le(mdp, &sec_buf_len); if (err != 0) { ret = err; goto out; } /* * Security buffer offset is from the beginning of SMB 2 Header * Calculate how much further we have to go to get to it. * Current offset is: SMB2_HDRLEN + 8 */ if (sec_buf_len != 0) { int skip = (int)sec_buf_off - (SMB2_HDRLEN + 8); if (skip < 0) { ret = EBADRPC; goto out; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } /* * Copy the security blob out to user space. * Buffer addr,size in vc_auth_rbuf,rlen */ if (wk->wk_u_auth_rlen < sec_buf_len) { SMBSDEBUG("vc_auth_rbuf too small"); /* Give caller required size. */ wk->wk_u_auth_rlen = sec_buf_len; ret = EMSGSIZE; goto out; } wk->wk_u_auth_rlen = sec_buf_len; err = md_get_mem(mdp, wk->wk_u_auth_rbuf.lp_ptr, sec_buf_len, MB_MUSER); if (err != 0) { ret = err; goto out; } } out: if (err != 0 && err != EINPROGRESS) { /* Session ID no longer valid. */ vcp->vc2_session_id = 0; } if (rqp) smb_rq_done(rqp); return (ret); } int smb2_smb_logoff(struct smb_vc *vcp, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; int error; if (vcp->vc2_session_id == 0) return (0); error = smb_rq_alloc(VCTOCP(vcp), SMB2_LOGOFF, scred, &rqp); if (error) return (error); /* * Fill in Logoff part */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 4); /* Struct size */ mb_put_uint16le(mbp, 0); /* Reserved */ /* * 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 = smb2_rq_internal(rqp, 5); smb_rq_done(rqp); return (error); } int smb2_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; char *unc_name = NULL; int error, unc_len; uint16_t plen, *plenp; uint16_t options = 0; uint_t cnt0; uint32_t net_stype; uint16_t structure_size = 0; uint8_t smb2stype; 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), SMB2_TREE_CONNECT, 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); /* * Build the request. */ mbp = &rqp->sr_rq; mb_put_uint16le(mbp, 9); /* Struct size */ mb_put_uint16le(mbp, 0); /* Reserved */ mb_put_uint16le(mbp, 72); /* Path Offset */ /* * Fill in path length after we put the string, so we know * the length after conversion from UTF-8 to UCS-2. */ plenp = mb_reserve(mbp, 2); cnt0 = mbp->mb_count; /* UNC resource name (without the null) */ error = smb_put_dmem(mbp, vcp, unc_name, unc_len - 1, SMB_CS_NONE, NULL); if (error) goto out; /* Now go back and fill in the path length. */ plen = (uint16_t)(mbp->mb_count - cnt0); *plenp = htoles(plen); /* * 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 = smb2_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 tree connect response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 16 */ md_get_uint16le(mdp, &structure_size); if (structure_size != 16) { error = EBADRPC; goto out; } md_get_uint8(mdp, &smb2stype); md_get_uint8(mdp, NULL); /* reserved */ md_get_uint32le(mdp, &ssp->ss2_share_flags); md_get_uint32le(mdp, &ssp->ss2_share_caps); error = md_get_uint32le(mdp, NULL); /* maxAccessRights */ if (error) goto out; /* * Convert SMB2 share type to NetShareEnum share type */ switch (smb2stype) { case SMB2_SHARE_TYPE_DISK: net_stype = STYPE_DISKTREE; break; case SMB2_SHARE_TYPE_PIPE: net_stype = STYPE_IPC; break; case SMB2_SHARE_TYPE_PRINT: net_stype = STYPE_PRINTQ; break; default: net_stype = STYPE_UNKNOWN; break; } ssp->ss_type = net_stype; /* * Map SMB 2/3 capabilities to SMB 1 options, * for common code that looks there. */ if (ssp->ss2_share_caps & SMB2_SHARE_CAP_DFS) options |= SMB_SHARE_IS_IN_DFS; /* Update share state */ SMB_SS_LOCK(ssp); ssp->ss2_tree_id = rqp->sr2_rsptreeid; 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 smb2_smb_treedisconnect(struct smb_share *ssp, struct smb_cred *scred) { struct smb_vc *vcp; struct smb_rq *rqp; struct mbchain *mbp; int error; if (ssp->ss2_tree_id == SMB2_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), SMB2_TREE_DISCONNECT, scred, &rqp); if (error) return (error); rqp->sr_share = ssp; /* See "by hand" above. */ /* * Fill in SMB2 Tree Disconnect part */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 4); /* Struct size */ mb_put_uint16le(mbp, 0); /* Reserved */ /* * 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 = smb2_rq_simple_timed(rqp, 5); smb_rq_done(rqp); /* Whether we get an error or not... */ ssp->ss2_tree_id = SMB2_TID_UNKNOWN; return (error); } /* * Put the name, first skipping a leading slash. */ static int put_name_skip_slash(struct mbchain *mbp, struct mbchain *name_mbp) { mblk_t *m; if (name_mbp == NULL) return (0); m = name_mbp->mb_top; if (m == NULL) return (0); /* Use a dup of the message to leave the passed one untouched. */ m = dupmsg(m); if (m == NULL) return (ENOSR); if (MBLKL(m) >= 2 && m->b_rptr[0] == '\\' && m->b_rptr[1] == '\0') m->b_rptr += 2; return (mb_put_mbuf(mbp, m)); } /* * Modern create/open of file or directory. * * The passed name is a full path relative to the share root. * Callers prepare paths with a leading slash (backslash) * because that's what SMB1 expected. SMB2 does not allow the * leading slash here. To make life simpler for callers skip a * leading slash here. That allows callers use use common logic * for building paths without needing to know if the connection * is using SMB1 or SMB2 (just build paths with a leading slash). */ int smb2_smb_ntcreate( struct smb_share *ssp, struct mbchain *name_mb, struct mbchain *cctx_in, struct mdchain *cctx_out, 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, smb2fid_t *fidp, /* returned FID */ uint32_t *cr_act_p, /* optional create action */ struct smbfattr *fap) /* optional attributes */ { struct smbfattr fa; struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; uint16_t *name_offp; uint16_t *name_lenp; uint32_t *cctx_offp; uint32_t *cctx_lenp; uint32_t rcc_off, rcc_len; smb2fid_t smb2_fid; uint64_t llongint; uint32_t longint, createact; uint_t off, len; int error; uint16_t StructSize = 57; // [MS-SMB2] bzero(&fa, sizeof (fa)); error = smb_rq_alloc(SSTOCP(ssp), SMB2_CREATE, scrp, &rqp); if (error) return (error); /* * Todo: Assemble creat contexts (if needed) * into an mbchain. */ /* * Build the SMB 2/3 Create Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, StructSize); mb_put_uint8(mbp, 0); /* Security flags */ mb_put_uint8(mbp, SMB2_OPLOCK_LEVEL_NONE); /* Oplock level */ mb_put_uint32le(mbp, impersonate); /* Impersonation Level */ mb_put_uint64le(mbp, cr_flags); mb_put_uint64le(mbp, 0); /* Reserved */ mb_put_uint32le(mbp, req_acc); mb_put_uint32le(mbp, efa); /* File attributes */ mb_put_uint32le(mbp, share_acc); /* Share access */ mb_put_uint32le(mbp, open_disp); /* Create disposition */ mb_put_uint32le(mbp, createopt); /* Create options */ name_offp = mb_reserve(mbp, 2); /* Name offset */ name_lenp = mb_reserve(mbp, 2); /* Name len */ cctx_offp = mb_reserve(mbp, 4); /* Context offset */ cctx_lenp = mb_reserve(mbp, 4); /* Context len */ /* * Put the file name, which is provided in an mbchain. * If there's a leading slash, skip it (see above). */ off = mbp->mb_count; *name_offp = htoles((uint16_t)off); error = put_name_skip_slash(mbp, name_mb); if (error) goto out; len = mbp->mb_count - off; *name_lenp = htoles((uint16_t)len); /* * Now the create contexts (if provided) */ if (cctx_in != NULL) { off = mbp->mb_count; *cctx_offp = htolel((uint32_t)off); mb_put_mbchain(mbp, cctx_in); len = mbp->mb_count - off; *cctx_lenp = htolel((uint32_t)len); } else { *cctx_offp = 0; *cctx_lenp = 0; } /* * If we didn't put any variable-sized data, we'll have * put exactly 56 bytes of data, and we need to pad out * this request to the 57 bytes StructSize indicated. */ if (mbp->mb_count < (StructSize + SMB2_HDRLEN)) mb_put_uint8(mbp, 0); /* * Don't want to risk missing a successful * open response, or we could "leak" FIDs. */ rqp->sr_flags |= SMBR_NOINTR_RECV; error = smb2_rq_simple_timed(rqp, smb2_timo_open); if (error) goto out; /* * Parse SMB 2/3 Create Response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 89 */ error = md_get_uint16le(mdp, &StructSize); if (StructSize != 89) { error = EBADRPC; goto out; } md_get_uint8(mdp, NULL); /* oplock lvl granted */ md_get_uint8(mdp, NULL); /* mbz */ 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_uint64le(mdp, &llongint); /* allocation size */ fa.fa_allocsz = llongint; md_get_uint64le(mdp, &llongint); /* EOF position */ fa.fa_size = llongint; md_get_uint32le(mdp, &longint); /* attributes */ fa.fa_attr = longint; md_get_uint32le(mdp, NULL); /* reserved */ /* Get SMB 2/3 File ID and create user fid to return */ md_get_uint64le(mdp, &smb2_fid.fid_persistent); error = md_get_uint64le(mdp, &smb2_fid.fid_volatile); if (error) goto out; /* Get Context Offset */ error = md_get_uint32le(mdp, &rcc_off); if (error) goto out; /* Get Context Length */ error = md_get_uint32le(mdp, &rcc_len); if (error) goto out; /* * If the caller wants the returned create contexts, parse. * Context offset is from the beginning of SMB 2/3 Header * Calculate how much further we have to go to get to it. * Current offset is: SMB2_HDRLEN + 88 */ if (rcc_len != 0) { int skip = (int)rcc_off - (SMB2_HDRLEN + 88); if (skip < 0) { error = EBADRPC; goto out; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } if (cctx_out != NULL) { mblk_t *m = NULL; error = md_get_mbuf(mdp, rcc_len, &m); if (error) goto out; md_initm(cctx_out, m); } } out: smb_rq_done(rqp); if (error) return (error); *fidp = smb2_fid; if (cr_act_p) *cr_act_p = createact; if (fap) *fap = fa; /* struct copy */ return (0); } int smb2_smb_close(struct smb_share *ssp, smb2fid_t *fid, struct smb_cred *scrp) { struct smb_rq *rqp; struct mbchain *mbp; int error; error = smb_rq_alloc(SSTOCP(ssp), SMB2_CLOSE, scrp, &rqp); if (error) return (error); /* * Build the SMB 2/3 Close Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 24); /* Struct size */ mb_put_uint16le(mbp, 0); /* Flags */ mb_put_uint32le(mbp, 0); /* Reserved */ mb_put_uint64le(mbp, fid->fid_persistent); mb_put_uint64le(mbp, fid->fid_volatile); /* Make sure we send, but only if already connected */ rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT); error = smb2_rq_simple(rqp); smb_rq_done(rqp); return (error); } int smb2_smb_ioctl( struct smb_share *ssp, smb2fid_t *fid, struct mbchain *data_in, struct mdchain *data_out, uint32_t *data_out_sz, /* max / returned */ uint32_t ctl_code, struct smb_cred *scrp) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; uint32_t *data_in_offp; uint32_t *data_in_lenp; uint32_t data_out_off; uint32_t data_out_len; uint16_t length = 0; uint_t off, len; int error; error = smb_rq_alloc(SSTOCP(ssp), SMB2_IOCTL, scrp, &rqp); if (error) return (error); /* * Build the SMB 2 IOCTL Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 57); /* Struct size */ mb_put_uint16le(mbp, 0); /* Reserved */ mb_put_uint32le(mbp, ctl_code); mb_put_uint64le(mbp, fid->fid_persistent); mb_put_uint64le(mbp, fid->fid_volatile); data_in_offp = mb_reserve(mbp, 4); data_in_lenp = mb_reserve(mbp, 4); mb_put_uint32le(mbp, 0); /* Max input resp */ mb_put_uint32le(mbp, 0); /* Output offset */ mb_put_uint32le(mbp, 0); /* Output count */ mb_put_uint32le(mbp, *data_out_sz); mb_put_uint32le(mbp, SMB2_IOCTL_IS_FSCTL); /* Flags */ mb_put_uint32le(mbp, 0); /* Reserved2 */ /* * Now data_in (if provided) */ if (data_in != NULL) { off = mbp->mb_count; *data_in_offp = htolel((uint32_t)off); mb_put_mbchain(mbp, data_in); len = mbp->mb_count - off; *data_in_lenp = htolel((uint32_t)len); } else { *data_in_offp = 0; *data_in_lenp = 0; } /* * Run the request */ error = smb2_rq_simple_timed(rqp, smb2_timo_default); if (error) goto out; /* * Parse SMB 2 Ioctl Response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 49 */ md_get_uint16le(mdp, &length); if (length != 49) { error = EBADRPC; goto out; } md_get_uint16le(mdp, NULL); /* reserved */ md_get_uint32le(mdp, NULL); /* Get CtlCode */ md_get_uint64le(mdp, NULL); /* fid_persistent */ md_get_uint64le(mdp, NULL); /* fid_volatile */ md_get_uint32le(mdp, NULL); /* Get Input offset */ md_get_uint32le(mdp, NULL); /* Get Input count */ error = md_get_uint32le(mdp, &data_out_off); if (error) goto out; error = md_get_uint32le(mdp, &data_out_len); if (error) goto out; md_get_uint32le(mdp, NULL); /* Flags */ md_get_uint32le(mdp, NULL); /* reserved */ /* * If the caller wants the ioctl output data, parse. * Current offset is: SMB2_HDRLEN + 48 * Always return the received length. */ *data_out_sz = data_out_len; if (data_out_len != 0) { int skip = (int)data_out_off - (SMB2_HDRLEN + 48); if (skip < 0) { error = EBADRPC; goto out; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } if (data_out != NULL) { mblk_t *m = NULL; error = md_get_mbuf(mdp, data_out_len, &m); if (error) goto out; md_initm(data_out, m); } } out: smb_rq_done(rqp); return (error); } int smb2_smb_read(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; uint64_t off64 = uiop->uio_loffset; uint32_t rlen; uint16_t length = 0; uint8_t data_offset; error = smb_rq_alloc(SSTOCP(ssp), SMB2_READ, scred, &rqp); if (error) return (error); /* * Build the SMB 2 Read Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 49); /* Struct size */ mb_put_uint16le(mbp, 0); /* Padding and Reserved */ mb_put_uint32le(mbp, *lenp); /* Length of read */ mb_put_uint64le(mbp, off64); /* Offset */ mb_put_uint64le(mbp, fhp->fh_fid2.fid_persistent); mb_put_uint64le(mbp, fhp->fh_fid2.fid_volatile); mb_put_uint32le(mbp, 1); /* MinCount */ /* (only indicates blocking) */ mb_put_uint32le(mbp, 0); /* Channel */ mb_put_uint32le(mbp, 0); /* Remaining */ mb_put_uint32le(mbp, 0); /* Channel offset/len */ mb_put_uint8(mbp, 0); /* data "blob" (pad) */ if (timo == 0) timo = smb2_timo_read; error = smb2_rq_simple_timed(rqp, timo); if (error) goto out; /* * Parse SMB 2 Read Response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 17 */ md_get_uint16le(mdp, &length); if (length != 17) { error = EBADRPC; goto out; } md_get_uint8(mdp, &data_offset); md_get_uint8(mdp, NULL); /* reserved */ /* Get Data Length read */ error = md_get_uint32le(mdp, &rlen); if (error) goto out; md_get_uint32le(mdp, NULL); /* Data Remaining (always 0) */ md_get_uint32le(mdp, NULL); /* Get Reserved2 (always 0) */ /* * Data offset is from the beginning of SMB 2/3 Header * Calculate how much further we have to go to get to it. */ if (data_offset < (SMB2_HDRLEN + 16)) { error = EBADRPC; goto out; } if (data_offset > (SMB2_HDRLEN + 16)) { int skip = data_offset - (SMB2_HDRLEN + 16); md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } /* * Get the data */ 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 smb2_smb_write(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; uint64_t off64 = uiop->uio_loffset; uint32_t rlen; uint16_t data_offset; uint16_t length = 0; error = smb_rq_alloc(SSTOCP(ssp), SMB2_WRITE, scred, &rqp); if (error) return (error); /* * Build the SMB 2 Write Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 49); /* Struct size */ data_offset = SMB2_HDRLEN + 48; mb_put_uint16le(mbp, data_offset); /* Data Offset */ mb_put_uint32le(mbp, *lenp); /* Length of write */ mb_put_uint64le(mbp, off64); /* Offset */ mb_put_uint64le(mbp, fhp->fh_fid2.fid_persistent); mb_put_uint64le(mbp, fhp->fh_fid2.fid_volatile); mb_put_uint32le(mbp, 0); /* Channel */ mb_put_uint32le(mbp, 0); /* Remaining */ mb_put_uint32le(mbp, 0); /* Channel offset/len */ mb_put_uint32le(mbp, 0); /* Write flags */ error = mb_put_uio(mbp, uiop, *lenp); if (error) goto out; if (timo == 0) timo = smb2_timo_write; error = smb2_rq_simple_timed(rqp, timo); if (error) goto out; /* * Parse SMB 2/3 Write Response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 17 */ md_get_uint16le(mdp, &length); if (length != 17) { error = EBADRPC; goto out; } md_get_uint16le(mdp, NULL); /* Get Reserved */ /* Get Data Length written */ error = md_get_uint32le(mdp, &rlen); if (error) goto out; /* Get Data Remaining (always 0) */ md_get_uint32le(mdp, NULL); /* Get Reserved2 (always 0) */ md_get_uint32le(mdp, NULL); /* Success */ *lenp = rlen; out: smb_rq_done(rqp); return (error); } /* * Note: the IOD calls this, so this request must not wait for * connection state changes, etc. (uses smb2_rq_internal) */ int smb2_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), SMB2_ECHO, scred, &rqp); if (error) return (error); /* * Build the SMB 2 Echo Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 4); /* Struct size */ mb_put_uint16le(mbp, 0); /* Reserved */ rqp->sr_flags |= SMBR_NORECONNECT; error = smb2_rq_internal(rqp, timo); smb_rq_done(rqp); return (error); }