From 6e8dd5651065fd234233d88dad0b0361d455e3fa Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Thu, 6 Nov 2008 13:19:14 +0100 Subject: [PATCH] jcifs-1.3.0 from tgz Sat Oct 25 17:45:51 EDT 2008 jcifs-1.3.0 released NTLMv2 has been FULLY implemented and is now the default. Signatures without and without various NTLMSSP flags (e.g. NTLMSSP_NEGOTIATE_NTLM2) have been tested with Windows 2003 and Windows 2000. New default values are: jcifs.lmCompatibility = 3 jcifs.smb.client.useExtendedSecurity = true Note: The NTLM HTTP Filter does not and can never support NTLMv2 as it uses the main-in-the-middle technique which is specifically thwarted by factoring the NTLMSSP TargetInformation block into the computed hashes. A proper NTLMv2 HTTP authentication filter would require NETLOGON RPCs (or possibly some kind of Kerberos digest authentication like Heimdal uses). --- README.txt | 18 + build.xml | 4 +- examples/runtests.sh | 14 +- src/jcifs/http/NtlmHttpFilter.java | 10 +- src/jcifs/http/NtlmHttpURLConnection.java | 2 +- src/jcifs/netbios/NameServiceClient.java | 2 +- .../netbios/NameServiceClient.java-2008-02-05 | 431 --------------------- src/jcifs/ntlmssp/Type1Message.java | 4 +- src/jcifs/ntlmssp/Type3Message.java | 128 +++++- src/jcifs/smb/Dfs.java | 4 +- src/jcifs/smb/NtStatus.java | 3 + src/jcifs/smb/NtlmContext.java | 97 +++++ src/jcifs/smb/NtlmPasswordAuthentication.java | 200 ++++++++-- src/jcifs/smb/ServerMessageBlock.java | 41 +- src/jcifs/smb/SigningDigest.java | 25 ++ src/jcifs/smb/SmbComNegotiateResponse.java | 66 ++-- src/jcifs/smb/SmbComSessionSetupAndX.java | 134 ++++--- src/jcifs/smb/SmbComSessionSetupAndXResponse.java | 44 +-- src/jcifs/smb/SmbConstants.java | 5 +- src/jcifs/smb/SmbSession.java | 157 ++++++-- src/jcifs/smb/SmbTransport.java | 13 +- 21 files changed, 739 insertions(+), 663 deletions(-) delete mode 100644 src/jcifs/netbios/NameServiceClient.java-2008-02-05 create mode 100644 src/jcifs/smb/NtlmContext.java diff --git a/README.txt b/README.txt index 217f899..8ae86df 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,21 @@ +Sat Oct 25 17:45:51 EDT 2008 +jcifs-1.3.0 released + +NTLMv2 has been FULLY implemented and is now the default. Signatures +without and without various NTLMSSP flags (e.g. NTLMSSP_NEGOTIATE_NTLM2) +have been tested with Windows 2003 and Windows 2000. + +New default values are: + + jcifs.lmCompatibility = 3 + jcifs.smb.client.useExtendedSecurity = true + +Note: The NTLM HTTP Filter does not and can never support NTLMv2 as it +uses the main-in-the-middle technique which is specifically thwarted by +factoring the NTLMSSP TargetInformation block into the computed hashes. A +proper NTLMv2 HTTP authentication filter would require NETLOGON RPCs (or +possibly some kind of Kerberos digest authentication like Heimdal uses). + Sun Oct 19 23:25:45 EDT 2008 jcifs-1.2.25 diff --git a/build.xml b/build.xml index 3cbd385..3d1a159 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,7 @@ - - + + diff --git a/examples/runtests.sh b/examples/runtests.sh index 4c83ba3..c408eed 100644 --- a/examples/runtests.sh +++ b/examples/runtests.sh @@ -1,8 +1,8 @@ #!/bin/sh -JAVA_HOME=/usr/local/java +JAVA_HOME=/usr/local/java6 CLASSPATH=../build:. -PROPERTIES=../../user2.prp +PROPERTIES=../user1.prp RUN="${JAVA_HOME}/bin/java -cp ${CLASSPATH} -Djcifs.properties=${PROPERTIES}" #SERVER=dc1.w.net @@ -13,10 +13,14 @@ RUN="${JAVA_HOME}/bin/java -cp ${CLASSPATH} -Djcifs.properties=${PROPERTIES}" #SHARE=root2 #DIR=link2/test +# smb://fs1.w.net/DFSStandaloneRoot/DFSStandaloneLink/test/ +# smb://dc1.w.net/root2/link2/test/ +# smb://dc1.w.net/tmp/test/ +# smb://dc3.x.net/tmp/test/ # Stand-alone DFS -SERVER=fs1.w.net -SHARE=standalone -DIR=sub1/dc1tmp/test +SERVER=dc3.x.net +SHARE=tmp +DIR=test WRITE_DIR=${DIR}/ SRC_DIR=${DIR}/Junk diff --git a/src/jcifs/http/NtlmHttpFilter.java b/src/jcifs/http/NtlmHttpFilter.java index fd67008..d4a5b28 100644 --- a/src/jcifs/http/NtlmHttpFilter.java +++ b/src/jcifs/http/NtlmHttpFilter.java @@ -60,10 +60,16 @@ public class NtlmHttpFilter implements Filter { String name; int level; - /* Set jcifs properties we know we want; soTimeout and cachePolicy to 10min. + /* Set jcifs properties we know we want; soTimeout and cachePolicy to 30min. */ - Config.setProperty( "jcifs.smb.client.soTimeout", "300000" ); + Config.setProperty( "jcifs.smb.client.soTimeout", "1800000" ); Config.setProperty( "jcifs.netbios.cachePolicy", "1200" ); + /* The Filter can only work with NTLMv1 as it uses a man-in-the-middle + * techinque that NTLMv2 specifically thwarts. A real NTLM Filter would + * need to do a NETLOGON RPC or possibly some kind of Kerberos based digest + * authentication like Hiemdal supposedly has. + */ + Config.setProperty( "jcifs.smb.lmCompatibility", "0" ); Enumeration e = filterConfig.getInitParameterNames(); while( e.hasMoreElements() ) { diff --git a/src/jcifs/http/NtlmHttpURLConnection.java b/src/jcifs/http/NtlmHttpURLConnection.java index a539ecc..dcd8adc 100644 --- a/src/jcifs/http/NtlmHttpURLConnection.java +++ b/src/jcifs/http/NtlmHttpURLConnection.java @@ -564,7 +564,7 @@ public class NtlmHttpURLConnection extends HttpURLConnection { } Type2Message type2 = (Type2Message) message; message = new Type3Message(type2, password, domain, user, - Type3Message.getDefaultWorkstation()); + Type3Message.getDefaultWorkstation(), 0); } return message; } diff --git a/src/jcifs/netbios/NameServiceClient.java b/src/jcifs/netbios/NameServiceClient.java index 80f6ab9..7d79254 100644 --- a/src/jcifs/netbios/NameServiceClient.java +++ b/src/jcifs/netbios/NameServiceClient.java @@ -84,7 +84,7 @@ class NameServiceClient implements Runnable { if( RO == null || RO.length() == 0 ) { /* No resolveOrder has been specified, use the - * default which is LMHOSTS,WINS,BCAST,DNS or just + * default which is LMHOSTS,DNS,WINS,BCAST * LMHOSTS,BCAST,DNS if jcifs.netbios.wins has not * been specified. */ diff --git a/src/jcifs/netbios/NameServiceClient.java-2008-02-05 b/src/jcifs/netbios/NameServiceClient.java-2008-02-05 deleted file mode 100644 index 00f4d3a..0000000 --- a/src/jcifs/netbios/NameServiceClient.java-2008-02-05 +++ /dev/null @@ -1,431 +0,0 @@ -/* 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.netbios; - -import java.net.*; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.util.HashMap; -import java.util.StringTokenizer; -import jcifs.Config; -import jcifs.util.Hexdump; -import jcifs.util.LogStream; - -class NameServiceClient implements Runnable { - - static final int DEFAULT_SO_TIMEOUT = 5000; - static final int DEFAULT_RCV_BUF_SIZE = 576; - static final int DEFAULT_SND_BUF_SIZE = 576; - static final int NAME_SERVICE_UDP_PORT = 137; - static final int DEFAULT_RETRY_COUNT = 2; - static final int DEFAULT_RETRY_TIMEOUT = 3000; - - static final int RESOLVER_LMHOSTS = 1; - static final int RESOLVER_BCAST = 2; - static final int RESOLVER_WINS = 3; - - private static final int SND_BUF_SIZE = Config.getInt( "jcifs.netbios.snd_buf_size", DEFAULT_SND_BUF_SIZE ); - private static final int RCV_BUF_SIZE = Config.getInt( "jcifs.netbios.rcv_buf_size", DEFAULT_RCV_BUF_SIZE ); - private static final int SO_TIMEOUT = Config.getInt( "jcifs.netbios.soTimeout", DEFAULT_SO_TIMEOUT ); - private static final int RETRY_COUNT = Config.getInt( "jcifs.netbios.retryCount", DEFAULT_RETRY_COUNT ); - private static final int RETRY_TIMEOUT = Config.getInt( "jcifs.netbios.retryTimeout", DEFAULT_RETRY_TIMEOUT); - private static final int LPORT = Config.getInt( "jcifs.netbios.lport", 0 ); - private static final InetAddress LADDR = Config.getInetAddress( "jcifs.netbios.laddr", null ); - private static final String RO = Config.getProperty( "jcifs.resolveOrder" ); - - private static LogStream log = LogStream.getInstance(); - - private final Object LOCK = new Object(); - - private int lport, closeTimeout; - private byte[] snd_buf, rcv_buf; - private DatagramSocket socket; - private DatagramPacket in, out; - private HashMap responseTable = new HashMap(); - private Thread thread; - private int nextNameTrnId = 0; - private int[] resolveOrder; - - InetAddress laddr, baddr; - - NameServiceClient() { - this( LPORT, LADDR ); - } - NameServiceClient( int lport, InetAddress laddr ) { - this.lport = lport; - this.laddr = laddr; - - try { - baddr = Config.getInetAddress( "jcifs.netbios.baddr", - InetAddress.getByName( "255.255.255.255" )); - } catch( UnknownHostException uhe ) { - } - - snd_buf = new byte[SND_BUF_SIZE]; - rcv_buf = new byte[RCV_BUF_SIZE]; - out = new DatagramPacket( snd_buf, SND_BUF_SIZE, baddr, NAME_SERVICE_UDP_PORT ); - in = new DatagramPacket( rcv_buf, RCV_BUF_SIZE ); - - if( RO == null || RO.length() == 0 ) { - - /* No resolveOrder has been specified, use the - * default which is LMHOSTS,WINS,BCAST,DNS or just - * LMHOSTS,BCAST,DNS if jcifs.netbios.wins has not - * been specified. - */ - - if( NbtAddress.getWINSAddress() == null ) { - resolveOrder = new int[2]; - resolveOrder[0] = RESOLVER_LMHOSTS; - resolveOrder[1] = RESOLVER_BCAST; - } else { - resolveOrder = new int[3]; - resolveOrder[0] = RESOLVER_LMHOSTS; - resolveOrder[1] = RESOLVER_WINS; - resolveOrder[2] = RESOLVER_BCAST; - } - } else { - int[] tmp = new int[3]; - StringTokenizer st = new StringTokenizer( RO, "," ); - int i = 0; - while( st.hasMoreTokens() ) { - String s = st.nextToken().trim(); - if( s.equalsIgnoreCase( "LMHOSTS" )) { - tmp[i++] = RESOLVER_LMHOSTS; - } else if( s.equalsIgnoreCase( "WINS" )) { - if( NbtAddress.getWINSAddress() == null ) { - if( log.level > 1 ) { - log.println( "NetBIOS resolveOrder specifies WINS however the " + - "jcifs.netbios.wins property has not been set" ); - } - continue; - } - tmp[i++] = RESOLVER_WINS; - } else if( s.equalsIgnoreCase( "BCAST" )) { - tmp[i++] = RESOLVER_BCAST; - } else if( s.equalsIgnoreCase( "DNS" )) { - ; // skip - } else if( log.level > 1 ) { - log.println( "unknown resolver method: " + s ); - } - } - resolveOrder = new int[i]; - System.arraycopy( tmp, 0, resolveOrder, 0, i ); - } - } - - int getNextNameTrnId() { - if(( ++nextNameTrnId & 0xFFFF ) == 0 ) { - nextNameTrnId = 1; - } - return nextNameTrnId; - } - void ensureOpen( int timeout ) throws IOException { - closeTimeout = 0; - if( SO_TIMEOUT != 0 ) { - closeTimeout = Math.max( SO_TIMEOUT, timeout ); - } - // If socket is still good, the new closeTimeout will - // be ignored; see tryClose comment. - if( socket == null ) { - socket = new DatagramSocket( lport, laddr ); - thread = new Thread( this, "JCIFS-NameServiceClient" ); - thread.setDaemon( true ); - thread.start(); - } - } - void tryClose() { - synchronized( LOCK ) { - - /* Yes, there is the potential to drop packets - * because we might close the socket during a - * request. However the chances are slim and the - * retry code should ensure the overall request - * is serviced. The alternative complicates things - * more than I think is worth it. - */ - - if( socket != null ) { - socket.close(); - socket = null; - } - thread = null; - responseTable.clear(); - } - } - public void run() { - int nameTrnId; - NameServicePacket response; - - try { - while( thread == Thread.currentThread() ) { - in.setLength( RCV_BUF_SIZE ); - - socket.setSoTimeout( closeTimeout ); - socket.receive( in ); - - if( log.level > 3 ) - log.println( "NetBIOS: new data read from socket" ); - - nameTrnId = NameServicePacket.readNameTrnId( rcv_buf, 0 ); - response = (NameServicePacket)responseTable.get( new Integer( nameTrnId )); - if( response == null || response.received ) { - continue; - } - synchronized( response ) { - response.readWireFormat( rcv_buf, 0 ); - response.received = true; - - if( log.level > 3 ) { - log.println( response ); - Hexdump.hexdump( log, rcv_buf, 0, in.getLength() ); - } - - response.notify(); - } - } - } catch(SocketTimeoutException ste) { - } catch( Exception ex ) { - if( log.level > 2 ) - ex.printStackTrace( log ); - } finally { - tryClose(); - } - } - void send( NameServicePacket request, NameServicePacket response, - int timeout ) throws IOException { - Integer nid = null; - int max = NbtAddress.NBNS.length; - - if (max == 0) - max = 1; /* No WINs, try only bcast addr */ - - synchronized( response ) { - while (max-- > 0) { - try { - synchronized( LOCK ) { - request.nameTrnId = getNextNameTrnId(); - nid = new Integer( request.nameTrnId ); - - out.setAddress( request.addr ); - out.setLength( request.writeWireFormat( snd_buf, 0 )); - response.received = false; - - responseTable.put( nid, response ); - ensureOpen( timeout + 1000 ); - socket.send( out ); - - if( log.level > 3 ) { - log.println( request ); - Hexdump.hexdump( log, snd_buf, 0, out.getLength() ); - } - } - - response.wait( timeout ); - - if (response.received) - return; - - } catch( InterruptedException ie ) { - } finally { - responseTable.remove( nid ); - } - - if (NbtAddress.isWINS( request.addr ) == false) - break; - - /* Message was sent to WINS but - * failed to receive response. - * Try a different WINS server. - */ - if (request.addr == NbtAddress.getWINSAddress()) - NbtAddress.switchWINS(); - request.addr = NbtAddress.getWINSAddress(); - } - } - } - - NbtAddress[] getAllByName( Name name, InetAddress addr ) - throws UnknownHostException { - int n; - NameQueryRequest request = new NameQueryRequest( name ); - NameQueryResponse response = new NameQueryResponse(); - - request.addr = addr != null ? addr : NbtAddress.getWINSAddress(); - request.isBroadcast = request.addr == null; - - if( request.isBroadcast ) { - request.addr = baddr; - n = RETRY_COUNT; - } else { - request.isBroadcast = false; - n = 1; - } - - do { - try { - send( request, response, RETRY_TIMEOUT ); - } catch( IOException ioe ) { - if( log.level > 1 ) - ioe.printStackTrace( log ); - throw new UnknownHostException( name.name ); - } - - if( response.received && response.resultCode == 0 ) { - return response.addrEntry; - } - } while( --n > 0 && request.isBroadcast ); - - throw new UnknownHostException( name.name ); - } - NbtAddress getByName( Name name, InetAddress addr ) - throws UnknownHostException { - int n; - NameQueryRequest request = new NameQueryRequest( name ); - NameQueryResponse response = new NameQueryResponse(); - - if( addr != null ) { /* UniAddress calls always use this - * because it specifies addr - */ - request.addr = addr; /* if addr ends with 255 flag it bcast */ - request.isBroadcast = (addr.getAddress()[3] == (byte)0xFF); - - n = RETRY_COUNT; - do { - try { - send( request, response, RETRY_TIMEOUT ); - } catch( IOException ioe ) { - if( log.level > 1 ) - ioe.printStackTrace( log ); - throw new UnknownHostException( name.name ); - } - - if( response.received && response.resultCode == 0 ) { - int last = response.addrEntry.length - 1; - response.addrEntry[last].hostName.srcHashCode = addr.hashCode(); - return response.addrEntry[last]; - } - } while( --n > 0 && request.isBroadcast ); - - throw new UnknownHostException( name.name ); - } - - /* If a target address to query was not specified explicitly - * with the addr parameter we fall into this resolveOrder routine. - */ - - for( int i = 0; i < resolveOrder.length; i++ ) { - try { - switch( resolveOrder[i] ) { - case RESOLVER_LMHOSTS: - NbtAddress ans = Lmhosts.getByName( name ); - if( ans != null ) { - ans.hostName.srcHashCode = 0; // just has to be different - // from other methods - return ans; - } - break; - case RESOLVER_WINS: - case RESOLVER_BCAST: - if( resolveOrder[i] == RESOLVER_WINS && - name.name != NbtAddress.MASTER_BROWSER_NAME && - name.hexCode != 0x1d ) { - request.addr = NbtAddress.getWINSAddress(); - request.isBroadcast = false; - } else { - request.addr = baddr; - request.isBroadcast = true; - } - - n = RETRY_COUNT; - while( n-- > 0 ) { - try { - send( request, response, RETRY_TIMEOUT ); - } catch( IOException ioe ) { - if( log.level > 1 ) - ioe.printStackTrace( log ); - throw new UnknownHostException( name.name ); - } - if( response.received && response.resultCode == 0 ) { - -/* Before we return, in anticipation of this address being cached we must - * augment the addresses name's hashCode to distinguish those resolved by - * 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[0].hostName.srcHashCode = - request.addr.hashCode(); - return response.addrEntry[0]; - } else if( resolveOrder[i] == RESOLVER_WINS ) { - /* If WINS reports negative, no point in retry - */ - break; - } - } - break; - } - } catch( IOException ioe ) { - } - } - throw new UnknownHostException( name.name ); - } - NbtAddress[] getNodeStatus( NbtAddress addr ) throws UnknownHostException { - int n, srcHashCode; - NodeStatusRequest request; - NodeStatusResponse response; - - response = new NodeStatusResponse( addr ); - request = new NodeStatusRequest( - new Name( NbtAddress.ANY_HOSTS_NAME, 0x00, null)); - request.addr = addr.getInetAddress(); - - n = RETRY_COUNT; - while( n-- > 0 ) { - try { - send( request, response, RETRY_TIMEOUT ); - } catch( IOException ioe ) { - if( log.level > 1 ) - ioe.printStackTrace( log ); - throw new UnknownHostException( addr.toString() ); - } - if( response.received && response.resultCode == 0 ) { - - /* For name queries resolved by different sources (e.g. WINS, - * BCAST, Node Status) we need to augment the hashcode generated - * for the addresses hostname or failed lookups for one type will - * be cached and cause other types to fail even though they may - * not be the authority for the name. For example, if a WINS lookup - * for FOO fails and caches unknownAddress for FOO, a subsequent - * lookup for FOO using BCAST should not fail because of that - * name cached from WINS. - * - * So, here we apply the source addresses hashCode to each name to - * make them specific to who resolved the name. - */ - - srcHashCode = request.addr.hashCode(); - for( int i = 0; i < response.addressArray.length; i++ ) { - response.addressArray[i].hostName.srcHashCode = srcHashCode; - } - return response.addressArray; - } - } - throw new UnknownHostException( addr.hostName.name ); - } -} diff --git a/src/jcifs/ntlmssp/Type1Message.java b/src/jcifs/ntlmssp/Type1Message.java index 2c5c829..754575b 100644 --- a/src/jcifs/ntlmssp/Type1Message.java +++ b/src/jcifs/ntlmssp/Type1Message.java @@ -71,8 +71,10 @@ public class Type1Message extends NtlmMessage { */ public Type1Message(int flags, String suppliedDomain, String suppliedWorkstation) { - setFlags(flags); + setFlags(getDefaultFlags() | flags); setSuppliedDomain(suppliedDomain); + if (suppliedWorkstation == null) + suppliedWorkstation = getDefaultWorkstation(); setSuppliedWorkstation(suppliedWorkstation); } diff --git a/src/jcifs/ntlmssp/Type3Message.java b/src/jcifs/ntlmssp/Type3Message.java index 83a5762..5ad1b22 100644 --- a/src/jcifs/ntlmssp/Type3Message.java +++ b/src/jcifs/ntlmssp/Type3Message.java @@ -30,6 +30,11 @@ import jcifs.Config; import jcifs.netbios.NbtAddress; import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.util.HMACT64; +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; +import jcifs.util.MD4; /** * Represents an NTLMSSP Type-3 message. @@ -60,7 +65,8 @@ public class Type3Message extends NtlmMessage { private String workstation; - private byte[] sessionKey; + private byte[] masterKey = null; + private byte[] sessionKey = null; static { DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | @@ -75,7 +81,7 @@ public class Type3Message extends NtlmMessage { defaultWorkstation = NbtAddress.getLocalHost().getHostName(); } catch (UnknownHostException ex) { } DEFAULT_WORKSTATION = defaultWorkstation; - LM_COMPATIBILITY = Config.getInt("jcifs.smb.lmCompatibility", 0); + LM_COMPATIBILITY = Config.getInt("jcifs.smb.lmCompatibility", 3); } /** @@ -143,16 +149,66 @@ public class Type3Message extends NtlmMessage { * taking place. */ public Type3Message(Type2Message type2, String password, String domain, - String user, String workstation) { - setFlags(getDefaultFlags(type2)); + String user, String workstation, int flags) { + setFlags(flags | getDefaultFlags(type2)); + if (workstation == null) + workstation = getDefaultWorkstation(); + setWorkstation(workstation); setDomain(domain); setUser(user); - setWorkstation(workstation); + switch (LM_COMPATIBILITY) { case 0: case 1: - setLMResponse(getLMResponse(type2, password)); - setNTResponse(getNTResponse(type2, password)); + if ((getFlags() & NTLMSSP_NEGOTIATE_NTLM2) == 0) { + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + } else { + // NTLM2 Session Response + + byte[] clientChallenge = new byte[24]; + RANDOM.nextBytes(clientChallenge); + java.util.Arrays.fill(clientChallenge, 8, 24, (byte)0x00); + + byte[] responseKeyNT = NtlmPasswordAuthentication.nTOWFv1(password); + byte[] ntlm2Response = NtlmPasswordAuthentication.getNTLM2Response(responseKeyNT, + type2.getChallenge(), + clientChallenge); + + setLMResponse(clientChallenge); + setNTResponse(ntlm2Response); + + if ((getFlags() & NTLMSSP_NEGOTIATE_SIGN) == NTLMSSP_NEGOTIATE_SIGN) { + byte[] sessionNonce = new byte[16]; + System.arraycopy(type2.getChallenge(), 0, sessionNonce, 0, 8); + System.arraycopy(clientChallenge, 0, sessionNonce, 8, 8); + + MD4 md4 = new MD4(); + md4.update(responseKeyNT); + HMACT64 hmac = new HMACT64(md4.digest()); + hmac.update(sessionNonce); + byte[] userSessionKey = hmac.digest(); // NTLM2 session key + + if ((getFlags() & NTLMSSP_NEGOTIATE_KEY_EXCH) != 0) { + masterKey = new byte[16]; + RANDOM.nextBytes(masterKey); + + byte[] exchangedKey = new byte[16]; + try { + Cipher rc4 = Cipher.getInstance("RC4"); + rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(userSessionKey, "RC4")); + rc4.update(masterKey, 0, 16, exchangedKey, 0); + } catch (GeneralSecurityException gse) { + throw new RuntimeException("", gse); + } + + setSessionKey(exchangedKey); + } else { + masterKey = userSessionKey; + setSessionKey(masterKey); + } + } + } break; case 2: byte[] nt = getNTResponse(type2, password); @@ -162,14 +218,38 @@ public class Type3Message extends NtlmMessage { case 3: case 4: case 5: + byte[] responseKeyNT = NtlmPasswordAuthentication.nTOWFv2(domain, user, password); + byte[] clientChallenge = new byte[8]; RANDOM.nextBytes(clientChallenge); - setLMResponse(getLMv2Response(type2, domain, user, password, - clientChallenge)); - /* - setNTResponse(getNTLMv2Response(type2, domain, user, password, - clientChallenge)); - */ + setLMResponse(getLMv2Response(type2, domain, user, password, clientChallenge)); + + byte[] clientChallenge2 = new byte[8]; + RANDOM.nextBytes(clientChallenge2); + setNTResponse(getNTLMv2Response(type2, responseKeyNT, clientChallenge2)); + + if ((getFlags() & NTLMSSP_NEGOTIATE_SIGN) == NTLMSSP_NEGOTIATE_SIGN) { + masterKey = new byte[16]; + RANDOM.nextBytes(masterKey); + + HMACT64 hmac = new HMACT64(responseKeyNT); + hmac.update(ntResponse, 0, 16); // only first 16 bytes of ntResponse + byte[] userSessionKey = hmac.digest(); + + /* TODO: don't do this if NTLMSSP_NEGOTIATE_KEY_EXCH not set + */ + byte[] exchangedKey = new byte[16]; + try { + Cipher rc4 = Cipher.getInstance("RC4"); + rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(userSessionKey, "RC4")); + rc4.update(masterKey, 0, 16, exchangedKey, 0); + } catch (GeneralSecurityException gse) { + throw new RuntimeException("", gse); + } + + setSessionKey(exchangedKey); + } + break; default: setLMResponse(getLMResponse(type2, password)); @@ -299,6 +379,16 @@ public class Type3Message extends NtlmMessage { } /** + * The real session key if the regular session key is actually + * the encrypted version used for key exchange. + * + * @return A byte[] containing the session key. + */ + public byte[] getMasterKey() { + return masterKey; + } + + /** * Returns the session key. * * @return A byte[] containing the session key. @@ -485,6 +575,18 @@ public class Type3Message extends NtlmMessage { return NtlmPasswordAuthentication.getLMv2Response(domain, user, password, type2.getChallenge(), clientChallenge); } + public static byte[] getNTLMv2Response(Type2Message type2, + byte[] responseKeyNT, + byte[] clientChallenge) { + if (type2 == null || responseKeyNT == null || clientChallenge == null) { + return null; + } + return NtlmPasswordAuthentication.getNTLMv2Response(responseKeyNT, + type2.getChallenge(), + clientChallenge, + System.currentTimeMillis(), + type2.getTargetInformation()); + } /** * Constructs the NT response to the given Type-2 message using diff --git a/src/jcifs/smb/Dfs.java b/src/jcifs/smb/Dfs.java index 1c0af43..5778981 100644 --- a/src/jcifs/smb/Dfs.java +++ b/src/jcifs/smb/Dfs.java @@ -50,7 +50,7 @@ public class Dfs { protected CacheEntry referrals = null; public HashMap getTrustedDomains(NtlmPasswordAuthentication auth) throws SmbAuthException { - if (DISABLED) + if (DISABLED || auth.domain == "?") return null; if (_domains != null && System.currentTimeMillis() > _domains.expiration) { @@ -125,7 +125,7 @@ public class Dfs { if (dr.length == 1) return dr[0]; } catch (IOException ioe) { - if (log.level >= 3) + if (log.level >= 4) ioe.printStackTrace(log); if (strictView && ioe instanceof SmbAuthException) { throw (SmbAuthException)ioe; diff --git a/src/jcifs/smb/NtStatus.java b/src/jcifs/smb/NtStatus.java index 1df7e48..02aebf9 100644 --- a/src/jcifs/smb/NtStatus.java +++ b/src/jcifs/smb/NtStatus.java @@ -82,6 +82,7 @@ public interface NtStatus { public static final int NT_STATUS_NOT_FOUND = 0xC0000225; public static final int NT_STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234; public static final int NT_STATUS_PATH_NOT_COVERED = 0xC0000257; + public static final int NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED = 0xC0000279; static final int[] NT_STATUS_CODES = { NT_STATUS_OK, @@ -142,6 +143,7 @@ public interface NtStatus { NT_STATUS_NOT_FOUND, NT_STATUS_ACCOUNT_LOCKED_OUT, NT_STATUS_PATH_NOT_COVERED, + NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED, }; static final String[] NT_STATUS_MESSAGES = { @@ -203,6 +205,7 @@ public interface NtStatus { "NT_STATUS_NOT_FOUND", "The referenced account is currently locked out and may not be logged on to.", "The remote system is not reachable by the transport.", + "NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED", }; } diff --git a/src/jcifs/smb/NtlmContext.java b/src/jcifs/smb/NtlmContext.java new file mode 100644 index 0000000..04d761a --- /dev/null +++ b/src/jcifs/smb/NtlmContext.java @@ -0,0 +1,97 @@ +/* jcifs smb client library in Java + * Copyright (C) 2008 "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.security.*; +import jcifs.ntlmssp.*; + +public class NtlmContext { + + NtlmPasswordAuthentication auth; + int ntlmsspFlags; + String workstation; + boolean isEstablished = false; + byte[] serverChallenge = null; + byte[] signingKey = null; + int state = 1; + + public NtlmContext(NtlmPasswordAuthentication auth, boolean doSigning) { + this.auth = auth; + this.ntlmsspFlags = ntlmsspFlags | + NtlmFlags.NTLMSSP_REQUEST_TARGET | + NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | + NtlmFlags.NTLMSSP_NEGOTIATE_128; + if (doSigning) { + this.ntlmsspFlags |= NtlmFlags.NTLMSSP_NEGOTIATE_SIGN | + NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | + NtlmFlags.NTLMSSP_NEGOTIATE_KEY_EXCH; + } + this.workstation = Type1Message.getDefaultWorkstation(); + } + + public boolean isEstablished() { + return isEstablished; + } + public byte[] getServerChallenge() + { + return serverChallenge; + } + public byte[] getSigningKey() + { + return signingKey; + } + + public byte[] initSecContext(byte[] token, int offset, int len) throws SmbException { + switch (state) { + case 1: + Type1Message msg1 = new Type1Message(ntlmsspFlags, auth.getDomain(), workstation); + token = msg1.toByteArray(); + state++; + break; + case 2: + try { + Type2Message msg2 = new Type2Message(token); + + serverChallenge = msg2.getChallenge(); + ntlmsspFlags &= msg2.getFlags(); + + Type3Message msg3 = new Type3Message(msg2, + auth.getPassword(), + auth.getDomain(), + auth.getUsername(), + workstation, + ntlmsspFlags); + token = msg3.toByteArray(); + + if ((ntlmsspFlags & NtlmFlags.NTLMSSP_NEGOTIATE_SIGN) != 0) + signingKey = msg3.getMasterKey(); + + isEstablished = true; + state++; + break; + } catch (Exception e) { + throw new SmbException(e.getMessage()); + } + default: + throw new SmbException("Invalid state"); + } + return token; + } +} diff --git a/src/jcifs/smb/NtlmPasswordAuthentication.java b/src/jcifs/smb/NtlmPasswordAuthentication.java index c7b977d..fddff77 100644 --- a/src/jcifs/smb/NtlmPasswordAuthentication.java +++ b/src/jcifs/smb/NtlmPasswordAuthentication.java @@ -22,6 +22,8 @@ package jcifs.smb; import java.io.UnsupportedEncodingException; import java.io.Serializable; import java.security.Principal; +import java.security.MessageDigest; +import java.security.GeneralSecurityException; import java.util.Random; import java.util.Arrays; import jcifs.Config; @@ -40,7 +42,7 @@ import jcifs.util.*; public final class NtlmPasswordAuthentication implements Principal, Serializable { private static final int LM_COMPATIBILITY = - Config.getInt("jcifs.smb.lmCompatibility", 0); + Config.getInt("jcifs.smb.lmCompatibility", 3); private static final Random RANDOM = new Random(); @@ -51,6 +53,10 @@ public final class NtlmPasswordAuthentication implements Principal, Serializable (byte)0x4b, (byte)0x47, (byte)0x53, (byte)0x21, (byte)0x40, (byte)0x23, (byte)0x24, (byte)0x25 }; + /* Accepts key multiple of 7 + * Returns enc multiple of 8 + * Multiple is the same like: 21 byte key gives 24 byte result + */ private static void E( byte[] key, byte[] data, byte[] e ) { byte[] key7 = new byte[7]; byte[] e8 = new byte[8]; @@ -156,6 +162,102 @@ public final class NtlmPasswordAuthentication implements Principal, Serializable return null; } } + public static byte[] getNTLM2Response(byte[] nTOWFv1, + byte[] serverChallenge, + byte[] clientChallenge) + { + byte[] sessionHash = new byte[8]; + + try { + MessageDigest md5; + md5 = MessageDigest.getInstance("MD5"); + md5.update(serverChallenge); + md5.update(clientChallenge, 0, 8); + System.arraycopy(md5.digest(), 0, sessionHash, 0, 8); + } catch (GeneralSecurityException gse) { + if (log.level > 0) + gse.printStackTrace(log); + throw new RuntimeException("MD5", gse); + } + + byte[] key = new byte[21]; + System.arraycopy(nTOWFv1, 0, key, 0, 16); + byte[] ntResponse = new byte[24]; + E(key, sessionHash, ntResponse); + + return ntResponse; + } + public static byte[] nTOWFv1(String password) + { + try { + MD4 md4 = new MD4(); + md4.update(password.getBytes("UnicodeLittleUnmarked")); + return md4.digest(); + } catch (UnsupportedEncodingException uee) { + throw new RuntimeException(uee.getMessage()); + } + } + public static byte[] nTOWFv2(String domain, String username, String password) + { + try { + MD4 md4 = new MD4(); + md4.update(password.getBytes("UnicodeLittleUnmarked")); + HMACT64 hmac = new HMACT64(md4.digest()); + hmac.update(username.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmac.update(domain.toUpperCase().getBytes("UnicodeLittleUnmarked")); + return hmac.digest(); + } catch (UnsupportedEncodingException uee) { + throw new RuntimeException(uee.getMessage()); + } + } + static byte[] computeResponse(byte[] responseKey, + byte[] serverChallenge, + byte[] clientData, + int offset, + int length) + { + HMACT64 hmac = new HMACT64(responseKey); + hmac.update(serverChallenge); + hmac.update(clientData, offset, length); + byte[] mac = hmac.digest(); + byte[] ret = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, ret, 0, mac.length); + System.arraycopy(clientData, 0, ret, mac.length, clientData.length); + return ret; + } + public static byte[] getLMv2Response( + byte[] responseKeyLM, + byte[] serverChallenge, + byte[] clientChallenge) + { + return NtlmPasswordAuthentication.computeResponse(responseKeyLM, + serverChallenge, + clientChallenge, + 0, + clientChallenge.length); + } + public static byte[] getNTLMv2Response( + byte[] responseKeyNT, + byte[] serverChallenge, + byte[] clientChallenge, + long time, + byte[] targetInfo) + { + byte[] temp = new byte[28 + targetInfo.length]; + + Encdec.enc_uint32le(0x00000101, temp, 0); // Header + Encdec.enc_uint32le(0x00000000, temp, 4); // Reserved + Encdec.enc_uint64le((time + SmbConstants.MILLISECONDS_BETWEEN_1970_AND_1601) * 10000L, temp, 8); + System.arraycopy(clientChallenge, 0, temp, 16, 8); + Encdec.enc_uint32le(0x00000000, temp, 24); // Unknown + System.arraycopy(targetInfo, 0, temp, 28, targetInfo.length); + + return NtlmPasswordAuthentication.computeResponse(responseKeyNT, + serverChallenge, + temp, + 0, + temp.length); + } static final NtlmPasswordAuthentication NULL = new NtlmPasswordAuthentication( "", "", "" ); @@ -335,6 +437,27 @@ public final class NtlmPasswordAuthentication implements Principal, Serializable } } + public byte[] getSigningKey(byte[] challenge) throws SmbException + { + switch (LM_COMPATIBILITY) { + case 0: + case 1: + case 2: + byte[] signingKey = new byte[40]; + getUserSessionKey(challenge, signingKey, 0); + System.arraycopy(getUnicodeHash(challenge), 0, signingKey, 16, 24); + return signingKey; + case 3: + case 4: + case 5: + /* This code is only called if extended security is not on. This will + * all be cleaned up an normalized in JCIFS 2.x. + */ + throw new SmbException("NTLMv2 requires extended security (jcifs.smb.client.useExtendedSecurity must be true if jcifs.smb.lmCompatibility >= 3)"); + } + return null; + } + /** * Returns the effective user session key. * @@ -363,44 +486,47 @@ public final class NtlmPasswordAuthentication implements Principal, Serializable * @param offset The offset in the destination array at which the * session key will start. */ - void getUserSessionKey(byte[] challenge, byte[] dest, int offset) - throws Exception { + void getUserSessionKey(byte[] challenge, byte[] dest, int offset) throws SmbException { if (hashesExternal) return; - MD4 md4 = new MD4(); - md4.update(password.getBytes("UnicodeLittleUnmarked")); - switch (LM_COMPATIBILITY) { - case 0: - case 1: - case 2: - md4.update(md4.digest()); - md4.digest(dest, offset, 16); - break; - case 3: - case 4: - case 5: - if( clientChallenge == null ) { - clientChallenge = new byte[8]; - RANDOM.nextBytes( clientChallenge ); - } + try { + MD4 md4 = new MD4(); + md4.update(password.getBytes("UnicodeLittleUnmarked")); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + case 2: + md4.update(md4.digest()); + md4.digest(dest, offset, 16); + break; + case 3: + case 4: + case 5: + if( clientChallenge == null ) { + clientChallenge = new byte[8]; + RANDOM.nextBytes( clientChallenge ); + } - HMACT64 hmac = new HMACT64(md4.digest()); - hmac.update(username.toUpperCase().getBytes( - "UnicodeLittleUnmarked")); - hmac.update(domain.toUpperCase().getBytes( - "UnicodeLittleUnmarked")); - byte[] ntlmv2Hash = hmac.digest(); - hmac = new HMACT64(ntlmv2Hash); - hmac.update(challenge); - hmac.update(clientChallenge); - HMACT64 userKey = new HMACT64(ntlmv2Hash); - userKey.update(hmac.digest()); - userKey.digest(dest, offset, 16); - break; - default: - md4.update(md4.digest()); - md4.digest(dest, offset, 16); - break; - } + HMACT64 hmac = new HMACT64(md4.digest()); + hmac.update(username.toUpperCase().getBytes( + "UnicodeLittleUnmarked")); + hmac.update(domain.toUpperCase().getBytes( + "UnicodeLittleUnmarked")); + byte[] ntlmv2Hash = hmac.digest(); + hmac = new HMACT64(ntlmv2Hash); + hmac.update(challenge); + hmac.update(clientChallenge); + HMACT64 userKey = new HMACT64(ntlmv2Hash); + userKey.update(hmac.digest()); + userKey.digest(dest, offset, 16); + break; + default: + md4.update(md4.digest()); + md4.digest(dest, offset, 16); + break; + } + } catch (Exception e) { + throw new SmbException("", e); + } } /** diff --git a/src/jcifs/smb/ServerMessageBlock.java b/src/jcifs/smb/ServerMessageBlock.java index 875dc08..fcb4b99 100644 --- a/src/jcifs/smb/ServerMessageBlock.java +++ b/src/jcifs/smb/ServerMessageBlock.java @@ -176,7 +176,7 @@ abstract class ServerMessageBlock extends Response implements Request, SmbConsta flags2, tid, pid, uid, mid, wordCount, byteCount; - boolean useUnicode, received; + boolean useUnicode, received, extendedSecurity; long responseTimeout = 1; int signSeq; boolean verifyFailed; @@ -267,6 +267,45 @@ Hexdump.hexdump( System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128 ); } return str; } + String readString(byte[] src, int srcIndex, int srcEnd, int maxLen, boolean useUnicode) { + int len = 0; + String str = null; + try { + if (useUnicode) { + // Unicode requires word alignment + if (((srcIndex - headerStart) % 2) != 0) { + srcIndex++; + } + for (len = 0; (srcIndex + len + 1) < srcEnd; len += 2) { + if (src[srcIndex + len] == (byte)0x00 && src[srcIndex + len + 1] == (byte)0x00) { + break; + } + if (len > maxLen) { + if (log.level > 0) + Hexdump.hexdump(System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128); + throw new RuntimeException("zero termination not found"); + } + } + str = new String(src, srcIndex, len, "UnicodeLittleUnmarked"); + } else { + for (len = 0; srcIndex < srcEnd; len++) { + if (src[srcIndex + len] == (byte)0x00) { + break; + } + if (len > maxLen) { + if (log.level > 0) + Hexdump.hexdump(System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128); + throw new RuntimeException("zero termination not found"); + } + } + str = new String(src, srcIndex, len, OEM_ENCODING); + } + } catch( UnsupportedEncodingException uee ) { + if( log.level > 1 ) + uee.printStackTrace( log ); + } + return str; + } int stringWireLength( String str, int offset ) { int len = str.length() + 1; if( useUnicode ) { diff --git a/src/jcifs/smb/SigningDigest.java b/src/jcifs/smb/SigningDigest.java index ae32161..0bde148 100644 --- a/src/jcifs/smb/SigningDigest.java +++ b/src/jcifs/smb/SigningDigest.java @@ -17,9 +17,30 @@ public class SigningDigest implements SmbConstants { private MessageDigest digest; private byte[] macSigningKey; + private boolean bypass = false; private int updates; private int signSequence; + public SigningDigest(byte[] macSigningKey, boolean bypass) throws SmbException { + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException ex) { + if( log.level > 0 ) + ex.printStackTrace( log ); + throw new SmbException( "MD5", ex ); + } + + this.macSigningKey = macSigningKey; + this.bypass = bypass; + this.updates = 0; + this.signSequence = 0; + + if( log.level >= 5 ) { + log.println("macSigningKey:"); + Hexdump.hexdump( log, macSigningKey, 0, macSigningKey.length ); + } + } + public SigningDigest( SmbTransport transport, NtlmPasswordAuthentication auth ) throws SmbException { try { @@ -114,6 +135,10 @@ public class SigningDigest implements SmbConstants { ServerMessageBlock.writeInt4(signSequence, data, index); update(data, offset, length); System.arraycopy(digest(), 0, data, index, 8); + if (bypass) { + bypass = false; + System.arraycopy("BSRSPYL ".getBytes(), 0, data, index, 8); + } } catch (Exception ex) { if( log.level > 0 ) ex.printStackTrace( log ); diff --git a/src/jcifs/smb/SmbComNegotiateResponse.java b/src/jcifs/smb/SmbComNegotiateResponse.java index d846e60..9b2d770 100644 --- a/src/jcifs/smb/SmbComNegotiateResponse.java +++ b/src/jcifs/smb/SmbComNegotiateResponse.java @@ -66,40 +66,47 @@ class SmbComNegotiateResponse extends ServerMessageBlock { int bufferIndex ) { int start = bufferIndex; - server.encryptionKey = new byte[server.encryptionKeyLength]; - System.arraycopy( buffer, bufferIndex, - server.encryptionKey, 0, server.encryptionKeyLength ); - bufferIndex += server.encryptionKeyLength; - if( byteCount > server.encryptionKeyLength ) { - int len = 0; - try { - if(( flags2 & FLAGS2_UNICODE ) == FLAGS2_UNICODE ) { - while( buffer[bufferIndex + len] != (byte)0x00 || - buffer[bufferIndex + len + 1] != (byte)0x00 ) { - len += 2; - if( len > 256 ) { - throw new RuntimeException( "zero termination not found" ); + if ((server.capabilities & FLAGS2_EXTENDED_SECURITY_NEGOTIATION) == 0) { + server.encryptionKey = new byte[server.encryptionKeyLength]; + System.arraycopy( buffer, bufferIndex, + server.encryptionKey, 0, server.encryptionKeyLength ); + bufferIndex += server.encryptionKeyLength; + if( byteCount > server.encryptionKeyLength ) { + int len = 0; +// TODO: we can use new string routine here + try { + if(( flags2 & FLAGS2_UNICODE ) == FLAGS2_UNICODE ) { + while( buffer[bufferIndex + len] != (byte)0x00 || + buffer[bufferIndex + len + 1] != (byte)0x00 ) { + len += 2; + if( len > 256 ) { + throw new RuntimeException( "zero termination not found" ); + } } - } - server.oemDomainName = new String( buffer, bufferIndex, - len, "UnicodeLittleUnmarked" ); - } else { - while( buffer[bufferIndex + len] != (byte)0x00 ) { - len++; - if( len > 256 ) { - throw new RuntimeException( "zero termination not found" ); + server.oemDomainName = new String( buffer, bufferIndex, + len, "UnicodeLittleUnmarked" ); + } else { + while( buffer[bufferIndex + len] != (byte)0x00 ) { + len++; + if( len > 256 ) { + throw new RuntimeException( "zero termination not found" ); + } } + server.oemDomainName = new String( buffer, bufferIndex, + len, ServerMessageBlock.OEM_ENCODING ); } - server.oemDomainName = new String( buffer, bufferIndex, - len, ServerMessageBlock.OEM_ENCODING ); + } catch( UnsupportedEncodingException uee ) { + if( log.level > 1 ) + uee.printStackTrace( log ); } - } catch( UnsupportedEncodingException uee ) { - if( log.level > 1 ) - uee.printStackTrace( log ); + bufferIndex += len; + } else { + server.oemDomainName = new String(); } - bufferIndex += len; } else { - server.oemDomainName = new String(); + server.guid = new byte[16]; + System.arraycopy(buffer, bufferIndex, server.guid, 0, 16); + // ignore SPNEGO token for now ... } return bufferIndex - start; @@ -122,9 +129,6 @@ class SmbComNegotiateResponse extends ServerMessageBlock { ",serverTimeZone=" + server.serverTimeZone + ",encryptionKeyLength=" + server.encryptionKeyLength + ",byteCount=" + byteCount + - ",encryptionKey=0x" + Hexdump.toHexString( server.encryptionKey, - 0, - server.encryptionKeyLength * 2 ) + ",oemDomainName=" + server.oemDomainName + "]" ); } } diff --git a/src/jcifs/smb/SmbComSessionSetupAndX.java b/src/jcifs/smb/SmbComSessionSetupAndX.java index 99b3b51..04d3fdf 100644 --- a/src/jcifs/smb/SmbComSessionSetupAndX.java +++ b/src/jcifs/smb/SmbComSessionSetupAndX.java @@ -28,65 +28,66 @@ class SmbComSessionSetupAndX extends AndXServerMessageBlock { private static final boolean DISABLE_PLAIN_TEXT_PASSWORDS = Config.getBoolean( "jcifs.smb.client.disablePlainTextPasswords", true ); - private byte[] accountPassword, unicodePassword; - private int passwordLength, unicodePasswordLength; + private byte[] lmHash, ntHash, blob = null; private int sessionKey; private String accountName, primaryDomain; SmbSession session; - NtlmPasswordAuthentication auth; + Object cred; - SmbComSessionSetupAndX( SmbSession session, ServerMessageBlock andx ) throws SmbException { + SmbComSessionSetupAndX( SmbSession session, ServerMessageBlock andx, Object cred ) throws SmbException { super( andx ); command = SMB_COM_SESSION_SETUP_ANDX; this.session = session; - this.auth = session.auth; - if( auth.hashesExternal && - Arrays.equals(auth.challenge, session.transport.server.encryptionKey) == false ) { - throw new SmbAuthException( SmbException.NT_STATUS_ACCESS_VIOLATION ); - } - } + this.cred = cred; - int getBatchLimit( byte command ) { - return command == SMB_COM_TREE_CONNECT_ANDX ? BATCH_LIMIT : 0; - } - int writeParameterWordsWireFormat( byte[] dst, int dstIndex ) { - int start = dstIndex; + sessionKey = session.transport.sessionKey; - if( session.transport.server.security == SECURITY_USER && - ( auth.hashesExternal || auth.password.length() > 0 )) { - if( session.transport.server.encryptedPasswords ) { - accountPassword = auth.getAnsiHash( session.transport.server.encryptionKey ); - passwordLength = accountPassword.length; - unicodePassword = auth.getUnicodeHash( session.transport.server.encryptionKey ); - unicodePasswordLength = unicodePassword.length; - // prohibit HTTP auth attempts for the null session - if (unicodePasswordLength == 0 && passwordLength == 0) { - throw new RuntimeException("Null setup prohibited."); + if (session.transport.server.security == SECURITY_USER) { + if (cred instanceof NtlmPasswordAuthentication) { + NtlmPasswordAuthentication auth = (NtlmPasswordAuthentication)cred; + + if (session.transport.server.encryptedPasswords) { + lmHash = auth.getAnsiHash( session.transport.server.encryptionKey ); + ntHash = auth.getUnicodeHash( session.transport.server.encryptionKey ); + // prohibit HTTP auth attempts for the null session + if (lmHash.length == 0 && ntHash.length == 0) { + throw new RuntimeException("Null setup prohibited."); + } + } else if( DISABLE_PLAIN_TEXT_PASSWORDS ) { + throw new RuntimeException( "Plain text passwords are disabled" ); + } else if( useUnicode ) { + // plain text + String password = auth.getPassword(); + lmHash = new byte[0]; + ntHash = new byte[(password.length() + 1) * 2]; + writeString( password, ntHash, 0 ); + } else { + // plain text + String password = auth.getPassword(); + lmHash = new byte[(password.length() + 1) * 2]; + ntHash = new byte[0]; + writeString( password, lmHash, 0 ); } - } else if( DISABLE_PLAIN_TEXT_PASSWORDS ) { - throw new RuntimeException( "Plain text passwords are disabled" ); - } else if( useUnicode ) { - // plain text - String password = auth.getPassword(); - accountPassword = new byte[0]; - passwordLength = 0; - unicodePassword = new byte[(password.length() + 1) * 2]; - unicodePasswordLength = writeString( password, unicodePassword, 0 ); + accountName = auth.username; + if (useUnicode) + accountName = accountName.toUpperCase(); + primaryDomain = auth.domain.toUpperCase(); + } else if (cred instanceof byte[]) { + blob = (byte[])cred; } else { - // plain text - String password = auth.getPassword(); - accountPassword = new byte[(password.length() + 1) * 2]; - passwordLength = writeString( password, accountPassword, 0 ); - unicodePassword = new byte[0]; - unicodePasswordLength = 0; + throw new SmbException("Unsupported credential type"); } } else { - // no password in session setup - passwordLength = unicodePasswordLength = 0; + throw new SmbException("Unsupported"); } + } - sessionKey = session.transport.sessionKey; + int getBatchLimit( byte command ) { + return command == SMB_COM_TREE_CONNECT_ANDX ? BATCH_LIMIT : 0; + } + int writeParameterWordsWireFormat( byte[] dst, int dstIndex ) { + int start = dstIndex; writeInt2( session.transport.snd_buf_size, dst, dstIndex ); dstIndex += 2; @@ -96,10 +97,15 @@ class SmbComSessionSetupAndX extends AndXServerMessageBlock { dstIndex += 2; writeInt4( sessionKey, dst, dstIndex ); dstIndex += 4; - writeInt2( passwordLength, dst, dstIndex ); - dstIndex += 2; - writeInt2( unicodePasswordLength, dst, dstIndex ); - dstIndex += 2; + if (blob != null) { + writeInt2( blob.length, dst, dstIndex ); + dstIndex += 2; + } else { + writeInt2( lmHash.length, dst, dstIndex ); + dstIndex += 2; + writeInt2( ntHash.length, dst, dstIndex ); + dstIndex += 2; + } dst[dstIndex++] = (byte)0x00; dst[dstIndex++] = (byte)0x00; dst[dstIndex++] = (byte)0x00; @@ -112,26 +118,18 @@ class SmbComSessionSetupAndX extends AndXServerMessageBlock { int writeBytesWireFormat( byte[] dst, int dstIndex ) { int start = dstIndex; - accountName = useUnicode ? auth.username : auth.username.toUpperCase(); - primaryDomain = auth.domain.toUpperCase(); - - if( session.transport.server.security == SECURITY_USER && - ( auth.hashesExternal || auth.password.length() > 0 )) { - System.arraycopy( accountPassword, 0, dst, dstIndex, passwordLength ); - dstIndex += passwordLength; - if (session.transport.server.encryptedPasswords == false && useUnicode) { - /* Align Unicode plain text password manually - */ - if ((( dstIndex - headerStart ) % 2 ) != 0 ) { - dst[dstIndex++] = (byte)'\0'; - } - } - System.arraycopy( unicodePassword, 0, dst, dstIndex, unicodePasswordLength ); - dstIndex += unicodePasswordLength; + if (blob != null) { + System.arraycopy(blob, 0, dst, dstIndex, blob.length ); + dstIndex += blob.length; + } else { + System.arraycopy( lmHash, 0, dst, dstIndex, lmHash.length ); + dstIndex += lmHash.length; + System.arraycopy( ntHash, 0, dst, dstIndex, ntHash.length ); + dstIndex += ntHash.length; + + dstIndex += writeString( accountName, dst, dstIndex ); + dstIndex += writeString( primaryDomain, dst, dstIndex ); } - - dstIndex += writeString( accountName, dst, dstIndex ); - dstIndex += writeString( primaryDomain, dst, dstIndex ); dstIndex += writeString( session.transport.NATIVE_OS, dst, dstIndex ); dstIndex += writeString( session.transport.NATIVE_LANMAN, dst, dstIndex ); @@ -150,8 +148,8 @@ class SmbComSessionSetupAndX extends AndXServerMessageBlock { ",maxMpxCount=" + session.transport.maxMpxCount + ",VC_NUMBER=" + session.transport.VC_NUMBER + ",sessionKey=" + sessionKey + - ",passwordLength=" + passwordLength + - ",unicodePasswordLength=" + unicodePasswordLength + + ",lmHash.length=" + (lmHash == null ? 0 : lmHash.length) + + ",ntHash.length=" + (ntHash == null ? 0 : ntHash.length) + ",capabilities=" + session.transport.capabilities + ",accountName=" + accountName + ",primaryDomain=" + primaryDomain + diff --git a/src/jcifs/smb/SmbComSessionSetupAndXResponse.java b/src/jcifs/smb/SmbComSessionSetupAndXResponse.java index 37748c7..7f7555d 100644 --- a/src/jcifs/smb/SmbComSessionSetupAndXResponse.java +++ b/src/jcifs/smb/SmbComSessionSetupAndXResponse.java @@ -27,6 +27,7 @@ class SmbComSessionSetupAndXResponse extends AndXServerMessageBlock { private String primaryDomain = ""; boolean isLoggedInAsGuest; + byte[] blob = null; SmbComSessionSetupAndXResponse( ServerMessageBlock andx ) { super( andx ); @@ -39,41 +40,30 @@ class SmbComSessionSetupAndXResponse extends AndXServerMessageBlock { return 0; } int readParameterWordsWireFormat( byte[] buffer, int bufferIndex ) { + int start = bufferIndex; isLoggedInAsGuest = ( buffer[bufferIndex] & 0x01 ) == 0x01 ? true : false; - return 2; + bufferIndex += 2; + if (extendedSecurity) { + int blobLength = readInt2(buffer, bufferIndex); + bufferIndex += 2; + blob = new byte[blobLength]; + } + return bufferIndex - start; } int readBytesWireFormat( byte[] buffer, int bufferIndex ) { int start = bufferIndex; + if (extendedSecurity) { + System.arraycopy(buffer, bufferIndex, blob, 0, blob.length); + bufferIndex += blob.length; + } nativeOs = readString( buffer, bufferIndex ); bufferIndex += stringWireLength( nativeOs, bufferIndex ); - nativeLanMan = readString( buffer, bufferIndex ); + nativeLanMan = readString( buffer, bufferIndex, start + byteCount, 255, useUnicode ); bufferIndex += stringWireLength( nativeLanMan, bufferIndex ); - - if( useUnicode ) { - int len; - - if((( bufferIndex - headerStart ) % 2 ) != 0 ) { - bufferIndex++; - } - - len = 0; - while( buffer[bufferIndex + len] != (byte)0x00 ) { - len += 2; - if( len > 256 ) { - throw new RuntimeException( "zero termination not found" ); - } - } - try { - primaryDomain = new String( buffer, bufferIndex, len, "UnicodeLittle" ); - } catch( UnsupportedEncodingException uee ) { - if( log.level > 1 ) - uee.printStackTrace( log ); - } - bufferIndex += len; - } else { - primaryDomain = readString( buffer, bufferIndex ); - bufferIndex += stringWireLength( primaryDomain, bufferIndex ); + if (!extendedSecurity) { + primaryDomain = readString(buffer, bufferIndex, start + byteCount, 255, useUnicode); + bufferIndex += stringWireLength(primaryDomain, bufferIndex); } return bufferIndex - start; diff --git a/src/jcifs/smb/SmbConstants.java b/src/jcifs/smb/SmbConstants.java index 439b634..a003582 100644 --- a/src/jcifs/smb/SmbConstants.java +++ b/src/jcifs/smb/SmbConstants.java @@ -26,10 +26,10 @@ interface SmbConstants { static final boolean USE_NTSTATUS = Config.getBoolean( "jcifs.smb.client.useNtStatus", true ); static final boolean SIGNPREF = Config.getBoolean("jcifs.smb.client.signingPreferred", false ); static final boolean USE_NTSMBS = Config.getBoolean( "jcifs.smb.client.useNTSmbs", true ); - static final boolean USE_EXTSEC = Config.getBoolean( "jcifs.smb.client.useExtendedSecurity", false ); + static final boolean USE_EXTSEC = Config.getBoolean( "jcifs.smb.client.useExtendedSecurity", true ); static final String NETBIOS_HOSTNAME = Config.getProperty( "jcifs.netbios.hostname", null ); - static final int LM_COMPATIBILITY = Config.getInt( "jcifs.smb.lmCompatibility", 0); + static final int LM_COMPATIBILITY = Config.getInt( "jcifs.smb.lmCompatibility", 3); static final int FLAGS_NONE = 0x00; static final int FLAGS_LOCK_AND_READ_WRITE_AND_UNLOCK = 0x01; @@ -62,6 +62,7 @@ interface SmbConstants { static final int CAP_LOCK_AND_READ = 0x0100; static final int CAP_NT_FIND = 0x0200; static final int CAP_DFS = 0x1000; + static final int CAP_EXTENDED_SECURITY = 0x80000000; // file attribute encoding static final int ATTR_READONLY = 0x01; diff --git a/src/jcifs/smb/SmbSession.java b/src/jcifs/smb/SmbSession.java index 67551d0..fc0944e 100644 --- a/src/jcifs/smb/SmbSession.java +++ b/src/jcifs/smb/SmbSession.java @@ -25,6 +25,7 @@ import java.net.UnknownHostException; import jcifs.Config; import jcifs.UniAddress; import jcifs.netbios.NbtAddress; +import jcifs.util.MD4; /** * The class represents a user's session established with an SMB/CIFS @@ -249,7 +250,12 @@ do { } void sessionSetup( ServerMessageBlock andx, ServerMessageBlock andxResponse ) throws SmbException { + NtlmContext nctx = null; SmbException ex = null; + SmbComSessionSetupAndX request; + SmbComSessionSetupAndXResponse response; + byte[] token = new byte[0]; + int state = 10; synchronized( transport() ) { if( sessionSetup ) { @@ -265,48 +271,125 @@ synchronized( transport() ) { if( transport.log.level >= 4 ) transport.log.println( "sessionSetup: accountName=" + auth.username + ",primaryDomain=" + auth.domain ); - SmbComSessionSetupAndX request = new SmbComSessionSetupAndX( this, andx ); - SmbComSessionSetupAndXResponse response = new SmbComSessionSetupAndXResponse( andxResponse ); + do { + switch (state) { + case 10: /* NTLM */ + if ((transport.capabilities & SmbConstants.CAP_EXTENDED_SECURITY) == SmbConstants.CAP_EXTENDED_SECURITY) { + state = 20; /* NTLMSSP */ + break; + } - /* Create SMB signature digest if necessary - * Only the first SMB_COM_SESSION_SETUP_ANX with non-null or - * blank password initializes signing. - */ - if (transport.isSignatureSetupRequired( auth )) { - if( auth.hashesExternal && NtlmPasswordAuthentication.DEFAULT_PASSWORD != NtlmPasswordAuthentication.BLANK ) { - /* preauthentication - */ - transport.getSmbSession( NtlmPasswordAuthentication.DEFAULT ).getSmbTree( LOGON_SHARE, null ).treeConnect( null, null ); - } else { - request.digest = new SigningDigest( transport, auth ); - } - } + request = new SmbComSessionSetupAndX( this, andx, auth ); + response = new SmbComSessionSetupAndXResponse( andxResponse ); + + /* Create SMB signature digest if necessary + * Only the first SMB_COM_SESSION_SETUP_ANX with non-null or + * blank password initializes signing. + */ + if (transport.isSignatureSetupRequired( auth )) { + if( auth.hashesExternal && NtlmPasswordAuthentication.DEFAULT_PASSWORD != NtlmPasswordAuthentication.BLANK ) { + /* preauthentication + */ + transport.getSmbSession( NtlmPasswordAuthentication.DEFAULT ).getSmbTree( LOGON_SHARE, null ).treeConnect( null, null ); + } else { + byte[] signingKey = auth.getSigningKey(transport.server.encryptionKey); + request.digest = new SigningDigest(signingKey, false); + } + } - request.auth = auth; + request.auth = auth; - try { - transport.send( request, response ); - } catch (SmbAuthException sae) { - throw sae; - } catch (SmbException se) { - ex = se; - } + try { + transport.send( request, response ); + } catch (SmbAuthException sae) { + throw sae; + } catch (SmbException se) { + ex = se; + } - if( response.isLoggedInAsGuest && - "GUEST".equalsIgnoreCase( auth.username ) == false) { - throw new SmbAuthException( NtStatus.NT_STATUS_LOGON_FAILURE ); - } + if( response.isLoggedInAsGuest && + "GUEST".equalsIgnoreCase( auth.username ) == false) { + throw new SmbAuthException( NtStatus.NT_STATUS_LOGON_FAILURE ); + } - uid = response.uid; - sessionSetup = true; + if (ex != null) + throw ex; - if( request.digest != null ) { - /* success - install the signing digest */ - transport.digest = request.digest; - } + uid = response.uid; + + if( request.digest != null ) { + /* success - install the signing digest */ + transport.digest = request.digest; + } + + sessionSetup = true; + state = 0; + + break; + case 20: + if (nctx == null) { + boolean doSigning = (transport.flags2 & ServerMessageBlock.FLAGS2_SECURITY_SIGNATURES) != 0; + nctx = new NtlmContext(auth, doSigning); + } - if (ex != null) - throw ex; + if (nctx.isEstablished()) { + sessionSetup = true; + state = 0; + break; + } + + token = nctx.initSecContext(token, 0, token.length); + if (token != null) { + request = new SmbComSessionSetupAndX(this, null, token); + response = new SmbComSessionSetupAndXResponse(null); + + if (transport.isSignatureSetupRequired( auth )) { + byte[] signingKey = nctx.getSigningKey(); + if (signingKey != null) + request.digest = new SigningDigest(signingKey, true); + } + + request.uid = uid; + uid = 0; + + try { + transport.send( request, response ); + } catch (SmbAuthException sae) { + throw sae; + } catch (SmbException se) { + ex = se; + /* Apparently once a successfull NTLMSSP login occurs, the + * server will return "Access denied" even if a logoff is + * sent. Unfortunately calling disconnect() doesn't always + * actually shutdown the connection before other threads + * have committed themselves (e.g. InterruptTest example). + */ + try { transport.disconnect(true); } catch (Exception e) {} + } + + if( response.isLoggedInAsGuest && + "GUEST".equalsIgnoreCase( auth.username ) == false) { + throw new SmbAuthException( NtStatus.NT_STATUS_LOGON_FAILURE ); + } + + if (ex != null) + throw ex; + + uid = response.uid; + + if (request.digest != null) { + /* success - install the signing digest */ + transport.digest = request.digest; + } + + token = response.blob; + } + + break; + default: + throw new SmbException("Unexpected session setup state: " + state); + } + } while (state != 0); } } void logoff( boolean inError ) { @@ -321,17 +404,17 @@ synchronized( transport() ) { } if( !inError && transport.server.security != ServerMessageBlock.SECURITY_SHARE ) { - /* * Logoff And X Request / Response */ - + SmbComLogoffAndX request = new SmbComLogoffAndX( null ); request.uid = uid; try { transport.send( request, null ); } catch( SmbException se ) { } + uid = 0; } sessionSetup = false; diff --git a/src/jcifs/smb/SmbTransport.java b/src/jcifs/smb/SmbTransport.java index e49853a..6677656 100644 --- a/src/jcifs/smb/SmbTransport.java +++ b/src/jcifs/smb/SmbTransport.java @@ -82,6 +82,7 @@ public class SmbTransport extends Transport implements SmbConstants { int serverTimeZone; int encryptionKeyLength; byte[] encryptionKey; + byte[] guid; } InetAddress localAddr; @@ -314,8 +315,10 @@ public class SmbTransport extends Transport implements SmbConstants { if( resp.dialectIndex > 10 ) { throw new SmbException( "This client does not support the negotiated dialect." ); } - if (server.encryptionKeyLength != 8 && LM_COMPATIBILITY == 0) { - throw new SmbException("Encryption key length is not 8 as expected. This could indicate that the server requires NTLMv2. JCIFS does not fully support NTLMv2 but you can try setting jcifs.smb.lmCompatibility = 3."); + if ((server.capabilities & CAP_EXTENDED_SECURITY) != CAP_EXTENDED_SECURITY && + server.encryptionKeyLength != 8 && + LM_COMPATIBILITY == 0) { + throw new SmbException("Unexpected encryption key length: " + server.encryptionKeyLength); } /* Adjust negotiated values */ @@ -330,6 +333,9 @@ public class SmbTransport extends Transport implements SmbConstants { if (maxMpxCount < 1) maxMpxCount = 1; snd_buf_size = Math.min( snd_buf_size, server.maxBufferSize ); capabilities &= server.capabilities; + if ((server.capabilities & CAP_EXTENDED_SECURITY) == CAP_EXTENDED_SECURITY) + capabilities |= CAP_EXTENDED_SECURITY; // & doesn't copy high bit + if ((capabilities & ServerMessageBlock.CAP_UNICODE) == 0) { // server doesn't want unicode if (FORCE_UNICODE) { @@ -444,6 +450,7 @@ public class SmbTransport extends Transport implements SmbConstants { protected void doRecv( Response response ) throws IOException { ServerMessageBlock resp = (ServerMessageBlock)response; resp.useUnicode = useUnicode; + resp.extendedSecurity = (capabilities & CAP_EXTENDED_SECURITY) == CAP_EXTENDED_SECURITY; synchronized (BUF) { System.arraycopy( sbuf, 0, BUF, 0, 4 + HEADER_LENGTH ); @@ -523,6 +530,8 @@ public class SmbTransport extends Transport implements SmbConstants { throw drs[0]; case 0x80000005: /* STATUS_BUFFER_OVERFLOW */ break; /* normal for DCERPC named pipes */ + case NtStatus.NT_STATUS_MORE_PROCESSING_REQUIRED: + break; /* normal for NTLMSSP */ default: throw new SmbException( resp.errorCode, null ); } -- 2.11.0