From: Felix Schumacher Date: Wed, 6 Aug 2008 14:23:03 +0000 (+0200) Subject: jcifs-1.1.0 from tgz X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=8a1a563cdf61c69cccc8f78ffe1f87241476c112;p=jcifs_without_docs.git jcifs-1.1.0 from tgz jcifs-1.1.0 released The behavior of the firstCalledName/nextCalledName methods has been changed to try SMBSERVER* first, then the NetBIOS hostname, then the 0x20 name from a Node Status. It is pretty universal now that SMBSERVER* rules the day and most servers return failure with the NetBIOS name so this behavior eliminates a round trip during session establishment. The NbtAddress.getByName method has been implemented. This will return the full list of RDATA for a name query response. Currently I believe only the 0x1C domain lookup actually returns multiple results. Note this is different from getAllByAddress which does a node status. The socket code in SmbTransport has been modified to open the socket using the transport thread. This permits the caller of the transport to call wait for RESPONSE_TIMEOUT. This is great if your application has a tendency to try to connect to hosts that do not exist. Normally that would take over a minute to timeout. The single threaded SmbCrawler actually performs quite well with the right properties set. An SmbSession.getChallengeForDomain() method has been added that returns an NtlmChallenge object containing the byte[] challenge and UniAddress of the domain controller from which it came. This method will rotate through a list of at most jcifs.netbios.lookupRespLimit addresses and will only return a challenge for a responsive server. Unresponsive servers will be removed from the list until the jcifs.netbios.cachePolicy has expired. This function is used by the NTLM HTTP Filter to locate suitable domain controllers. Because of the above rotation there is a greater potential for transports to remain open. Sessions with no activity (this is particularly true with the NTLM HTTP Filter which really only touches the session once when the user is authenticated) will be logged off after jcifs.smb.client.soTimeout. A read bug that only manafested itself with a certain EMC server has been fixed. --- diff --git a/README.txt b/README.txt index 5cb40b7..8243d20 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,40 @@ +jcifs-1.1.0 released + +The behavior of the firstCalledName/nextCalledName methods has been changed +to try SMBSERVER* first, then the NetBIOS hostname, then the 0x20 name from +a Node Status. It is pretty universal now that SMBSERVER* rules the day and +most servers return failure with the NetBIOS name so this behavior +eliminates a round trip during session establishment. + +The NbtAddress.getByName method has been implemented. This will return the +full list of RDATA for a name query response. Currently I believe only the +0x1C domain lookup actually returns multiple results. Note this is +different from getAllByAddress which does a node status. + +The socket code in SmbTransport has been modified to open the socket using +the transport thread. This permits the caller of the transport to call wait +for RESPONSE_TIMEOUT. This is great if your application has a tendency to +try to connect to hosts that do not exist. Normally that would take over a +minute to timeout. The single threaded SmbCrawler actually performs quite +well with the right properties set. + +An SmbSession.getChallengeForDomain() method has been added that returns an +NtlmChallenge object containing the byte[] challenge and UniAddress of the +domain controller from which it came. This method will rotate through a +list of at most jcifs.netbios.lookupRespLimit addresses and will only +return a challenge for a responsive server. Unresponsive servers will be +removed from the list until the jcifs.netbios.cachePolicy has expired. This +function is used by the NTLM HTTP Filter to locate suitable domain +controllers. + +Because of the above rotation there is a greater potential for transports +to remain open. Sessions with no activity (this is particularly true with +the NTLM HTTP Filter which really only touches the session once when the +user is authenticated) will be logged off after jcifs.smb.client.soTimeout. + +A read bug that only manafested itself with a certain EMC server has been +fixed. + Mon Sep 6 20:44:14 EDT 2004 jcifs-1.0.1 released diff --git a/build.xml b/build.xml index 7bca1eb..1a631f8 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,7 @@ - - + + @@ -10,6 +10,7 @@ Available Build Targets: dependencies: Checks that all class dependencies are met. compile: Builds the jCIFS classes. jar: Packages the jCIFS classes into a .jar file. + docs: XSLT generated website pages javadoc: Creates the Javadoc API documentation. all: Performs all of the above. @@ -79,7 +80,7 @@ dependencies: Checks that all class dependencies are met. - + diff --git a/examples/ListDC.java b/examples/ListDC.java new file mode 100644 index 0000000..b0ad0f2 --- /dev/null +++ b/examples/ListDC.java @@ -0,0 +1,13 @@ +import jcifs.netbios.NbtAddress; + +public class ListDC { + + public static void main( String argv[] ) throws Exception { + + NbtAddress[] addrs = NbtAddress.getAllByName( argv[0], 0x1C, null, null ); + + for( int i = 0; i < addrs.length; i++ ) { + System.out.println( addrs[i] ); + } + } +} diff --git a/examples/jcifs.prp b/examples/jcifs.prp index 2e10ae8..e5f0f17 100644 --- a/examples/jcifs.prp +++ b/examples/jcifs.prp @@ -1,11 +1,11 @@ jcifs.smb.client.domain = mydom jcifs.smb.client.username = miallen jcifs.smb.client.password = p@ssw0rd -;jcifs.netbios.wins = 196.22.20.21 -jcifs.netbios.wins = 164.25.87.5 +jcifs.resolveOrder = WINS,DNS +jcifs.netbios.wins = 196.22.20.21,164.25.87.5 +jcifs.smb.client.signingPreferred = true ;jcifs.util.loglevel = 10 -;jcifs.resolveOrder = WINS,DNS -;jcifs.netbios.cachePolicy = 3 +;jcifs.netbios.cachePolicy = 30 ;jcifs.smb.client.maxBuffers = 20 ;jcifs.smb.client.soTimeout = 4000 ;jcifs.smb.client.responseTimeout = 15000 @@ -14,7 +14,6 @@ jcifs.netbios.wins = 164.25.87.5 ;jcifs.smb.client.useUnicode = false ;jcifs.smb.client.disablePlainTextPasswords = false ;jcifs.smb.client.snd_buf_size = 8192 -;jcifs.smb.client.signingPreferred = true ;jcifs.smb.client.useNTSmbs = false ;jcifs.smb.client.useBatching = false ;jcifs.smb.client.listSize = 1200 diff --git a/examples/runtests.sh b/examples/runtests.sh index 6978e66..e690268 100644 --- a/examples/runtests.sh +++ b/examples/runtests.sh @@ -5,11 +5,11 @@ CLASSPATH=../build:. PROPERTIES=../../miallen.prp RUN="${JAVA_HOME}/bin/java -cp ${CLASSPATH} -Djcifs.properties=${PROPERTIES}" -SERVER=rnycwbmallen1 +SERVER=servername SHARE=pub WRITE_DIR=test -SRC_DIR=test/variety -FILE1=test/variety/Batch2/crash.bmp +SRC_DIR=test/Junk +FILE1=test/Junk/tracer/m31_gendler_big.jpg URL_SHARE=smb://${SERVER}/${SHARE}/ URL_WRITE_DIR=${URL_SHARE}${WRITE_DIR}/ @@ -43,7 +43,7 @@ $RUN Mkdir ${URL_WRITE_DIR}Mkdir $RUN NodeStatus ${SERVER} $RUN Put ${URL_WRITE_DIR}Makefile $RUN Query ${SERVER} -$RUN RenameTo ${URL_WRITE_DIR}Makefile ${URL_WRITE_DIR}/Makefile.txt +$RUN RenameTo ${URL_WRITE_DIR}Makefile ${URL_WRITE_DIR}Makefile.txt $RUN SetAttrs ${URL_WRITE_DIR}Makefile.txt FFFF $RUN SetTime ${URL_WRITE_DIR}Makefile.txt $RUN SlowWrite ${URL_WRITE_DIR}SlowWrite.txt diff --git a/examples/web.xml b/examples/web.xml index c8676ed..b5f61e4 100644 --- a/examples/web.xml +++ b/examples/web.xml @@ -1,20 +1,35 @@ - + + + + jcifs.netbios.wins + 164.152.78.10,164.152.4.22 + + jcifs.smb.client.domain - MYDOMAIN + NYC-USERS + - jcifs.netbios.wins - 164.215.78.56,196.141.101.3 + jcifs.smb.client.username + jcifsmachacct + jcifs.smb.client.password + VeRYr@nDoMpa$sW0rD + + + jcifs.util.loglevel 2 - - + diff --git a/src/jcifs/http/NtlmHttpFilter.java b/src/jcifs/http/NtlmHttpFilter.java index 5b020c5..3ecad04 100644 --- a/src/jcifs/http/NtlmHttpFilter.java +++ b/src/jcifs/http/NtlmHttpFilter.java @@ -28,6 +28,7 @@ import javax.servlet.*; import javax.servlet.http.*; import jcifs.*; import jcifs.smb.SmbSession; +import jcifs.smb.NtlmChallenge; import jcifs.smb.NtlmPasswordAuthentication; import jcifs.smb.SmbAuthException; import jcifs.util.Base64; @@ -112,17 +113,28 @@ public class NtlmHttpFilter implements Filter { if( msg != null && (msg.startsWith( "NTLM " ) || (offerBasic && msg.startsWith("Basic ")))) { - if( loadBalance ) { - dc = new UniAddress( NbtAddress.getByName( domainController, 0x1C, null )); - } else { - dc = UniAddress.getByName( domainController, true ); - } if (msg.startsWith("NTLM ")) { - req.getSession(); - byte[] challenge = SmbSession.getChallenge( dc ); + HttpSession ssn = req.getSession(); + byte[] challenge; + + if( loadBalance ) { + NtlmChallenge chal = (NtlmChallenge)ssn.getAttribute( "NtlmHttpChal" ); + if( chal == null ) { + chal = SmbSession.getChallengeForDomain(); + ssn.setAttribute( "NtlmHttpChal", chal ); + } + dc = chal.dc; + challenge = chal.challenge; + } else { + dc = UniAddress.getByName( domainController, true ); + challenge = SmbSession.getChallenge( dc ); + } + if(( ntlm = NtlmSsp.authenticate( req, resp, challenge )) == null ) { return; } + /* negotiation complete, remove the challenge object */ + ssn.removeAttribute( "NtlmHttpChal" ); } else { String auth = new String(Base64.decode(msg.substring(6)), "US-ASCII"); @@ -136,6 +148,7 @@ public class NtlmHttpFilter implements Filter { defaultDomain; user = (index != -1) ? user.substring(index + 1) : user; ntlm = new NtlmPasswordAuthentication(domain, user, password); + dc = UniAddress.getByName( domainController, true ); } try { diff --git a/src/jcifs/netbios/NameQueryResponse.java b/src/jcifs/netbios/NameQueryResponse.java index 5569839..693e0e9 100644 --- a/src/jcifs/netbios/NameQueryResponse.java +++ b/src/jcifs/netbios/NameQueryResponse.java @@ -20,8 +20,6 @@ package jcifs.netbios; class NameQueryResponse extends NameServicePacket { - NbtAddress addrEntry; - NameQueryResponse() { recordName = new Name(); } @@ -43,7 +41,12 @@ class NameQueryResponse extends NameServicePacket { int nodeType = ( src[srcIndex] & 0x60 ) >> 5; srcIndex += 2; int address = readInt4( src, srcIndex ); - addrEntry = new NbtAddress( recordName, address, groupName, nodeType ); + if( address != 0 ) { + addrEntry[addrIndex] = new NbtAddress( recordName, address, groupName, nodeType ); + } else { + addrEntry[addrIndex] = null; + } + return 6; } public String toString() { diff --git a/src/jcifs/netbios/NameServiceClient.java b/src/jcifs/netbios/NameServiceClient.java index 5eef004..ac9f574 100644 --- a/src/jcifs/netbios/NameServiceClient.java +++ b/src/jcifs/netbios/NameServiceClient.java @@ -256,6 +256,24 @@ class NameServiceClient implements Runnable { } } + NbtAddress[] getAllByName( Name name, InetAddress addr ) + throws UnknownHostException { + NameQueryRequest request = new NameQueryRequest( name ); + NameQueryResponse response = new NameQueryResponse(); + + request.addr = addr != null ? addr : NbtAddress.getWINSAddress(); + request.isBroadcast = false; + + try { + send( request, response, RETRY_TIMEOUT ); + } catch( IOException ioe ) { + if( log.level > 1 ) + ioe.printStackTrace( log ); + throw new UnknownHostException( name.name ); + } + + return response.addrEntry; + } NbtAddress getByName( Name name, InetAddress addr ) throws UnknownHostException { int n; @@ -279,8 +297,8 @@ class NameServiceClient implements Runnable { } if( response.received && response.resultCode == 0 ) { - response.addrEntry.hostName.srcHashCode = addr.hashCode(); - return response.addrEntry; + response.addrEntry[0].hostName.srcHashCode = addr.hashCode(); + return response.addrEntry[0]; } } while( --n > 0 && request.isBroadcast ); @@ -330,9 +348,9 @@ class NameServiceClient implements Runnable { * Lmhosts, WINS, or BCAST. Otherwise a failed query from say WINS would * get pulled out of the cache for a BCAST on the same name. */ - response.addrEntry.hostName.srcHashCode = + response.addrEntry[0].hostName.srcHashCode = request.addr.hashCode(); - return response.addrEntry; + return response.addrEntry[0]; } else if( resolveOrder[i] == RESOLVER_WINS ) { /* If WINS reports negative, no point in retry */ diff --git a/src/jcifs/netbios/NameServicePacket.java b/src/jcifs/netbios/NameServicePacket.java index ed407da..38b7d47 100644 --- a/src/jcifs/netbios/NameServicePacket.java +++ b/src/jcifs/netbios/NameServicePacket.java @@ -23,10 +23,6 @@ import jcifs.util.Hexdump; abstract class NameServicePacket { - private static final int LOOKUP_RESP_LIMIT = jcifs.Config.getInt( "jcifs.netbios.lookupRespLimit", 5 ); - - private static int addrIndex = 0; - // opcode static final int QUERY = 0; static final int WACK = 7; @@ -83,6 +79,9 @@ abstract class NameServicePacket { return readInt2( src, srcIndex ); } + int addrIndex; + NbtAddress[] addrEntry; + int nameTrnId; int opCode, @@ -221,18 +220,11 @@ abstract class NameServicePacket { rDataLength = readInt2( src, srcIndex ); srcIndex += 2; + addrEntry = new NbtAddress[rDataLength / 6]; end = srcIndex + rDataLength; - for( int i = 0; srcIndex < end; i++ ) { + for( addrIndex = 0; srcIndex < end; addrIndex++ ) { srcIndex += readRDataWireFormat( src, srcIndex ); - if( i == addrIndex ) { - addrIndex++; - if( addrIndex == LOOKUP_RESP_LIMIT ) { - addrIndex = 0; - } - return end - start; - } } - addrIndex = 0; return srcIndex - start; } diff --git a/src/jcifs/netbios/NbtAddress.java b/src/jcifs/netbios/NbtAddress.java index 889a6a4..cbff6e3 100644 --- a/src/jcifs/netbios/NbtAddress.java +++ b/src/jcifs/netbios/NbtAddress.java @@ -443,6 +443,14 @@ public final class NbtAddress { } } + public static NbtAddress[] getAllByName( String host, + int type, + String scope, + InetAddress svr ) + throws UnknownHostException { + return CLIENT.getAllByName( new Name( host, type, scope ), svr ); + } + /** * Retrieve all addresses of a host by it's address. NetBIOS hosts can * have many names for a given IP address. The name and IP address make the @@ -574,38 +582,14 @@ public final class NbtAddress { */ public String firstCalledName() { - - calledName = hostName.name; - - if( Character.isDigit( calledName.charAt( 0 ))) { - int i, len, dots; - char[] data; - - i = dots = 0; /* quick IP address validation */ - len = calledName.length(); - data = calledName.toCharArray(); - while( i < len && Character.isDigit( data[i++] )) { - if( i == len && dots == 3 ) { - // probably an IP address - calledName = SMBSERVER_NAME; - break; - } - if( i < len && data[i] == '.' ) { - dots++; - i++; - } - } - } else if( hostName.hexCode == 0x1D ) { - calledName = SMBSERVER_NAME; - } - + calledName = SMBSERVER_NAME; return calledName; } public String nextCalledName() { - if( calledName == hostName.name ) { - calledName = SMBSERVER_NAME; - } else if( calledName == SMBSERVER_NAME ) { + if( calledName == SMBSERVER_NAME ) { + calledName = hostName.name; + } else if( calledName == hostName.name ) { NbtAddress[] addrs; try { diff --git a/src/jcifs/smb/DosFileFilter.java b/src/jcifs/smb/DosFileFilter.java index 8ac89b4..a9514f3 100644 --- a/src/jcifs/smb/DosFileFilter.java +++ b/src/jcifs/smb/DosFileFilter.java @@ -23,18 +23,23 @@ public class DosFileFilter implements SmbFileFilter { protected String wildcard; protected int attributes; +/* This filter can be considerably more efficient than other file filters + * as the specifed wildcard and attributes are passed to the server for + * filtering there. + */ public DosFileFilter( String wildcard, int attributes ) { this.wildcard = wildcard; this.attributes = attributes; } - /** - * This always returns true as the wildcard and - * attributes members are passed to the server which uses them to - * filter on behalf of the client. Sub-classes might overload this - * method to further filter the list however. - */ +/* This returns true if the file's attributes contain any of the attributes + * specified for this filter. The wildcard has no influence on this + * method as the server should have performed that filtering already. The + * attributes are asserted here only because server file systems may not + * support filtering by all attributes (e.g. even though ATTR_DIRECTORY was + * specified the server may still return objects that are not directories). + */ public boolean accept( SmbFile file ) throws SmbException { - return true; + return (file.getAttributes() & attributes) != 0; } } diff --git a/src/jcifs/smb/NtlmChallenge.java b/src/jcifs/smb/NtlmChallenge.java new file mode 100644 index 0000000..c5c0874 --- /dev/null +++ b/src/jcifs/smb/NtlmChallenge.java @@ -0,0 +1,39 @@ +/* jcifs smb client library in Java + * Copyright (C) 2004 "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 jcifs.UniAddress; +import jcifs.util.Hexdump; + +public final class NtlmChallenge { + + public byte[] challenge; + public UniAddress dc; + + NtlmChallenge( byte[] challenge, UniAddress dc ) { + this.challenge = challenge; + this.dc = dc; + } + + public String toString() { + return "NtlmChallenge[challenge=0x" + + Hexdump.toHexString( challenge, 0, challenge.length * 2 ) + + ",dc=" + dc.toString() + "]"; + } +} diff --git a/src/jcifs/smb/SmbComReadAndXResponse.java b/src/jcifs/smb/SmbComReadAndXResponse.java index e8a7cdc..397dea4 100644 --- a/src/jcifs/smb/SmbComReadAndXResponse.java +++ b/src/jcifs/smb/SmbComReadAndXResponse.java @@ -66,7 +66,7 @@ class SmbComReadAndXResponse extends AndXServerMessageBlock { int pad = dataOffset - ( HEADER_LENGTH + 3 + wordCount * 2 ); in.read( buffer, bufferIndex, pad ); /* needed for signing */ in.read( b, off, dataLength ); - return dataLength; + return pad + dataLength; } public String toString() { return new String( "SmbComReadAndXResponse[" + diff --git a/src/jcifs/smb/SmbSession.java b/src/jcifs/smb/SmbSession.java index 3b95cc1..ad3c055 100644 --- a/src/jcifs/smb/SmbSession.java +++ b/src/jcifs/smb/SmbSession.java @@ -24,6 +24,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import jcifs.Config; import jcifs.UniAddress; +import jcifs.netbios.NbtAddress; /** * The class represents a user's session established with an SMB/CIFS @@ -52,6 +53,75 @@ changed without changing init parameters or reinitializing the webapp. public final class SmbSession { private static final String LOGON_SHARE = Config.getProperty( "jcifs.smb.client.logonShare", "IPC$" ); + private static final int LOOKUP_RESP_LIMIT = jcifs.Config.getInt( "jcifs.netbios.lookupRespLimit", 5 ); + private static final String DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + private static final String USERNAME = Config.getProperty("jcifs.smb.client.username", null); + private static final int DEFAULT_CACHE_POLICY = 30; + private static final int CACHE_POLICY = Config.getInt( "jcifs.netbios.cachePolicy", DEFAULT_CACHE_POLICY ); + + static long dc_list_expiration; + static NbtAddress[] dc_list = null; + static int dc_list_index; /* always less than dc_list_range */ + static int dc_list_range = 1; /* always less than LOOKUP_RESP_LIMIT */ + + static void incr_dc_list_range() { + if( dc_list_range < LOOKUP_RESP_LIMIT && + dc_list_range < dc_list.length ) { + dc_list_range++; + } + } + public static NtlmChallenge getChallengeForDomain() + throws SmbException, UnknownHostException { + int starting_index; + + if( DOMAIN == null ) { + throw new SmbException( "A domain was not specified" ); + } +synchronized( DOMAIN ) { + if( dc_list_expiration < System.currentTimeMillis() ) { + dc_list = NbtAddress.getAllByName( DOMAIN, 0x1C, null, null ); + dc_list_expiration = System.currentTimeMillis() + CACHE_POLICY * 1000L; + if( dc_list_range > 1 ) { + dc_list_range /= 2; /* shrink dc_list_range */ + } + } + + starting_index = dc_list_index; + do { + if( dc_list_index == dc_list_range ) { + dc_list_index = 0; + } + NbtAddress addr = dc_list[dc_list_index]; + if( addr != null ) { + try { + UniAddress dc = new UniAddress( addr ); + SmbTransport trans = SmbTransport.getSmbTransport( dc, 0 ); + if( USERNAME == null ) { + if( SmbTransport.log.level > 1 ) + SmbTransport.log.println( "Default credentials (jcifs.smb.client.username/password) not specified. SMB signing may not work propertly. Skipping DC interrogation." ); + trans.negotiate(); + } else { + trans.getSmbSession( NtlmPasswordAuthentication.DEFAULT ).getSmbTree( LOGON_SHARE, null ).treeConnect( null, null ); + } + if( trans.sessions.size() > (trans.SSN_LIMIT / 10)) { + incr_dc_list_range(); + } + dc_list_index++; + return new NtlmChallenge( trans.server.encryptionKey, dc ); + } catch( SmbException se ) { + if( SmbTransport.log.level > 1 ) + SmbTransport.log.println( "Failed validate DC: " + addr + ": " + se.getMessage() ); + } + dc_list[dc_list_index] = null; /* dc no good */ + incr_dc_list_range(); + } + dc_list_index++; + } while( dc_list_index != starting_index ); +} + + throw new UnknownHostException( "Failed to negotiate with a suitable domain controller for " + DOMAIN ); + } + public static byte[] getChallenge( UniAddress dc ) throws SmbException, UnknownHostException { @@ -95,6 +165,7 @@ public final class SmbSession { SmbTransport transport = SmbTransport.NULL_TRANSPORT; NtlmPasswordAuthentication auth; + long expiration; SmbSession( UniAddress address, int port, InetAddress localAddr, int localPort, @@ -156,6 +227,8 @@ public final class SmbSession { void sessionSetup( ServerMessageBlock andx, ServerMessageBlock andxResponse ) throws SmbException { + expiration = System.currentTimeMillis() + SmbTransport.SO_TIMEOUT; + synchronized( transport() ) { if( sessionSetup ) { return; diff --git a/src/jcifs/smb/SmbTransport.java b/src/jcifs/smb/SmbTransport.java index 2fbc70f..605a3e6 100644 --- a/src/jcifs/smb/SmbTransport.java +++ b/src/jcifs/smb/SmbTransport.java @@ -55,7 +55,6 @@ class SmbTransport implements Runnable { private static final InetAddress LADDR = Config.getInetAddress( "jcifs.smb.client.laddr", null ); private static final int LPORT = Config.getInt( "jcifs.smb.client.lport", 0 ); - private static final int SSN_LIMIT = Config.getInt( "jcifs.smb.client.ssnLimit", DEFAULT_SSN_LIMIT ); private static final int MAX_MPX_COUNT = Config.getInt( "jcifs.smb.client.maxMpxCount", DEFAULT_MAX_MPX_COUNT ); private static final int SND_BUF_SIZE = Config.getInt( "jcifs.smb.client.snd_buf_size", DEFAULT_SND_BUF_SIZE ); private static final int RCV_BUF_SIZE = Config.getInt( "jcifs.smb.client.rcv_buf_size", DEFAULT_RCV_BUF_SIZE ); @@ -77,7 +76,6 @@ class SmbTransport implements Runnable { ServerMessageBlock.CAP_DFS; private static final int FLAGS2 = Config.getInt( "jcifs.smb.client.flags2", DEFAULT_FLAGS2 ); private static final int CAPABILITIES = Config.getInt( "jcifs.smb.client.capabilities", DEFAULT_CAPABILITIES ); - private static final int SO_TIMEOUT = Config.getInt( "jcifs.smb.client.soTimeout", DEFAULT_SO_TIMEOUT ); private static final boolean TCP_NODELAY = Config.getBoolean( "jcifs.smb.client.tcpNoDelay", false ); private static final int RESPONSE_TIMEOUT = Config.getInt( "jcifs.smb.client.responseTimeout", DEFAULT_RESPONSE_TIMEOUT ); @@ -85,14 +83,17 @@ class SmbTransport implements Runnable { private static final int PUSHBACK_BUF_SIZE = 64; private static final int MID_OFFSET = 30; private static final int FLAGS_RESPONSE = 0x80; - private static final int ST_GROUND = 0; - private static final int ST_NEGOTIATING = 1; private static final LinkedList CONNECTIONS = new LinkedList(); private static final int MAGIC[] = { 0xFF, 'S', 'M', 'B' }; + private static final int ST_GROUND = 0; + private static final int ST_NEGOTIATING = 1; + private static final int ST_NEGOTIATED = 2; + private static byte[] snd_buf = new byte[0xFFFF]; private static byte[] rcv_buf = new byte[0xFFFF]; + private int state; private NbtSocket socket; private HashMap responseTable; private InputStream in; @@ -103,12 +104,16 @@ private static byte[] rcv_buf = new byte[0xFFFF]; private Object outLock; private UniAddress address; private int port; - private LinkedList sessions; private LinkedList referrals = new LinkedList(); - private int state; private Mid[] mids = new Mid[MAX_MPX_COUNT]; private short mid_next; + private IOException socketException; + private long sessionExpiration = System.currentTimeMillis() + SO_TIMEOUT; + static final int SSN_LIMIT = + Config.getInt( "jcifs.smb.client.ssnLimit", DEFAULT_SSN_LIMIT ); + static final int SO_TIMEOUT = + Config.getInt( "jcifs.smb.client.soTimeout", DEFAULT_SO_TIMEOUT ); static final String NATIVE_OS = Config.getProperty( "jcifs.smb.client.nativeOs", System.getProperty( "os.name" )); static final String NATIVE_LANMAN = @@ -159,6 +164,7 @@ private static byte[] rcv_buf = new byte[0xFFFF]; String tconHostName; ServerData server; SigningDigest digest = null; + LinkedList sessions; static synchronized SmbTransport getSmbTransport( UniAddress address, int port ) { return getSmbTransport( address, port, LADDR, LPORT ); @@ -219,6 +225,21 @@ private static byte[] rcv_buf = new byte[0xFFFF]; return ssn; } } + + /* close old sessions */ + long now = System.currentTimeMillis(); + if( sessionExpiration < now ) { + sessionExpiration = now + SO_TIMEOUT; + iter = sessions.listIterator(); + while( iter.hasNext() ) { + ssn = (SmbSession)iter.next(); + if( ssn.expiration < now ) { + ssn.logoff( false ); + sessions.remove( ssn ); + } + } + } + ssn = new SmbSession( address, port, localAddr, localPort, auth ); ssn.transport = this; sessions.add( ssn ); @@ -252,8 +273,60 @@ private static byte[] rcv_buf = new byte[0xFFFF]; auth != NtlmPasswordAuthentication.NULL && NtlmPasswordAuthentication.NULL.equals( auth ) == false; } - void ensureOpen() throws IOException { + void tryClose( boolean inError ) { + SmbSession ssn; + if( socket == null ) { + inError = true; + } + + ListIterator iter = sessions.listIterator(); + while( iter.hasNext() ) { + ssn = (SmbSession)iter.next(); + ssn.logoff( inError ); + } + if( socket != null ) { + try { + socket.close(); + } catch( IOException ioe ) { + } + } + digest = null; + in = null; + out = null; + socket = null; + thread = null; + responseTable.clear(); + referrals.clear(); + sessions.clear(); + synchronized( CONNECTIONS ) { + CONNECTIONS.remove( this ); + } + state = ST_GROUND; + } + void start() throws SmbException { + try { + thread = new Thread( this, "JCIFS-SmbTransport" ); + thread.setDaemon( true ); + thread.start(); + + wait( RESPONSE_TIMEOUT ); /* wait for the thread to be started and socket opened */ + + if( socket == null ) { /* failed to open socket for some reason */ + tryClose( true ); + throw new SmbException( "Timeout trying to open socket (probably no ACK to SYN) for address", socketException ); + } + } catch( InterruptedException ie ) { + tryClose( true ); + throw new SmbException( "Interrupted opening socket", ie ); + } + } + public void run() { + Mid mid = new Mid(); + int i, m, nbtlen; + ServerMessageBlock response; + + try { Object obj; NbtAddress naddr; String calledName; @@ -273,6 +346,12 @@ private static byte[] rcv_buf = new byte[0xFFFF]; do { try { socket = new NbtSocket( naddr, calledName, port, localAddr, localPort ); + if( TCP_NODELAY ) { + socket.setTcpNoDelay( true ); + } + socket.setSoTimeout( SO_TIMEOUT ); + in = new PushbackInputStream( socket.getInputStream(), PUSHBACK_BUF_SIZE ); + out = socket.getOutputStream(); break; } catch( NbtException ne ) { if( ne.errorClass == NbtException.ERR_SSN_SRVC && @@ -286,7 +365,7 @@ private static byte[] rcv_buf = new byte[0xFFFF]; } } while(( calledName = address.nextCalledName()) != null ); - if( calledName == null ) { + if( socket == null ) { throw new IOException( "Failed to establish session with " + address ); } @@ -297,57 +376,19 @@ private static byte[] rcv_buf = new byte[0xFFFF]; } else { tconHostName = calledName; } - - if( TCP_NODELAY ) { - socket.setTcpNoDelay( true ); - } - in = new PushbackInputStream( socket.getInputStream(), PUSHBACK_BUF_SIZE ); - out = socket.getOutputStream(); - thread = new Thread( this, "JCIFS-SmbTransport" ); - thread.setDaemon( true ); - thread.start(); - } - } - void tryClose( boolean inError ) { - SmbSession ssn; - - if( socket == null ) { - inError = true; - } - - ListIterator iter = sessions.listIterator(); - while( iter.hasNext() ) { - ssn = (SmbSession)iter.next(); - ssn.logoff( inError ); - } - if( socket != null ) { - try { - socket.close(); - } catch( IOException ioe ) { + } catch( IOException ioe ) { + socketException = ioe; + if( log.level > 1 ) + ioe.printStackTrace( log ); + return; + } finally { + synchronized( this ) { + notifyAll(); } } - digest = null; - in = null; - out = null; - socket = null; - thread = null; - responseTable.clear(); - referrals.clear(); - sessions.clear(); - synchronized( CONNECTIONS ) { - CONNECTIONS.remove( this ); - } - state = ST_GROUND; - } - public void run() { - Mid mid = new Mid(); - int i, m, nbtlen; - ServerMessageBlock response; while( thread == Thread.currentThread() ) { try { - socket.setSoTimeout( SO_TIMEOUT ); - m = 0; while( m < 4 ) { if(( i = in.read() ) < 0 ) { @@ -395,7 +436,7 @@ synchronized( rcv_buf ) { response = (ServerMessageBlock)responseTable.get( mid ); if( response == null ) { - if( log.level > 1 ) { + if( log.level > 2 ) { log.println( "no handler for mid=" + mid.mid + ", purging session message: " + address ); } @@ -437,8 +478,6 @@ synchronized( rcv_buf ) { } } response.notify(); - } else { - ensureOpen(); } } else { response.readWireFormat( in, rcv_buf, 0 ); @@ -545,9 +584,8 @@ synchronized( rcv_buf ) { ServerMessageBlock response ) throws SmbException { Mid mid = null; - if (state == ST_GROUND) { + if( request.command != request.SMB_COM_NEGOTIATE ) negotiate(); - } request.flags2 |= flags2; request.useUnicode = useUnicode; @@ -557,7 +595,6 @@ synchronized( rcv_buf ) { try { mid = aquireMid(); request.mid = mid.mid; - ensureOpen(); synchronized( snd_buf ) { request.digest = digest; request.response = null; @@ -597,7 +634,6 @@ synchronized( snd_buf ) { mid = aquireMid(); request.mid = mid.mid; responseTable.put( mid, response ); - ensureOpen(); synchronized( snd_buf ) { if( digest != null ) { request.digest = digest; @@ -697,7 +733,6 @@ synchronized( snd_buf ) { mid = aquireMid(); request.mid = mid.mid; responseTable.put( mid, interimResponse ); - ensureOpen(); synchronized(snd_buf) { request.digest = digest; request.response = response; @@ -761,7 +796,6 @@ synchronized(snd_buf) { request.mid = mid.mid; responseTable.put( mid, response ); do { - ensureOpen(); synchronized( snd_buf ) { request.digest = digest; request.response = response; @@ -835,13 +869,25 @@ synchronized( snd_buf ) { } synchronized void negotiate() throws SmbException { + if( state == ST_GROUND ) { + state = ST_NEGOTIATING; + } else { + while( state != ST_NEGOTIATED ) { + try { + wait(); + } catch( InterruptedException ie ) { + tryClose( true ); + throw new SmbException( "Interrupted opening socket", ie ); + } + } + return; + } + + start(); /* start the transport thread (which opens the socket) */ + if( this == NULL_TRANSPORT ) { throw new RuntimeException( "Null transport cannot be used" ); } - if( state >= ST_NEGOTIATING ) { - return; - } - state = ST_NEGOTIATING; if( log.level > 2 ) log.println( "requesting negotiation with " + address ); @@ -897,6 +943,8 @@ synchronized( snd_buf ) { flags2 &= 0xFFFF ^ ServerMessageBlock.FLAGS2_UNICODE; } } + + state = ST_NEGOTIATED; } public String toString() { String ret = "SmbTransport[address=" + address;