jcifs.smb.SmbTransportImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcifs Show documentation
Show all versions of jcifs Show documentation
JCIFS is an Open Source client library that implements the CIFS/SMB networking protocol in 100% Java
/* jcifs smb client library in Java
* Copyright (C) 2005 "Michael B. Allen"
* "Eric Glass"
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcifs.smb;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.crypto.Cipher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jcifs.Address;
import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.DfsReferralData;
import jcifs.DialectVersion;
import jcifs.SmbConstants;
import jcifs.SmbTransport;
import jcifs.internal.CommonServerMessageBlock;
import jcifs.internal.CommonServerMessageBlockRequest;
import jcifs.internal.CommonServerMessageBlockResponse;
import jcifs.internal.RequestWithPath;
import jcifs.internal.SMBProtocolDecodingException;
import jcifs.internal.SMBSigningDigest;
import jcifs.internal.SmbNegotiation;
import jcifs.internal.SmbNegotiationResponse;
import jcifs.internal.dfs.DfsReferralDataImpl;
import jcifs.internal.dfs.DfsReferralRequestBuffer;
import jcifs.internal.dfs.DfsReferralResponseBuffer;
import jcifs.internal.dfs.Referral;
import jcifs.internal.smb1.AndXServerMessageBlock;
import jcifs.internal.smb1.ServerMessageBlock;
import jcifs.internal.smb1.com.SmbComBlankResponse;
import jcifs.internal.smb1.com.SmbComLockingAndX;
import jcifs.internal.smb1.com.SmbComNegotiate;
import jcifs.internal.smb1.com.SmbComNegotiateResponse;
import jcifs.internal.smb1.com.SmbComReadAndXResponse;
import jcifs.internal.smb1.trans.SmbComTransaction;
import jcifs.internal.smb1.trans.SmbComTransactionResponse;
import jcifs.internal.smb1.trans2.Trans2GetDfsReferral;
import jcifs.internal.smb1.trans2.Trans2GetDfsReferralResponse;
import jcifs.internal.smb2.ServerMessageBlock2;
import jcifs.internal.smb2.ServerMessageBlock2Request;
import jcifs.internal.smb2.ServerMessageBlock2Response;
import jcifs.internal.smb2.Smb2Constants;
import jcifs.internal.smb2.io.Smb2ReadResponse;
import jcifs.internal.smb2.ioctl.Smb2IoctlRequest;
import jcifs.internal.smb2.ioctl.Smb2IoctlResponse;
import jcifs.internal.smb2.lock.Smb2OplockBreakNotification;
import jcifs.internal.smb2.nego.EncryptionNegotiateContext;
import jcifs.internal.smb2.nego.Smb2NegotiateRequest;
import jcifs.internal.smb2.nego.Smb2NegotiateResponse;
import jcifs.netbios.Name;
import jcifs.netbios.NbtException;
import jcifs.netbios.SessionRequestPacket;
import jcifs.netbios.SessionServicePacket;
import jcifs.util.Crypto;
import jcifs.util.Encdec;
import jcifs.util.Hexdump;
import jcifs.util.transport.Request;
import jcifs.util.transport.Response;
import jcifs.util.transport.Transport;
import jcifs.util.transport.TransportException;
/**
*
*/
class SmbTransportImpl extends Transport implements SmbTransportInternal, SmbConstants {
private static Logger log = LoggerFactory.getLogger(SmbTransportImpl.class);
private boolean smb2 = false;
private InetAddress localAddr;
private int localPort;
private Address address;
private Socket socket;
private int port;
private final AtomicLong mid = new AtomicLong();
private OutputStream out;
private InputStream in;
private final byte[] sbuf = new byte[1024]; /* small local buffer */
private long sessionExpiration;
private final List sessions = new LinkedList<>();
private String tconHostName = null;
private final CIFSContext transportContext;
private final boolean signingEnforced;
private SmbNegotiationResponse negotiated;
private SMBSigningDigest digest;
private final Semaphore credits = new Semaphore(1, true);
private final int desiredCredits = 512;
private byte[] preauthIntegrityHash = new byte[64];
SmbTransportImpl ( CIFSContext tc, Address address, int port, InetAddress localAddr, int localPort, boolean forceSigning ) {
this.transportContext = tc;
this.signingEnforced = forceSigning || this.getContext().getConfig().isSigningEnforced();
this.sessionExpiration = System.currentTimeMillis() + tc.getConfig().getSessionTimeout();
this.address = address;
this.port = port;
this.localAddr = localAddr;
this.localPort = localPort;
}
/**
* {@inheritDoc}
*
* @see jcifs.util.transport.Transport#getResponseTimeout()
*/
@Override
protected int getResponseTimeout ( Request req ) {
if ( req instanceof CommonServerMessageBlockRequest ) {
Integer overrideTimeout = ( (CommonServerMessageBlockRequest) req ).getOverrideTimeout();
if ( overrideTimeout != null ) {
return overrideTimeout;
}
}
return getContext().getConfig().getResponseTimeout();
}
@Override
public Address getRemoteAddress () {
return this.address;
}
@Override
public String getRemoteHostName () {
return this.tconHostName;
}
/**
*
* @return number of sessions on this transport
*/
public int getNumSessions () {
return this.sessions.size();
}
@Override
public int getInflightRequests () {
return this.response_map.size();
}
@Override
public boolean isDisconnected () {
Socket s = this.socket;
return super.isDisconnected() || s == null || s.isClosed();
}
@Override
public boolean isFailed () {
Socket s = this.socket;
return super.isFailed() || s == null || s.isClosed();
}
@Override
public boolean hasCapability ( int cap ) throws SmbException {
return getNegotiateResponse().haveCapabilitiy(cap);
}
/**
* @return the negotiated
* @throws SmbException
*/
SmbNegotiationResponse getNegotiateResponse () throws SmbException {
try {
if ( this.negotiated == null ) {
connect(this.transportContext.getConfig().getResponseTimeout());
}
}
catch ( IOException ioe ) {
throw new SmbException(ioe.getMessage(), ioe);
}
SmbNegotiationResponse r = this.negotiated;
if ( r == null ) {
throw new SmbException("Connection did not complete, failed to get negotiation response");
}
return r;
}
/**
* @return whether this is SMB2 transport
* @throws SmbException
*/
@Override
public boolean isSMB2 () throws SmbException {
return this.smb2 || getNegotiateResponse() instanceof Smb2NegotiateResponse;
}
/**
* @param digest
*/
public void setDigest ( SMBSigningDigest digest ) {
this.digest = digest;
}
/**
* @return the digest
*/
public SMBSigningDigest getDigest () {
return this.digest;
}
/**
* @return the context associated with this transport connection
*/
@Override
public CIFSContext getContext () {
return this.transportContext;
}
/**
* {@inheritDoc}
*
* @see jcifs.util.transport.Transport#acquire()
*/
@Override
public SmbTransportImpl acquire () {
return (SmbTransportImpl) super.acquire();
}
/**
* @return the server's encryption key
*/
@Override
public byte[] getServerEncryptionKey () {
if ( this.negotiated == null ) {
return null;
}
if ( this.negotiated instanceof SmbComNegotiateResponse ) {
return ( (SmbComNegotiateResponse) this.negotiated ).getServerData().encryptionKey;
}
return null;
}
@Override
public boolean isSigningOptional () throws SmbException {
if ( this.signingEnforced ) {
return false;
}
SmbNegotiationResponse nego = getNegotiateResponse();
return nego.isSigningNegotiated() && !nego.isSigningRequired();
}
@Override
public boolean isSigningEnforced () throws SmbException {
if ( this.signingEnforced ) {
return true;
}
return getNegotiateResponse().isSigningRequired();
}
/**
* {@inheritDoc}
*
* @see jcifs.SmbTransport#unwrap(java.lang.Class)
*/
@SuppressWarnings ( "unchecked" )
@Override
public T unwrap ( Class type ) {
if ( type.isAssignableFrom(this.getClass()) ) {
return (T) this;
}
throw new ClassCastException();
}
/**
*
* @param tf
* @return a session for the context
*/
@Override
public SmbSessionImpl getSmbSession ( CIFSContext tf ) {
return getSmbSession(tf, null, null);
}
/**
*
* @param tf
* context to use
* @return a session for the context
*/
@Override
@SuppressWarnings ( "resource" )
public synchronized SmbSessionImpl getSmbSession ( CIFSContext tf, String targetHost, String targetDomain ) {
long now;
if ( log.isTraceEnabled() ) {
log.trace("Currently " + this.sessions.size() + " session(s) active for " + this);
}
if ( targetHost != null ) {
targetHost = targetHost.toLowerCase(Locale.ROOT);
}
if ( targetDomain != null ) {
targetDomain = targetDomain.toUpperCase(Locale.ROOT);
}
ListIterator iter = this.sessions.listIterator();
while ( iter.hasNext() ) {
SmbSessionImpl ssn = iter.next();
if ( ssn.matches(tf, targetHost, targetDomain) ) {
if ( log.isTraceEnabled() ) {
log.trace("Reusing existing session " + ssn);
}
return ssn.acquire();
}
else if ( log.isTraceEnabled() ) {
log.trace("Existing session " + ssn + " does not match " + tf.getCredentials());
}
}
/* logoff old sessions */
if ( tf.getConfig().getSessionTimeout() > 0 && this.sessionExpiration < ( now = System.currentTimeMillis() ) ) {
this.sessionExpiration = now + tf.getConfig().getSessionTimeout();
iter = this.sessions.listIterator();
while ( iter.hasNext() ) {
SmbSessionImpl ssn = iter.next();
if ( ssn.getExpiration() != null && ssn.getExpiration() < now && !ssn.isInUse() ) {
if ( log.isDebugEnabled() ) {
log.debug("Closing session after timeout " + ssn);
}
ssn.logoff(false, false);
}
}
}
SmbSessionImpl ssn = new SmbSessionImpl(tf, targetHost, targetDomain, this);
if ( log.isDebugEnabled() ) {
log.debug("Establishing new session " + ssn + " on " + this.name);
}
this.sessions.add(ssn);
return ssn;
}
boolean matches ( Address addr, int prt, InetAddress laddr, int lprt, String hostName ) {
if ( this.state == 5 || this.state == 6 ) {
// don't reuse disconnecting/disconnected transports
return false;
}
if ( hostName == null )
hostName = addr.getHostName();
return ( this.tconHostName == null || hostName.equalsIgnoreCase(this.tconHostName) ) && addr.equals(this.address)
&& ( prt == 0 || prt == this.port ||
/* port 139 is ok if 445 was requested */
( prt == 445 && this.port == 139 ) )
&& ( laddr == this.localAddr || ( laddr != null && laddr.equals(this.localAddr) ) ) && lprt == this.localPort;
}
void ssn139 () throws IOException {
CIFSContext tc = this.transportContext;
Name calledName = new Name(tc.getConfig(), this.address.firstCalledName(), 0x20, null);
do {
this.socket = new Socket();
if ( this.localAddr != null )
this.socket.bind(new InetSocketAddress(this.localAddr, this.localPort));
this.socket.connect(new InetSocketAddress(this.address.getHostAddress(), 139), tc.getConfig().getConnTimeout());
this.socket.setSoTimeout(tc.getConfig().getSoTimeout());
this.out = this.socket.getOutputStream();
this.in = this.socket.getInputStream();
SessionServicePacket ssp = new SessionRequestPacket(tc.getConfig(), calledName, tc.getNameServiceClient().getLocalName());
this.out.write(this.sbuf, 0, ssp.writeWireFormat(this.sbuf, 0));
if ( readn(this.in, this.sbuf, 0, 4) < 4 ) {
try {
this.socket.close();
}
catch ( IOException ioe ) {
log.debug("Failed to close socket", ioe);
}
throw new SmbException("EOF during NetBIOS session request");
}
switch ( this.sbuf[ 0 ] & 0xFF ) {
case SessionServicePacket.POSITIVE_SESSION_RESPONSE:
if ( log.isDebugEnabled() ) {
log.debug("session established ok with " + this.address);
}
return;
case SessionServicePacket.NEGATIVE_SESSION_RESPONSE:
int errorCode = this.in.read() & 0xFF;
switch ( errorCode ) {
case NbtException.CALLED_NOT_PRESENT:
case NbtException.NOT_LISTENING_CALLED:
this.socket.close();
break;
default:
disconnect(true);
throw new NbtException(NbtException.ERR_SSN_SRVC, errorCode);
}
break;
case -1:
disconnect(true);
throw new NbtException(NbtException.ERR_SSN_SRVC, NbtException.CONNECTION_REFUSED);
default:
disconnect(true);
throw new NbtException(NbtException.ERR_SSN_SRVC, 0);
}
}
while ( ( calledName.name = this.address.nextCalledName(tc) ) != null );
throw new IOException("Failed to establish session with " + this.address);
}
private SmbNegotiation negotiate ( int prt ) throws IOException {
/*
* We cannot use Transport.sendrecv() yet because
* the Transport thread is not setup until doConnect()
* returns and we want to suppress all communication
* until we have properly negotiated.
*/
synchronized ( this.inLock ) {
if ( prt == 139 ) {
ssn139();
}
else {
if ( prt == 0 )
prt = DEFAULT_PORT; // 445
this.socket = new Socket();
if ( this.localAddr != null )
this.socket.bind(new InetSocketAddress(this.localAddr, this.localPort));
this.socket.connect(new InetSocketAddress(this.address.getHostAddress(), prt), this.transportContext.getConfig().getConnTimeout());
this.socket.setSoTimeout(this.transportContext.getConfig().getSoTimeout());
this.out = this.socket.getOutputStream();
this.in = this.socket.getInputStream();
}
if ( this.credits.drainPermits() == 0 ) {
log.debug("It appears we previously lost some credits");
}
if ( this.smb2 || this.getContext().getConfig().isUseSMB2OnlyNegotiation() ) {
log.debug("Using SMB2 only negotiation");
return negotiate2(null);
}
SmbComNegotiate comNeg = new SmbComNegotiate(getContext().getConfig(), this.signingEnforced);
int n = negotiateWrite(comNeg, true);
negotiatePeek();
SmbNegotiationResponse resp = null;
if ( !this.smb2 ) {
if ( this.getContext().getConfig().getMinimumVersion().isSMB2() ) {
throw new CIFSException("Server does not support SMB2");
}
resp = new SmbComNegotiateResponse(getContext());
resp.decode(this.sbuf, 4);
resp.received();
if ( log.isTraceEnabled() ) {
log.trace(resp.toString());
log.trace(Hexdump.toHexString(this.sbuf, 4, n));
}
}
else {
Smb2NegotiateResponse r = new Smb2NegotiateResponse(getContext().getConfig());
r.decode(this.sbuf, 4);
r.received();
if ( r.getDialectRevision() == Smb2Constants.SMB2_DIALECT_ANY ) {
return negotiate2(r);
}
else if ( r.getDialectRevision() != Smb2Constants.SMB2_DIALECT_0202 ) {
throw new CIFSException("Server returned invalid dialect verison in multi protocol negotiation");
}
int permits = r.getInitialCredits();
if ( permits > 0 ) {
this.credits.release(permits);
}
Arrays.fill(this.sbuf, (byte) 0);
return new SmbNegotiation(
new Smb2NegotiateRequest(
getContext().getConfig(),
this.signingEnforced ? Smb2Constants.SMB2_NEGOTIATE_SIGNING_REQUIRED : Smb2Constants.SMB2_NEGOTIATE_SIGNING_ENABLED),
r,
null,
null);
}
int permits = resp.getInitialCredits();
if ( permits > 0 ) {
this.credits.release(permits);
}
Arrays.fill(this.sbuf, (byte) 0);
return new SmbNegotiation(comNeg, resp, null, null);
}
}
/**
* @return
* @throws IOException
*/
private int negotiateWrite ( CommonServerMessageBlockRequest req, boolean setmid ) throws IOException {
if ( setmid ) {
makeKey(req);
}
else {
req.setMid(0);
this.mid.set(1);
}
int n = req.encode(this.sbuf, 4);
Encdec.enc_uint32be(n & 0xFFFF, this.sbuf, 0); /* 4 byte ssn msg header */
if ( log.isTraceEnabled() ) {
log.trace(req.toString());
log.trace(Hexdump.toHexString(this.sbuf, 4, n));
}
this.out.write(this.sbuf, 0, 4 + n);
this.out.flush();
log.trace("Wrote negotiate request");
return n;
}
/**
* @throws SocketException
* @throws IOException
*/
private void negotiatePeek () throws SocketException, IOException {
/*
* Note the Transport thread isn't running yet so we can
* read from the socket here.
*/
try {
this.socket.setSoTimeout(this.transportContext.getConfig().getConnTimeout());
if ( peekKey() == null ) /* try to read header */
throw new IOException("transport closed in negotiate");
}
finally {
this.socket.setSoTimeout(this.transportContext.getConfig().getSoTimeout());
}
int size = Encdec.dec_uint16be(this.sbuf, 2) & 0xFFFF;
if ( size < 33 || ( 4 + size ) > this.sbuf.length ) {
throw new IOException("Invalid payload size: " + size);
}
int hdrSize = this.smb2 ? Smb2Constants.SMB2_HEADER_LENGTH : SMB1_HEADER_LENGTH;
readn(this.in, this.sbuf, 4 + hdrSize, size - hdrSize);
log.trace("Read negotiate response");
}
/**
* @param first
* @param n
* @return
* @throws IOException
* @throws SocketException
* @throws InterruptedException
*/
private SmbNegotiation negotiate2 ( Smb2NegotiateResponse first ) throws IOException, SocketException {
int size = 0;
int securityMode = getRequestSecurityMode(first);
// further negotiation needed
Smb2NegotiateRequest smb2neg = new Smb2NegotiateRequest(getContext().getConfig(), securityMode);
Smb2NegotiateResponse r = null;
byte[] negoReqBuffer = null;
byte[] negoRespBuffer = null;
try {
smb2neg.setRequestCredits(Math.max(1, this.desiredCredits - this.credits.availablePermits()));
int reqLen = negotiateWrite(smb2neg, first != null);
boolean doPreauth = getContext().getConfig().getMaximumVersion().atLeast(DialectVersion.SMB311);
if ( doPreauth ) {
negoReqBuffer = new byte[reqLen];
System.arraycopy(this.sbuf, 4, negoReqBuffer, 0, reqLen);
}
negotiatePeek();
r = smb2neg.initResponse(getContext());
int respLen = r.decode(this.sbuf, 4);
r.received();
if ( doPreauth ) {
negoRespBuffer = new byte[respLen];
System.arraycopy(this.sbuf, 4, negoRespBuffer, 0, respLen);
}
else {
negoReqBuffer = null;
}
if ( log.isTraceEnabled() ) {
log.trace(r.toString());
log.trace(Hexdump.toHexString(this.sbuf, 4, size));
}
return new SmbNegotiation(smb2neg, r, negoReqBuffer, negoRespBuffer);
}
finally {
int grantedCredits = r != null ? r.getGrantedCredits() : 0;
if ( grantedCredits == 0 ) {
grantedCredits = 1;
}
this.credits.release(grantedCredits);
Arrays.fill(this.sbuf, (byte) 0);
}
}
/**
* Connect the transport
*
* @throws SmbException
*/
@Override
public boolean ensureConnected () throws SmbException {
try {
return super.connect(this.transportContext.getConfig().getResponseTimeout());
}
catch ( TransportException te ) {
throw new SmbException("Failed to connect: " + this.address, te);
}
}
@Override
protected void doConnect () throws IOException {
/*
* Negotiate Protocol Request / Response
*/
if ( log.isDebugEnabled() ) {
log.debug("Connecting in state " + this.state + " addr " + this.address.getHostAddress());
}
SmbNegotiation resp;
try {
resp = negotiate(this.port);
}
catch ( IOException ce ) {
if ( getContext().getConfig().isPort139FailoverEnabled() ) {
this.port = ( this.port == 0 || this.port == DEFAULT_PORT ) ? 139 : DEFAULT_PORT;
this.smb2 = false;
this.mid.set(0);
resp = negotiate(this.port);
}
else {
throw ce;
}
}
if ( resp == null || resp.getResponse() == null ) {
throw new SmbException("Failed to connect.");
}
if ( log.isDebugEnabled() ) {
log.debug("Negotiation response on " + this.name + " :" + resp);
}
if ( !resp.getResponse().isValid(getContext(), resp.getRequest()) ) {
throw new SmbException("This client is not compatible with the server.");
}
boolean serverRequireSig = resp.getResponse().isSigningRequired();
boolean serverEnableSig = resp.getResponse().isSigningEnabled();
if ( log.isDebugEnabled() ) {
log.debug(
"Signature negotiation enforced " + this.signingEnforced + " (server " + serverRequireSig + ") enabled "
+ this.getContext().getConfig().isSigningEnabled() + " (server " + serverEnableSig + ")");
}
/* Adjust negotiated values */
this.tconHostName = this.address.getHostName();
this.negotiated = resp.getResponse();
if ( resp.getResponse().getSelectedDialect().atLeast(DialectVersion.SMB311) ) {
updatePreauthHash(resp.getRequestRaw());
updatePreauthHash(resp.getResponseRaw());
if ( log.isDebugEnabled() ) {
log.debug("Preauth hash after negotiate " + Hexdump.toHexString(this.preauthIntegrityHash));
}
}
}
protected synchronized void doDisconnect ( boolean hard ) throws IOException {
doDisconnect(hard, false);
}
@Override
protected synchronized boolean doDisconnect ( boolean hard, boolean inUse ) throws IOException {
ListIterator iter = this.sessions.listIterator();
boolean wasInUse = false;
long l = getUsageCount();
if ( ( inUse && l != 1 ) || ( !inUse && l > 0 ) ) {
log.warn("Disconnecting transport while still in use " + this + ": " + this.sessions);
wasInUse = true;
}
if ( log.isDebugEnabled() ) {
log.debug("Disconnecting transport " + this);
}
try {
if ( log.isTraceEnabled() ) {
log.trace("Currently " + this.sessions.size() + " session(s) active for " + this);
}
while ( iter.hasNext() ) {
@SuppressWarnings ( "resource" )
SmbSessionImpl ssn = iter.next();
try {
wasInUse |= ssn.logoff(hard, false);
}
catch ( Exception e ) {
log.debug("Failed to close session", e);
}
finally {
iter.remove();
}
}
if ( this.socket != null ) {
this.socket.shutdownOutput();
this.out.close();
this.in.close();
this.socket.close();
log.trace("Socket closed");
}
else {
log.trace("Not yet initialized");
}
}
catch ( Exception e ) {
log.debug("Exception in disconnect", e);
}
finally {
this.socket = null;
this.digest = null;
this.tconHostName = null;
this.transportContext.getTransportPool().removeTransport(this);
}
return wasInUse;
}
@Override
protected long makeKey ( Request request ) throws IOException {
long m = this.mid.incrementAndGet() - 1;
if ( !this.smb2 ) {
m = ( m % 32000 );
}
( (CommonServerMessageBlock) request ).setMid(m);
return m;
}
@Override
protected Long peekKey () throws IOException {
do {
if ( ( readn(this.in, this.sbuf, 0, 4) ) < 4 ) {
return null;
}
}
while ( this.sbuf[ 0 ] == (byte) 0x85 ); /* Dodge NetBIOS keep-alive */
/* read smb header */
if ( ( readn(this.in, this.sbuf, 4, SmbConstants.SMB1_HEADER_LENGTH) ) < SmbConstants.SMB1_HEADER_LENGTH ) {
return null;
}
if ( log.isTraceEnabled() ) {
log.trace("New data read: " + this);
log.trace(Hexdump.toHexString(this.sbuf, 4, 32));
}
for ( ;; ) {
/*
* 01234567
* 00SSFSMB
* 0 - 0's
* S - size of payload
* FSMB - 0xFF SMB magic #
*/
if ( this.sbuf[ 0 ] == (byte) 0x00 && this.sbuf[ 4 ] == (byte) 0xFE && this.sbuf[ 5 ] == (byte) 'S' && this.sbuf[ 6 ] == (byte) 'M'
&& this.sbuf[ 7 ] == (byte) 'B' ) {
this.smb2 = true;
// also read the rest of the header
int lenDiff = Smb2Constants.SMB2_HEADER_LENGTH - SmbConstants.SMB1_HEADER_LENGTH;
if ( readn(this.in, this.sbuf, 4 + SmbConstants.SMB1_HEADER_LENGTH, lenDiff) < lenDiff ) {
return null;
}
return (long) Encdec.dec_uint64le(this.sbuf, 28);
}
if ( this.sbuf[ 0 ] == (byte) 0x00 && this.sbuf[ 1 ] == (byte) 0x00 && ( this.sbuf[ 4 ] == (byte) 0xFF ) && this.sbuf[ 5 ] == (byte) 'S'
&& this.sbuf[ 6 ] == (byte) 'M' && this.sbuf[ 7 ] == (byte) 'B' ) {
break; /* all good (SMB) */
}
/* out of phase maybe? */
/* inch forward 1 byte and try again */
for ( int i = 0; i < 35; i++ ) {
log.warn("Possibly out of phase, trying to resync " + Hexdump.toHexString(this.sbuf, 0, 16));
this.sbuf[ i ] = this.sbuf[ i + 1 ];
}
int b;
if ( ( b = this.in.read() ) == -1 )
return null;
this.sbuf[ 35 ] = (byte) b;
}
/*
* Unless key returned is null or invalid Transport.loop() always
* calls doRecv() after and no one else but the transport thread
* should call doRecv(). Therefore it is ok to expect that the data
* in sbuf will be preserved for copying into BUF in doRecv().
*/
return (long) Encdec.dec_uint16le(this.sbuf, 34) & 0xFFFF;
}
@Override
protected void doSend ( Request request ) throws IOException {
CommonServerMessageBlock smb = (CommonServerMessageBlock) request;
byte[] buffer = this.getContext().getBufferCache().getBuffer();
try {
// synchronize around encode and write so that the ordering for SMB1 signing can be maintained
synchronized ( this.outLock ) {
int n = smb.encode(buffer, 4);
Encdec.enc_uint32be(n & 0xFFFF, buffer, 0); /* 4 byte session message header */
if ( log.isTraceEnabled() ) {
do {
log.trace(smb.toString());
}
while ( smb instanceof AndXServerMessageBlock && ( smb = ( (AndXServerMessageBlock) smb ).getAndx() ) != null );
log.trace(Hexdump.toHexString(buffer, 4, n));
}
/*
* For some reason this can sometimes get broken up into another
* "NBSS Continuation Message" frame according to WireShark
*/
this.out.write(buffer, 0, 4 + n);
this.out.flush();
}
}
finally {
this.getContext().getBufferCache().releaseBuffer(buffer);
}
}
@SuppressWarnings ( "unchecked" )
public T sendrecv ( CommonServerMessageBlockRequest request, T response, Set params )
throws IOException {
if ( request instanceof jcifs.internal.Request ) {
if ( response == null ) {
response = (T) ( (jcifs.internal.Request>) request ).initResponse(getContext());
}
else if ( isSMB2() ) {
throw new IOException("Should not provide response argument for SMB2");
}
}
else {
request.setResponse(response);
}
if ( response == null ) {
throw new IOException("Invalid response");
}
CommonServerMessageBlockRequest curHead = request;
int maxSize = getContext().getConfig().getMaximumBufferSize();
while ( curHead != null ) {
CommonServerMessageBlockRequest nextHead = null;
int totalSize = 0;
int n = 0;
CommonServerMessageBlockRequest last = null;
CommonServerMessageBlockRequest chain = curHead;
while ( chain != null ) {
n++;
int size = chain.size();
int cost = chain.getCreditCost();
CommonServerMessageBlockRequest next = chain.getNext();
if ( log.isTraceEnabled() ) {
log.trace(
String.format("%s costs %d avail %d (%s)", chain.getClass().getName(), cost, this.credits.availablePermits(), this.name));
}
if ( ( next == null || chain.allowChain(next) ) && totalSize + size < maxSize && this.credits.tryAcquire(cost) ) {
totalSize += size;
last = chain;
chain = next;
}
else if ( last == null && totalSize + size > maxSize ) {
throw new SmbException(String.format("Request size %d exceeds allowable size %d: %s", size, maxSize, chain));
}
else if ( last == null ) {
// don't have enough credits/space for the first request, block until available
// for space there is nothing we can do, callers need to make sure that a single message fits
try {
long timeout = getResponseTimeout(chain);
if ( params.contains(RequestParam.NO_TIMEOUT) ) {
this.credits.acquire(cost);
}
else {
if ( !this.credits.tryAcquire(cost, timeout, TimeUnit.MILLISECONDS) ) {
throw new SmbException("Failed to acquire credits in time");
}
}
totalSize += size;
// split off first request
synchronized ( chain ) {
CommonServerMessageBlockRequest snext = chain.split();
nextHead = snext;
if ( log.isDebugEnabled() && snext != null ) {
log.debug("Insufficient credits, send only first " + chain + " next is " + snext);
}
}
break;
}
catch ( InterruptedException e ) {
InterruptedIOException ie = new InterruptedIOException("Interrupted while acquiring credits");
ie.initCause(e);
throw ie;
}
}
else {
// not enough credits available or too big, split
if ( log.isDebugEnabled() ) {
log.debug("Not enough credits, split at " + last);
}
synchronized ( last ) {
nextHead = last.split();
}
break;
}
}
int reqCredits = Math.max(1, this.desiredCredits - this.credits.availablePermits() - n + 1);
if ( log.isTraceEnabled() ) {
log.trace("Request credits " + reqCredits);
}
request.setRequestCredits(reqCredits);
CommonServerMessageBlockRequest thisReq = curHead;
try {
CommonServerMessageBlockResponse resp = thisReq.getResponse();
if ( log.isTraceEnabled() ) {
log.trace("Sending " + thisReq);
}
resp = super.sendrecv(curHead, resp, params);
if ( !checkStatus(curHead, resp) ) {
if ( log.isDebugEnabled() ) {
log.debug("Breaking on error " + resp);
}
break;
}
if ( nextHead != null ) {
// prepare remaining
// (e.g. set session/tree/fileid returned by the previous requests)
resp.prepare(nextHead);
}
curHead = nextHead;
}
finally {
CommonServerMessageBlockRequest curReq = thisReq;
int grantedCredits = 0;
// if
while ( curReq != null ) {
if ( curReq.isResponseAsync() ) {
log.trace("Async");
break;
}
CommonServerMessageBlockResponse resp = curReq.getResponse();
if ( resp.isReceived() ) {
grantedCredits += resp.getGrantedCredits();
}
CommonServerMessageBlockRequest next = curReq.getNext();
if ( next == null ) {
break;
}
curReq = next;
}
if ( !isDisconnected() && !curReq.isResponseAsync() && !curReq.getResponse().isAsync() && !curReq.getResponse().isError()
&& grantedCredits == 0 ) {
if ( this.credits.availablePermits() > 0 || n > 0 ) {
log.debug("Server " + this + " returned zero credits for " + curReq);
}
else {
log.warn("Server " + this + " took away all our credits");
}
}
else if ( !curReq.isResponseAsync() ) {
if ( log.isTraceEnabled() ) {
log.trace("Adding credits " + grantedCredits);
}
this.credits.release(grantedCredits);
}
}
}
if ( !response.isReceived() ) {
throw new IOException("No response", response.getException());
}
return response;
}
@Override
protected boolean handleIntermediate ( Request request, T response ) {
if ( !this.smb2 ) {
return false;
}
ServerMessageBlock2Request> req = (ServerMessageBlock2Request>) request;
ServerMessageBlock2Response resp = (ServerMessageBlock2Response) response;
synchronized ( resp ) {
if ( resp.isAsync() && !resp.isAsyncHandled() && resp.getStatus() == NtStatus.NT_STATUS_PENDING && resp.getAsyncId() != 0 ) {
resp.setAsyncHandled(true);
boolean first = !req.isAsync();
req.setAsyncId(resp.getAsyncId());
Long exp = resp.getExpiration();
if ( exp != null ) {
resp.setExpiration(System.currentTimeMillis() + getResponseTimeout(request));
}
if ( log.isDebugEnabled() ) {
log.debug("Have intermediate reply " + response);
}
if ( first ) {
int credit = resp.getCredit();
if ( log.isDebugEnabled() ) {
log.debug("Credit from intermediate " + credit);
}
this.credits.release(credit);
}
return true;
}
}
return false;
}
protected void doSend0 ( Request request ) throws IOException {
try {
doSend(request);
}
catch ( IOException ioe ) {
log.warn("send failed", ioe);
try {
disconnect(true);
}
catch ( IOException ioe2 ) {
ioe.addSuppressed(ioe2);
log.error("disconnect failed", ioe2);
}
throw ioe;
}
}
// must be synchronized with peekKey
@Override
protected void doRecv ( Response response ) throws IOException {
CommonServerMessageBlock resp = (CommonServerMessageBlock) response;
this.negotiated.setupResponse(response);
try {
if ( this.smb2 ) {
doRecvSMB2(resp);
}
else {
doRecvSMB1(resp);
}
}
catch ( Exception e ) {
log.warn("Failure decoding message, disconnecting transport", e);
response.exception(e);
synchronized ( response ) {
response.notifyAll();
}
throw e;
}
}
/**
* @param response
* @throws IOException
* @throws SMBProtocolDecodingException
*/
private void doRecvSMB2 ( CommonServerMessageBlock response ) throws IOException, SMBProtocolDecodingException {
int size = ( Encdec.dec_uint16be(this.sbuf, 2) & 0xFFFF ) | ( this.sbuf[ 1 ] & 0xFF ) << 16;
if ( size < ( Smb2Constants.SMB2_HEADER_LENGTH + 1 ) ) {
throw new IOException("Invalid payload size: " + size);
}
if ( this.sbuf[ 0 ] != (byte) 0x00 || this.sbuf[ 4 ] != (byte) 0xFE || this.sbuf[ 5 ] != (byte) 'S' || this.sbuf[ 6 ] != (byte) 'M'
|| this.sbuf[ 7 ] != (byte) 'B' ) {
throw new IOException("Houston we have a synchronization problem");
}
int nextCommand = Encdec.dec_uint32le(this.sbuf, 4 + 20);
int maximumBufferSize = getContext().getConfig().getMaximumBufferSize();
int msgSize = nextCommand != 0 ? nextCommand : size;
if ( msgSize > maximumBufferSize ) {
throw new IOException(String.format("Message size %d exceeds maxiumum buffer size %d", msgSize, maximumBufferSize));
}
ServerMessageBlock2Response cur = (ServerMessageBlock2Response) response;
byte[] buffer = getContext().getBufferCache().getBuffer();
try {
int rl = nextCommand != 0 ? nextCommand : size;
// read and decode first
System.arraycopy(this.sbuf, 4, buffer, 0, Smb2Constants.SMB2_HEADER_LENGTH);
readn(this.in, buffer, Smb2Constants.SMB2_HEADER_LENGTH, rl - Smb2Constants.SMB2_HEADER_LENGTH);
cur.setReadSize(rl);
int len = cur.decode(buffer, 0);
if ( len > rl ) {
throw new IOException(String.format("WHAT? ( read %d decoded %d ): %s", rl, len, cur));
}
else if ( nextCommand != 0 && len > nextCommand ) {
throw new IOException("Overlapping commands");
}
size -= rl;
while ( size > 0 && nextCommand != 0 ) {
cur = (ServerMessageBlock2Response) cur.getNextResponse();
if ( cur == null ) {
log.warn("Response not properly set up");
this.in.skip(size);
break;
}
// read next header
readn(this.in, buffer, 0, Smb2Constants.SMB2_HEADER_LENGTH);
nextCommand = Encdec.dec_uint32le(buffer, 20);
if ( ( nextCommand != 0 && nextCommand > maximumBufferSize ) || ( nextCommand == 0 && size > maximumBufferSize ) ) {
throw new IOException(
String.format("Message size %d exceeds maxiumum buffer size %d", nextCommand != 0 ? nextCommand : size, maximumBufferSize));
}
rl = nextCommand != 0 ? nextCommand : size;
if ( log.isDebugEnabled() ) {
log.debug(String.format("Compound next command %d read size %d remain %d", nextCommand, rl, size));
}
cur.setReadSize(rl);
readn(this.in, buffer, Smb2Constants.SMB2_HEADER_LENGTH, rl - Smb2Constants.SMB2_HEADER_LENGTH);
len = cur.decode(buffer, 0, true);
if ( len > rl ) {
throw new IOException(String.format("WHAT? ( read %d decoded %d ): %s", rl, len, cur));
}
else if ( nextCommand != 0 && len > nextCommand ) {
throw new IOException("Overlapping commands");
}
size -= rl;
}
}
finally {
getContext().getBufferCache().releaseBuffer(buffer);
}
}
/**
* @param resp
* @throws IOException
* @throws SMBProtocolDecodingException
*/
private void doRecvSMB1 ( CommonServerMessageBlock resp ) throws IOException, SMBProtocolDecodingException {
byte[] buffer = getContext().getBufferCache().getBuffer();
try {
System.arraycopy(this.sbuf, 0, buffer, 0, 4 + SMB1_HEADER_LENGTH);
int size = ( Encdec.dec_uint16be(buffer, 2) & 0xFFFF );
if ( size < ( SMB1_HEADER_LENGTH + 1 ) || ( 4 + size ) > Math.min(0xFFFF, getContext().getConfig().getMaximumBufferSize()) ) {
throw new IOException("Invalid payload size: " + size);
}
int errorCode = Encdec.dec_uint32le(buffer, 9) & 0xFFFFFFFF;
if ( resp.getCommand() == ServerMessageBlock.SMB_COM_READ_ANDX
&& ( errorCode == 0 || errorCode == NtStatus.NT_STATUS_BUFFER_OVERFLOW ) ) {
// overflow indicator normal for pipe
SmbComReadAndXResponse r = (SmbComReadAndXResponse) resp;
int off = SMB1_HEADER_LENGTH;
/* WordCount thru dataOffset always 27 */
readn(this.in, buffer, 4 + off, 27);
off += 27;
resp.decode(buffer, 4);
/* EMC can send pad w/o data */
int pad = r.getDataOffset() - off;
if ( r.getByteCount() > 0 && pad > 0 && pad < 4 )
readn(this.in, buffer, 4 + off, pad);
if ( r.getDataLength() > 0 ) {
readn(this.in, r.getData(), r.getOffset(), r.getDataLength()); /* read direct */
}
}
else {
readn(this.in, buffer, 4 + SMB1_HEADER_LENGTH, size - SMB1_HEADER_LENGTH);
resp.decode(buffer, 4);
}
}
finally {
getContext().getBufferCache().releaseBuffer(buffer);
}
}
@Override
protected void doSkip ( Long key ) throws IOException {
synchronized ( this.inLock ) {
int size = Encdec.dec_uint16be(this.sbuf, 2) & 0xFFFF;
if ( size < 33 || ( 4 + size ) > this.getContext().getConfig().getReceiveBufferSize() ) {
/* log message? */
log.warn("Flusing stream input");
this.in.skip(this.in.available());
}
else {
Response notification = createNotification(key);
if ( notification != null ) {
log.debug("Parsing notification");
doRecv(notification);
handleNotification(notification);
return;
}
log.warn("Skipping message " + key);
if ( this.isSMB2() ) {
this.in.skip(size - Smb2Constants.SMB2_HEADER_LENGTH);
}
else {
this.in.skip(size - SmbConstants.SMB1_HEADER_LENGTH);
}
}
}
}
/**
* @param notification
*/
protected void handleNotification ( Response notification ) {
log.info("Received notification " + notification);
}
/**
* @param key
* @return
* @throws SmbException
*/
protected Response createNotification ( Long key ) throws SmbException {
if ( key == null ) {
// no valid header
return null;
}
if ( this.smb2 ) {
if ( key != -1 ) {
return null;
}
int cmd = Encdec.dec_uint16le(this.sbuf, 4 + 12) & 0xFFFF;
if ( cmd == 0x12 ) {
return new Smb2OplockBreakNotification(getContext().getConfig());
}
}
else {
if ( key != 0xFFFF ) {
return null;
}
int cmd = this.sbuf[ 4 + 4 ];
if ( cmd == 0x24 ) {
return new SmbComLockingAndX(getContext().getConfig());
}
}
return null;
}
boolean checkStatus ( ServerMessageBlock req, ServerMessageBlock resp ) throws SmbException {
boolean cont = false;
if ( resp.getErrorCode() == 0x30002 ) {
// if using DOS error codes this indicates a DFS referral
resp.setErrorCode(NtStatus.NT_STATUS_PATH_NOT_COVERED);
}
else {
resp.setErrorCode(SmbException.getStatusByCode(resp.getErrorCode()));
}
switch ( resp.getErrorCode() ) {
case NtStatus.NT_STATUS_OK:
cont = true;
break;
case NtStatus.NT_STATUS_ACCESS_DENIED:
case NtStatus.NT_STATUS_WRONG_PASSWORD:
case NtStatus.NT_STATUS_LOGON_FAILURE:
case NtStatus.NT_STATUS_ACCOUNT_RESTRICTION:
case NtStatus.NT_STATUS_INVALID_LOGON_HOURS:
case NtStatus.NT_STATUS_INVALID_WORKSTATION:
case NtStatus.NT_STATUS_PASSWORD_EXPIRED:
case NtStatus.NT_STATUS_ACCOUNT_DISABLED:
case NtStatus.NT_STATUS_ACCOUNT_LOCKED_OUT:
case NtStatus.NT_STATUS_TRUSTED_DOMAIN_FAILURE:
throw new SmbAuthException(resp.getErrorCode());
case 0xC00000BB: // NT_STATUS_NOT_SUPPORTED
throw new SmbUnsupportedOperationException();
case NtStatus.NT_STATUS_PATH_NOT_COVERED:
// samba fails to report the proper status for some operations
case 0xC00000A2: // NT_STATUS_MEDIA_WRITE_PROTECTED
checkReferral(resp, req.getPath(), req);
case NtStatus.NT_STATUS_BUFFER_OVERFLOW:
break; /* normal for DCERPC named pipes */
case NtStatus.NT_STATUS_MORE_PROCESSING_REQUIRED:
break; /* normal for NTLMSSP */
default:
if ( log.isDebugEnabled() ) {
log.debug("Error code: 0x" + Hexdump.toHexString(resp.getErrorCode(), 8) + " for " + req.getClass().getSimpleName());
}
throw new SmbException(resp.getErrorCode(), null);
}
if ( resp.isVerifyFailed() ) {
throw new SmbException("Signature verification failed.");
}
return cont;
}
/**
* @param request
* @param response
* @throws SmbException
*/
boolean checkStatus2 ( ServerMessageBlock2 req, Response resp ) throws SmbException {
boolean cont = false;
switch ( resp.getErrorCode() ) {
case NtStatus.NT_STATUS_OK:
case NtStatus.NT_STATUS_NO_MORE_FILES:
cont = true;
break;
case NtStatus.NT_STATUS_PENDING:
// must be the last
cont = false;
break;
case NtStatus.NT_STATUS_ACCESS_DENIED:
case NtStatus.NT_STATUS_WRONG_PASSWORD:
case NtStatus.NT_STATUS_LOGON_FAILURE:
case NtStatus.NT_STATUS_ACCOUNT_RESTRICTION:
case NtStatus.NT_STATUS_INVALID_LOGON_HOURS:
case NtStatus.NT_STATUS_INVALID_WORKSTATION:
case NtStatus.NT_STATUS_PASSWORD_EXPIRED:
case NtStatus.NT_STATUS_ACCOUNT_DISABLED:
case NtStatus.NT_STATUS_ACCOUNT_LOCKED_OUT:
case NtStatus.NT_STATUS_TRUSTED_DOMAIN_FAILURE:
throw new SmbAuthException(resp.getErrorCode());
case NtStatus.NT_STATUS_MORE_PROCESSING_REQUIRED:
break; /* normal for SPNEGO */
case 0x10B: // NT_STATUS_NOTIFY_CLEANUP:
case NtStatus.NT_STATUS_NOTIFY_ENUM_DIR:
break;
case 0xC00000BB: // NT_STATUS_NOT_SUPPORTED
case 0xC0000010: // NT_STATUS_INVALID_DEVICE_REQUEST
throw new SmbUnsupportedOperationException();
case NtStatus.NT_STATUS_PATH_NOT_COVERED:
if ( ! ( req instanceof RequestWithPath ) ) {
throw new SmbException("Invalid request for a DFS NT_STATUS_PATH_NOT_COVERED response " + req.getClass().getName());
}
String path = ( (RequestWithPath) req ).getFullUNCPath();
checkReferral(resp, path, ( (RequestWithPath) req ));
// checkReferral always throws and exception but put break here for clarity
break;
case NtStatus.NT_STATUS_BUFFER_OVERFLOW:
if ( resp instanceof Smb2ReadResponse ) {
break;
}
if ( resp instanceof Smb2IoctlResponse ) {
int ctlCode = ( (Smb2IoctlResponse) resp ).getCtlCode();
if ( ctlCode == Smb2IoctlRequest.FSCTL_PIPE_TRANSCEIVE || ctlCode == Smb2IoctlRequest.FSCTL_PIPE_PEEK ) {
break;
}
}
// fall through
default:
if ( log.isDebugEnabled() ) {
log.debug("Error code: 0x" + Hexdump.toHexString(resp.getErrorCode(), 8) + " for " + req.getClass().getSimpleName());
}
throw new SmbException(resp.getErrorCode(), null);
}
if ( resp.isVerifyFailed() ) {
throw new SMBSignatureValidationException("Signature verification failed.");
}
return cont;
}
/**
* @param resp
* @param path
* @param req
* @throws SmbException
* @throws DfsReferral
*/
private void checkReferral ( Response resp, String path, RequestWithPath req ) throws SmbException, DfsReferral {
DfsReferralData dr = null;
if ( !getContext().getConfig().isDfsDisabled() ) {
try {
dr = getDfsReferrals(getContext(), path, req.getServer(), req.getDomain(), 1);
}
catch ( CIFSException e ) {
throw new SmbException("Failed to get DFS referral", e);
}
}
if ( dr == null ) {
if ( log.isDebugEnabled() ) {
log.debug("Error code: 0x" + Hexdump.toHexString(resp.getErrorCode(), 8));
}
throw new SmbException(resp.getErrorCode(), null);
}
if ( req.getDomain() != null && getContext().getConfig().isDfsConvertToFQDN() && dr instanceof DfsReferralDataImpl ) {
( (DfsReferralDataImpl) dr ).fixupDomain(req.getDomain());
}
if ( log.isDebugEnabled() ) {
log.debug("Got referral " + dr);
}
getContext().getDfs().cache(getContext(), path, dr);
throw new DfsReferral(dr);
}
T send ( CommonServerMessageBlockRequest request, T response ) throws SmbException {
return send(request, response, Collections. emptySet());
}
T send ( CommonServerMessageBlockRequest request, T response, Set params )
throws SmbException {
ensureConnected(); /* must negotiate before we can test flags2, useUnicode, etc */
if ( this.smb2 && ! ( request instanceof ServerMessageBlock2 ) ) {
throw new SmbException("Not an SMB2 request " + request.getClass().getName());
}
else if ( !this.smb2 && ! ( request instanceof ServerMessageBlock ) ) {
throw new SmbException("Not an SMB1 request");
}
this.negotiated.setupRequest(request);
if ( response != null ) {
request.setResponse(response); /* needed by sign */
response.setDigest(request.getDigest());
}
try {
if ( log.isTraceEnabled() ) {
log.trace("Sending " + request);
}
if ( request.isCancel() ) {
doSend0(request);
return null;
}
else if ( request instanceof SmbComTransaction ) {
response = sendComTransaction(request, response, params);
}
else {
if ( response != null ) {
response.setCommand(request.getCommand());
}
response = sendrecv(request, response, params);
}
}
catch ( SmbException se ) {
throw se;
}
catch ( IOException ioe ) {
throw new SmbException(ioe.getMessage(), ioe);
}
if ( log.isTraceEnabled() ) {
log.trace("Response is " + response);
}
checkStatus(request, response);
return response;
}
/**
* @param request
* @param response
* @throws SmbException
*/
private boolean checkStatus ( CommonServerMessageBlockRequest request, T response )
throws SmbException {
CommonServerMessageBlockRequest cur = request;
while ( cur != null ) {
if ( this.smb2 ) {
if ( !checkStatus2((ServerMessageBlock2) cur, cur.getResponse()) ) {
return false;
}
}
else {
if ( !checkStatus((ServerMessageBlock) cur, (ServerMessageBlock) cur.getResponse()) ) {
return false;
}
}
cur = cur.getNext();
}
return true;
}
/**
* @param request
* @param response
* @param params
* @throws IOException
* @throws SmbException
* @throws TransportException
* @throws EOFException
*/
private T sendComTransaction ( CommonServerMessageBlockRequest request, T response,
Set params ) throws IOException, SmbException, TransportException, EOFException {
response.setCommand(request.getCommand());
SmbComTransaction req = (SmbComTransaction) request;
SmbComTransactionResponse resp = (SmbComTransactionResponse) response;
resp.reset();
long k;
/*
* First request w/ interim response
*/
try {
req.setBuffer(getContext().getBufferCache().getBuffer());
req.nextElement();
if ( req.hasMoreElements() ) {
SmbComBlankResponse interim = new SmbComBlankResponse(getContext().getConfig());
super.sendrecv(req, interim, params);
if ( interim.getErrorCode() != 0 ) {
checkStatus(req, interim);
}
k = req.nextElement().getMid();
}
else {
k = makeKey(req);
}
try {
resp.clearReceived();
long timeout = getResponseTimeout(req);
if ( !params.contains(RequestParam.NO_TIMEOUT) ) {
resp.setExpiration(System.currentTimeMillis() + timeout);
}
else {
resp.setExpiration(null);
}
byte[] txbuf = getContext().getBufferCache().getBuffer();
resp.setBuffer(txbuf);
this.response_map.put(k, resp);
/*
* Send multiple fragments
*/
do {
doSend0(req);
}
while ( req.hasMoreElements() && req.nextElement() != null );
/*
* Receive multiple fragments
*/
synchronized ( resp ) {
while ( !resp.isReceived() || resp.hasMoreElements() ) {
if ( !params.contains(RequestParam.NO_TIMEOUT) ) {
resp.wait(timeout);
timeout = resp.getExpiration() - System.currentTimeMillis();
if ( timeout <= 0 ) {
throw new TransportException(this + " timedout waiting for response to " + req);
}
}
else {
resp.wait();
if ( log.isTraceEnabled() ) {
log.trace("Wait returned " + isDisconnected());
}
if ( isDisconnected() ) {
throw new EOFException("Transport closed while waiting for result");
}
}
}
}
if ( !resp.isReceived() ) {
throw new TransportException("Failed to read response");
}
if ( resp.getErrorCode() != 0 ) {
checkStatus(req, resp);
}
return response;
}
finally {
this.response_map.remove(k);
getContext().getBufferCache().releaseBuffer(resp.releaseBuffer());
}
}
catch ( InterruptedException ie ) {
throw new TransportException(ie);
}
finally {
getContext().getBufferCache().releaseBuffer(req.releaseBuffer());
}
}
@Override
public String toString () {
return super.toString() + "[" + this.address + ":" + this.port + ",state=" + this.state + ",signingEnforced=" + this.signingEnforced
+ ",usage=" + this.getUsageCount() + "]";
}
/* DFS */
@Override
public DfsReferralData getDfsReferrals ( CIFSContext ctx, String path, String targetHost, String targetDomain, int rn ) throws CIFSException {
if ( log.isDebugEnabled() ) {
log.debug("Resolving DFS path " + path);
}
if ( path.length() >= 2 && path.charAt(0) == '\\' && path.charAt(1) == '\\' ) {
throw new SmbException("Path must not start with double slash: " + path);
}
try ( SmbSessionImpl sess = getSmbSession(ctx, targetHost, targetDomain);
SmbTransportImpl transport = sess.getTransport();
SmbTreeImpl ipc = sess.getSmbTree("IPC$", null) ) {
DfsReferralRequestBuffer dfsReq = new DfsReferralRequestBuffer(path, 3);
DfsReferralResponseBuffer dfsResp;
if ( isSMB2() ) {
Smb2IoctlRequest req = new Smb2IoctlRequest(ctx.getConfig(), Smb2IoctlRequest.FSCTL_DFS_GET_REFERRALS);
req.setFlags(Smb2IoctlRequest.SMB2_O_IOCTL_IS_FSCTL);
req.setInputData(dfsReq);
dfsResp = ipc.send(req).getOutputData(DfsReferralResponseBuffer.class);
}
else {
Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse(ctx.getConfig());
ipc.send(new Trans2GetDfsReferral(ctx.getConfig(), path), resp);
dfsResp = resp.getDfsResponse();
}
if ( dfsResp.getNumReferrals() == 0 ) {
return null;
}
else if ( rn == 0 || dfsResp.getNumReferrals() < rn ) {
rn = dfsResp.getNumReferrals();
}
DfsReferralDataImpl cur = null;
long expiration = System.currentTimeMillis() + ( ctx.getConfig().getDfsTtl() * 1000 );
Referral[] refs = dfsResp.getReferrals();
for ( int di = 0; di < rn; di++ ) {
DfsReferralDataImpl dr = DfsReferralDataImpl.fromReferral(refs[ di ], path, expiration, dfsResp.getPathConsumed());
dr.setDomain(targetDomain);
if ( ( dfsResp.getTflags() & 0x2 ) == 0 && ( dr.getFlags() & 0x2 ) == 0 ) {
log.debug("Non-root referral is not final " + dfsResp);
dr.intermediate();
}
if ( cur == null ) {
cur = dr;
}
else {
cur.append(dr);
cur = dr;
}
}
if ( log.isDebugEnabled() ) {
log.debug("Got referral " + cur);
}
return cur;
}
}
byte[] getPreauthIntegrityHash () {
return this.preauthIntegrityHash;
}
private void updatePreauthHash ( byte[] input ) throws CIFSException {
synchronized ( this.preauthIntegrityHash ) {
this.preauthIntegrityHash = calculatePreauthHash(input, 0, input.length, this.preauthIntegrityHash);
}
}
byte[] calculatePreauthHash ( byte[] input, int off, int len, byte[] oldHash ) throws CIFSException {
if ( !this.smb2 || this.negotiated == null ) {
throw new SmbUnsupportedOperationException();
}
Smb2NegotiateResponse resp = (Smb2NegotiateResponse) this.negotiated;
if ( !resp.getSelectedDialect().atLeast(DialectVersion.SMB311) ) {
throw new SmbUnsupportedOperationException();
}
MessageDigest dgst;
switch ( resp.getSelectedPreauthHash() ) {
case 1:
dgst = Crypto.getSHA512();
break;
default:
throw new SmbUnsupportedOperationException();
}
if ( oldHash != null ) {
dgst.update(oldHash);
}
dgst.update(input, off, len);
return dgst.digest();
}
Cipher createEncryptionCipher ( byte[] key ) throws CIFSException {
if ( !this.smb2 || this.negotiated == null ) {
throw new SmbUnsupportedOperationException();
}
Smb2NegotiateResponse resp = (Smb2NegotiateResponse) this.negotiated;
int cipherId = -1;
if ( resp.getSelectedDialect().atLeast(DialectVersion.SMB311) ) {
cipherId = resp.getSelectedCipher();
}
else if ( resp.getSelectedDialect().atLeast(DialectVersion.SMB300) ) {
cipherId = EncryptionNegotiateContext.CIPHER_AES128_CCM;
}
else {
throw new SmbUnsupportedOperationException();
}
switch ( cipherId ) {
case EncryptionNegotiateContext.CIPHER_AES128_CCM:
case EncryptionNegotiateContext.CIPHER_AES128_GCM:
default:
throw new SmbUnsupportedOperationException();
}
}
public int getRequestSecurityMode ( Smb2NegotiateResponse first ) {
int securityMode = Smb2Constants.SMB2_NEGOTIATE_SIGNING_ENABLED;
if ( this.signingEnforced || ( first != null && first.isSigningRequired() ) ) {
securityMode = Smb2Constants.SMB2_NEGOTIATE_SIGNING_REQUIRED | Smb2Constants.SMB2_NEGOTIATE_SIGNING_ENABLED;
}
return securityMode;
}
}