smb_smb.c revision 4bff34e37def8a90f9194d81bc345c52ba20086a
/*
* Copyright (c) 2000-2001 Boris Popov
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: smb_smb.c,v 1.35.100.2 2005/06/02 00:55:39 lindak Exp $
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* various SMB requests. Most of the routines merely packs data into mbufs.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/random.h>
#include <sys/note.h>
#include <sys/cmn_err.h>
#ifdef APPLE
#include <sys/smb_apple.h>
#include <sys/utfconv.h>
#else
#include <netsmb/smb_osdep.h>
#endif
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
/*
* Largest size to use with LARGE_READ/LARGE_WRITE.
* Specs say up to 64k data bytes, but Windows traffic
* uses 60k... no doubt for some good reason.
* (Probably to keep 4k block alignment.)
* XXX: Move to smb.h maybe?
*/
#define SMB_MAX_LARGE_RW_SIZE (60*1024)
/*
* Default timeout values, all in seconds.
* Make these tunable (only via mdb for now).
*/
int smb_timo_notice = 15;
int smb_timo_default = 30; /* was SMB_DEFRQTIMO */
int smb_timo_open = 45;
int smb_timo_read = 45;
int smb_timo_write = 60; /* was SMBWRTTIMO */
int smb_timo_append = 90;
static int smb_smb_read(struct smb_share *ssp, u_int16_t fid,
int *len, int *rresid, uio_t *uiop, struct smb_cred *scred, int timo);
static int smb_smb_write(struct smb_share *ssp, u_int16_t fid,
int *len, int *rresid, uio_t *uiop, struct smb_cred *scred, int timo);
struct smb_dialect {
int d_id;
const char *d_name;
};
smb_unichar smb_unieol = 0;
static struct smb_dialect smb_dialects[] = {
{SMB_DIALECT_CORE, "PC NETWORK PROGRAM 1.0"},
{SMB_DIALECT_LANMAN1_0, "LANMAN1.0"},
{SMB_DIALECT_LANMAN2_0, "LM1.2X002"},
{SMB_DIALECT_LANMAN2_1, "LANMAN2.1"},
{SMB_DIALECT_NTLM0_12, "NT LM 0.12"},
{-1, NULL}
};
#define SMB_DIALECT_MAX \
(sizeof (smb_dialects) / sizeof (struct smb_dialect) - 2)
/*
* Number of seconds between 1970 and 1601 year
*/
const u_int64_t DIFF1970TO1601 = 11644473600ULL;
void
smb_time_local2server(struct timespec *tsp, int tzoff, long *seconds)
{
/*
* XXX - what if we connected to the server when it was in
* daylight savings/summer time and we've subsequently switched
* to standard time, or vice versa, so that the time zone
* offset we got from the server is now wrong?
*/
*seconds = tsp->tv_sec - tzoff * 60;
/* - tz.tz_minuteswest * 60 - (wall_cmos_clock ? adjkerntz : 0) */
}
void
smb_time_server2local(ulong_t seconds, int tzoff, struct timespec *tsp)
{
/*
* XXX - what if we connected to the server when it was in
* daylight savings/summer time and we've subsequently switched
* to standard time, or vice versa, so that the time zone
* offset we got from the server is now wrong?
*/
tsp->tv_sec = seconds + tzoff * 60;
/* + tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); */
tsp->tv_nsec = 0;
}
/*
* Time from server comes as UTC, so no need to use tz
*/
/*ARGSUSED*/
void
smb_time_NT2local(u_int64_t nsec, int tzoff, struct timespec *tsp)
{
smb_time_server2local(nsec / 10000000 - DIFF1970TO1601, 0, tsp);
}
/*ARGSUSED*/
void
smb_time_local2NT(struct timespec *tsp, int tzoff, u_int64_t *nsec)
{
long seconds;
smb_time_local2server(tsp, 0, &seconds);
*nsec = (((u_int64_t)(seconds) & ~1) + DIFF1970TO1601) *
(u_int64_t)10000000;
}
#if defined(NOICONVSUPPORT) || defined(lint)
extern int iconv_open(const char *to, const char *from, void **handle);
extern int iconv_close(void *handle);
#endif
int
smb_smb_negotiate(struct smb_vc *vcp, struct smb_cred *scred)
{
struct smb_dialect *dp;
struct smb_sopt *sp = NULL;
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
u_int8_t wc, stime[8], sblen;
u_int16_t dindex, tw, tw1, swlen, bc;
int error;
int unicode = 0;
char *servercs;
void *servercshandle = NULL;
void *localcshandle = NULL;
u_int16_t toklen;
vcp->vc_hflags = SMB_FLAGS_CASELESS; /* XXX on Unix? */
/*
* Make sure SMB_FLAGS2_UNICODE is "off" so mb_put_dstring
* marshalls the dialect strings in plain ascii.
*/
vcp->vc_hflags2 &= ~SMB_FLAGS2_UNICODE;
vcp->vc_hflags2 |= SMB_FLAGS2_ERR_STATUS;
SMB_VC_LOCK(vcp);
vcp->vc_flags &= ~(SMBV_ENCRYPT);
SMB_VC_UNLOCK(vcp);
sp = &vcp->vc_sopt;
bzero(sp, sizeof (struct smb_sopt));
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_NEGOTIATE, scred, &rqp);
if (error)
return (error);
smb_rq_getrequest(rqp, &mbp);
smb_rq_wstart(rqp);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
for (dp = smb_dialects; dp->d_id != -1; dp++) {
mb_put_uint8(mbp, SMB_DT_DIALECT);
smb_put_dstring(mbp, vcp, dp->d_name, SMB_CS_NONE);
}
smb_rq_bend(rqp);
/*
* This request should not wait for
* connection state changes, etc.
*/
rqp->sr_flags |= SMBR_INTERNAL;
error = smb_rq_simple(rqp);
SMBSDEBUG("%d\n", error);
if (error)
goto bad;
smb_rq_getreply(rqp, &mdp);
do {
error = md_get_uint8(mdp, &wc);
if (error)
break;
error = md_get_uint16le(mdp, &dindex);
if (error)
break;
error = EBADRPC;
if (dindex > SMB_DIALECT_MAX) {
SMBERROR(
"Don't know how to talk with server %s (%d)\n",
vcp->vc_srvname, dindex);
break;
}
dp = smb_dialects + dindex;
if (dindex < SMB_DIALECT_MAX) {
SMBERROR(
"Server %s negotiated old dialect (%s)\n",
vcp->vc_srvname, dp->d_name);
}
sp->sv_proto = dp->d_id;
SMBSDEBUG("Dialect %s (%d, %d)\n", dp->d_name, dindex, wc);
if (dp->d_id >= SMB_DIALECT_NTLM0_12) {
if (wc != 17)
break;
md_get_uint8(mdp, &sp->sv_sm);
md_get_uint16le(mdp, &sp->sv_maxmux);
md_get_uint16le(mdp, &sp->sv_maxvcs);
md_get_uint32le(mdp, &sp->sv_maxtx);
md_get_uint32le(mdp, &sp->sv_maxraw);
md_get_uint32le(mdp, &sp->sv_skey);
md_get_uint32le(mdp, &sp->sv_caps);
md_get_mem(mdp, (char *)stime, 8, MB_MSYSTEM);
md_get_uint16le(mdp, (u_int16_t *)&sp->sv_tz);
md_get_uint8(mdp, &sblen);
error = md_get_uint16le(mdp, &bc);
if (error)
break;
if (sp->sv_sm & SMB_SM_SIGS_REQUIRE)
SMBERROR("server configuration requires "
"packet signing, which we dont support: "
"sp->sv_sm %d\n", sp->sv_sm);
if (sp->sv_caps & SMB_CAP_UNICODE) {
SMB_VC_LOCK(vcp);
vcp->vc_flags |= SMBV_UNICODE;
SMB_VC_UNLOCK(vcp);
unicode = 1;
}
if (!(sp->sv_caps & SMB_CAP_STATUS32)) {
/*
* They don't do NT error codes.
*
* If we send requests with
* SMB_FLAGS2_ERR_STATUS set in
* Flags2, Windows 98, at least,
* appears to send replies with that
* bit set even though it sends back
* DOS error codes. (They probably
* just use the request header as
* a template for the reply header,
* and don't bother clearing that bit.)
*
* Therefore, we clear that bit in
* our vc_hflags2 field.
*/
vcp->vc_hflags2 &= ~SMB_FLAGS2_ERR_STATUS;
}
if (dp->d_id == SMB_DIALECT_NTLM0_12 &&
sp->sv_maxtx < 4096 &&
(sp->sv_caps & SMB_CAP_NT_SMBS) == 0) {
SMB_VC_LOCK(vcp);
vcp->vc_flags |= SMBV_WIN95;
SMB_VC_UNLOCK(vcp);
SMBSDEBUG("Win95 detected\n");
}
/*
* 3 cases here:
*
* 1) Extended security.
* Read bc bytes below for security blob.
* Note that we DON'T put the Caps flag in outtok.
* outtoklen = bc
*
* 2) No extended security, have challenge data and
* possibly a domain name (which might be zero
* bytes long, meaning "missing").
* Copy challenge stuff to vcp->vc_ch (sblen bytes),
* then copy Cap flags and domain name (bc-sblen
* bytes) to outtok.
* outtoklen = bc-sblen+4, where the 4 is for the
* Caps flag.
*
* 3) No extended security, no challenge data, just
* possibly a domain name.
* Copy Capsflags and domain name (bc) to outtok.
* outtoklen = bc+4, where 4 is for the Caps flag
*/
/*
* Sanity check: make sure the challenge length
* isn't bigger than the byte count.
*/
if (sblen > bc) {
error = EBADRPC;
break;
}
toklen = bc;
if (sblen && sblen <= SMB_MAXCHALLENGELEN &&
sp->sv_sm & SMB_SM_ENCRYPT) {
error = md_get_mem(mdp,
(char *)vcp->vc_challenge,
sblen, MB_MSYSTEM);
if (error)
break;
vcp->vc_chlen = sblen;
toklen -= sblen;
SMB_VC_LOCK(vcp);
vcp->vc_flags |= SMBV_ENCRYPT;
SMB_VC_UNLOCK(vcp);
}
/*
* For servers that don't support unicode
* there are 2 things we could do:
* 1) Pass the server Caps flags up to the
* user level so the logic up there will
* know whether the domain name is unicode
* (this is what I did).
* 2) Try to convert the non-unicode string
* to unicode. This doubles the length of
* the outtok buffer and would be guessing that
* the string was single-byte ascii, and that
* might be wrong. Why ask for trouble?
*/
/* Warning: NetApp may omit the GUID */
if (!(sp->sv_caps & SMB_CAP_EXT_SECURITY)) {
/*
* No extended security.
* Stick domain name, if present,
* and caps in outtok.
*/
toklen = toklen + 4; /* space for Caps flags */
vcp->vc_outtoklen = toklen;
vcp->vc_outtok = kmem_alloc(toklen, KM_SLEEP);
/* first store server capability bits */
/*LINTED*/
ASSERT(vcp->vc_outtok ==
(caddr_t)(((u_int32_t *)vcp->vc_outtok)));
/*LINTED*/
*(u_int32_t *)(vcp->vc_outtok) = sp->sv_caps;
/*
* Then store the domain name if present;
* be sure to subtract 4 from the length
* for the Caps flag.
*/
if (toklen > 4) {
error = md_get_mem(mdp,
vcp->vc_outtok+4, toklen-4,
MB_MSYSTEM);
}
} else {
/*
* Extended security.
* Stick the rest of the buffer in outtok.
*/
vcp->vc_outtoklen = toklen;
vcp->vc_outtok = kmem_alloc(toklen, KM_SLEEP);
error = md_get_mem(mdp, vcp->vc_outtok, toklen,
MB_MSYSTEM);
}
break;
}
vcp->vc_hflags2 &= ~(SMB_FLAGS2_EXT_SEC|SMB_FLAGS2_DFS|
SMB_FLAGS2_ERR_STATUS|SMB_FLAGS2_UNICODE);
if (dp->d_id > SMB_DIALECT_CORE) {
md_get_uint16le(mdp, &tw);
sp->sv_sm = (uchar_t)tw;
md_get_uint16le(mdp, &tw);
sp->sv_maxtx = tw;
md_get_uint16le(mdp, &sp->sv_maxmux);
md_get_uint16le(mdp, &sp->sv_maxvcs);
md_get_uint16le(mdp, &tw); /* rawmode */
md_get_uint32le(mdp, &sp->sv_skey);
if (wc == 13) { /* >= LANMAN1 */
md_get_uint16(mdp, &tw); /* time */
md_get_uint16(mdp, &tw1); /* date */
md_get_uint16le(mdp, (u_int16_t *)&sp->sv_tz);
md_get_uint16le(mdp, &swlen);
if (swlen > SMB_MAXCHALLENGELEN)
break;
md_get_uint16(mdp, NULL); /* mbz */
if (md_get_uint16le(mdp, &bc) != 0)
break;
if (bc < swlen)
break;
if (swlen && (sp->sv_sm & SMB_SM_ENCRYPT)) {
error = md_get_mem(mdp,
(char *)vcp->vc_challenge,
swlen, MB_MSYSTEM);
if (error)
break;
vcp->vc_chlen = swlen;
SMB_VC_LOCK(vcp);
vcp->vc_flags |= SMBV_ENCRYPT;
SMB_VC_UNLOCK(vcp);
}
}
} else { /* an old CORE protocol */
vcp->vc_hflags2 &= ~SMB_FLAGS2_KNOWS_LONG_NAMES;
sp->sv_maxmux = 1;
}
error = 0;
/*LINTED*/
} while (0);
if (error == 0) {
uint32_t x;
/*
* Maximum outstanding requests.
*/
if (vcp->vc_maxmux < 1)
vcp->vc_maxmux = 1;
/*
* Max VCs between server and client.
* We only use one.
*/
vcp->vc_maxvcs = sp->sv_maxvcs;
if (vcp->vc_maxvcs < 1)
vcp->vc_maxvcs = 1;
/*
* Maximum transfer size.
* Sanity checks:
*
* Spec. says lower limit is 1024. OK.
*
* Let's be conservative about an upper limit here.
* Win2k uses 16644 (and others) so 32k should be a
* reasonable sanity limit for this value.
*
* Note that this limit does NOT affect READX/WRITEX
* with CAP_LARGE_xxx, which we nearly always use.
*/
vcp->vc_txmax = sp->sv_maxtx;
if (vcp->vc_txmax < 1024)
vcp->vc_txmax = 1024;
if (vcp->vc_txmax > 0x8000)
vcp->vc_txmax = 0x8000;
/*
* Max read/write sizes, WITHOUT overhead.
* This is just the payload size, so we must
* leave room for the SMB headers, etc.
*
* With CAP_LARGE_xxx, always use 60k.
* Otherwise use the vc_txmax value, but
* reduced and rounded down. Tricky bit:
*
* Servers typically give us a value that's
* some nice "round" number, i.e 0x4000 plus
* some overhead, i.e. Win2k: 16644==0x4104
* Subtract for the SMB header (32) and the
* SMB command word and byte vectors (34?),
* then round down to a 512 byte multiple.
*/
x = (vcp->vc_txmax - 68) & 0xFE00;
if (sp->sv_caps & SMB_CAP_LARGE_READX)
vcp->vc_rxmax = SMB_MAX_LARGE_RW_SIZE;
else
vcp->vc_rxmax = x;
if (sp->sv_caps & SMB_CAP_LARGE_WRITEX)
vcp->vc_wxmax = SMB_MAX_LARGE_RW_SIZE;
else
vcp->vc_wxmax = x;
SMBSDEBUG("TZ = %d\n", sp->sv_tz);
SMBSDEBUG("CAPS = %x\n", sp->sv_caps);
SMBSDEBUG("maxmux = %d\n", vcp->vc_maxmux);
SMBSDEBUG("maxvcs = %d\n", vcp->vc_maxvcs);
SMBSDEBUG("txmax = %d\n", vcp->vc_txmax);
SMBSDEBUG("rxmax = %d\n", vcp->vc_rxmax);
SMBSDEBUG("wxmax = %d\n", vcp->vc_wxmax);
}
/*
* If the server supports Unicode, set up to use Unicode
* when talking to them. Othewise, use code page 437.
*/
if (unicode)
servercs = "ucs-2";
else {
/*
* todo: if we can't determine the server's encoding, we
* need to try a best-guess here.
*/
servercs = "cp437";
}
#if defined(NOICONVSUPPORT) || defined(lint)
/*
* REVISIT
*/
error = iconv_open(servercs, "utf-8", &servercshandle);
if (error != 0)
goto bad;
error = iconv_open("utf-8", servercs, &localcshandle);
if (error != 0) {
iconv_close(servercshandle);
goto bad;
}
if (vcp->vc_toserver)
iconv_close(vcp->vc_toserver);
if (vcp->vc_tolocal)
iconv_close(vcp->vc_tolocal);
vcp->vc_toserver = servercshandle;
vcp->vc_tolocal = localcshandle;
#endif
if (unicode)
vcp->vc_hflags2 |= SMB_FLAGS2_UNICODE;
bad:
smb_rq_done(rqp);
return (error);
}
static void
get_ascii_password(struct smb_vc *vcp, int upper, char *pbuf)
{
const char *pw = smb_vc_getpass(vcp);
if (upper)
smb_toupper(pw, pbuf, SMB_MAXPASSWORDLEN);
else
strncpy(pbuf, pw, SMB_MAXPASSWORDLEN);
pbuf[SMB_MAXPASSWORDLEN] = '\0';
}
#ifdef APPLE
static void
get_unicode_password(struct smb_vc *vcp, char *pbuf)
{
strncpy(pbuf, smb_vc_getpass(vcp), SMB_MAXPASSWORDLEN);
pbuf[SMB_MAXPASSWORDLEN] = '\0';
}
#endif
/*ARGSUSED*/
static uchar_t *
add_name_to_blob(uchar_t *blobnames, struct smb_vc *vcp, const uchar_t *name,
size_t namelen, int nametype, int uppercase)
{
struct ntlmv2_namehdr namehdr;
char *namebuf;
u_int16_t *uninamebuf;
size_t uninamelen;
if (name != NULL) {
uninamebuf = kmem_alloc(2 * namelen, KM_SLEEP);
if (uppercase) {
namebuf = kmem_alloc(namelen + 1, KM_SLEEP);
smb_toupper((const char *)name, namebuf, namelen);
namebuf[namelen] = '\0';
uninamelen = smb_strtouni(uninamebuf, namebuf, namelen,
UCONV_IGNORE_NULL);
kmem_free(namebuf, namelen + 1);
} else {
uninamelen = smb_strtouni(uninamebuf, (char *)name,
namelen, UCONV_IGNORE_NULL);
}
} else {
uninamelen = 0;
uninamebuf = NULL;
}
namehdr.type = htoles(nametype);
namehdr.len = htoles(uninamelen);
bcopy(&namehdr, blobnames, sizeof (namehdr));
blobnames += sizeof (namehdr);
if (uninamebuf != NULL) {
bcopy(uninamebuf, blobnames, uninamelen);
blobnames += uninamelen;
kmem_free(uninamebuf, namelen * 2);
}
return (blobnames);
}
static uchar_t *
make_ntlmv2_blob(struct smb_vc *vcp, u_int64_t client_nonce, size_t *bloblen)
{
uchar_t *blob;
size_t blobsize;
size_t domainlen, srvlen;
struct ntlmv2_blobhdr *blobhdr;
struct timespec now;
u_int64_t timestamp;
uchar_t *blobnames;
ptrdiff_t diff;
/*
* XXX - the information at
*
* http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
*
* says that the "target information" comes from the Type 2 message,
* but, as we're not doing NTLMSSP, we don't have that.
*
* Should we use the names from the NegProt response? Can we trust
* the NegProt response? (I've seen captures where the primary
* domain name has an extra byte in front of it.)
*
* For now, we don't trust it - we use vcp->vc_domain and
* vcp->vc_srvname, instead. We upper-case them and convert
* them to Unicode, as that's what's supposed to be in the blob.
*/
domainlen = strlen(vcp->vc_domain);
srvlen = strlen(vcp->vc_srvname);
blobsize = sizeof (struct ntlmv2_blobhdr)
+ 3*sizeof (struct ntlmv2_namehdr) + 4 + 2*domainlen + 2*srvlen;
blob = kmem_zalloc(blobsize, KM_SLEEP);
/*LINTED*/
ASSERT(blob == (uchar_t *)((struct ntlmv2_blobhdr *)blob));
/*LINTED*/
blobhdr = (struct ntlmv2_blobhdr *)blob;
blobhdr->header = htolel(0x00000101);
gethrestime(&now);
smb_time_local2NT(&now, 0, &timestamp);
blobhdr->timestamp = htoleq(timestamp);
blobhdr->client_nonce = client_nonce;
blobnames = blob + sizeof (struct ntlmv2_blobhdr);
blobnames = add_name_to_blob(blobnames, vcp, (uchar_t *)vcp->vc_domain,
domainlen, NAMETYPE_DOMAIN_NB, 1);
blobnames = add_name_to_blob(blobnames, vcp, (uchar_t *)vcp->vc_srvname,
srvlen, NAMETYPE_MACHINE_NB, 1);
blobnames = add_name_to_blob(blobnames, vcp, NULL, 0, NAMETYPE_EOL, 0);
diff = (intptr_t)blobnames - (intptr_t)blob;
ASSERT(diff == (ptrdiff_t)((size_t)diff));
*bloblen = (size_t)diff;
return (blob);
}
/*
* See radar 4134676. This define helps us avoid how a certain old server
* grants limited Guest access when we try NTLMv2, but works fine with NTLM.
* The fingerprint we are looking for here is DOS error codes and no-Unicode.
* Note XP grants Guest access but uses Unicode and NT error codes.
*/
#define smb_antique(rqp) (!((rqp)->sr_rpflags2 & SMB_FLAGS2_ERR_STATUS) && \
!((rqp)->sr_rpflags2 & SMB_FLAGS2_UNICODE))
/*
* When not doing Kerberos, we can try, in order:
*
* NTLMv2
* NTLM with the ASCII password not upper-cased
* NTLM with the ASCII password upper-cased
*
* if the server supports encrypted passwords, or
*
* plain-text with the ASCII password not upper-cased
* plain-text with the ASCII password upper-cased
*
* if it doesn't.
*/
#define STATE_NTLMV2 0
#define STATE_NOUCPW 1
#define STATE_UCPW 2
int
smb_smb_ssnsetup(struct smb_vc *vcp, struct smb_cred *scred)
{
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
u_int8_t wc;
int minauth;
smb_uniptr unipp = NULL, ntencpass = NULL;
char *pp = NULL, *up = NULL, *ucup = NULL, *ucdp = NULL;
char *pbuf = NULL;
char *encpass = NULL;
int error = 0;
size_t plen = 0, uniplen = 0, uniplen2 = 0, tmplen;
size_t ucup_sl = 0, ucdp_sl = 0;
int state;
size_t ntlmv2_bloblen;
uchar_t *ntlmv2_blob;
u_int64_t client_nonce;
u_int32_t caps;
u_int16_t bl; /* BLOB length */
u_int16_t saveflags2 = vcp->vc_hflags2;
void * savetoserver = vcp->vc_toserver;
u_int16_t action;
int declinedguest = 0;
static const char NativeOS[] = "Solaris";
static const char LanMan[] = "NETSMB";
/*
* Most of the "capability" bits we offer should be copied
* from those offered by the server, with a mask applied.
* This is the mask of capabilies copied from the server.
* Some others get special handling below.
*/
static const uint32_t caps_mask =
SMB_CAP_UNICODE |
SMB_CAP_LARGE_FILES |
SMB_CAP_NT_SMBS |
SMB_CAP_STATUS32 |
SMB_CAP_LARGE_READX |
SMB_CAP_LARGE_WRITEX;
caps = vcp->vc_sopt.sv_caps & caps_mask;
/* No unicode unless server supports and encryption on */
if (!((vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) &&
(vcp->vc_flags & SMBV_UNICODE))) {
vcp->vc_hflags2 &= 0xffff - SMB_FLAGS2_UNICODE;
vcp->vc_toserver = 0;
}
minauth = vcp->vc_vopt & SMBVOPT_MINAUTH;
if (vcp->vc_intok) {
if (vcp->vc_intoklen > 65536 ||
!(vcp->vc_hflags2 & SMB_FLAGS2_EXT_SEC) ||
SMB_DIALECT(vcp) < SMB_DIALECT_NTLM0_12) {
error = EINVAL;
goto ssn_exit;
}
vcp->vc_smbuid = 0;
}
/*
* Try only plain text passwords.
*/
if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) {
state = STATE_NTLMV2; /* try NTLMv2 first */
} else {
state = STATE_NOUCPW; /* try plain-text mixed-case first */
}
again:
if (!vcp->vc_intok)
vcp->vc_smbuid = SMB_UID_UNKNOWN;
if (!vcp->vc_intok) {
/*
* We're not doing extended security, which, for
* now, means we're not doing Kerberos.
* Fail if the minimum authentication level is
* Kerberos.
*/
if (minauth >= SMBVOPT_MINAUTH_KERBEROS) {
error = EAUTH;
goto ssn_exit;
}
if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) {
/*
* Server wants encrypted passwords.
*/
if (state > STATE_NTLMV2) {
/*
* We tried NTLMv2 in STATE_NTLMV2.
* Shall we allow fallback? (to NTLM)
*/
if (minauth >= SMBVOPT_MINAUTH_NTLMV2) {
error = EAUTH;
goto ssn_exit;
}
}
if (state > STATE_NOUCPW) {
/*
* We tried NTLM in STATE_NOUCPW.
* No need to try it again.
*/
error = EAUTH;
goto ssn_exit;
}
} else {
/*
* Plain-text passwords.
* Fail if the minimum authentication level is
* LM or better.
*/
if (minauth > SMBVOPT_MINAUTH_NTLM) {
error = EAUTH;
goto ssn_exit;
}
}
}
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_SESSION_SETUP_ANDX,
scred, &rqp);
if (error)
goto ssn_exit;
/*
* Domain name must be upper-case, as that's what's used
* when computing LMv2 and NTLMv2 responses - and, for NTLMv2,
* the domain name in the request has to be upper-cased as well.
* (That appears not to be the case for the user name. Go
* figure.)
*
* don't need to uppercase domain string. It's already uppercase UTF-8.
*/
ucdp_sl = strlen(vcp->vc_domain);
ucdp = kmem_zalloc(ucdp_sl + 1, KM_SLEEP);
memcpy(ucdp, vcp->vc_domain, ucdp_sl + 1);
if (vcp->vc_intok) {
caps |= SMB_CAP_EXT_SECURITY;
} else if (!(vcp->vc_sopt.sv_sm & SMB_SM_USER)) {
/*
* In the share security mode password will be used
* only in the tree authentication
*/
pp = "";
plen = 1;
unipp = &smb_unieol;
uniplen = sizeof (smb_unieol);
} else {
pbuf = kmem_alloc(SMB_MAXPASSWORDLEN + 1, KM_SLEEP);
if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) {
if (state == STATE_NTLMV2) {
/*
* Compute the LMv2 and NTLMv2 responses,
* derived from the challenge, the user name,
* the domain/workgroup into which we're
* logging, and the Unicode password.
*/
/*
* Construct the client nonce by getting
* a bunch of random data.
*/
(void) random_get_pseudo_bytes((void *)
&client_nonce, sizeof (client_nonce));
/*
* Convert the user name to upper-case, as
* that's what's used when computing LMv2
* and NTLMv2 responses.
*/
ucup_sl = strlen(vcp->vc_username);
ucup = kmem_alloc(ucup_sl + 1, KM_SLEEP);
smb_toupper((const char *)vcp->vc_username,
ucup, ucup_sl);
ucup[ucup_sl] = '\0';
/*
* Compute the LMv2 response, derived
* from the server challenge, the
* user name, the domain/workgroup
* into which we're logging, the
* client nonce, and the NT hash.
*/
smb_ntlmv2response(vcp->vc_nthash,
(uchar_t *)ucup, (uchar_t *)ucdp,
vcp->vc_challenge,
(uchar_t *)&client_nonce, 8,
(uchar_t **)&encpass, &plen);
pp = encpass;
/*
* Construct the blob.
*/
ntlmv2_blob = make_ntlmv2_blob(vcp,
client_nonce, &ntlmv2_bloblen);
/*
* Compute the NTLMv2 response, derived
* from the server challenge, the
* user name, the domain/workgroup
* into which we're logging, the
* blob, and the NT hash.
*/
smb_ntlmv2response(vcp->vc_nthash,
(uchar_t *)ucup, (uchar_t *)ucdp,
vcp->vc_challenge,
ntlmv2_blob, ntlmv2_bloblen,
(uchar_t **)&ntencpass, &uniplen);
uniplen2 = uniplen;
unipp = ntencpass;
tmplen = plen;
kmem_free(ucup, ucup_sl + 1);
kmem_free((char *)ntlmv2_blob,
sizeof (struct ntlmv2_blobhdr) +
3 * sizeof (struct ntlmv2_namehdr) +
4 +
2 * strlen(vcp->vc_domain) +
2 * strlen(vcp->vc_srvname));
} else {
plen = 24;
encpass = kmem_zalloc(plen, KM_SLEEP);
/*
* Compute the LM response, derived
* from the challenge and the ASCII
* password.
*/
if (minauth < SMBVOPT_MINAUTH_NTLM) {
smb_lmresponse(vcp->vc_lmhash,
vcp->vc_challenge,
(uchar_t *)encpass);
}
pp = encpass;
/*
* Compute the NTLM response, derived from
* the challenge and the NT hash.
*/
uniplen = 24;
uniplen2 = uniplen;
ntencpass = kmem_alloc(uniplen, KM_SLEEP);
smb_lmresponse(vcp->vc_nthash,
vcp->vc_challenge,
(uchar_t *)ntencpass);
unipp = ntencpass;
}
} else {
/*
* We try w/o uppercasing first so Samba mixed case
* passwords work. If that fails, we come back and
* try uppercasing to satisfy OS/2 and Windows for
* Workgroups.
*/
get_ascii_password(vcp, (state == STATE_UCPW), pbuf);
plen = strlen(pbuf) + 1;
pp = pbuf;
uniplen = plen * 2;
uniplen2 = uniplen;
ntencpass = kmem_alloc(uniplen, KM_SLEEP);
(void) smb_strtouni(ntencpass, smb_vc_getpass(vcp),
0, 0);
plen--;
/*
* The uniplen is zeroed because Samba cannot deal
* with this 2nd cleartext password. This Samba
* "bug" is actually a workaround for problems in
* Microsoft clients.
*/
uniplen = 0; /* -= 2 */
unipp = ntencpass;
}
}
smb_rq_wstart(rqp);
mbp = &rqp->sr_rq;
up = vcp->vc_username;
/*
* If userid is null we are attempting anonymous browse login
* so passwords must be zero length.
*/
if (*up == '\0') {
plen = uniplen = 0;
}
mb_put_uint8(mbp, 0xff);
mb_put_uint8(mbp, 0);
mb_put_uint16le(mbp, 0);
mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxtx);
mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxmux);
mb_put_uint16le(mbp, vcp->vc_number);
mb_put_uint32le(mbp, vcp->vc_sopt.sv_skey);
if ((SMB_DIALECT(vcp)) < SMB_DIALECT_NTLM0_12) {
mb_put_uint16le(mbp, plen);
mb_put_uint32le(mbp, 0);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_mem(mbp, pp, plen, MB_MSYSTEM);
smb_put_dstring(mbp, vcp, up, SMB_CS_NONE); /* user */
smb_put_dstring(mbp, vcp, ucdp, SMB_CS_NONE); /* domain */
} else {
if (vcp->vc_intok) {
mb_put_uint16le(mbp, vcp->vc_intoklen);
mb_put_uint32le(mbp, 0); /* reserved */
mb_put_uint32le(mbp, caps); /* my caps */
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_mem(mbp, vcp->vc_intok, vcp->vc_intoklen,
MB_MSYSTEM); /* security blob */
} else {
mb_put_uint16le(mbp, plen);
mb_put_uint16le(mbp, uniplen);
mb_put_uint32le(mbp, 0); /* reserved */
mb_put_uint32le(mbp, caps); /* my caps */
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_mem(mbp, pp, plen, MB_MSYSTEM); /* password */
mb_put_mem(mbp, (caddr_t)unipp, uniplen, MB_MSYSTEM);
smb_put_dstring(mbp, vcp, up, SMB_CS_NONE); /* user */
smb_put_dstring(mbp, vcp, ucdp, SMB_CS_NONE); /* dom */
}
}
smb_put_dstring(mbp, vcp, NativeOS, SMB_CS_NONE); /* OS */
smb_put_dstring(mbp, vcp, LanMan, SMB_CS_NONE); /* LAN Mgr */
smb_rq_bend(rqp);
if (ntencpass) {
kmem_free(ntencpass, uniplen2);
ntencpass = NULL;
}
if (encpass) {
kmem_free(encpass, 24);
encpass = NULL;
}
if (ucdp) {
kmem_free(ucdp, ucdp_sl + 1);
ucdp = NULL;
}
/*
* This request should not wait for
* connection state changes, etc.
*/
rqp->sr_flags |= SMBR_INTERNAL;
error = smb_rq_simple_timed(rqp, SMBSSNSETUPTIMO);
SMBSDEBUG("%d\n", error);
if (error) {
if (rqp->sr_errclass == ERRDOS && rqp->sr_serror == ERRnoaccess)
error = EAUTH;
if (!(rqp->sr_errclass == ERRDOS &&
rqp->sr_serror == ERRmoredata))
goto bad;
}
vcp->vc_smbuid = rqp->sr_rpuid;
smb_rq_getreply(rqp, &mdp);
do {
error = md_get_uint8(mdp, &wc);
if (error)
break;
error = EBADRPC;
if (vcp->vc_intok) {
if (wc != 4)
break;
} else if (wc != 3)
break;
md_get_uint8(mdp, NULL); /* secondary cmd */
md_get_uint8(mdp, NULL); /* mbz */
md_get_uint16le(mdp, NULL); /* andxoffset */
md_get_uint16le(mdp, &action); /* action */
if (vcp->vc_intok)
md_get_uint16le(mdp, &bl); /* ext security */
md_get_uint16le(mdp, NULL); /* byte count */
if (vcp->vc_intok) {
vcp->vc_outtoklen = bl;
vcp->vc_outtok = kmem_alloc(bl, KM_SLEEP);
error = md_get_mem(mdp, vcp->vc_outtok, bl, MB_MSYSTEM);
if (error)
break;
}
/* server OS, LANMGR, & Domain here */
error = 0;
/*LINTED*/
} while (0);
bad:
if (encpass) {
kmem_free(encpass, tmplen);
encpass = NULL;
}
if (pbuf) {
kmem_free(pbuf, SMB_MAXPASSWORDLEN + 1);
pbuf = NULL;
}
if (vcp->vc_sopt.sv_sm & SMB_SM_USER && !vcp->vc_intok &&
(error || (*up != '\0' && action & SMB_ACT_GUEST &&
state == STATE_NTLMV2 && smb_antique(rqp)))) {
/*
* We're doing user-level authentication (so we are actually
* sending authentication stuff over the wire), and we're
* not doing extended security, and the stuff we tried
* failed (or we we're trying to login a real user but
* got granted guest access instead.)
*/
if (!error)
declinedguest = 1;
/*
* Should we try the next type of authentication?
*/
if (state < STATE_UCPW) {
/*
* Yes, we still have more to try.
*/
state++;
smb_rq_done(rqp);
goto again;
}
}
smb_rq_done(rqp);
ssn_exit:
if (error && declinedguest)
SMBERROR("we declined ntlmv2 guest access. errno will be %d\n",
error);
/* Restore things we changed and return */
vcp->vc_hflags2 = saveflags2;
vcp->vc_toserver = savetoserver;
return (error);
}
int
smb_smb_ssnclose(struct smb_vc *vcp, struct smb_cred *scred)
{
struct smb_rq *rqp;
struct mbchain *mbp;
int error;
if (vcp->vc_smbuid == SMB_UID_UNKNOWN)
return (0);
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_LOGOFF_ANDX, scred, &rqp);
if (error)
return (error);
mbp = &rqp->sr_rq;
smb_rq_wstart(rqp);
mb_put_uint8(mbp, 0xff);
mb_put_uint8(mbp, 0);
mb_put_uint16le(mbp, 0);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
smb_rq_bend(rqp);
/*
* Run this with a relatively short timeout.
* We don't really care about the result,
* as we're just trying to play nice and
* "say goodbye" before we hangup.
* XXX: Add SMBLOGOFFTIMO somewhere?
*/
error = smb_rq_simple_timed(rqp, 5);
SMBSDEBUG("%d\n", error);
smb_rq_done(rqp);
return (error);
}
static char smb_any_share[] = "?????";
static char *
smb_share_typename(int stype)
{
char *pp;
switch (stype) {
case STYPE_DISKTREE:
pp = "A:";
break;
case STYPE_PRINTQ:
pp = smb_any_share; /* can't use LPT: here... */
break;
case STYPE_DEVICE:
pp = "COMM";
break;
case STYPE_IPC:
pp = "IPC";
break;
default:
pp = smb_any_share;
break;
}
return (pp);
}
int
smb_smb_treeconnect(struct smb_share *ssp, struct smb_cred *scred)
{
struct smb_vc *vcp;
struct smb_rq rq, *rqp = &rq;
struct mbchain *mbp;
char *pp, *pbuf, *encpass;
const char *pw;
uchar_t hash[SMB_PWH_MAX];
int error, plen, caseopt;
int upper = 0;
again:
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.
*
* No longer need to set ssp->ss_tid
* here, but it's harmless enough.
*/
ssp->ss_tid = SMB_TID_UNKNOWN;
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_TREE_CONNECT_ANDX,
scred, &rqp);
if (error)
return (error);
caseopt = SMB_CS_NONE;
if (vcp->vc_sopt.sv_sm & SMB_SM_USER) {
plen = 1;
pp = "";
pbuf = NULL;
encpass = NULL;
} else {
pbuf = kmem_alloc(SMB_MAXPASSWORDLEN + 1, KM_SLEEP);
encpass = kmem_alloc(24, KM_SLEEP);
pw = smb_share_getpass(ssp);
/*
* We try w/o uppercasing first so Samba mixed case
* passwords work. If that fails we come back and try
* uppercasing to satisfy OS/2 and Windows for Workgroups.
*/
if (upper++) {
smb_toupper(pw, pbuf, SMB_MAXPASSWORDLEN);
smb_oldlm_hash(pw, hash);
} else {
strncpy(pbuf, pw, SMB_MAXPASSWORDLEN);
smb_ntlmv1hash(pw, hash);
}
pbuf[SMB_MAXPASSWORDLEN] = '\0';
#ifdef NOICONVSUPPORT
/*
* We need to convert here to the server codeset.
* Initially we will send the same stuff and see what happens
* witout the conversion. REVISIT.
*/
iconv_convstr(vcp->vc_toserver, pbuf, pbuf, SMB_MAXPASSWORDLEN);
#endif
if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) {
plen = 24;
smb_lmresponse(hash,
vcp->vc_challenge,
(uchar_t *)encpass);
pp = encpass;
} else {
plen = strlen(pbuf) + 1;
pp = pbuf;
}
}
mbp = &rqp->sr_rq;
smb_rq_wstart(rqp);
mb_put_uint8(mbp, 0xff);
mb_put_uint8(mbp, 0);
mb_put_uint16le(mbp, 0);
mb_put_uint16le(mbp, 0); /* Flags */
mb_put_uint16le(mbp, plen);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
error = mb_put_mem(mbp, pp, plen, MB_MSYSTEM);
if (error) {
SMBSDEBUG("error %d from mb_put_mem for pp\n", error);
goto bad;
}
smb_put_dmem(mbp, vcp, "\\\\", 2, caseopt, NULL);
pp = vcp->vc_srvname;
error = smb_put_dmem(mbp, vcp, pp, strlen(pp), caseopt, NULL);
if (error) {
SMBSDEBUG("error %d from smb_put_dmem for srvname\n", error);
goto bad;
}
smb_put_dmem(mbp, vcp, "\\", 1, caseopt, NULL);
pp = ssp->ss_name;
error = smb_put_dstring(mbp, vcp, pp, caseopt);
if (error) {
SMBSDEBUG("error %d from smb_put_dstring for ss_name\n", error);
goto bad;
}
/* The type name is always ASCII */
pp = smb_share_typename(ssp->ss_type);
error = mb_put_mem(mbp, pp, strlen(pp) + 1, MB_MSYSTEM);
if (error) {
SMBSDEBUG("error %d from mb_put_mem for ss_type\n", error);
goto bad;
}
smb_rq_bend(rqp);
/*
* Don't want to risk missing a successful
* tree connect response.
*/
rqp->sr_flags |= SMBR_NOINTR_RECV;
error = smb_rq_simple(rqp);
SMBSDEBUG("%d\n", error);
if (error)
goto bad;
/* Success! */
SMB_SS_LOCK(ssp);
ssp->ss_tid = rqp->sr_rptid;
ssp->ss_vcgenid = vcp->vc_genid;
ssp->ss_flags |= SMBS_CONNECTED;
SMB_SS_UNLOCK(ssp);
bad:
if (encpass)
kmem_free(encpass, 24);
if (pbuf)
kmem_free(pbuf, SMB_MAXPASSWORDLEN + 1);
smb_rq_done(rqp);
if (error && upper == 1)
goto again;
return (error);
}
int
smb_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->ss_tid == SMB_TID_UNKNOWN)
return (0);
/*
* Build this as a "VC-level" request, so it will
* avoid testing the _GONE flag on the share,
* which has already been set at this point.
* Add the share pointer "by hand" below, so
* smb_iod_sendrq will plug in the TID.
*/
vcp = SSTOVC(ssp);
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_TREE_DISCONNECT, scred, &rqp);
if (error)
return (error);
rqp->sr_share = ssp; /* by hand */
mbp = &rqp->sr_rq;
#ifdef lint
mbp = mbp;
#endif
smb_rq_wstart(rqp);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
smb_rq_bend(rqp);
/*
* Run this with a relatively short timeout. (5 sec.)
* We don't really care about the result here, but we
* do need to make sure we send this out, or we could
* "leak" active tree IDs on interrupt or timeout.
* The NOINTR_SEND flag makes this request immune to
* interrupt or timeout until the send is done.
*/
rqp->sr_flags |= SMBR_NOINTR_SEND;
error = smb_rq_simple_timed(rqp, 5);
SMBSDEBUG("%d\n", error);
smb_rq_done(rqp);
ssp->ss_tid = SMB_TID_UNKNOWN;
return (error);
}
static int
smb_smb_readx(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid,
uio_t *uiop, struct smb_cred *scred, int timo)
{
struct smb_vc *vcp = SSTOVC(ssp);
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
u_int8_t wc;
int error;
u_int16_t residhi, residlo, off, doff;
u_int32_t resid;
if ((vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_READX) == 0) {
/* Fall back to the old cmd. */
return (smb_smb_read(ssp, fid, len, rresid, uiop,
scred, timo));
}
if ((vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_FILES) == 0) {
/* Have ReadX but not large files? */
if ((uiop->uio_loffset + *len) > UINT32_MAX)
return (EFBIG);
}
*len = min(*len, vcp->vc_rxmax);
error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_READ_ANDX, scred, &rqp);
if (error)
return (error);
smb_rq_getrequest(rqp, &mbp);
smb_rq_wstart(rqp);
mb_put_uint8(mbp, 0xff); /* no secondary command */
mb_put_uint8(mbp, 0); /* MBZ */
mb_put_uint16le(mbp, 0); /* offset to secondary */
mb_put_mem(mbp, (caddr_t)&fid, sizeof (fid), MB_MSYSTEM);
mb_put_uint32le(mbp, (u_int32_t)(uiop->uio_offset));
mb_put_uint16le(mbp, (u_int16_t)*len); /* MaxCount */
mb_put_uint16le(mbp, (u_int16_t)*len); /* MinCount */
/* (only indicates blocking) */
mb_put_uint32le(mbp, (unsigned)*len >> 16); /* MaxCountHigh */
mb_put_uint16le(mbp, (u_int16_t)*len); /* Remaining ("obsolete") */
mb_put_uint32le(mbp, (u_int32_t)((uiop->uio_loffset) >> 32));
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
smb_rq_bend(rqp);
do {
if (timo == 0)
timo = smb_timo_read;
error = smb_rq_simple_timed(rqp, timo);
if (error)
break;
smb_rq_getreply(rqp, &mdp);
off = SMB_HDRLEN;
md_get_uint8(mdp, &wc);
off++;
if (wc != 12) {
error = EBADRPC;
break;
}
md_get_uint8(mdp, NULL);
off++;
md_get_uint8(mdp, NULL);
off++;
md_get_uint16le(mdp, NULL);
off += 2;
md_get_uint16le(mdp, NULL);
off += 2;
md_get_uint16le(mdp, NULL); /* data compaction mode */
off += 2;
md_get_uint16le(mdp, NULL);
off += 2;
md_get_uint16le(mdp, &residlo);
off += 2;
md_get_uint16le(mdp, &doff); /* data offset */
off += 2;
md_get_uint16le(mdp, &residhi);
off += 2;
resid = (residhi << 16) | residlo;
md_get_mem(mdp, NULL, 4 * 2, MB_MSYSTEM);
off += 4*2;
md_get_uint16le(mdp, NULL); /* ByteCount */
off += 2;
if (doff > off) /* pad byte(s)? */
md_get_mem(mdp, NULL, doff - off, MB_MSYSTEM);
if (resid == 0) {
*rresid = resid;
break;
}
error = md_get_uio(mdp, uiop, resid);
if (error)
break;
*rresid = resid;
/*LINTED*/
} while (0);
smb_rq_done(rqp);
return (error);
}
static int
smb_smb_writex(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid,
uio_t *uiop, struct smb_cred *scred, int timo)
{
struct smb_vc *vcp = SSTOVC(ssp);
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
int error;
u_int8_t wc;
u_int16_t resid;
if ((vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX) == 0) {
/* Fall back to the old cmd. */
return (smb_smb_write(ssp, fid, len, rresid, uiop,
scred, timo));
}
if ((vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_FILES) == 0) {
/* Have WriteX but not large files? */
if ((uiop->uio_loffset + *len) > UINT32_MAX)
return (EFBIG);
}
*len = min(*len, vcp->vc_wxmax);
error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_WRITE_ANDX, scred, &rqp);
if (error)
return (error);
smb_rq_getrequest(rqp, &mbp);
smb_rq_wstart(rqp);
mb_put_uint8(mbp, 0xff); /* no secondary command */
mb_put_uint8(mbp, 0); /* MBZ */
mb_put_uint16le(mbp, 0); /* offset to secondary */
mb_put_mem(mbp, (caddr_t)&fid, sizeof (fid), MB_MSYSTEM);
mb_put_uint32le(mbp, (u_int32_t)(uiop->uio_offset));
mb_put_uint32le(mbp, 0); /* MBZ (timeout) */
mb_put_uint16le(mbp, 0); /* !write-thru */
mb_put_uint16le(mbp, 0);
mb_put_uint16le(mbp, (u_int16_t)((unsigned)*len >> 16));
mb_put_uint16le(mbp, (u_int16_t)*len);
mb_put_uint16le(mbp, 64); /* data offset from header start */
mb_put_uint32le(mbp, (u_int32_t)((uiop->uio_loffset) >> 32));
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
do {
mb_put_uint8(mbp, 0xee); /* mimic xp pad byte! */
error = mb_put_uio(mbp, uiop, *len);
if (error)
break;
smb_rq_bend(rqp);
if (timo == 0)
timo = smb_timo_write;
error = smb_rq_simple_timed(rqp, timo);
if (error)
break;
smb_rq_getreply(rqp, &mdp);
md_get_uint8(mdp, &wc);
if (wc != 6) {
error = EBADRPC;
break;
}
md_get_uint8(mdp, NULL);
md_get_uint8(mdp, NULL);
md_get_uint16le(mdp, NULL);
md_get_uint16le(mdp, &resid); /* actually is # written */
*rresid = resid;
/*
* if LARGE_WRITEX then there's one more bit of # written
*/
if ((vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX)) {
md_get_uint16le(mdp, NULL);
md_get_uint16le(mdp, &resid);
*rresid |= (int)(resid & 1) << 16;
}
/*LINTED*/
} while (0);
smb_rq_done(rqp);
return (error);
}
static int
smb_smb_read(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid,
uio_t *uiop, struct smb_cred *scred, int timo)
{
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
u_int16_t resid, bc;
u_int8_t wc;
int error, rlen;
/* This cmd is limited to 32-bit offsets. */
if ((uiop->uio_loffset + *len) > UINT32_MAX)
return (EFBIG);
*len = rlen = min(*len, SSTOVC(ssp)->vc_rxmax);
error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_READ, scred, &rqp);
if (error)
return (error);
smb_rq_getrequest(rqp, &mbp);
smb_rq_wstart(rqp);
mb_put_mem(mbp, (caddr_t)&fid, sizeof (fid), MB_MSYSTEM);
mb_put_uint16le(mbp, (u_int16_t)rlen);
mb_put_uint32le(mbp, (u_int32_t)uiop->uio_offset);
mb_put_uint16le(mbp, (u_int16_t)min(uiop->uio_resid, 0xffff));
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
smb_rq_bend(rqp);
do {
if (timo == 0)
timo = smb_timo_read;
error = smb_rq_simple_timed(rqp, timo);
if (error)
break;
smb_rq_getreply(rqp, &mdp);
md_get_uint8(mdp, &wc);
if (wc != 5) {
error = EBADRPC;
break;
}
md_get_uint16le(mdp, &resid);
md_get_mem(mdp, NULL, 4 * 2, MB_MSYSTEM);
md_get_uint16le(mdp, &bc);
md_get_uint8(mdp, NULL); /* ignore buffer type */
md_get_uint16le(mdp, &resid);
if (resid == 0) {
*rresid = resid;
break;
}
error = md_get_uio(mdp, uiop, resid);
if (error)
break;
*rresid = resid;
/*LINTED*/
} while (0);
smb_rq_done(rqp);
return (error);
}
static int
smb_smb_write(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid,
uio_t *uiop, struct smb_cred *scred, int timo)
{
struct smb_rq *rqp;
struct mbchain *mbp;
struct mdchain *mdp;
u_int16_t resid;
u_int8_t wc;
int error;
/* This cmd is limited to 32-bit offsets. */
if ((uiop->uio_loffset + *len) > UINT32_MAX)
return (EFBIG);
*len = resid = min(*len, SSTOVC(ssp)->vc_wxmax);
error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_WRITE, scred, &rqp);
if (error)
return (error);
smb_rq_getrequest(rqp, &mbp);
smb_rq_wstart(rqp);
mb_put_mem(mbp, (caddr_t)&fid, sizeof (fid), MB_MSYSTEM);
mb_put_uint16le(mbp, resid);
mb_put_uint32le(mbp, (u_int32_t)uiop->uio_offset);
mb_put_uint16le(mbp, (u_int16_t)min(uiop->uio_resid, 0xffff));
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_uint8(mbp, SMB_DT_DATA);
mb_put_uint16le(mbp, resid);
do {
error = mb_put_uio(mbp, uiop, resid);
if (error)
break;
smb_rq_bend(rqp);
if (timo == 0)
timo = smb_timo_write;
error = smb_rq_simple_timed(rqp, timo);
if (error)
break;
smb_rq_getreply(rqp, &mdp);
md_get_uint8(mdp, &wc);
if (wc != 1) {
error = EBADRPC;
break;
}
md_get_uint16le(mdp, &resid);
*rresid = resid;
/*LINTED*/
} while (0);
smb_rq_done(rqp);
return (error);
}
/*
* Common function for read/write with UIO.
* Called by netsmb smb_usr_rw,
* smbfs_readvnode, smbfs_writevnode
*/
int
smb_rwuio(struct smb_share *ssp, u_int16_t fid, uio_rw_t rw,
uio_t *uiop, struct smb_cred *scred, int timo)
{
ssize_t old_resid, tsize;
offset_t old_offset;
int len, resid;
int error = 0;
old_offset = uiop->uio_loffset;
old_resid = tsize = uiop->uio_resid;
while (tsize > 0) {
/* Lint: tsize may be 64-bits */
len = SMB_MAX_LARGE_RW_SIZE;
if (len > tsize)
len = (int)tsize;
if (rw == UIO_READ)
error = smb_smb_readx(ssp, fid, &len, &resid, uiop,
scred, timo);
else
error = smb_smb_writex(ssp, fid, &len, &resid, uiop,
scred, timo);
if (error)
break;
if (resid < len) {
error = EIO;
break;
}
tsize -= resid;
timo = 0; /* only first write is special */
}
if (error) {
/*
* Errors can happen in copyin/copyout, the rpc, etc. so
* they imply resid is unreliable. The only safe thing is
* to pretend zero bytes made it. We needn't restore the
* iovs because callers don't depend on them in error
* paths - uio_resid and uio_offset are what matter.
*/
uiop->uio_loffset = old_offset;
uiop->uio_resid = old_resid;
}
return (error);
}
static u_int32_t smbechoes = 0;
int
smb_smb_echo(struct smb_vc *vcp, struct smb_cred *scred, int timo)
{
struct smb_rq *rqp;
struct mbchain *mbp;
int error;
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_ECHO, scred, &rqp);
if (error)
return (error);
mbp = &rqp->sr_rq;
smb_rq_wstart(rqp);
mb_put_uint16le(mbp, 1); /* echo count */
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_uint32le(mbp, atomic_inc_32_nv(&smbechoes));
smb_rq_bend(rqp);
/*
* Note: the IOD calls this, so
* this request must not wait for
* connection state changes, etc.
*/
rqp->sr_flags |= SMBR_INTERNAL;
error = smb_rq_simple_timed(rqp, timo);
SMBSDEBUG("%d\n", error);
smb_rq_done(rqp);
return (error);
}
#ifdef APPLE
int
smb_smb_checkdir(struct smb_share *ssp, void *dnp, char *name,
int nmlen, struct smb_cred *scred)
{
struct smb_rq *rqp;
struct mbchain *mbp;
int error;
error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_CHECK_DIRECTORY, scred, &rqp);
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);
/*
* All we need to do is marshall the path: "\\"
* (the root of the share) into this request.
* We essentially in-line smbfs_fullpath() here,
* except no mb_put_padbyte (already aligned).
*/
smb_put_dstring(mbp, SSTOVC(ssp), "\\", SMB_CS_NONE);
smb_rq_bend(rqp);
error = smb_rq_simple(rqp);
SMBSDEBUG("%d\n", error);
smb_rq_done(rqp);
return (error);
}
#endif /* APPLE */