
jcifs.smb.SmbTreeImpl 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) 2000 "Michael B. Allen"
*
* 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.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.DfsReferralData;
import jcifs.DialectVersion;
import jcifs.RuntimeCIFSException;
import jcifs.SmbConstants;
import jcifs.SmbTree;
import jcifs.internal.CommonServerMessageBlockRequest;
import jcifs.internal.CommonServerMessageBlockResponse;
import jcifs.internal.RequestWithPath;
import jcifs.internal.SmbNegotiationResponse;
import jcifs.internal.TreeConnectResponse;
import jcifs.internal.smb1.ServerMessageBlock;
import jcifs.internal.smb1.com.SmbComBlankResponse;
import jcifs.internal.smb1.com.SmbComNegotiateResponse;
import jcifs.internal.smb1.com.SmbComTreeConnectAndX;
import jcifs.internal.smb1.com.SmbComTreeConnectAndXResponse;
import jcifs.internal.smb1.com.SmbComTreeDisconnect;
import jcifs.internal.smb1.trans.SmbComTransaction;
import jcifs.internal.smb1.trans2.Trans2FindFirst2;
import jcifs.internal.smb1.trans2.Trans2FindFirst2Response;
import jcifs.internal.smb2.ServerMessageBlock2;
import jcifs.internal.smb2.ioctl.Smb2IoctlRequest;
import jcifs.internal.smb2.ioctl.Smb2IoctlResponse;
import jcifs.internal.smb2.ioctl.ValidateNegotiateInfoRequest;
import jcifs.internal.smb2.ioctl.ValidateNegotiateInfoResponse;
import jcifs.internal.smb2.nego.Smb2NegotiateRequest;
import jcifs.internal.smb2.nego.Smb2NegotiateResponse;
import jcifs.internal.smb2.tree.Smb2TreeConnectRequest;
import jcifs.internal.smb2.tree.Smb2TreeDisconnectRequest;
class SmbTreeImpl implements SmbTreeInternal {
private static final Logger log = LoggerFactory.getLogger(SmbTreeImpl.class);
private static AtomicLong TREE_CONN_COUNTER = new AtomicLong();
/*
* 0 - not connected
* 1 - connecting
* 2 - connected
* 3 - disconnecting
*/
private final AtomicInteger connectionState = new AtomicInteger();
private final String share;
private final String service0;
private final SmbSessionImpl session;
private volatile int tid = -1;
private volatile String service = "?????";
private volatile boolean inDfs, inDomainDfs;
private volatile long treeNum; // used by SmbFile.isOpen
private final AtomicLong usageCount = new AtomicLong(0);
private final AtomicBoolean sessionAcquired = new AtomicBoolean(true);
private final boolean traceResource;
private final List acquires;
private final List releases;
private DfsReferralData treeReferral;
SmbTreeImpl ( SmbSessionImpl session, String share, String service ) {
this.session = session.acquire();
this.share = share.toUpperCase();
if ( service != null && !service.startsWith("??") ) {
this.service = service;
}
this.service0 = this.service;
this.traceResource = this.session.getConfig().isTraceResourceUsage();
if ( this.traceResource ) {
this.acquires = new LinkedList<>();
this.releases = new LinkedList<>();
}
else {
this.acquires = null;
this.releases = null;
}
}
boolean matches ( String shr, String servc ) {
return this.share.equalsIgnoreCase(shr) && ( servc == null || servc.startsWith("??") || this.service.equalsIgnoreCase(servc) );
}
@Override
public boolean equals ( Object obj ) {
if ( obj instanceof SmbTreeImpl ) {
SmbTreeImpl tree = (SmbTreeImpl) obj;
return matches(tree.share, tree.service);
}
return false;
}
public SmbTreeImpl acquire () {
return acquire(true);
}
/**
* {@inheritDoc}
*
* @see jcifs.SmbTree#unwrap(java.lang.Class)
*/
@SuppressWarnings ( "unchecked" )
@Override
public T unwrap ( Class type ) {
if ( type.isAssignableFrom(this.getClass()) ) {
return (T) this;
}
throw new ClassCastException();
}
/**
* @param track
* @return tree with increased usage count
*/
public SmbTreeImpl acquire ( boolean track ) {
long usage = this.usageCount.incrementAndGet();
if ( log.isTraceEnabled() ) {
log.trace("Acquire tree " + usage + " " + this);
}
if ( track && this.traceResource ) {
synchronized ( this.acquires ) {
this.acquires.add(truncateTrace(Thread.currentThread().getStackTrace()));
}
}
if ( usage == 1 ) {
synchronized ( this ) {
if ( this.sessionAcquired.compareAndSet(false, true) ) {
log.debug("Reacquire session");
this.session.acquire();
}
}
}
return this;
}
/**
* {@inheritDoc}
*
* @see java.lang.AutoCloseable#close()
*/
@Override
public void close () {
release(false);
}
public void release () {
release(true);
}
/**
* @param track
*/
public void release ( boolean track ) {
long usage = this.usageCount.decrementAndGet();
if ( log.isTraceEnabled() ) {
log.trace("Release tree " + usage + " " + this);
}
if ( track && this.traceResource ) {
synchronized ( this.releases ) {
this.releases.add(truncateTrace(Thread.currentThread().getStackTrace()));
}
}
if ( usage == 0 ) {
synchronized ( this ) {
log.debug("Usage dropped to zero, release session");
if ( this.sessionAcquired.compareAndSet(true, false) ) {
this.session.release();
}
}
}
else if ( usage < 0 ) {
log.error("Usage count dropped below zero " + this);
dumpResource();
throw new RuntimeCIFSException("Usage count dropped below zero");
}
}
/**
* @param stackTrace
* @return
*/
private static StackTraceElement[] truncateTrace ( StackTraceElement[] stackTrace ) {
int s = 2;
int e = stackTrace.length;
for ( int i = s; i < e; i++ ) {
StackTraceElement se = stackTrace[ i ];
if ( i == s && SmbTreeImpl.class.getName().equals(se.getClassName()) && "close".equals(se.getMethodName()) ) {
s++;
continue;
}
if ( se.getClassName().startsWith("org.junit.runners.") ) {
e = i - 4;
break;
}
}
StackTraceElement[] res = new StackTraceElement[e - s];
System.arraycopy(stackTrace, s, res, 0, e - s);
return res;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize () throws Throwable {
if ( isConnected() && this.usageCount.get() != 0 ) {
log.warn("Tree was not properly released");
}
}
/**
*
* @return whether the tree is connected
*/
public boolean isConnected () {
return this.tid != -1 && this.session.isConnected() && this.connectionState.get() == 2;
}
/**
* @return the type of this tree
*/
public int getTreeType () {
String connectedService = getService();
if ( "LPT1:".equals(connectedService) ) {
return SmbConstants.TYPE_PRINTER;
}
else if ( "COMM".equals(connectedService) ) {
return SmbConstants.TYPE_COMM;
}
return SmbConstants.TYPE_SHARE;
}
/**
* @return the service
*/
public String getService () {
return this.service;
}
/**
* @return the share
*/
public String getShare () {
return this.share;
}
/**
* @return whether this is a DFS share
*/
public boolean isDfs () {
return this.inDfs;
}
/**
*
*/
void markDomainDfs () {
this.inDomainDfs = true;
}
/**
* @return whether this tree was accessed using domain DFS
*/
public boolean isInDomainDfs () {
return this.inDomainDfs;
}
/**
* @param referral
*/
public void setTreeReferral ( DfsReferralData referral ) {
this.treeReferral = referral;
}
/**
* @return the treeReferral
*/
public DfsReferralData getTreeReferral () {
return this.treeReferral;
}
/**
* @return whether this tree may be a DFS share
* @throws SmbException
*/
public boolean isPossiblyDfs () throws SmbException {
if ( this.connectionState.get() == 2 ) {
// we are connected, so we know
return isDfs();
}
try ( SmbTransportImpl transport = this.session.getTransport() ) {
return transport.getNegotiateResponse().isDFSSupported();
}
}
/**
* @return the session this tree is connected in
*/
public SmbSessionImpl getSession () {
return this.session.acquire();
}
/**
* @return the tid
*/
public int getTid () {
return this.tid;
}
/**
* @return the tree_num (monotonically increasing counter to track reconnects)
*/
public long getTreeNum () {
return this.treeNum;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode () {
return this.share.hashCode() + 7 * this.service.hashCode();
}
@Override
public T send ( jcifs.internal.Request request, RequestParam... params ) throws CIFSException {
return send(
(CommonServerMessageBlockRequest) request,
request.getResponse(),
( params != null && params.length > 0 ) ? EnumSet.copyOf(Arrays.asList(params)) : EnumSet.noneOf(RequestParam.class));
}
T send ( CommonServerMessageBlockRequest request, T response ) throws CIFSException {
return send(request, response, Collections. emptySet());
}
T send ( CommonServerMessageBlockRequest request, T response, Set params )
throws CIFSException {
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
if ( response != null ) {
response.clearReceived();
}
// try TreeConnectAndX with the request
// this does not make any sense if we are disconnecting right now
T chainedResponse = null;
if ( ! ( request instanceof SmbComTreeDisconnect ) && ! ( request instanceof Smb2TreeDisconnectRequest ) ) {
chainedResponse = treeConnect(request, response);
}
if ( request == null || ( chainedResponse != null && chainedResponse.isReceived() ) ) {
return chainedResponse;
}
// fall trough if the tree connection is already established
// and send it as a separate request instead
String svc = null;
int t = this.tid;
request.setTid(t);
if ( !transport.isSMB2() ) {
ServerMessageBlock req = (ServerMessageBlock) request;
svc = this.service;
if ( svc == null ) {
// there still is some kind of race condition, where?
// this used to trigger "invalid operation..."
throw new SmbException("Service is null in state " + this.connectionState.get());
}
checkRequest(transport, req, svc);
}
if ( this.isDfs() && !"IPC".equals(svc) && !"IPC$".equals(this.share) && request instanceof RequestWithPath ) {
/*
* When DFS is in action all request paths are
* full UNC paths minus the first backslash like
* \server\share\path\to\file
* as opposed to normally
* \path\to\file
*/
RequestWithPath preq = (RequestWithPath) request;
if ( preq.getPath() != null && preq.getPath().length() > 0 ) {
if ( log.isDebugEnabled() ) {
log.debug(String.format("Setting DFS request path from %s to %s", preq.getPath(), preq.getFullUNCPath()));
}
preq.setResolveInDfs(true);
preq.setPath(preq.getFullUNCPath());
}
}
try {
return sess.send(request, response, params);
}
catch ( SmbException se ) {
if ( se.getNtStatus() == NtStatus.NT_STATUS_NETWORK_NAME_DELETED ) {
/*
* Someone removed the share while we were
* connected. Bastards! Disconnect this tree
* so that it reconnects cleanly should the share
* reappear in this client's lifetime.
*/
log.debug("Disconnect tree on NT_STATUS_NETWORK_NAME_DELETED");
treeDisconnect(true, true);
}
throw se;
}
}
}
/**
* @param transport
* @param request
* @throws SmbException
*/
private static void checkRequest ( SmbTransportImpl transport, ServerMessageBlock request, String svc ) throws SmbException {
if ( !"A:".equals(svc) ) {
switch ( request.getCommand() ) {
case ServerMessageBlock.SMB_COM_OPEN_ANDX:
case ServerMessageBlock.SMB_COM_NT_CREATE_ANDX:
case ServerMessageBlock.SMB_COM_READ_ANDX:
case ServerMessageBlock.SMB_COM_WRITE_ANDX:
case ServerMessageBlock.SMB_COM_CLOSE:
case ServerMessageBlock.SMB_COM_TREE_DISCONNECT:
break;
case ServerMessageBlock.SMB_COM_TRANSACTION:
case ServerMessageBlock.SMB_COM_TRANSACTION2:
switch ( ( (SmbComTransaction) request ).getSubCommand() & 0xFF ) {
case SmbComTransaction.NET_SHARE_ENUM:
case SmbComTransaction.NET_SERVER_ENUM2:
case SmbComTransaction.NET_SERVER_ENUM3:
case SmbComTransaction.TRANS_PEEK_NAMED_PIPE:
case SmbComTransaction.TRANS_WAIT_NAMED_PIPE:
case SmbComTransaction.TRANS_CALL_NAMED_PIPE:
case SmbComTransaction.TRANS_TRANSACT_NAMED_PIPE:
case SmbComTransaction.TRANS2_GET_DFS_REFERRAL:
break;
default:
throw new SmbException("Invalid operation for " + svc + " service: " + request);
}
break;
default:
throw new SmbException("Invalid operation for " + svc + " service" + request);
}
}
}
@SuppressWarnings ( "unchecked" )
T treeConnect ( CommonServerMessageBlockRequest andx, T andxResponse ) throws CIFSException {
CommonServerMessageBlockRequest request = null;
TreeConnectResponse response = null;
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
synchronized ( transport ) {
// this needs to be done before the reference to the remote hostname later
transport.ensureConnected();
if ( waitForState(transport) == 2 ) {
// already connected
return null;
}
int before = this.connectionState.getAndSet(1);
if ( before == 1 ) {
// concurrent connection attempt
if ( waitForState(transport) == 2 ) {
// finished connecting
return null;
}
// failure to connect
throw new SmbException("Tree disconnected while waiting for connection");
}
else if ( before == 2 ) {
// concurrently connected
return null;
}
if ( log.isDebugEnabled() ) {
log.debug("Connection state was " + before);
}
try {
/*
* The hostname to use in the path is only known for
* sure if the NetBIOS session has been successfully
* established.
*/
String tconHostName = sess.getTargetHost();
if ( tconHostName == null ) {
throw new SmbException("Transport disconnected while waiting for connection");
}
SmbNegotiationResponse nego = transport.getNegotiateResponse();
String unc = "\\\\" + tconHostName + '\\' + this.share;
/*
* IBM iSeries doesn't like specifying a service. Always reset
* the service to whatever was determined in the constructor.
*/
String svc = this.service0;
/*
* Tree Connect And X Request / Response
*/
if ( log.isDebugEnabled() ) {
log.debug("treeConnect: unc=" + unc + ",service=" + svc);
}
if ( transport.isSMB2() ) {
Smb2TreeConnectRequest req = new Smb2TreeConnectRequest(sess.getConfig(), unc);
if ( andx != null ) {
req.chain((ServerMessageBlock2) andx);
}
request = req;
}
else {
response = new SmbComTreeConnectAndXResponse(sess.getConfig(), (ServerMessageBlock) andxResponse);
request = new SmbComTreeConnectAndX(
sess.getContext(),
( (SmbComNegotiateResponse) nego ).getServerData(),
unc,
svc,
(ServerMessageBlock) andx);
}
response = sess.send(request, response);
treeConnected(transport, sess, response);
if ( andxResponse != null && andxResponse.isReceived() ) {
return andxResponse;
}
else if ( transport.isSMB2() ) {
return (T) response.getNextResponse();
}
return null;
}
catch ( IOException se ) {
if ( request != null && request.getResponse() != null ) {
// tree connect might still have succeeded
response = (TreeConnectResponse) request.getResponse();
if ( response.isReceived() && !response.isError() && response.getErrorCode() == NtStatus.NT_STATUS_OK ) {
if ( !transport.isDisconnected() ) {
treeConnected(transport, sess, response);
}
throw se;
}
}
try {
log.debug("Disconnect tree on treeConnectFailure", se);
treeDisconnect(true, true);
}
finally {
this.connectionState.set(0);
}
throw se;
}
finally {
transport.notifyAll();
}
}
}
}
/**
* @param transport
* @param sess
* @param response
* @throws IOException
*/
private void treeConnected ( SmbTransportImpl transport, SmbSessionImpl sess, TreeConnectResponse response ) throws CIFSException {
if ( !response.isValidTid() ) {
throw new SmbException("TreeID is invalid");
}
this.tid = response.getTid();
String rsvc = response.getService();
if ( rsvc == null && !transport.isSMB2() ) {
throw new SmbException("Service is NULL");
}
if ( transport.getContext().getConfig().isIpcSigningEnforced() && ( "IPC$".equals(this.getShare()) || "IPC".equals(rsvc) )
&& !sess.getCredentials().isAnonymous() && sess.getDigest() == null ) {
throw new SmbException("IPC signing is enforced, but no signing is available");
}
this.service = rsvc;
this.inDfs = response.isShareDfs();
this.treeNum = TREE_CONN_COUNTER.incrementAndGet();
this.connectionState.set(2); // connected
try {
validateNegotiation(transport, sess);
}
catch ( CIFSException se ) {
try {
transport.disconnect(true);
}
catch ( IOException e ) {
log.warn("Failed to disconnect transport", e);
se.addSuppressed(e);
}
throw se;
}
}
/**
* @param trans
* @param sess
* @throws CIFSException
*
*/
private void validateNegotiation ( SmbTransportImpl trans, SmbSessionImpl sess ) throws CIFSException {
if ( !trans.isSMB2() || trans.getDigest() == null || !sess.getConfig().isRequireSecureNegotiate() ) {
log.debug("Secure negotiation does not apply");
return;
}
Smb2NegotiateResponse nego = (Smb2NegotiateResponse) trans.getNegotiateResponse();
if ( nego.getSelectedDialect().atLeast(DialectVersion.SMB311) ) {
// have preauth integrity instead
log.debug("Secure negotiation does not apply, is SMB3.1");
return;
}
Smb2NegotiateRequest negoReq = new Smb2NegotiateRequest(sess.getConfig(), trans.getRequestSecurityMode(nego));
log.debug("Sending VALIDATE_NEGOTIATE_INFO");
Smb2IoctlRequest req = new Smb2IoctlRequest(sess.getConfig(), Smb2IoctlRequest.FSCTL_VALIDATE_NEGOTIATE_INFO);
req.setFlags(Smb2IoctlRequest.SMB2_O_IOCTL_IS_FSCTL);
req.setInputData(
new ValidateNegotiateInfoRequest(
negoReq.getCapabilities(),
negoReq.getClientGuid(),
(short) negoReq.getSecurityMode(),
negoReq.getDialects()));
Smb2IoctlResponse resp;
try {
resp = send(req, RequestParam.NO_RETRY);
}
catch ( SMBSignatureValidationException e ) {
throw new SMBProtocolDowngradeException("Signature error during negotiate validation", e);
}
catch ( SmbException e ) {
if ( log.isDebugEnabled() ) {
log.debug(String.format("VALIDATE_NEGOTIATE_INFO response code 0x%x", e.getNtStatus()));
}
log.trace("VALIDATE_NEGOTIATE_INFO returned error", e);
if ( ( req.getResponse().isReceived() && req.getResponse().isVerifyFailed() ) || e.getNtStatus() == NtStatus.NT_STATUS_ACCESS_DENIED ) {
// this is the signature error
throw new SMBProtocolDowngradeException("Signature error during negotiate validation", e);
}
// other errors are treated as success
return;
}
ValidateNegotiateInfoResponse out = resp.getOutputData(ValidateNegotiateInfoResponse.class);
if ( nego.getSecurityMode() != out.getSecurityMode() || nego.getCapabilities() != out.getCapabilities()
|| nego.getDialectRevision() != out.getDialect() || !Arrays.equals(nego.getServerGuid(), out.getServerGuid()) ) {
log.debug("Secure negotiation failure");
throw new CIFSException("Mismatched attributes validating negotiate info");
}
log.debug("Secure negotiation OK");
}
/**
* @param transport
* @return
* @throws SmbException
*/
private int waitForState ( SmbTransportImpl transport ) throws SmbException {
int cs;
while ( ( cs = this.connectionState.get() ) != 0 ) {
if ( cs == 2 ) {
return cs;
}
if ( cs == 3 ) {
throw new SmbException("Disconnecting during tree connect");
}
try {
log.debug("Waiting for transport");
transport.wait();
}
catch ( InterruptedException ie ) {
throw new SmbException(ie.getMessage(), ie);
}
}
return cs;
}
/**
*
* {@inheritDoc}
*
* @see jcifs.smb.SmbTreeInternal#connectLogon(jcifs.CIFSContext)
*/
@Override
@Deprecated
public void connectLogon ( CIFSContext tf ) throws SmbException {
if ( tf.getConfig().getLogonShare() == null ) {
try {
treeConnect(null, null);
}
catch ( SmbException e ) {
throw e;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
else {
Trans2FindFirst2 req = new Trans2FindFirst2(
tf.getConfig(),
"\\",
"*",
SmbConstants.ATTR_DIRECTORY,
tf.getConfig().getListCount(),
tf.getConfig().getListSize());
Trans2FindFirst2Response resp = new Trans2FindFirst2Response(tf.getConfig());
try {
send(req, resp);
}
catch ( SmbException e ) {
throw e;
}
catch ( CIFSException e ) {
throw new SmbException("Logon share connection failed", e);
}
}
}
boolean treeDisconnect ( boolean inError, boolean inUse ) {
boolean wasInUse = false;
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
synchronized ( transport ) {
int st = this.connectionState.getAndSet(3);
if ( st == 2 ) {
long l = this.usageCount.get();
if ( ( inUse && l != 1 ) || ( !inUse && l > 0 ) ) {
log.warn("Disconnected tree while still in use " + this);
dumpResource();
wasInUse = true;
if ( sess.getConfig().isTraceResourceUsage() ) {
throw new RuntimeCIFSException("Disconnected tree while still in use");
}
}
if ( !inError && this.tid != -1 ) {
try {
if ( transport.isSMB2() ) {
Smb2TreeDisconnectRequest req = new Smb2TreeDisconnectRequest(sess.getConfig());
send(req.ignoreDisconnect());
}
else {
send(new SmbComTreeDisconnect(sess.getConfig()), new SmbComBlankResponse(sess.getConfig()));
}
}
catch ( CIFSException se ) {
log.error("Tree disconnect failed", se);
}
}
}
this.inDfs = false;
this.inDomainDfs = false;
this.connectionState.set(0);
transport.notifyAll();
}
}
return wasInUse;
}
/**
*
*/
private void dumpResource () {
if ( !this.traceResource ) {
return;
}
synchronized ( this.acquires ) {
for ( StackTraceElement[] acq : this.acquires ) {
log.debug("Acquire " + Arrays.toString(acq));
}
}
synchronized ( this.releases ) {
for ( StackTraceElement[] rel : this.releases ) {
log.debug("Release " + Arrays.toString(rel));
}
}
}
@Override
public String toString () {
return "SmbTree[share=" + this.share + ",service=" + this.service + ",tid=" + this.tid + ",inDfs=" + this.inDfs + ",inDomainDfs="
+ this.inDomainDfs + ",connectionState=" + this.connectionState + ",usage=" + this.usageCount.get() + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy