/* * 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: smbfs_smb.c,v 1.73 2005/03/18 02:11:03 lindak Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Lack of inode numbers leads us to the problem of generating them. * Partially this problem can be solved by having a dir/file cache * with inode numbers generated from the incremented by one counter. * However this way will require too much kernel memory, gives all * sorts of locking and consistency problems, not to mentinon counter overflows. * So, I'm decided to use a hash function to generate pseudo random (and unique) * inode numbers. */ static long smbfs_getino(struct smbnode *dnp, const char *name, int nmlen) { u_int32_t ino; ino = dnp->n_ino + smbfs_hash(name, nmlen); if (ino <= 2) ino += 3; return ino; } static int smbfs_smb_lockandx(struct smbnode *np, int op, u_int32_t pid, off_t start, u_int64_t len, int largelock, struct smb_cred *scrp, u_int32_t timeout) { struct smb_share *ssp = np->n_mount->sm_share; struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; u_char ltype = 0; int error; if (op == SMB_LOCK_SHARED) ltype |= SMB_LOCKING_ANDX_SHARED_LOCK; if (largelock) ltype |= SMB_LOCKING_ANDX_LARGE_FILES; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_LOCKING_ANDX, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); mb_put_mem(mbp, (caddr_t)&np->n_fid, 2, MB_MSYSTEM); mb_put_uint8(mbp, ltype); /* locktype */ mb_put_uint8(mbp, 0); /* oplocklevel - 0 seems is NO_OPLOCK */ mb_put_uint32le(mbp, timeout); /* 0 nowait, -1 infinite wait */ mb_put_uint16le(mbp, op == SMB_LOCK_RELEASE ? 1 : 0); mb_put_uint16le(mbp, op == SMB_LOCK_RELEASE ? 0 : 1); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint16le(mbp, pid); if (!largelock) { mb_put_uint32le(mbp, start); mb_put_uint32le(mbp, len); } else { mb_put_uint16le(mbp, 0); /* pad */ mb_put_uint32le(mbp, start >> 32); /* OffsetHigh */ mb_put_uint32le(mbp, start & 0xffffffff); /* OffsetLow */ mb_put_uint32le(mbp, len >> 32); /* LengthHigh */ mb_put_uint32le(mbp, len & 0xffffffff); /* LengthLow */ } smb_rq_bend(rqp); error = smb_rq_simple(rqp); smb_rq_done(rqp); return error; } int smbfs_smb_lock(struct smbnode *np, int op, caddr_t id, off_t start, u_int64_t len, int largelock, struct smb_cred *scrp, u_int32_t timeout) { struct smb_share *ssp = np->n_mount->sm_share; if (SMB_DIALECT(SSTOVC(ssp)) < SMB_DIALECT_LANMAN1_0) /* * TODO: use LOCK_BYTE_RANGE here. */ return EINVAL; else return smbfs_smb_lockandx(np, op, (u_int32_t)id, start, len, largelock, scrp, timeout); } int smbfs_smb_qpathinfo(struct smbnode *np, struct smbfattr *fap, struct smb_cred *scrp, short infolevel) { struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct smb_t2rq *t2p; int error, svtz, timesok = 1; struct mbchain *mbp; struct mdchain *mdp; u_int16_t date, time, wattr; u_int64_t llint; u_int32_t size, dattr; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_QUERY_PATH_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); if (!infolevel) { if (SMB_DIALECT(vcp) < SMB_DIALECT_NTLM0_12) infolevel = SMB_QFILEINFO_STANDARD; else infolevel = SMB_QFILEINFO_BASIC_INFO; } mb_put_uint16le(mbp, infolevel); mb_put_uint32le(mbp, 0); /* mb_put_uint8(mbp, SMB_DT_ASCII); specs are wrong */ error = smbfs_fullpath(mbp, vcp, np, NULL, NULL, '\\'); if (error) { smb_t2_done(t2p); return error; } t2p->t2_maxpcount = 2; t2p->t2_maxdcount = vcp->vc_txmax; error = smb_t2_request(t2p); if (error) { smb_t2_done(t2p); if (infolevel == SMB_QFILEINFO_BASIC_INFO && error == EINVAL) return smbfs_smb_qpathinfo(np, fap, scrp, SMB_QFILEINFO_STANDARD); return error; } mdp = &t2p->t2_rdata; svtz = vcp->vc_sopt.sv_tz; switch (infolevel) { case SMB_QFILEINFO_STANDARD: timesok = 0; md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, NULL); /* creation time */ md_get_uint16le(mdp, &date); md_get_uint16le(mdp, &time); /* access time */ if (date || time) { timesok++; smb_dos2unixtime(date, time, 0, svtz, &fap->fa_atime); } md_get_uint16le(mdp, &date); md_get_uint16le(mdp, &time); /* modify time */ if (date || time) { timesok++; smb_dos2unixtime(date, time, 0, svtz, &fap->fa_mtime); } md_get_uint32le(mdp, &size); fap->fa_size = size; md_get_uint32(mdp, NULL); /* allocation size */ md_get_uint16le(mdp, &wattr); fap->fa_attr = wattr; break; case SMB_QFILEINFO_BASIC_INFO: timesok = 0; md_get_uint64(mdp, NULL); /* creation time */ md_get_uint64le(mdp, &llint); if (llint) { timesok++; smb_time_NT2local(llint, svtz, &fap->fa_atime); } md_get_uint64le(mdp, &llint); if (llint) { timesok++; smb_time_NT2local(llint, svtz, &fap->fa_mtime); } md_get_uint64le(mdp, &llint); if (llint) { timesok++; smb_time_NT2local(llint, svtz, &fap->fa_ctime); } md_get_uint32le(mdp, &dattr); fap->fa_attr = dattr; /* 4 byte pad may or may not be here (specs and servers vary) */ /* XXX could use ALL_INFO to get size */ break; default: SMBERROR("unexpected info level %d\n", infolevel); error = EINVAL; } smb_t2_done(t2p); /* * if all times are zero (observed with FAT on NT4SP6) * then fall back to older info level */ if (!timesok) { if (infolevel == SMB_QFILEINFO_BASIC_INFO) return smbfs_smb_qpathinfo(np, fap, scrp, SMB_QFILEINFO_STANDARD); error = EINVAL; } return error; } static char * sfm2xattr(char *sfm) { if (!strncasecmp(sfm, SFM_RESOURCEFORK_NAME, sizeof(SFM_RESOURCEFORK_NAME))) return (XATTR_RESOURCEFORK_NAME); if (!strncasecmp(sfm, SFM_FINDERINFO_NAME, sizeof(SFM_FINDERINFO_NAME))) return (XATTR_FINDERINFO_NAME); return (NULL); } static int smbfs_smb_undollardata(struct smbnode *np, struct smbfs_fctx *ctx) { char *cp; int len = strlen(SMB_DATASTREAM); if (!ctx->f_name) /* sanity check */ goto bad; if (ctx->f_nmlen < len + 1) /* "::$DATA" at a minimum */ goto bad; if (*ctx->f_name != ':') /* leading colon - "always" */ goto bad; cp = &ctx->f_name[ctx->f_nmlen - len]; /* point to 2nd colon */ if (bcmp(cp, SMB_DATASTREAM, len)) goto bad; if (ctx->f_nmlen == len + 1) /* merely the data fork? */ return (0); /* skip it */ /* * XXX here we should be calling KPI to validate the stream name */ if (ctx->f_nmlen >= 18 && !bcmp(ctx->f_name, ":com.apple.system.", 18)) return (0); /* skip protected system attrs */ if (ctx->f_nmlen - len > XATTR_MAXNAMELEN + 1) goto bad; /* mustnt return more than 128 bytes */ /* * Un-count a colon and the $DATA, then the * 2nd colon is replaced by a terminating null. */ ctx->f_nmlen -= len; *cp = '\0'; return (1); bad: SMBERROR("file \"%.*s\" has bad stream \"%.*s\"\n", np->n_nmlen, np->n_name, ctx->f_nmlen, ctx->f_name); return (0); /* skip it */ } PRIVSYM int smbfs_smb_qstreaminfo(struct smbnode *np, struct smb_cred *scrp, uio_t uio, size_t *sizep) { struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct smb_t2rq *t2p; int error; struct mbchain *mbp; struct mdchain *mdp; u_int32_t next, nlen, used; struct smbfs_fctx ctx; *sizep = 0; ctx.f_ssp = ssp; ctx.f_name = NULL; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_QUERY_PATH_INFORMATION, scrp, &t2p); if (error) return (error); mbp = &t2p->t2_tparam; mb_init(mbp); /* * SMB_QFILEINFO_STREAM_INFORMATION is an option to consider * here. Samba declined to support the older info level with * a comment claiming doing so caused a BSOD. */ mb_put_uint16le(mbp, SMB_QFILEINFO_STREAM_INFO); mb_put_uint32le(mbp, 0); /* mb_put_uint8(mbp, SMB_DT_ASCII); specs are wrong */ error = smbfs_fullpath(mbp, vcp, np, NULL, NULL, '\\'); if (error) goto out; t2p->t2_maxpcount = 2; t2p->t2_maxdcount = vcp->vc_txmax; error = smb_t2_request(t2p); if (error) { if (smb_t2_err(t2p) == NT_STATUS_INVALID_PARAMETER) error = ENOTSUP; goto out; } mdp = &t2p->t2_rdata; /* * On a directory Windoze is likely to return a zero data count. * Check for that now to avoid EBADRPC from md_get_uint32le */ if (mdp->md_cur == NULL) goto out; do { if ((error = md_get_uint32le(mdp, &next))) goto out; if ((error = md_get_uint32le(mdp, &nlen))) /* name length */ goto out; if ((error = md_get_uint64le(mdp, NULL))) /* stream size */ goto out; if ((error = md_get_uint64le(mdp, NULL))) /* allocated size */ goto out; /* * Sanity check to limit DoS or buffer overrun attempts. * The arbitrary 16384 is sufficient for all legit packets. */ if (nlen > 16384) { SMBERROR("huge name length in packet!\n"); error = EBADRPC; goto out; } ctx.f_name = malloc(nlen, M_SMBFSDATA, M_WAITOK); if ((error = md_get_mem(mdp, ctx.f_name, nlen, MB_MSYSTEM))) goto out; /* * skip pad bytes and/or tail of overlong name */ used = 4 + 4 + 8 + 8 + nlen; if (next && next > used) { if (next - used > 16384) { SMBERROR("huge offset in packet!\n"); error = EBADRPC; goto out; } md_get_mem(mdp, NULL, next - used, MB_MSYSTEM); } /* ignore a trailing null, not that we expect them */ if (SMB_UNICODE_STRINGS(vcp)) { if (nlen > 1 && !ctx.f_name[nlen - 1] && !ctx.f_name[nlen - 2]) nlen -= 2; } else { if (nlen && !ctx.f_name[nlen - 1]) nlen -= 1; } ctx.f_nmlen = nlen; smbfs_fname_tolocal(&ctx); /* converts from UCS2LE */ /* * We should now have a name in the form * : :$DATA * Where is UTF-8 w/o null termination * If it isn't in that form we want to LOG it and skip it. * Note we want to skip w/o logging the "data fork" entry, * which is simply ::$DATA * Otherwise we want to uiomove out with a null added. */ if (smbfs_smb_undollardata(np, &ctx)) { char *s; /* the "+ 1" skips over the leading colon */ s = sfm2xattr(ctx.f_name + 1); #ifndef DUAL_EAS /* XXX */ /* * In Tiger Carbon still accesses dot-underscore files directly, so... * For Tiger we preserve the SFM/Thursby AFP_* stream names rather * than mapping them to com.apple.*. This means our copy engines * will preserve SFM/Thursby resource-fork and finder-info. */ s = NULL; #endif if (s) ctx.f_nmlen = strlen(s) + 1; else s = ctx.f_name + 1; if (uio) uiomove(s, ctx.f_nmlen, uio); else *sizep += ctx.f_nmlen; } free(ctx.f_name, M_SMBFSDATA); ctx.f_name = NULL; } while (next && !error); out: if (ctx.f_name) free(ctx.f_name, M_SMBFSDATA); smb_t2_done(t2p); return (error); } int smbfs_smb_qfsattr(struct smb_share *ssp, u_int32_t *attrp, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct mbchain *mbp; struct mdchain *mdp; u_int32_t nlen; int error; u_int8_t *fs_name; /* will malloc whatever the size is */ u_int8_t UCfat_name[6] = {'F', 0, 'A', 0, 'T', 0}; /* unicode */ u_int8_t ASCfat_name[3] = {'F', 'A', 'T'}; /* ascii */ error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_QUERY_FS_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_uint16le(mbp, SMB_QFS_ATTRIBUTE_INFO); t2p->t2_maxpcount = 4; t2p->t2_maxdcount = 4 * 3 + 512; error = smb_t2_request(t2p); if (error) { smb_t2_done(t2p); return error; } mdp = &t2p->t2_rdata; md_get_uint32le(mdp, attrp); md_get_uint32le(mdp, NULL); /* max filename length */ md_get_uint32le(mdp, &nlen); /* fs name length */ fs_name = malloc(nlen, M_SMBFSDATA, M_WAITOK); md_get_mem(mdp, fs_name, nlen, MB_MSYSTEM); /* fs name, w/o null */ /* * If fs_name starts with FAT they probably require resume keys. * This is another example of the client trying to fix a server * bug. This change (PR-4051871) is using the logic created by * PR-3983209. See the long block comment in * smbfs_smb_findnextLM2. */ if ((nlen >= 6 && !bcmp(fs_name, UCfat_name, 6)) || (nlen >= 3 && !bcmp(fs_name, ASCfat_name, 3))) { ssp->ss_flags |= SMBS_RESUMEKEYS; SMBERROR("FAT share; using resume keys\n"); } free(fs_name, M_SMBFSDATA); smb_t2_done(t2p); return 0; } int smbfs_smb_statfs2(struct smb_share *ssp, struct statfs *sbp, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct mbchain *mbp; struct mdchain *mdp; u_int16_t bsize; u_int32_t units, bpu, funits; u_int64_t s, t, f; int error; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_QUERY_FS_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_uint16le(mbp, SMB_QFS_ALLOCATION); t2p->t2_maxpcount = 4; t2p->t2_maxdcount = 4 * 4 + 2; error = smb_t2_request(t2p); if (error) { smb_t2_done(t2p); return error; } mdp = &t2p->t2_rdata; md_get_uint32(mdp, NULL); /* fs id */ md_get_uint32le(mdp, &bpu); md_get_uint32le(mdp, &units); md_get_uint32le(mdp, &funits); md_get_uint16le(mdp, &bsize); s = bsize; s *= bpu; t = units; f = funits; /* * Don't allow over-large blocksizes as they determine * Finder List-view size granularities. On the other * hand, we mustn't let the block count overflow the * 31 bits available. */ while (s > 16 * 1024) { if (t > LONG_MAX) break; s /= 2; t *= 2; f *= 2; } while (t > LONG_MAX) { t /= 2; f /= 2; s *= 2; } sbp->f_bsize = s; /* fundamental file system block size */ sbp->f_blocks= t; /* total data blocks in file system */ sbp->f_bfree = f; /* free blocks in fs */ sbp->f_bavail= f; /* free blocks avail to non-superuser */ sbp->f_files = (-1); /* total file nodes in file system */ sbp->f_ffree = (-1); /* free file nodes in fs */ smb_t2_done(t2p); return 0; } int smbfs_smb_statfs(struct smb_share *ssp, struct statfs *sbp, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct mdchain *mdp; u_int16_t units, bpu, bsize, funits; u_int64_t s, t, f; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_QUERY_INFORMATION_DISK, scrp); if (error) return error; smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (error) { smb_rq_done(rqp); return error; } smb_rq_getreply(rqp, &mdp); md_get_uint16le(mdp, &units); md_get_uint16le(mdp, &bpu); md_get_uint16le(mdp, &bsize); md_get_uint16le(mdp, &funits); s = bsize; s *= bpu; t = units; f = funits; /* * Don't allow over-large blocksizes as they determine * Finder List-view size granularities. On the other * hand, we mustn't let the block count overflow the * 31 bits available. */ while (s > 16 * 1024) { if (t > LONG_MAX) break; s /= 2; t *= 2; f *= 2; } while (t > LONG_MAX) { t /= 2; f /= 2; s *= 2; } sbp->f_bsize = s; /* fundamental file system block size */ sbp->f_blocks= t; /* total data blocks in file system */ sbp->f_bfree = f; /* free blocks in fs */ sbp->f_bavail= f; /* free blocks avail to non-superuser */ sbp->f_files = (-1); /* total file nodes in file system */ sbp->f_ffree = (-1); /* free file nodes in fs */ smb_rq_done(rqp); return 0; } int smbfs_smb_seteof(struct smb_share *ssp, u_int16_t fid, u_int64_t newsize, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; int error; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_SET_FILE_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&fid, 2, MB_MSYSTEM); if (vcp->vc_sopt.sv_caps & SMB_CAP_INFOLEVEL_PASSTHRU) mb_put_uint16le(mbp, SMB_SFILEINFO_END_OF_FILE_INFORMATION); else mb_put_uint16le(mbp, SMB_SFILEINFO_END_OF_FILE_INFO); mb_put_uint32le(mbp, 0); /* XXX should be 16 not 32(?) */ mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint64le(mbp, newsize); mb_put_uint32le(mbp, 0); /* padding */ mb_put_uint16le(mbp, 0); t2p->t2_maxpcount = 2; t2p->t2_maxdcount = 0; error = smb_t2_request(t2p); smb_t2_done(t2p); return error; } int smbfs_smb_t2rename(struct smbnode *np, struct smbnode *tdnp, const char *tname, int tnmlen, struct smb_cred *scrp, int overwrite) { struct smb_t2rq *t2p; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; int32_t len, *ucslenp; int error, cerror; u_int16_t fid = 0; char convbuf[1024]; if (!(vcp->vc_sopt.sv_caps & SMB_CAP_INFOLEVEL_PASSTHRU)) return (ENOTSUP); error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_SET_FILE_INFORMATION, scrp, &t2p); if (error) return error; if (tdnp) { error = smbfs_smb_tmpopen(tdnp, SA_RIGHT_FILE_READ_DATA, scrp, &fid); if (error) goto exit; } mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&np->n_fid, 2, MB_MSYSTEM); mb_put_uint16le(mbp, SMB_SFILEINFO_RENAME_INFORMATION); mb_put_uint16le(mbp, 0); /* reserved, nowadays */ mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint32le(mbp, overwrite); mb_put_mem(mbp, (caddr_t)&fid, 2, MB_MSYSTEM); /* base for tname */ mb_put_uint16le(mbp, 0); /* part of a 32bit fid? */ ucslenp = (int32_t *)mb_reserve(mbp, sizeof(int32_t)); len = smb_strtouni((u_int16_t *)convbuf, tname, tnmlen, UTF_PRECOMPOSED|UTF_NO_NULL_TERM); *ucslenp = htolel(len); error = mb_put_mem(mbp, convbuf, len, MB_MSYSTEM); if (error) goto exit; error = mb_put_uint16le(mbp, 0); if (error) goto exit; t2p->t2_maxpcount = 2; t2p->t2_maxdcount = 0; error = smb_t2_request(t2p); exit:; if (fid) { cerror = smbfs_smb_tmpclose(tdnp, fid, scrp); if (cerror) SMBERROR("error %d closing fid %d\n", cerror, fid); } smb_t2_done(t2p); return (error); } int smbfs_smb_flush(struct smbnode *np, struct smb_cred *scrp) { struct smb_share *ssp = np->n_mount->sm_share; struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; int error; if (!(np->n_flag & NFLUSHWIRE)) return (0); if (np->n_fidrefs <= 0 || !SMBTOV(np) || !vnode_isreg(SMBTOV(np))) return 0; /* not a regular open file */ error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_FLUSH, scrp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&np->n_fid, 2, MB_MSYSTEM); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); smb_rq_done(rqp); if (!error) np->n_flag &= ~NFLUSHWIRE; return (error); } int smbfs_smb_setfsize(struct smbnode *np, u_int16_t fid, u_int64_t newsize, struct smb_cred *scrp) { struct smb_share *ssp = np->n_mount->sm_share; struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; int error; if (!smbfs_smb_seteof(ssp, fid, newsize, scrp)) { np->n_flag |= (NFLUSHWIRE | NATTRCHANGED); return (0); } if (newsize > UINT32_MAX) return (EFBIG); error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_WRITE, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&fid, 2, MB_MSYSTEM); mb_put_uint16le(mbp, 0); mb_put_uint32le(mbp, newsize); mb_put_uint16le(mbp, 0); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_DATA); mb_put_uint16le(mbp, 0); smb_rq_bend(rqp); error = smb_rq_simple(rqp); smb_rq_done(rqp); np->n_flag |= (NFLUSHWIRE | NATTRCHANGED); return error; } int smbfs_smb_query_info(struct smbnode *np, const char *name, int len, struct smbfattr *fap, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct mbchain *mbp; struct mdchain *mdp; u_int8_t wc; int error; u_int16_t wattr; u_int32_t lint; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_QUERY_INFORMATION, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); do { error = smbfs_fullpath(mbp, SSTOVC(ssp), np, name, &len, '\\'); if (error) break; smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); if (md_get_uint8(mdp, &wc) != 0 || wc != 10) { error = EBADRPC; break; } md_get_uint16le(mdp, &wattr); fap->fa_attr = wattr; /* * Be careful using the time returned here, as * with FAT on NT4SP6, at least, the time returned is low * 32 bits of 100s of nanoseconds (since 1601) so it rolls * over about every seven minutes! */ md_get_uint32le(mdp, &lint); /* specs: secs since 1970 */ if (lint) /* avoid bogus zero returns */ smb_time_server2local(lint, SSTOVC(ssp)->vc_sopt.sv_tz, &fap->fa_mtime); md_get_uint32le(mdp, &lint); fap->fa_size = lint; } while(0); smb_rq_done(rqp); return error; } /* * Set DOS file attributes. mtime should be NULL for dialects above lm10 */ int smbfs_smb_setpattr(struct smbnode *np, const char *name, int len, u_int16_t attr, struct timespec *mtime, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct mbchain *mbp; long time; int error, svtz; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_SET_INFORMATION, scrp); if (error) return error; svtz = SSTOVC(ssp)->vc_sopt.sv_tz; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, attr); if (mtime) { smb_time_local2server(mtime, svtz, &time); } else time = 0; mb_put_uint32le(mbp, time); /* mtime */ mb_put_mem(mbp, NULL, 5 * 2, MB_MZERO); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); do { error = smbfs_fullpath(mbp, SSTOVC(ssp), np, name, &len, '\\'); if (error) break; mb_put_uint8(mbp, SMB_DT_ASCII); if (SMB_UNICODE_STRINGS(SSTOVC(ssp))) { mb_put_padbyte(mbp); mb_put_uint8(mbp, 0); /* 1st byte of NULL Unicode char */ } mb_put_uint8(mbp, 0); smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (error) break; } while(0); smb_rq_done(rqp); return error; } int smbfs_smb_hideit(struct smbnode *np, const char *name, int len, struct smb_cred *scrp) { struct smbfattr fa; int error; u_int16_t attr; error = smbfs_smb_query_info(np, name, len, &fa, scrp); attr = fa.fa_attr; if (!error && !(attr & SMB_FA_HIDDEN)) { attr |= SMB_FA_HIDDEN; error = smbfs_smb_setpattr(np, name, len, attr, NULL, scrp); } return (error); } int smbfs_smb_unhideit(struct smbnode *np, const char *name, int len, struct smb_cred *scrp) { struct smbfattr fa; u_int16_t attr; int error; error = smbfs_smb_query_info(np, name, len, &fa, scrp); attr = fa.fa_attr; if (!error && (attr & SMB_FA_HIDDEN)) { attr &= ~SMB_FA_HIDDEN; error = smbfs_smb_setpattr(np, name, len, attr, NULL, scrp); } return (error); } /* * Note, win95 doesn't support this call. */ int smbfs_smb_setptime2(struct smbnode *np, struct timespec *mtime, struct timespec *atime, int attr, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; u_int16_t date, time; int error, tzoff; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_SET_PATH_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_uint16le(mbp, SMB_SFILEINFO_STANDARD); mb_put_uint32le(mbp, 0); /* MBZ */ /* mb_put_uint8(mbp, SMB_DT_ASCII); specs incorrect */ error = smbfs_fullpath(mbp, vcp, np, NULL, NULL, '\\'); if (error) { smb_t2_done(t2p); return error; } tzoff = vcp->vc_sopt.sv_tz; mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint32le(mbp, 0); /* creation time */ if (atime) smb_time_unix2dos(atime, tzoff, &date, &time, NULL); else time = date = 0; mb_put_uint16le(mbp, date); mb_put_uint16le(mbp, time); if (mtime) smb_time_unix2dos(mtime, tzoff, &date, &time, NULL); else time = date = 0; mb_put_uint16le(mbp, date); mb_put_uint16le(mbp, time); mb_put_uint32le(mbp, 0); /* file size */ mb_put_uint32le(mbp, 0); /* allocation unit size */ mb_put_uint16le(mbp, attr); /* DOS attr */ mb_put_uint32le(mbp, 0); /* EA size */ t2p->t2_maxpcount = 5 * 2; t2p->t2_maxdcount = vcp->vc_txmax; error = smb_t2_request(t2p); smb_t2_done(t2p); return error; } /* * *BASIC_INFO works with Samba, but Win2K servers say it is an * invalid information level on a SET_PATH_INFO. Note Win2K does * support *BASIC_INFO on a SET_FILE_INFO, and they support the * equivalent *BASIC_INFORMATION on SET_PATH_INFO. Go figure. */ int smbfs_smb_setpattrNT(struct smbnode *np, u_int32_t attr, struct timespec *mtime, struct timespec *atime, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; u_int64_t tm; int error, tzoff; /* 64 bit value for Jan 1 1980 */ PRIVSYM u_int64_t DIFF1980TO1601 = 11960035200ULL*10000000ULL; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_SET_PATH_INFORMATION, scrp, &t2p); if (error) return error; mbp = &t2p->t2_tparam; mb_init(mbp); if (vcp->vc_sopt.sv_caps & SMB_CAP_INFOLEVEL_PASSTHRU) mb_put_uint16le(mbp, SMB_SFILEINFO_BASIC_INFORMATION); else mb_put_uint16le(mbp, SMB_SFILEINFO_BASIC_INFO); mb_put_uint32le(mbp, 0); /* MBZ */ /* mb_put_uint8(mbp, SMB_DT_ASCII); specs incorrect */ error = smbfs_fullpath(mbp, vcp, np, NULL, NULL, '\\'); if (error) { smb_t2_done(t2p); return error; } tzoff = vcp->vc_sopt.sv_tz; /* do we know it won't support dates < 1980? */ if (!(ssp->ss_flags & SMBS_1980)) { mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint64le(mbp, 0); /* creation time */ if (atime) { smb_time_local2NT(atime, tzoff, &tm); } else tm = 0; mb_put_uint64le(mbp, tm); /* access time */ if (mtime) { smb_time_local2NT(mtime, tzoff, &tm); } else tm = 0; mb_put_uint64le(mbp, tm); /* last write time */ mb_put_uint64le(mbp, tm); /* change time */ mb_put_uint32le(mbp, attr); /* attr */ mb_put_uint32le(mbp, 0); /* undocumented padding */ t2p->t2_maxpcount = 24; t2p->t2_maxdcount = 56; error = smb_t2_request(t2p); } /* * "invalid argument" error probably means it's a * FAT drive that doesn't accept dates earlier * than 1980, so adjust dates and retry. If the * 1980 flag is on we fell thru the if {} above */ if ((ssp->ss_flags & SMBS_1980) || (error == EINVAL)) { mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint64le(mbp, 0); /* creation time */ if (atime) { smb_time_local2NT(atime, tzoff, &tm); if (tm < DIFF1980TO1601) tm = DIFF1980TO1601; } else tm = 0; mb_put_uint64le(mbp, tm); /* access time */ if (mtime) { smb_time_local2NT(mtime, tzoff, &tm); if (tm < DIFF1980TO1601) tm = DIFF1980TO1601; } else tm = 0; mb_put_uint64le(mbp, tm); /* last write time */ mb_put_uint64le(mbp, tm); /* change time */ mb_put_uint32le(mbp, attr); /* attr */ mb_put_uint32le(mbp, 0); /* undocumented padding */ t2p->t2_maxpcount = 24; t2p->t2_maxdcount = 56; error = smb_t2_request(t2p); /* if this worked set the flag so we will do the right thing next time */ if (!(error)) ssp->ss_flags |= SMBS_1980; } smb_t2_done(t2p); return error; } /* * Set file atime and mtime. Isn't supported by core dialect. */ int smbfs_smb_setftime(struct smbnode *np, struct timespec *mtime, struct timespec *atime, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct mbchain *mbp; u_int16_t date, time; int error, tzoff; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_SET_INFORMATION2, scrp); if (error) return error; tzoff = SSTOVC(ssp)->vc_sopt.sv_tz; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&np->n_fid, 2, MB_MSYSTEM); mb_put_uint32le(mbp, 0); /* creation time */ if (atime) smb_time_unix2dos(atime, tzoff, &date, &time, NULL); else time = date = 0; mb_put_uint16le(mbp, date); mb_put_uint16le(mbp, time); if (mtime) smb_time_unix2dos(mtime, tzoff, &date, &time, NULL); else time = date = 0; mb_put_uint16le(mbp, date); mb_put_uint16le(mbp, time); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); return error; } /* * Set DOS file attributes. * Looks like this call can be used only if CAP_NT_SMBS bit is on. */ int smbfs_smb_setfattrNT(struct smbnode *np, u_int32_t attr, struct timespec *mtime, struct timespec *atime, struct smb_cred *scrp) { struct smb_t2rq *t2p; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; u_int64_t tm; int error, svtz; error = smb_t2_alloc(SSTOCP(ssp), SMB_TRANS2_SET_FILE_INFORMATION, scrp, &t2p); if (error) return error; svtz = SSTOVC(ssp)->vc_sopt.sv_tz; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&np->n_fid, 2, MB_MSYSTEM); if (vcp->vc_sopt.sv_caps & SMB_CAP_INFOLEVEL_PASSTHRU) mb_put_uint16le(mbp, SMB_SFILEINFO_BASIC_INFORMATION); else mb_put_uint16le(mbp, SMB_SFILEINFO_BASIC_INFO); mb_put_uint32le(mbp, 0); /* XXX should be 16 not 32(?) */ mbp = &t2p->t2_tdata; mb_init(mbp); mb_put_uint64le(mbp, 0); /* creation time */ if (atime) { smb_time_local2NT(atime, svtz, &tm); } else tm = 0; mb_put_uint64le(mbp, tm); /* access time */ if (mtime) { smb_time_local2NT(mtime, svtz, &tm); } else tm = 0; mb_put_uint64le(mbp, tm); /* last write time */ mb_put_uint64le(mbp, tm); /* change time */ mb_put_uint32le(mbp, attr); mb_put_uint32le(mbp, 0); /* padding */ t2p->t2_maxpcount = 2; t2p->t2_maxdcount = 0; error = smb_t2_request(t2p); smb_t2_done(t2p); return error; } /* * Modern create/open of file or directory. * * If disp is NTCREATEX_DISP_OPEN then this is an open attempt, and: * If xattr then name is the stream to be opened at np, * Else np should be opened. * ...we won't touch *fidp, * ...we will set or clear *attrcacheupdated. * Else this is a creation attempt, and: * If xattr then name is the stream to create at np, * Else name is the thing to create under directory np. * ...we will return *fidp, * ...we won't touch *attrcacheupdated. */ int smbfs_smb_ntcreatex(struct smbnode *np, u_int32_t rights, struct smb_cred *scrp, enum vtype vt, int *attrcacheupdated, u_int16_t *fidp, const char *name, int nmlen, u_int32_t disp, int xattr, size_t *sizep, u_int32_t *rightsp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; struct mdchain *mdp; struct smbfattr fap; u_int8_t wc; u_int32_t lint, createopt, efa; u_int64_t llint; int error; u_int16_t fid, *namelenp; nanotime(&fap.fa_reqtime); error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_NT_CREATE_ANDX, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); 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 */ namelenp = (u_int16_t *)mb_reserve(mbp, sizeof(u_int16_t)); /* * XP to a W2K Server does not use NTCREATEX_FLAGS_OPEN_DIRECTORY * for creating nor for opening a directory. Samba ignores the bit. */ #if 0 /* causes sharing violation when making dir on W2K!*/ mb_put_uint32le(mbp, vt == VDIR ? NTCREATEX_FLAGS_OPEN_DIRECTORY : 0); #else mb_put_uint32le(mbp, 0); /* NTCREATEX_FLAGS_* */ #endif mb_put_uint32le(mbp, 0); /* FID - basis for path if not root */ mb_put_uint32le(mbp, rights); mb_put_uint64le(mbp, 0); /* "initial allocation size" */ efa = (vt == VDIR) ? SMB_EFA_DIRECTORY : SMB_EFA_NORMAL; if (disp != NTCREATEX_DISP_OPEN && !xattr) { if (efa == SMB_EFA_NORMAL) efa |= SMB_EFA_ARCHIVE; if (*name == '.') efa |= SMB_EFA_HIDDEN; } mb_put_uint32le(mbp, efa); mb_put_uint32le(mbp, NTCREATEX_SHARE_ACCESS_ALL); mb_put_uint32le(mbp, disp); createopt = 0; if (disp != NTCREATEX_DISP_OPEN) { if (vt == VDIR) createopt |= NTCREATEX_OPTIONS_DIRECTORY; /* (other create options currently not useful) */ } mb_put_uint32le(mbp, createopt); mb_put_uint32le(mbp, NTCREATEX_IMPERSONATION_IMPERSONATION); /* (?) */ mb_put_uint8(mbp, 0); /* security flags (?) */ smb_rq_wend(rqp); smb_rq_bstart(rqp); do { if (name == NULL) nmlen = 0; error = smbfs_fullpath(mbp, vcp, np, name, &nmlen, xattr ? ':' : '\\'); if (error) break; *namelenp = htoles(nmlen); /* includes null */ smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); /* * spec says 26 for word count, but 34 words are defined * and observed from win2000 */ if (md_get_uint8(mdp, &wc) != 0 || (wc != 26 && wc != 34 && wc != 42)) { error = EBADRPC; break; } 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_uint16(mdp, &fid); /* yes, leaving it LE */ md_get_uint32le(mdp, NULL); /* create_action */ md_get_uint64le(mdp, &llint); /* creation time */ md_get_uint64le(mdp, &llint); /* access time */ md_get_uint64le(mdp, &llint); /* write time */ if (llint) /* avoid bogus 0 time (on FAT roots) */ smb_time_NT2local(llint, vcp->vc_sopt.sv_tz, &fap.fa_mtime); md_get_uint64le(mdp, &llint); /* change time */ md_get_uint32le(mdp, &lint); /* attributes */ fap.fa_attr = lint; md_get_uint64le(mdp, NULL); /* allocation size */ md_get_uint64le(mdp, &llint); /* EOF */ fap.fa_size = llint; if (sizep) *sizep = fap.fa_size; md_get_uint16le(mdp, NULL); /* file type */ md_get_uint16le(mdp, NULL); /* device state */ md_get_uint8(mdp, NULL); /* directory (boolean) */ } while(0); smb_rq_done(rqp); if (error) return error; if (fidp) *fidp = fid; if (rightsp) *rightsp = rights; if (disp != NTCREATEX_DISP_OPEN || xattr) /* creating, or xattr */ goto uncached; if (attrcacheupdated) *attrcacheupdated = 0; /* * Update the cached attributes if they are still valid * in the cache and if nothing has changed. */ if (np->n_vnode == NULL) goto uncached; if (smbfs_attr_cachelookup(np->n_vnode, NULL, scrp) != 0) goto uncached; /* the cached attributes are not valid */ if (fap.fa_size != np->n_size) goto uncached; /* the size is different */ if (fap.fa_attr != np->n_dosattr) goto uncached; /* the attrs are different */ /* * fap.fa_mtime is in two second increments while np->n_mtime * may be in one second increments, so comparing the times is * somewhat sloppy. * * XXX: true fap.fa_mtime resolution must depend upon server's * local filesystem and is thus indeterminate... XXX ...TBD how that * affects this code... note wire resolution here is 100ns versus * 1sec down in smbfs_smb_oldopen (SMB_COM_OPEN) */ if (fap.fa_mtime.tv_sec != np->n_mtime.tv_sec && fap.fa_mtime.tv_sec != np->n_mtime.tv_sec - 1 && fap.fa_mtime.tv_sec != np->n_mtime.tv_sec + 1) goto uncached; /* the mod time is different */ fap.fa_mtime.tv_sec = np->n_mtime.tv_sec; /* keep higher res time */ smbfs_attr_cacheenter(np->n_vnode, &fap); if (attrcacheupdated) *attrcacheupdated = 1; uncached: return (0); } static u_int32_t smb_mode2rights(int mode) { mode = mode & SMB_AM_OPENMODE; switch(mode) { case SMB_AM_OPENREAD: return (GENERIC_RIGHT_READ_ACCESS); case SMB_AM_OPENWRITE: return (GENERIC_RIGHT_WRITE_ACCESS); case SMB_AM_OPENRW: return (GENERIC_RIGHT_ALL_ACCESS); case SMB_AM_OPENEXEC: return (GENERIC_RIGHT_EXECUTE_ACCESS); } return (0); } static int smb_rights2mode(u_int32_t rights) { int accmode = SMB_AM_OPENEXEC; /* our fallback */ if (rights & (SA_RIGHT_FILE_APPEND_DATA | SA_RIGHT_FILE_DELETE_CHILD | SA_RIGHT_FILE_WRITE_EA | SA_RIGHT_FILE_WRITE_ATTRIBUTES | SA_RIGHT_FILE_WRITE_DATA | STD_RIGHT_WRITE_OWNER_ACCESS | STD_RIGHT_DELETE_ACCESS | STD_RIGHT_WRITE_DAC_ACCESS | GENERIC_RIGHT_ALL_ACCESS | GENERIC_RIGHT_WRITE_ACCESS)) accmode = SMB_AM_OPENWRITE; if (rights & (SA_RIGHT_FILE_READ_DATA | SA_RIGHT_FILE_READ_ATTRIBUTES | SA_RIGHT_FILE_READ_EA | STD_RIGHT_READ_CONTROL_ACCESS | GENERIC_RIGHT_ALL_ACCESS | GENERIC_RIGHT_READ_ACCESS)) accmode = (accmode == SMB_AM_OPENEXEC) ? SMB_AM_OPENREAD : SMB_AM_OPENRW; return (accmode); } static int smbfs_smb_oldopen(struct smbnode *np, int accmode, struct smb_cred *scrp, int *attrcacheupdated, u_int16_t *fidp, const char *name, int nmlen, int xattr, size_t *sizep, u_int32_t *rightsp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); struct mbchain *mbp; struct mdchain *mdp; struct smbfattr fap; u_int8_t wc; u_int16_t fid, wattr, grantedmode; u_int32_t lint; int error; /* * Use DENYNONE to give unixy semantics of permitting * everything not forbidden by permissions. Ie denial * is up to server with clients/openers needing to use * advisory locks for further control. */ accmode |= SMB_SM_DENYNONE; nanotime(&fap.fa_reqtime); error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_OPEN, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, accmode); mb_put_uint16le(mbp, SMB_FA_SYSTEM | SMB_FA_HIDDEN | SMB_FA_RDONLY | SMB_FA_DIR); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); do { error = smbfs_fullpath(mbp, vcp, np, name, &nmlen, xattr ? ':' : '\\'); if (error) break; smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); /* * 8/2002 a DAVE server returned wc of 15 so we ignore that. * (the actual packet length and data was correct) */ if (md_get_uint8(mdp, &wc) != 0 || (wc != 7 && wc != 15)) { error = EBADRPC; break; } md_get_uint16(mdp, &fid); /* yes, we leave it LE */ md_get_uint16le(mdp, &wattr); fap.fa_attr = wattr; /* * Be careful using the time returned here, as * with FAT on NT4SP6, at least, the time returned is low * 32 bits of 100s of nanoseconds (since 1601) so it rolls * over about every seven minutes! */ md_get_uint32le(mdp, &lint); /* specs: secs since 1970 */ if (lint) /* avoid bogus zero returns */ smb_time_server2local(lint, vcp->vc_sopt.sv_tz, &fap.fa_mtime); md_get_uint32le(mdp, &lint); fap.fa_size = lint; if (sizep) *sizep = fap.fa_size; md_get_uint16le(mdp, &grantedmode); } while(0); smb_rq_done(rqp); if (error) return error; if (fidp) *fidp = fid; if (xattr) goto uncached; if (rightsp) *rightsp = smb_mode2rights(grantedmode); if (attrcacheupdated) *attrcacheupdated = 0; /* * Update the cached attributes if they are still valid * in the cache and if nothing has changed. * Note that this won't ever update if the file size is * greater than the 32-bits returned by SMB_COM_OPEN. * For 64-bit file sizes, SMB_COM_NT_CREATE_ANDX must * be used instead of SMB_COM_OPEN. */ if (np->n_vnode == NULL) goto uncached; if (smbfs_attr_cachelookup(np->n_vnode, NULL, scrp) != 0) goto uncached; /* the cached attributes are not valid */ if (fap.fa_size != np->n_size) goto uncached; /* the size is different */ if (fap.fa_attr != np->n_dosattr) goto uncached; /* the attrs are different */ /* * fap.fa_mtime is in two second increments while np->n_mtime * may be in one second increments, so comparing the times is * somewhat sloppy. */ if (fap.fa_mtime.tv_sec != np->n_mtime.tv_sec && fap.fa_mtime.tv_sec != np->n_mtime.tv_sec - 1 && fap.fa_mtime.tv_sec != np->n_mtime.tv_sec + 1) goto uncached; /* the mod time is different */ fap.fa_mtime.tv_sec = np->n_mtime.tv_sec; /* keep higher res time */ smbfs_attr_cacheenter(np->n_vnode, &fap); if (attrcacheupdated) *attrcacheupdated = 1; uncached: return 0; } int smbfs_smb_tmpopen(struct smbnode *np, u_int32_t rights, struct smb_cred *scrp, u_int16_t *fidp) { struct smb_vc *vcp = SSTOVC(np->n_mount->sm_share); enum vtype vt = VREG; if (np->n_fid && (rights & np->n_rights) == rights) { np->n_fidrefs++; *fidp = np->n_fid; return (0); } if (!(vcp->vc_sopt.sv_caps & SMB_CAP_NT_SMBS)) return(smbfs_smb_oldopen(np, smb_rights2mode(rights), scrp, NULL, fidp, NULL, 0, 0, NULL, NULL)); if (SMBTOV(np)) vt = vnode_vtype(SMBTOV(np)); return (smbfs_smb_ntcreatex(np, rights, scrp, vt, NULL, fidp, NULL, 0, NTCREATEX_DISP_OPEN, 0, NULL, NULL)); } int smbfs_smb_tmpclose(struct smbnode *np, u_int16_t fid, struct smb_cred *scrp) { struct smb_share *ssp = np->n_mount->sm_share; int error; if (fid != np->n_fid) return (smbfs_smb_close(ssp, fid, NULL, scrp)); SMBASSERT(np->n_fidrefs > 0); np->n_fidrefs--; if (np->n_fidrefs) return (0); error = smbfs_smb_close(ssp, fid, NULL, scrp); np->n_fid = 0; return (error); } int smbfs_smb_open(struct smbnode *np, u_int32_t rights, struct smb_cred *scrp, int *attrcacheupdated, u_int16_t *fidp, const char *name, int nmlen, int xattr, size_t *sizep, u_int32_t *rightsp) { int error; struct smb_share *ssp = np->n_mount->sm_share; struct smb_vc *vcp = SSTOVC(ssp); enum vtype vt = VREG; if (vcp->vc_sopt.sv_caps & SMB_CAP_NT_SMBS) { if (SMBTOV(np)) vt = vnode_vtype(SMBTOV(np)); error = smbfs_smb_ntcreatex(np, rights, scrp, vt, attrcacheupdated, fidp, name, nmlen, NTCREATEX_DISP_OPEN, xattr, sizep, rightsp); } else { error = smbfs_smb_oldopen(np, smb_rights2mode(rights), scrp, attrcacheupdated, fidp, name, nmlen, xattr, sizep, rightsp); } if (!error && !name) np->n_fidrefs++; return (error); } int smbfs_smb_close(struct smb_share *ssp, u_int16_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_mem(mbp, (caddr_t)&fid, sizeof(fid), MB_MSYSTEM); if (mtime) { smb_time_local2server(mtime, SSTOVC(ssp)->vc_sopt.sv_tz, &time); } else time = 0; mb_put_uint32le(mbp, time); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); smb_rq_done(rqp); /* * ENOTCONN isn't interesting - if the connection is closed, * so are all our FIDs - and ENXIO is also not interesting, * as it means a forced unmount was done. * * Don't clog up the system log with warnings about those failures * on closes. */ if (error == ENOTCONN || error == ENXIO) error = 0; return error; } static int smbfs_smb_oldcreate(struct smbnode *dnp, const char *name, int nmlen, struct smb_cred *scrp, u_int16_t *fidp, int xattr) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = dnp->n_mount->sm_share; struct mbchain *mbp; struct mdchain *mdp; struct timespec ctime; u_int8_t wc; long tm; int error; u_int16_t attr = SMB_FA_ARCHIVE; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_CREATE, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); if (name && *name == '.') attr |= SMB_FA_HIDDEN; mb_put_uint16le(mbp, attr); /* attributes */ nanotime(&ctime); smb_time_local2server(&ctime, SSTOVC(ssp)->vc_sopt.sv_tz, &tm); mb_put_uint32le(mbp, tm); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), dnp, name, &nmlen, xattr ? ':' : '\\'); if (!error) { smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (!error) { smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc == 1) md_get_uint16(mdp, fidp); else error = EBADRPC; } } smb_rq_done(rqp); return error; } int smbfs_smb_create(struct smbnode *dnp, const char *name, int nmlen, struct smb_cred *scrp, u_int16_t *fidp, u_int32_t disp, int xattr) { struct smb_vc *vcp = SSTOVC(dnp->n_mount->sm_share); /* * At present the only access we might need is to WRITE data, * and that only if we are creating a "symlink". When/if the * access needed gets more complex it should made a parameter * and be set upstream. */ if (vcp->vc_sopt.sv_caps & SMB_CAP_NT_SMBS) { return (smbfs_smb_ntcreatex(dnp, SA_RIGHT_FILE_WRITE_DATA, scrp, VREG, NULL, fidp, name, nmlen, disp, xattr, NULL, NULL)); } else return (smbfs_smb_oldcreate(dnp, name, nmlen, scrp, fidp, xattr)); } int smbfs_smb_delete(struct smbnode *np, struct smb_cred *scrp, const char *name, int nmlen, int xattr) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_DELETE, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, SMB_FA_SYSTEM | SMB_FA_HIDDEN); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), np, name, &nmlen, xattr ? ':' : '\\'); if (!error) { smb_rq_bend(rqp); error = smb_rq_simple(rqp); } smb_rq_done(rqp); return error; } int smbfs_smb_rename(struct smbnode *src, struct smbnode *tdnp, const char *tname, int tnmlen, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = src->n_mount->sm_share; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_RENAME, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); /* freebsd bug: Let directories be renamed - Win98 requires DIR bit */ mb_put_uint16le(mbp, (vnode_isdir(SMBTOV(src)) ? SMB_FA_DIR : 0) | SMB_FA_SYSTEM | SMB_FA_HIDDEN); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); do { error = smbfs_fullpath(mbp, SSTOVC(ssp), src, NULL, NULL, '\\'); if (error) break; mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), tdnp, tname, &tnmlen, '\\'); if (error) break; smb_rq_bend(rqp); error = smb_rq_simple(rqp); } while(0); smb_rq_done(rqp); return error; } int smbfs_smb_move(struct smbnode *src, struct smbnode *tdnp, const char *tname, int tnmlen, u_int16_t flags, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = src->n_mount->sm_share; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_MOVE, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, SMB_TID_UNKNOWN); mb_put_uint16le(mbp, 0x20); /* delete target file */ mb_put_uint16le(mbp, flags); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); do { error = smbfs_fullpath(mbp, SSTOVC(ssp), src, NULL, NULL, '\\'); if (error) break; mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), tdnp, tname, &tnmlen, '\\'); if (error) break; smb_rq_bend(rqp); error = smb_rq_simple(rqp); } while(0); smb_rq_done(rqp); return error; } static int smbfs_smb_oldmkdir(struct smbnode *dnp, const char *name, int len, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = dnp->n_mount->sm_share; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_CREATE_DIRECTORY, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), dnp, name, &len, '\\'); if (!error) { smb_rq_bend(rqp); error = smb_rq_simple(rqp); } smb_rq_done(rqp); return error; } int smbfs_smb_mkdir(struct smbnode *dnp, const char *name, int len, struct smb_cred *scrp) { struct smb_share *ssp = dnp->n_mount->sm_share; u_int16_t fid; int error; /* * We ask for SA_RIGHT_FILE_READ_DATA not because we need it, but * just to be asking for something. The rights==0 case could * easily be broken on some old or unusual servers. */ if (SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_NT_SMBS) { error = smbfs_smb_ntcreatex(dnp, SA_RIGHT_FILE_READ_DATA, scrp, VDIR, NULL, &fid, name, len, NTCREATEX_DISP_CREATE, 0, NULL, NULL); if (error) return (error); error = smbfs_smb_close(ssp, fid, NULL, scrp); if (error) SMBERROR("error %d closing fid %d\n", error, fid); return (0); } else return (smbfs_smb_oldmkdir(dnp, name, len, scrp)); } int smbfs_smb_rmdir(struct smbnode *np, struct smb_cred *scrp) { struct smb_rq rq, *rqp = &rq; struct smb_share *ssp = np->n_mount->sm_share; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ssp), SMB_COM_DELETE_DIRECTORY, scrp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); error = smbfs_fullpath(mbp, SSTOVC(ssp), np, NULL, NULL, '\\'); if (!error) { smb_rq_bend(rqp); error = smb_rq_simple(rqp); } smb_rq_done(rqp); return error; } static int smbfs_smb_search(struct smbfs_fctx *ctx) { struct smb_vc *vcp = SSTOVC(ctx->f_ssp); struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int8_t wc, bt; u_int16_t ec, dlen, bc; int len, maxent,error, iseof = 0; maxent = min(ctx->f_left, (vcp->vc_txmax - SMB_HDRLEN - 2*2) / SMB_DENTRYLEN); if (ctx->f_rq) { smb_rq_done(ctx->f_rq); ctx->f_rq = NULL; } error = smb_rq_alloc(SSTOCP(ctx->f_ssp), SMB_COM_SEARCH, ctx->f_scred, &rqp); if (error) return error; ctx->f_rq = rqp; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint16le(mbp, maxent); /* max entries to return */ mb_put_uint16le(mbp, ctx->f_attrmask); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); /* buffer format */ if (ctx->f_flags & SMBFS_RDD_FINDFIRST) { len = ctx->f_wclen; error = smbfs_fullpath(mbp, vcp, ctx->f_dnp, ctx->f_wildcard, &len, '\\'); if (error) return error; mb_put_uint8(mbp, SMB_DT_VARIABLE); mb_put_uint16le(mbp, 0); /* context length */ ctx->f_flags &= ~SMBFS_RDD_FINDFIRST; } else { /* XXX - use "smbfs_fullpath()" and a null string? */ if (SMB_UNICODE_STRINGS(vcp)) { mb_put_padbyte(mbp); mb_put_uint8(mbp, 0); } mb_put_uint8(mbp, 0); mb_put_uint8(mbp, SMB_DT_VARIABLE); mb_put_uint16le(mbp, SMB_SKEYLEN); mb_put_mem(mbp, ctx->f_skey, SMB_SKEYLEN, MB_MSYSTEM); } smb_rq_bend(rqp); error = smb_rq_simple(rqp); if (rqp->sr_errclass == ERRDOS && rqp->sr_serror == ERRnofiles) { error = 0; iseof = 1; ctx->f_flags |= SMBFS_RDD_EOF; } else if (error) return error; smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc != 1) return iseof ? ENOENT : EBADRPC; md_get_uint16le(mdp, &ec); if (ec == 0) return ENOENT; ctx->f_ecnt = ec; md_get_uint16le(mdp, &bc); if (bc < 3) return EBADRPC; bc -= 3; md_get_uint8(mdp, &bt); if (bt != SMB_DT_VARIABLE) return EBADRPC; md_get_uint16le(mdp, &dlen); if (dlen != bc || dlen % SMB_DENTRYLEN != 0) return EBADRPC; return 0; } static int smbfs_smb_findopenLM1(struct smbfs_fctx *ctx, struct smbnode *dnp, const char *wildcard, int wclen, int attr, struct smb_cred *scrp) { #pragma unused(dnp, scrp) ctx->f_attrmask = attr; if (wildcard) { if (wclen == 1 && wildcard[0] == '*') { ctx->f_wildcard = "*.*"; ctx->f_wclen = 3; } else { ctx->f_wildcard = wildcard; ctx->f_wclen = wclen; } } else { ctx->f_wildcard = NULL; ctx->f_wclen = 0; } ctx->f_name = ctx->f_fname; return 0; } static int smbfs_smb_findnextLM1(struct smbfs_fctx *ctx, int limit) { struct mdchain *mdp; struct smb_rq *rqp; char *cp; u_int8_t battr; u_int16_t date, time; u_int32_t size; int error; struct timespec ts; if (ctx->f_ecnt == 0) { if (ctx->f_flags & SMBFS_RDD_EOF) return ENOENT; ctx->f_left = ctx->f_limit = limit; nanotime(&ts); error = smbfs_smb_search(ctx); if (error) return error; ctx->f_attr.fa_reqtime = ts; } rqp = ctx->f_rq; smb_rq_getreply(rqp, &mdp); md_get_mem(mdp, ctx->f_skey, SMB_SKEYLEN, MB_MSYSTEM); md_get_uint8(mdp, &battr); md_get_uint16le(mdp, &time); md_get_uint16le(mdp, &date); md_get_uint32le(mdp, &size); cp = ctx->f_name; md_get_mem(mdp, cp, sizeof(ctx->f_fname), MB_MSYSTEM); cp[sizeof(ctx->f_fname) - 1] = 0; cp += strlen(cp) - 1; while (*cp == ' ' && cp >= ctx->f_name) *cp-- = 0; ctx->f_attr.fa_attr = battr; smb_dos2unixtime(date, time, 0, rqp->sr_vc->vc_sopt.sv_tz, &ctx->f_attr.fa_mtime); ctx->f_attr.fa_size = size; ctx->f_nmlen = strlen(ctx->f_name); ctx->f_ecnt--; ctx->f_left--; return 0; } static int smbfs_smb_findcloseLM1(struct smbfs_fctx *ctx) { if (ctx->f_rq) smb_rq_done(ctx->f_rq); return 0; } /* * TRANS2_FIND_FIRST2/NEXT2, used for NT LM12 dialect */ static int smbfs_smb_trans2find2(struct smbfs_fctx *ctx) { struct smb_t2rq *t2p; struct smb_vc *vcp = SSTOVC(ctx->f_ssp); struct mbchain *mbp; struct mdchain *mdp; u_int16_t tw, flags; int len, error; if (ctx->f_t2) { smb_t2_done(ctx->f_t2); ctx->f_t2 = NULL; } ctx->f_flags &= ~SMBFS_RDD_GOTRNAME; flags = FIND2_RETURN_RESUME_KEYS | FIND2_CLOSE_ON_EOS; if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) { flags |= FIND2_CLOSE_AFTER_REQUEST; ctx->f_flags |= SMBFS_RDD_NOCLOSE; } if (ctx->f_flags & SMBFS_RDD_FINDFIRST) { error = smb_t2_alloc(SSTOCP(ctx->f_ssp), SMB_TRANS2_FIND_FIRST2, ctx->f_scred, &t2p); if (error) return error; ctx->f_t2 = t2p; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_uint16le(mbp, ctx->f_attrmask); mb_put_uint16le(mbp, ctx->f_limit); mb_put_uint16le(mbp, flags); mb_put_uint16le(mbp, ctx->f_infolevel); mb_put_uint32le(mbp, 0); /* mb_put_uint8(mbp, SMB_DT_ASCII); specs? hah! */ len = ctx->f_wclen; error = smbfs_fullpath(mbp, vcp, ctx->f_dnp, ctx->f_wildcard, &len, '\\'); if (error) return error; } else { error = smb_t2_alloc(SSTOCP(ctx->f_ssp), SMB_TRANS2_FIND_NEXT2, ctx->f_scred, &t2p); if (error) return error; ctx->f_t2 = t2p; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&ctx->f_Sid, 2, MB_MSYSTEM); mb_put_uint16le(mbp, ctx->f_limit); mb_put_uint16le(mbp, ctx->f_infolevel); if (ctx->f_ssp->ss_flags & SMBS_RESUMEKEYS) { mb_put_uint32le(mbp, ctx->f_rkey); } else mb_put_uint32le(mbp, 0); mb_put_uint16le(mbp, flags); if (ctx->f_rname) { /* resume file name */ mb_put_mem(mbp, ctx->f_rname, ctx->f_rnamelen, MB_MSYSTEM); } /* Add trailing null - 1 byte if ASCII, 2 if Unicode */ if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) mb_put_uint8(mbp, 0); /* 1st byte of NULL Unicode char */ mb_put_uint8(mbp, 0); #if 0 struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 200 * 1000 * 1000; /* 200ms */ if (vcp->vc_flags & SMBC_WIN95) { /* * some implementations suggests to sleep here * for 200ms, due to the bug in the Win95. * I've didn't notice any problem, but put code * for it. */ msleep(&flags, 0, PVFS, "fix95", &ts); } #endif } t2p->t2_maxpcount = 5 * 2; t2p->t2_maxdcount = vcp->vc_txmax; error = smb_t2_request(t2p); if (error) return error; mdp = &t2p->t2_rparam; if (ctx->f_flags & SMBFS_RDD_FINDFIRST) { if ((error = md_get_uint16(mdp, &ctx->f_Sid)) != 0) return error; ctx->f_flags &= ~SMBFS_RDD_FINDFIRST; } if ((error = md_get_uint16le(mdp, &tw)) != 0) return error; ctx->f_ecnt = tw; /* search count - # entries returned */ if ((error = md_get_uint16le(mdp, &tw)) != 0) return error; /* * tw now is the "end of search" flag. against an XP server tw * comes back zero when the prior find_next returned exactly * the number of entries requested. in which case we'd try again * but the search has in fact been closed so an EBADF results. our * circumvention is to check here for a zero search count. */ if (tw || ctx->f_ecnt == 0) ctx->f_flags |= SMBFS_RDD_EOF | SMBFS_RDD_NOCLOSE; if ((error = md_get_uint16le(mdp, &tw)) != 0) return error; if ((error = md_get_uint16le(mdp, &tw)) != 0) return error; if (ctx->f_ecnt == 0) return ENOENT; ctx->f_rnameofs = tw; mdp = &t2p->t2_rdata; if (mdp->md_top == NULL) { printf("bug: ecnt = %d, but data is NULL (please report)\n", ctx->f_ecnt); return ENOENT; } if (mdp->md_top->m_len == 0) { printf("bug: ecnt = %d, but m_len = 0 and m_next = %p (please report)\n", ctx->f_ecnt,mbp->mb_top->m_next); return ENOENT; } ctx->f_eofs = 0; return 0; } static int smbfs_smb_findclose2(struct smbfs_fctx *ctx) { struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; int error; error = smb_rq_init(rqp, SSTOCP(ctx->f_ssp), SMB_COM_FIND_CLOSE2, ctx->f_scred); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&ctx->f_Sid, 2, MB_MSYSTEM); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); smb_rq_done(rqp); return error; } static int smbfs_smb_findopenLM2(struct smbfs_fctx *ctx, struct smbnode *dnp, const char *wildcard, int wclen, int attr, struct smb_cred *scrp) { #pragma unused(dnp, scrp) if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) ctx->f_name = malloc(SMB_MAXFNAMELEN*2, M_SMBFSDATA, M_WAITOK); else ctx->f_name = malloc(SMB_MAXFNAMELEN, M_SMBFSDATA, M_WAITOK); if (ctx->f_name == NULL) return ENOMEM; ctx->f_infolevel = SMB_DIALECT(SSTOVC(ctx->f_ssp)) < SMB_DIALECT_NTLM0_12 ? SMB_FIND_STANDARD : SMB_FIND_BOTH_DIRECTORY_INFO; ctx->f_attrmask = attr; ctx->f_wildcard = wildcard; ctx->f_wclen = wclen; return 0; } static int smbfs_smb_findnextLM2(struct smbfs_fctx *ctx, int limit) { struct mdchain *mdp; struct smb_t2rq *t2p; char *cp; u_int8_t tb; u_int16_t date, time, wattr; u_int32_t size, next, dattr, resumekey = 0; u_int64_t llint; int error, svtz, cnt, fxsz, nmlen, recsz, otw; struct timespec ts; again: otw = 0; /* nothing sent Over The Wire (yet) */ if (ctx->f_ecnt == 0) { if (ctx->f_flags & SMBFS_RDD_EOF) return ENOENT; ctx->f_left = ctx->f_limit = limit; nanotime(&ts); error = smbfs_smb_trans2find2(ctx); if (error) return error; ctx->f_attr.fa_reqtime = ts; ctx->f_otws++; otw = 1; } t2p = ctx->f_t2; mdp = &t2p->t2_rdata; svtz = SSTOVC(ctx->f_ssp)->vc_sopt.sv_tz; switch (ctx->f_infolevel) { case SMB_FIND_STANDARD: next = 0; fxsz = 0; md_get_uint16le(mdp, &date); md_get_uint16le(mdp, &time); /* creation time */ md_get_uint16le(mdp, &date); md_get_uint16le(mdp, &time); /* access time */ smb_dos2unixtime(date, time, 0, svtz, &ctx->f_attr.fa_atime); md_get_uint16le(mdp, &date); md_get_uint16le(mdp, &time); /* modify time */ smb_dos2unixtime(date, time, 0, svtz, &ctx->f_attr.fa_mtime); md_get_uint32le(mdp, &size); ctx->f_attr.fa_size = size; md_get_uint32(mdp, NULL); /* allocation size */ md_get_uint16le(mdp, &wattr); ctx->f_attr.fa_attr = wattr; md_get_uint8(mdp, &tb); size = nmlen = tb; fxsz = 23; recsz = next = 24 + nmlen; /* docs misses zero byte at end */ break; case SMB_FIND_DIRECTORY_INFO: case SMB_FIND_BOTH_DIRECTORY_INFO: md_get_uint32le(mdp, &next); md_get_uint32le(mdp, &resumekey); /* file index (resume key) */ md_get_uint64(mdp, NULL); /* creation time */ md_get_uint64le(mdp, &llint); smb_time_NT2local(llint, svtz, &ctx->f_attr.fa_atime); md_get_uint64le(mdp, &llint); smb_time_NT2local(llint, svtz, &ctx->f_attr.fa_mtime); md_get_uint64le(mdp, &llint); smb_time_NT2local(llint, svtz, &ctx->f_attr.fa_ctime); md_get_uint64le(mdp, &llint); /* file size */ ctx->f_attr.fa_size = llint; md_get_uint64(mdp, NULL); /* real size (should use) */ /* freebsd bug: fa_attr endian bug */ md_get_uint32le(mdp, &dattr); /* extended file attributes */ ctx->f_attr.fa_attr = dattr; md_get_uint32le(mdp, &size); /* name len */ fxsz = 64; /* size ofinfo up to filename */ if (ctx->f_infolevel == SMB_FIND_BOTH_DIRECTORY_INFO) { /* * Skip EaSize (4 bytes), a byte of ShortNameLength, * a reserved byte, and ShortName (8.3 means 24 bytes, * as Leach defined it to always be Unicode) */ md_get_mem(mdp, NULL, 30, MB_MSYSTEM); fxsz += 30; } recsz = next ? next : fxsz + size; break; default: SMBERROR("unexpected info level %d\n", ctx->f_infolevel); return EINVAL; } if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) nmlen = min(size, SMB_MAXFNAMELEN * 2); else nmlen = min(size, SMB_MAXFNAMELEN); cp = ctx->f_name; error = md_get_mem(mdp, cp, nmlen, MB_MSYSTEM); if (error) return error; if (next) { cnt = next - nmlen - fxsz; if (cnt > 0) md_get_mem(mdp, NULL, cnt, MB_MSYSTEM); else if (cnt < 0) { SMBERROR("out of sync\n"); return EBADRPC; } } /* Don't count any trailing null in the name. */ if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) { if (nmlen > 1 && cp[nmlen - 1] == 0 && cp[nmlen - 2] == 0) nmlen -= 2; } else { if (nmlen && cp[nmlen - 1] == 0) nmlen--; } if (nmlen == 0) return EBADRPC; /* * Ref radar 3983209. On a find-next we expect a server will * 1) if the continue bit is set, use the server's idea of current loc, * 2) else if the resume key is non-zero, use that location, * 3) else if the resume name is set, use that location, * 4) else use the server's idea of current location. * * Current NetApps don't do that. If we send no continue bit, a zero * resume key, and a resume name, the NetApp ignores the resume name * and acts on the (zero) resume key, sending back the start of the * directory again. Panther doesn't expose the netapp bug; Panther used * the continue bit, but that was changed for 2866172. Win2000 as a * client also relies upon the resume name, but they request a very * large number of files, so the bug would be seen only with very * large directories. * * Our fix is to notice if the second OTW op (the first find-next) * returns, in the first filename, the same filename we got back * at the start of the first OTW (the find-first). In that case * we've detected the server bug and set SMBS_RESUMEKEYS, causing us * to send non-zero resume keys henceforth. * * Caveat: if there's a netapp so old it doesn't negotiate NTLM 0.12 * then we get no resume keys so f_rkey stays zero and this "fix" * changes nothing. * * Note due to a similar problem (4051871) we also set SMBS_RESUMEKEYS * for FAT volumes, at mount time. */ if (otw && !(ctx->f_ssp->ss_flags & SMBS_RESUMEKEYS)) { if (ctx->f_otws == 1) { ctx->f_firstnmlen = nmlen; ctx->f_firstnm = malloc(nmlen, M_SMBFSDATA, M_WAITOK); bcopy(ctx->f_name, ctx->f_firstnm, nmlen); } else if (ctx->f_otws == 2 && nmlen == ctx->f_firstnmlen && !bcmp(ctx->f_name, ctx->f_firstnm, nmlen)) { SMBERROR("server resume_name bug; using resume keys\n"); ctx->f_ssp->ss_flags |= SMBS_RESUMEKEYS; ctx->f_ecnt = 0; goto again; /* must redo last otw op! */ } } ctx->f_rkey = resumekey; next = ctx->f_eofs + recsz; if (ctx->f_rnameofs && (ctx->f_flags & SMBFS_RDD_GOTRNAME) == 0 && (ctx->f_rnameofs >= ctx->f_eofs && ctx->f_rnameofs < (int)next)) { /* * Server needs a resume filename. */ if (ctx->f_rnamelen < nmlen) { if (ctx->f_rname) free(ctx->f_rname, M_SMBFSDATA); ctx->f_rname = malloc(nmlen, M_SMBFSDATA, M_WAITOK); } ctx->f_rnamelen = nmlen; bcopy(ctx->f_name, ctx->f_rname, nmlen); ctx->f_flags |= SMBFS_RDD_GOTRNAME; } ctx->f_nmlen = nmlen; ctx->f_eofs = next; ctx->f_ecnt--; ctx->f_left--; return 0; } static int smbfs_smb_findcloseLM2(struct smbfs_fctx *ctx) { if (ctx->f_name) free(ctx->f_name, M_SMBFSDATA); if (ctx->f_t2) smb_t2_done(ctx->f_t2); if ((ctx->f_flags & SMBFS_RDD_NOCLOSE) == 0) smbfs_smb_findclose2(ctx); return 0; } PRIVSYM int smbfs_smb_findopen(struct smbnode *dnp, const char *wildcard, int wclen, int attr, struct smb_cred *scrp, struct smbfs_fctx **ctxpp) { struct smbfs_fctx *ctx; int error; ctx = malloc(sizeof(*ctx), M_SMBFSDATA, M_WAITOK); if (ctx == NULL) return ENOMEM; bzero(ctx, sizeof(*ctx)); if (dnp->n_mount->sm_share) { ctx->f_ssp = dnp->n_mount->sm_share; smb_share_ref(ctx->f_ssp); } ctx->f_dnp = dnp; ctx->f_flags = SMBFS_RDD_FINDFIRST; ctx->f_scred = scrp; if (SMB_DIALECT(SSTOVC(ctx->f_ssp)) < SMB_DIALECT_LANMAN2_0 || (dnp->n_mount->sm_args.flags & SMBFS_MOUNT_NO_LONG)) { ctx->f_flags |= SMBFS_RDD_USESEARCH; error = smbfs_smb_findopenLM1(ctx, dnp, wildcard, wclen, attr, scrp); } else error = smbfs_smb_findopenLM2(ctx, dnp, wildcard, wclen, attr, scrp); if (error) smbfs_smb_findclose(ctx, scrp); else *ctxpp = ctx; return error; } PRIVSYM int smbfs_smb_findnext(struct smbfs_fctx *ctx, int limit, struct smb_cred *scrp) { int error; if (limit == 0) limit = 1000000; else limit += 3; /* ensures we ask for 1 extra, plus . and .. */ ctx->f_scred = scrp; for (;;) { if (ctx->f_flags & SMBFS_RDD_USESEARCH) { error = smbfs_smb_findnextLM1(ctx, limit); } else error = smbfs_smb_findnextLM2(ctx, limit); if (error) return error; if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) { if ((ctx->f_nmlen == 2 && *(u_int16_t *)ctx->f_name == 0x2e00) || (ctx->f_nmlen == 4 && *(u_int32_t *)ctx->f_name == 0x2e002e00)) continue; } else { if ((ctx->f_nmlen == 1 && ctx->f_name[0] == '.') || (ctx->f_nmlen == 2 && ctx->f_name[0] == '.' && ctx->f_name[1] == '.')) continue; } break; } smbfs_fname_tolocal(ctx); ctx->f_attr.fa_ino = smbfs_getino(ctx->f_dnp, ctx->f_name, ctx->f_nmlen); return 0; } PRIVSYM int smbfs_smb_findclose(struct smbfs_fctx *ctx, struct smb_cred *scrp) { ctx->f_scred = scrp; if (ctx->f_flags & SMBFS_RDD_USESEARCH) { smbfs_smb_findcloseLM1(ctx); } else smbfs_smb_findcloseLM2(ctx); if (ctx->f_rname) free(ctx->f_rname, M_SMBFSDATA); if (ctx->f_firstnm) free(ctx->f_firstnm, M_SMBFSDATA); if (ctx->f_ssp) smb_share_rele(ctx->f_ssp, ctx->f_scred); free(ctx, M_SMBFSDATA); return 0; } int smbfs_smb_lookup(struct smbnode *dnp, const char **namep, int *nmlenp, struct smbfattr *fap, struct smb_cred *scrp) { struct smbfs_fctx *ctx; int error; const char *name = (namep ? *namep : NULL); int nmlen = (nmlenp ? *nmlenp : 0); if (dnp == NULL || (dnp->n_ino == 2 && name == NULL)) { bzero(fap, sizeof(*fap)); fap->fa_attr = SMB_FA_DIR; fap->fa_ino = 2; if (dnp == NULL) return 0; error = smbfs_smb_qpathinfo(dnp, fap, scrp, 0); if (error != EINVAL) return error; error = smbfs_smb_query_info(dnp, NULL, 0, fap, scrp); if (error || fap->fa_mtime.tv_sec) return error; smbfs_attr_touchdir(dnp); return 0; } if (nmlen == 1 && name[0] == '.') { error = smbfs_smb_lookup(dnp, NULL, NULL, fap, scrp); return error; } else if (nmlen == 2 && name[0] == '.' && name[1] == '.') { error = smbfs_smb_lookup(dnp->n_parent, NULL, NULL, fap, scrp); printf("%s: knows NOTHING about '..'\n", __FUNCTION__); return error; } /* * This hides a server bug observable in Win98: * size changes may not show until a CLOSE or a FLUSH op */ error = smbfs_smb_flush(dnp, scrp); if (error) return (error); error = smbfs_smb_findopen(dnp, name, nmlen, SMB_FA_SYSTEM | SMB_FA_HIDDEN | SMB_FA_DIR, scrp, &ctx); if (error) return error; ctx->f_flags |= SMBFS_RDD_FINDSINGLE; error = smbfs_smb_findnext(ctx, 1, scrp); if (error == 0) { *fap = ctx->f_attr; if (name == NULL) fap->fa_ino = dnp->n_ino; if (namep) *namep = smbfs_name_alloc(ctx->f_name, ctx->f_nmlen); if (nmlenp) *nmlenp = ctx->f_nmlen; } smbfs_smb_findclose(ctx, scrp); return error; } int smbfs_smb_getsec_int(struct smb_share *ssp, u_int16_t fid, struct smb_cred *scrp, u_int32_t selector, struct ntsecdesc **res, int *reslen) { struct smb_ntrq *ntp; struct mbchain *mbp; struct mdchain *mdp; int error, len; error = smb_nt_alloc(SSTOCP(ssp), NT_TRANSACT_QUERY_SECURITY_DESC, scrp, &ntp); if (error) return error; mbp = &ntp->nt_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&fid, 2, MB_MSYSTEM); mb_put_uint16le(mbp, 0); /* reserved */ mb_put_uint32le(mbp, selector); ntp->nt_maxpcount = 4; ntp->nt_maxdcount = *reslen; error = smb_nt_request(ntp); if (error && !(ntp->nt_flags & SMBT2_MOREDATA)) goto done; *res = NULL; /* * if there's more data than we said we could receive, here * is where we pick up the length of it */ mdp = &ntp->nt_rparam; md_get_uint32le(mdp, reslen); mdp = &ntp->nt_rdata; if (mdp->md_top) { /* XXX md_cur safer than md_top */ len = m_fixhdr(mdp->md_top); /* * The following "if (len < *reslen)" handles a Windows bug * observed when the underlying filesystem is FAT32. In that * case a 32 byte security descriptor comes back (S-1-1-0, ie * "Everyone") but the Parameter Block claims 44 is the length * of the security descriptor. (The Data Block length * claimed is 32. This server bug was reported against NT * first and I've personally observed it with W2K. */ if (len < *reslen) *reslen = len; if (len == *reslen) { MALLOC(*res, struct ntsecdesc *, len, M_TEMP, M_WAITOK); md_get_mem(mdp, (caddr_t)*res, len, MB_MSYSTEM); } else if (len > *reslen) SMBERROR("len %d *reslen %d fid 0x%x\n", len, *reslen, letohs(fid)); } else SMBERROR("null md_top? fid 0x%x\n", letohs(fid)); done: smb_nt_done(ntp); return (error); } int smbfs_smb_getsec(struct smb_share *ssp, u_int16_t fid, struct smb_cred *scrp, u_int32_t selector, struct ntsecdesc **res) { int error, olen, seclen; olen = seclen = 500; /* "overlarge" values => server errors */ error = smbfs_smb_getsec_int(ssp, fid, scrp, selector, res, &seclen); if (error && seclen > olen) error = smbfs_smb_getsec_int(ssp, fid, scrp, selector, res, &seclen); return (error); } int smbfs_smb_setsec(struct smb_share *ssp, u_int16_t fid, struct smb_cred *scrp, u_int32_t selector, u_int16_t flags, struct ntsid *owner, struct ntsid *group, struct ntacl *sacl, struct ntacl *dacl) { struct smb_ntrq *ntp; struct mbchain *mbp; int error, off; struct ntsecdesc ntsd; error = smb_nt_alloc(SSTOCP(ssp), NT_TRANSACT_SET_SECURITY_DESC, scrp, &ntp); if (error) return error; mbp = &ntp->nt_tparam; mb_init(mbp); mb_put_mem(mbp, (caddr_t)&fid, 2, MB_MSYSTEM); mb_put_uint16le(mbp, 0); /* reserved */ mb_put_uint32le(mbp, selector); mbp = &ntp->nt_tdata; mb_init(mbp); bzero(&ntsd, sizeof ntsd); wset_sdrevision(&ntsd); /* * A note about flags ("SECURITY_DESCRIPTOR_CONTROL" in MSDN) * We set here only those bits we can be sure must be set. The rest * are up to the caller. In particular, the caller may intentionally * set an acl PRESENT bit while giving us a null pointer for the * acl - that sets a null acl, giving access to everyone. Note also * that the AUTO_INHERITED bits should probably always be set unless * the server is NT. */ flags |= SD_SELF_RELATIVE; off = sizeof ntsd; if (owner) { wset_sdowneroff(&ntsd, off); off += sidlen(owner); } if (group) { wset_sdgroupoff(&ntsd, off); off += sidlen(group); } if (sacl) { flags |= SD_SACL_PRESENT; wset_sdsacloff(&ntsd, off); off += acllen(sacl); } if (dacl) { flags |= SD_DACL_PRESENT; wset_sddacloff(&ntsd, off); } wset_sdflags(&ntsd, flags); mb_put_mem(mbp, (caddr_t)&ntsd, sizeof ntsd, MB_MSYSTEM); if (owner) mb_put_mem(mbp, (caddr_t)owner, sidlen(owner), MB_MSYSTEM); if (group) mb_put_mem(mbp, (caddr_t)group, sidlen(group), MB_MSYSTEM); if (sacl) mb_put_mem(mbp, (caddr_t)sacl, acllen(sacl), MB_MSYSTEM); if (dacl) mb_put_mem(mbp, (caddr_t)dacl, acllen(dacl), MB_MSYSTEM); ntp->nt_maxpcount = 0; ntp->nt_maxdcount = 0; error = smb_nt_request(ntp); smb_nt_done(ntp); return (error); }