/* * 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. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 2011 - 2013 Apple Inc. All rights reserved. * 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 /* * Todo: locking over-the-wire */ #if 0 // todo int smbfs_smb2_locking(struct smbnode *np, int op, uint32_t pid, offset_t start, uint64_t len, int largelock, struct smb_cred *scrp, uint32_t timeout) { return (ENOTSUP); } #endif // todo /* * Helper for smbfs_getattr_otw * used when we don't have an open FID * * For SMB2 we need to do an attribute-only open. The * data returned by open gets us everything we need, so * just close the handle and we're done. */ int smbfs_smb2_getpattr( struct smbnode *np, struct smbfattr *fap, struct smb_cred *scrp) { smb_fh_t tmp_fh; struct smb_share *ssp = np->n_mount->smi_share; uint32_t rights = (STD_RIGHT_READ_CONTROL_ACCESS | SA_RIGHT_FILE_READ_ATTRIBUTES); int error; bzero(&tmp_fh, sizeof (tmp_fh)); error = smbfs_smb_ntcreatex(np, NULL, 0, 0, /* name nmlen xattr */ rights, SMB_EFA_NORMAL, NTCREATEX_SHARE_ACCESS_ALL, NTCREATEX_DISP_OPEN, 0, /* create options */ scrp, &tmp_fh, NULL, fap); if (error == 0) { (void) smb_smb_close(ssp, &tmp_fh, scrp); } return (error); } /* * Common SMB2 query file info */ static int smbfs_smb2_query_info(struct smb_share *ssp, smb2fid_t *fid, struct mdchain *info_mdp, uint32_t *iolen, uint8_t type, uint8_t level, uint32_t addl_info, struct smb_cred *scrp) { struct smb_rq *rqp = NULL; struct mbchain *mbp; struct mdchain *mdp; uint32_t dlen = 0; uint16_t doff = 0; uint16_t ssize = 0; int error; error = smb_rq_alloc(SSTOCP(ssp), SMB2_QUERY_INFO, scrp, &rqp); if (error) goto out; /* * Build the SMB 2 Query Info req. */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 41); // struct size mb_put_uint8(mbp, type); mb_put_uint8(mbp, level); mb_put_uint32le(mbp, *iolen); // out buf len mb_put_uint16le(mbp, 0); // in buf off mb_put_uint16le(mbp, 0); // reserved mb_put_uint32le(mbp, 0); // in buf len mb_put_uint32le(mbp, addl_info); mb_put_uint32le(mbp, 0); // flags mb_put_uint64le(mbp, fid->fid_persistent); mb_put_uint64le(mbp, fid->fid_volatile); error = smb2_rq_simple(rqp); if (error) { if (rqp->sr_error == NT_STATUS_INVALID_PARAMETER) error = ENOTSUP; goto out; } /* * Parse SMB 2 Query Info response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 9 */ md_get_uint16le(mdp, &ssize); if (ssize != 9) { error = EBADRPC; goto out; } /* Get data off, len */ md_get_uint16le(mdp, &doff); md_get_uint32le(mdp, &dlen); *iolen = dlen; /* * Skip ahead to the payload, as needed. * Current offset is SMB2_HDRLEN + 8. */ if (dlen != 0) { mblk_t *m = NULL; int skip = (int)doff - (SMB2_HDRLEN + 8); if (skip < 0) { error = EBADRPC; goto out; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } error = md_get_mbuf(mdp, dlen, &m); if (error) goto out; md_initm(info_mdp, m); } out: smb_rq_done(rqp); return (error); } /* * Get FileAllInformation for an open file * and parse into *fap */ int smbfs_smb2_qfileinfo(struct smb_share *ssp, smb2fid_t *fid, struct smbfattr *fap, struct smb_cred *scrp) { struct mdchain info_mdc, *mdp = &info_mdc; uint32_t iolen = 1024; int error; bzero(mdp, sizeof (*mdp)); error = smbfs_smb2_query_info(ssp, fid, mdp, &iolen, SMB2_0_INFO_FILE, FileAllInformation, 0, scrp); if (error) goto out; error = smbfs_decode_file_all_info(ssp, mdp, fap); out: md_done(mdp); return (error); } /* * Get some SMB2_0_INFO_FILESYSTEM info * * Note: This can be called during mount. We don't have any * smbfs_node_t or pathname, so do our own attr. open on * the root of the share to get a handle for this request. */ static int smbfs_smb2_query_fs_info(struct smb_share *ssp, struct mdchain *mdp, uint8_t level, struct smb_cred *scrp) { smb2fid_t fid; uint32_t iolen = 1024; boolean_t opened = B_FALSE; int error; /* * Need a FID for smb2, and this is called during mount * so "go behind" the usual open/close functions. */ error = smb2_smb_ntcreate( ssp, NULL, // name NULL, NULL, // create ctx in, out 0, /* NTCREATEX_FLAGS... */ SA_RIGHT_FILE_READ_ATTRIBUTES, SMB_EFA_NORMAL, NTCREATEX_SHARE_ACCESS_ALL, NTCREATEX_DISP_OPEN, 0, /* create options */ NTCREATEX_IMPERSONATION_IMPERSONATION, scrp, &fid, NULL, NULL); if (error != 0) goto out; opened = B_TRUE; error = smbfs_smb2_query_info(ssp, &fid, mdp, &iolen, SMB2_0_INFO_FILESYSTEM, level, 0, scrp); out: if (opened) (void) smb2_smb_close(ssp, &fid, scrp); return (error); } /* * Get FileFsAttributeInformation and * parse into *info */ int smbfs_smb2_qfsattr(struct smb_share *ssp, struct smb_fs_attr_info *info, struct smb_cred *scrp) { struct mdchain info_mdc, *mdp = &info_mdc; int error; bzero(mdp, sizeof (*mdp)); error = smbfs_smb2_query_fs_info(ssp, mdp, FileFsAttributeInformation, scrp); if (error) goto out; error = smbfs_decode_fs_attr_info(ssp, mdp, info); out: md_done(mdp); return (error); } /* * Get FileFsFullSizeInformation and * parse into *info */ int smbfs_smb2_statfs(struct smb_share *ssp, struct smb_fs_size_info *info, struct smb_cred *scrp) { struct mdchain info_mdc, *mdp = &info_mdc; int error; bzero(mdp, sizeof (*mdp)); error = smbfs_smb2_query_fs_info(ssp, mdp, FileFsFullSizeInformation, scrp); if (error) goto out; md_get_uint64le(mdp, &info->total_units); md_get_uint64le(mdp, &info->caller_avail); md_get_uint64le(mdp, &info->actual_avail); md_get_uint32le(mdp, &info->sect_per_unit); error = md_get_uint32le(mdp, &info->bytes_per_sect); out: md_done(mdp); return (error); } int smbfs_smb2_flush(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_FLUSH, scrp, &rqp); if (error) return (error); /* * Build the SMB 2 Flush Request */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 24); /* struct size */ mb_put_uint16le(mbp, 0); /* reserved */ mb_put_uint32le(mbp, 0); /* reserved */ mb_put_uint64le(mbp, fid->fid_persistent); mb_put_uint64le(mbp, fid->fid_volatile); rqp->sr_flags |= SMBR_NORECONNECT; error = smb2_rq_simple(rqp); smb_rq_done(rqp); return (error); } /* * Set file info via an open handle. * Caller provides payload, info level. */ static int smbfs_smb2_set_info(struct smb_share *ssp, smb2fid_t *fid, struct mbchain *info_mbp, uint8_t type, uint8_t level, uint32_t addl_info, struct smb_cred *scrp) { struct smb_rq *rqp = NULL; struct mbchain *mbp; uint32_t *buffer_lenp; int base, len; int error; error = smb_rq_alloc(SSTOCP(ssp), SMB2_SET_INFO, scrp, &rqp); if (error) goto out; /* * Build the SMB 2 Set Info req. */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 33); // struct size mb_put_uint8(mbp, type); mb_put_uint8(mbp, level); buffer_lenp = mb_reserve(mbp, sizeof (uint32_t)); mb_put_uint16le(mbp, SMB2_HDRLEN + 32); // Buffer Offset mb_put_uint16le(mbp, 0); // Reserved mb_put_uint32le(mbp, addl_info); // Additional Info mb_put_uint64le(mbp, fid->fid_persistent); mb_put_uint64le(mbp, fid->fid_volatile); /* * Now the payload */ base = mbp->mb_count; error = mb_put_mbchain(mbp, info_mbp); if (error) goto out; len = mbp->mb_count - base; *buffer_lenp = htolel(len); if (error) goto out; /* * Run the request. * Don't care about the (empty) reply. */ error = smb2_rq_simple(rqp); out: smb_rq_done(rqp); return (error); } int smbfs_smb2_seteof(struct smb_share *ssp, smb2fid_t *fid, uint64_t newsize, struct smb_cred *scrp) { struct mbchain data_mb, *mbp = &data_mb; uint8_t level = FileEndOfFileInformation; int error; mb_init(mbp); mb_put_uint64le(mbp, newsize); error = smbfs_smb2_set_info(ssp, fid, mbp, SMB2_0_INFO_FILE, level, 0, scrp); mb_done(mbp); return (error); } int smbfs_smb2_setdisp(struct smb_share *ssp, smb2fid_t *fid, uint8_t newdisp, struct smb_cred *scrp) { struct mbchain data_mb, *mbp = &data_mb; uint8_t level = FileDispositionInformation; int error; mb_init(mbp); mb_put_uint8(mbp, newdisp); error = smbfs_smb2_set_info(ssp, fid, mbp, SMB2_0_INFO_FILE, level, 0, scrp); mb_done(mbp); return (error); } /* * Set FileBasicInformation on an open handle * Caller builds the mbchain. */ int smbfs_smb2_setfattr(struct smb_share *ssp, smb2fid_t *fid, struct mbchain *mbp, struct smb_cred *scrp) { uint8_t level = FileBasicInformation; int error; error = smbfs_smb2_set_info(ssp, fid, mbp, SMB2_0_INFO_FILE, level, 0, scrp); return (error); } /* * Build a FileRenameInformation and call setinfo */ int smbfs_smb2_rename(struct smbnode *np, struct smbnode *tdnp, const char *tname, int tnlen, int overwrite, smb2fid_t *fid, struct smb_cred *scrp) { struct smb_share *ssp = np->n_mount->smi_share; struct mbchain data_mb, *mbp = &data_mb; uint32_t *name_lenp; uint8_t level = FileRenameInformation; int base, len; int error; mb_init(mbp); mb_put_uint32le(mbp, (overwrite & 1)); mb_put_uint32le(mbp, 0); // reserved mb_put_uint64le(mbp, 0); // Root Dir name_lenp = mb_reserve(mbp, 4); /* Target name (full path) */ base = mbp->mb_count; if (tnlen > 0) { error = smbfs_fullpath(mbp, SSTOVC(ssp), tdnp, tname, tnlen, '\\'); if (error) goto out; } len = mbp->mb_count - base; *name_lenp = htolel(len); error = smbfs_smb2_set_info(ssp, fid, mbp, SMB2_0_INFO_FILE, level, 0, scrp); out: mb_done(mbp); return (error); } /* * Later servers have maxtransact at a megabyte or more, * but we don't want to buffer up that much data, so use * the lesser of that or 64k. */ #define SMBFS_QDIR_MAX_BUF (1<<16) /* * SMB2 query directory */ static int smbfs_smb2_qdir(struct smbfs_fctx *ctx) { smb_fh_t *fhp = ctx->f_fhp; smb_share_t *ssp = ctx->f_ssp; smb_vc_t *vcp = SSTOVC(ssp); struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; uint16_t *name_lenp; uint8_t level, flags; uint16_t ssize = 0; uint16_t obuf_off = 0; uint32_t obuf_len = 0; uint32_t obuf_req; int error; level = (uint8_t)ctx->f_infolevel; flags = 0; if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) flags |= SMB2_QDIR_FLAG_SINGLE; if (ctx->f_flags & SMBFS_RDD_FINDFIRST) ctx->f_rkey = 0; else flags |= SMB2_QDIR_FLAG_INDEX; obuf_req = SMBFS_QDIR_MAX_BUF; if (obuf_req > vcp->vc_sopt.sv2_maxtransact) obuf_req = vcp->vc_sopt.sv2_maxtransact; if (ctx->f_rq) { smb_rq_done(ctx->f_rq); ctx->f_rq = NULL; } error = smb_rq_alloc(SSTOCP(ctx->f_ssp), SMB2_QUERY_DIRECTORY, ctx->f_scred, &rqp); if (error) return (error); ctx->f_rq = rqp; /* * Build an SMB2 Query Dir req. */ smb_rq_getrequest(rqp, &mbp); mb_put_uint16le(mbp, 33); /* Struct size */ mb_put_uint8(mbp, level); mb_put_uint8(mbp, flags); mb_put_uint32le(mbp, ctx->f_rkey); /* FileIndex */ mb_put_uint64le(mbp, fhp->fh_fid2.fid_persistent); mb_put_uint64le(mbp, fhp->fh_fid2.fid_volatile); mb_put_uint16le(mbp, 96); name_lenp = mb_reserve(mbp, sizeof (uint16_t)); /* FileNameLen */ mb_put_uint32le(mbp, obuf_req); /* Output Buf Len */ /* Add in the name if any */ if (ctx->f_wclen > 0) { int base, len; /* Put the match pattern. */ base = mbp->mb_count; error = smb_put_dmem(mbp, vcp, ctx->f_wildcard, ctx->f_wclen, SMB_CS_NONE, NULL); if (error) return (error); /* Update the FileNameLen */ len = mbp->mb_count - base; *name_lenp = htoles(len); } else { /* Empty string */ mb_put_uint16le(mbp, 0); *name_lenp = 0; } error = smb2_rq_simple(rqp); if (error != 0) goto out; /* * Parse the SMB2 Query Dir response */ smb_rq_getreply(rqp, &mdp); /* Check structure size is 9 */ md_get_uint16le(mdp, &ssize); if (ssize != 9) { error = EBADRPC; goto out; } /* Get output buffer offset, length */ md_get_uint16le(mdp, &obuf_off); md_get_uint32le(mdp, &obuf_len); /* * After read at EOF we'll have just one word: * NextEntryOffset == 0 Allow some padding. */ if (obuf_len < 8) { error = ENOENT; goto out; } /* * If this reply is shorter than requested by 1k * or more, we must have reached EOF. */ if ((obuf_len + 1024) < obuf_req) ctx->f_flags |= SMBFS_RDD_EOF; /* * Have data. Put the payload in ctx->f_mdchain * Current offset is SMB2_HDRLEN + 8. */ { mblk_t *m = NULL; int skip = (int)obuf_off - (SMB2_HDRLEN + 8); if (skip < 0) { error = EBADRPC; goto out; } if (skip > 0) { md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } error = md_get_mbuf(mdp, obuf_len, &m); if (error) goto out; md_done(&ctx->f_mdchain); md_initm(&ctx->f_mdchain, m); } /* * SMB2 Query Directory does not provie an EntryCount. * Instead, we'll advance f_eofs (entry offset) * through the range [0..f_left] */ ctx->f_left = obuf_len; ctx->f_eofs = 0; return (0); out: if (error != 0) { /* * Failed parsing the FindFirst or FindNext response. * Force this directory listing closed, otherwise the * calling process may hang in an infinite loop. */ ctx->f_left = 0; ctx->f_eofs = 0; ctx->f_flags |= SMBFS_RDD_EOF; } return (error); } int smbfs_smb2_findopen(struct smbfs_fctx *ctx, struct smbnode *dnp, const char *wildcard, int wclen, uint32_t attr) { smb_fh_t *fhp = NULL; uint32_t rights = STD_RIGHT_READ_CONTROL_ACCESS | SA_RIGHT_FILE_READ_ATTRIBUTES | SA_RIGHT_FILE_READ_DATA; int error; /* * Set f_type no matter what, so cleanup will call * smbfs_smb2_findclose, error or not. */ ctx->f_type = ft_SMB2; ASSERT(ctx->f_dnp == dnp); /* * Get a file handle on the directory */ error = smb_fh_create(ctx->f_ssp, &fhp); if (error != 0) goto errout; error = smbfs_smb_ntcreatex(dnp, NULL, 0, 0, /* name nmlen xattr */ rights, SMB_EFA_NORMAL, NTCREATEX_SHARE_ACCESS_ALL, NTCREATEX_DISP_OPEN, 0, /* create options */ ctx->f_scred, fhp, NULL, NULL); /* cr_act_p fa_p */ if (error != 0) goto errout; fhp->fh_rights = rights; smb_fh_opened(fhp); ctx->f_fhp = fhp; ctx->f_namesz = SMB_MAXFNAMELEN + 1; ctx->f_name = kmem_alloc(ctx->f_namesz, KM_SLEEP); ctx->f_infolevel = FileFullDirectoryInformation; ctx->f_attrmask = attr; ctx->f_wildcard = wildcard; ctx->f_wclen = wclen; return (0); errout: if (fhp != NULL) smb_fh_rele(fhp); return (error); } int smbfs_smb2_findclose(struct smbfs_fctx *ctx) { smb_fh_t *fhp = NULL; if ((fhp = ctx->f_fhp) != NULL) { ctx->f_fhp = NULL; smb_fh_rele(fhp); } if (ctx->f_name) kmem_free(ctx->f_name, ctx->f_namesz); if (ctx->f_rq) smb_rq_done(ctx->f_rq); md_done(&ctx->f_mdchain); return (0); } /* * Get a buffer of directory entries (if we don't already have * some remaining in the current buffer) then decode one. */ int smbfs_smb2_findnext(struct smbfs_fctx *ctx, uint16_t limit) { int error; /* * If we've scanned to the end of the current buffer * try to read anohther buffer of dir entries. * Treat anything less than 8 bytes as an "empty" * buffer to ensure we can read something. * (There may be up to 8 bytes of padding.) */ if ((ctx->f_eofs + 8) > ctx->f_left) { /* Scanned the whole buffer. */ if (ctx->f_flags & SMBFS_RDD_EOF) return (ENOENT); ctx->f_limit = limit; error = smbfs_smb2_qdir(ctx); if (error) return (error); ctx->f_otws++; } /* * Decode one entry */ error = smbfs_decode_dirent(ctx); return (error); } /* * Helper for smbfs_xa_get_streaminfo * Query stream info */ int smbfs_smb2_get_streaminfo(smbnode_t *np, struct mdchain *mdp, struct smb_cred *scrp) { smb_share_t *ssp = np->n_mount->smi_share; smb_fh_t *fhp = NULL; uint32_t rights = STD_RIGHT_READ_CONTROL_ACCESS | SA_RIGHT_FILE_READ_ATTRIBUTES; uint32_t iolen = INT16_MAX; int error; /* * Get a file handle on the object * with read attr. rights. */ error = smb_fh_create(ssp, &fhp); if (error != 0) goto out; error = smbfs_smb_ntcreatex(np, NULL, 0, 0, /* name nmlen xattr */ rights, SMB_EFA_NORMAL, NTCREATEX_SHARE_ACCESS_ALL, NTCREATEX_DISP_OPEN, 0, /* create options */ scrp, fhp, NULL, NULL); if (error != 0) goto out; smb_fh_opened(fhp); /* * Query stream info */ error = smbfs_smb2_query_info(ssp, &fhp->fh_fid2, mdp, &iolen, SMB2_0_INFO_FILE, FileStreamInformation, 0, scrp); out: if (fhp != NULL) smb_fh_rele(fhp); return (error); } /* * OTW function to Get a security descriptor (SD). * * The *reslen param is bufsize(in) / length(out) * Note: On success, this fills in mdp->md_top, * which the caller should free. */ int smbfs_smb2_getsec(struct smb_share *ssp, smb2fid_t *fid, uint32_t selector, mblk_t **res, uint32_t *reslen, struct smb_cred *scrp) { struct mdchain info_mdc, *mdp = &info_mdc; int error; bzero(mdp, sizeof (*mdp)); error = smbfs_smb2_query_info(ssp, fid, mdp, reslen, SMB2_0_INFO_SECURITY, 0, selector, scrp); if (error) goto out; if (mdp->md_top == NULL) { error = EBADRPC; goto out; } *res = mdp->md_top; mdp->md_top = NULL; out: md_done(mdp); return (error); } /* * OTW function to Set a security descriptor (SD). * Caller data are carried in an mbchain_t. * * Note: This normally consumes mbp->mb_top, and clears * that pointer when it does. */ int smbfs_smb2_setsec(struct smb_share *ssp, smb2fid_t *fid, uint32_t selector, mblk_t **mp, struct smb_cred *scrp) { struct mbchain info_mbp, *mbp = &info_mbp; int error; ASSERT(*mp != NULL); mb_initm(mbp, *mp); *mp = NULL; /* consumed */ error = smbfs_smb2_set_info(ssp, fid, mbp, SMB2_0_INFO_SECURITY, 0, selector, scrp); mb_done(mbp); return (error); }