From a91d4b646e14b5d784a2401a5eef7d25c8c5b9c2 Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Wed, 6 Aug 2008 16:06:32 +0200 Subject: [PATCH] jcifs-0.7.11 from tgz Thu Jul 10 22:07:09 EDT 2003 Support for LMv2 authentication has been added. See the Overview page in the API documentation regarding the jcifs.smb.lmCompatibility property. Some additonal "cleanup" has also been performed. One side-effect that might be noticed is that the default domain/username/password can no longer be set at runtime. Once the client is initialized the default credentials are fixed. Setting default credentials are runtime was always an unsafe operation. Create an NtlmPasswordAuthentication object instead. Thu Jul 3 20:59:25 EDT 2003 There have been two small bug fixes in the SMB layer; the count field in the SMB_COM_WRITE_ANDX response was being ignored. Apparently the count specified in the request is always honored or we would have seen plenty of file corruption by now. Second, the list() and listFiles() did not properly handle Transaction buffering with certain values for the listSize and listCount properties (if multipart transaction responses are send). Also removed a few obnoxious warnings getting logged during file transfers if Log.WARNINGS is set. Regarding NTLM HTTP Authentication; there have been additions and updates to the documentation on the NTLM flags, a fix to NtlmSsp to only provide the target when requested by the client via the NTLM "request target" flag (this is the correct behavior), and a bugfix to NtlmSsp to do Base64.encodeBytes(bytes, false) instead of Base64.encodeBytes(bytes) (a linewrap in the header can cause an error). Eric has also excellent documentation detailing his analysis of the NTLM protocol: http://davenport.sourceforge.net/ntlm.html Thu Jun 12 00:18:05 EDT 2003 It was discovered that if a server responds to the NBT session setup but not to the SMB_COM_NEGOTIATE request the client will hang indefinately. This is not a likely thing to occur because servers usually respond or not at all. Regardless it has been fixed. The client will timeout after jcifs.netbios.soTimeout in this situation. Some tweeking of the NTLM http code has been performed. Wed May 28 19:09:17 EDT 2003 jcifs-0.7.8 released A bug in the new ntlm http client code was identified and fixed. Tue May 27 21:40:58 EDT 2003 jcifs-0.7.7 released A deadlock was identified in SmbTransport very similar to the one found last year. A significant change has been made that greatly simplifies synchronization in the transport layer. It eliminates this issue as well as the extra synchronization introduced to fix the previous problem. It is an elemental change however so proceed with caution. Two new packages have been introduced. The jcifs.ntlmssp package now contains all NTLMSSP base code. This code is used by the NtlmHttpFilter as well has the new NtlmHttpURLConnection class for transparently enabling your HTTP and HTTPS client to negotiate NTLM authentication. The other new package is jcifs.https which just contains the HTTPS protocol handler necesary for proper protocol handler registration. Please read the document entitled "Using jCIFS NTLM Authentication for HTTP Connections" for important details. To permit SmbFileOutputStream to be used with unusual named pipe modes (see 5/1/03 message) that will be both read and written the open flags in SmbFileOutputStream have been changed from O_WRONLY to O_RDWR. The attrExpiration period of SmbFile is now reset in SmbFileOutputStream.write() to prevent jCIFS from reporting incorrect attributes values after writing the file. Previously it was possible to write to the file and immediated check the timestamp or file size and get an old value. This should no longer happen. The SmbFileInputStream.skip() method has been implemented in a way that will not result in any IO to the server. Thus it is possible to resume large downloads from the point of failure for instance. Wed Apr 16 22:46:07 EDT 2003 jcifs-0.7.6 released The isDirectory method has been changed to return false if the target does not exist. Previously this would return true which is inconsistent with java.io.File. Wed Apr 2 23:56:26 EST 2003 jcifs-0.7.5 released More refinement to the NTLM HTTP authentication code has been applied. There is also a fix for listing hosts from Windows ME/98/95 local master browsers. Wed Mar 26 19:17:24 EST 2003 jcifs-0.7.4 released Some NtlmHttpFilter issues were reported by several users of Win98 clients. Eric has provided a new NtlmSsp.java that works correctly with these clients. Wed Feb 12 01:23:02 EST 2003 From the beginning jCIFS identified SmbSessions uniquely by domain\username. This meant that once a session was established it could be shared by another requestor even if they supplied an incorrect password. This was done for reasons that strangely enough had to do with SMB chaining. However with the introduction of the http package it is now common to use jCIFS to authenticate web clients from a central jCIFS instance. This introduces a security flaw because there would be a brief window of opportunity after a user logs in during which an impostor could log in as that user with a simple change to their MSIE security settings. This problem has been fixed. Another related issue has also been fixed. If the NtlmHttpFilter or NetworkExplorer servlet is supplied with bad credentials the behavior was to throw an SmbAuthException up through the container. Instead these classes will respond with the standard Authentication: NTLM response triggering the password dialog to be displayed until correct credentials are displayed. --- CHANGES.txt | 104 +++++ README.txt | 56 +++ build.xml | 23 +- examples/FileOps.java | 12 +- examples/LogonTest.java | 17 - examples/NtlmHttpClient.java | 49 ++ examples/ThreadedSmbCrawler.java | 8 +- examples/VerifyReads.java | 2 +- src/jcifs/http/Handler.java | 159 +++++++ src/jcifs/http/NtlmHttpFilter.java | 91 ++-- src/jcifs/http/NtlmHttpServletRequest.java | 5 +- src/jcifs/http/NtlmHttpURLConnection.java | 563 ++++++++++++++++++++++ src/jcifs/http/NtlmServlet.java | 104 +++-- src/jcifs/http/NtlmSsp.java | 153 +++--- src/jcifs/https/Handler.java | 44 ++ src/jcifs/netbios/NbtAddress.java | 11 +- src/jcifs/netbios/NbtSocket.java | 5 + src/jcifs/ntlmssp/NtlmFlags.java | 155 +++++++ src/jcifs/ntlmssp/NtlmMessage.java | 138 ++++++ src/jcifs/ntlmssp/Type1Message.java | 248 ++++++++++ src/jcifs/ntlmssp/Type2Message.java | 414 +++++++++++++++++ src/jcifs/ntlmssp/Type3Message.java | 580 +++++++++++++++++++++++ src/jcifs/smb/NtlmPasswordAuthentication.java | 154 ++++-- src/jcifs/smb/SmbComSessionSetupAndX.java | 8 +- src/jcifs/smb/SmbComSessionSetupAndX.java0 | 172 +++++++ src/jcifs/smb/SmbComTransactionResponse.java | 5 + src/jcifs/smb/SmbComTreeConnectAndX.java | 10 +- src/jcifs/smb/SmbComWriteAndXResponse.java | 104 ++--- src/jcifs/smb/SmbFile.java | 76 ++- src/jcifs/smb/SmbFileInputStream.java | 18 +- src/jcifs/smb/SmbFileOutputStream.java | 408 ++++++++-------- src/jcifs/smb/SmbSession.java | 8 +- src/jcifs/smb/SmbTransport.java | 3 +- src/jcifs/smb/SmbTree.java | 15 +- src/jcifs/util/Base64.java | 643 ++++---------------------- src/jcifs/util/HMACT64.java | 116 +++++ src/jcifs/util/URLDecoder.jav | 81 ---- update/Base64.java | 94 ++++ update/HMACT64.java | 116 +++++ update/NtlmHttpFilter.java | 164 +++++++ update/NtlmHttpURLConnection.java | 563 ++++++++++++++++++++++ update/NtlmMessage.java | 138 ++++++ update/NtlmPasswordAuthentication.java | 348 ++++++++++++++ update/NtlmServlet.java | 158 +++++++ update/NtlmSsp.java | 112 +++++ update/Type1Message.java | 248 ++++++++++ update/Type2Message.java | 414 +++++++++++++++++ update/Type3Message.java | 580 +++++++++++++++++++++++ 48 files changed, 6570 insertions(+), 1127 deletions(-) delete mode 100644 examples/LogonTest.java create mode 100644 examples/NtlmHttpClient.java create mode 100644 src/jcifs/http/Handler.java create mode 100644 src/jcifs/http/NtlmHttpURLConnection.java create mode 100644 src/jcifs/https/Handler.java create mode 100644 src/jcifs/ntlmssp/NtlmFlags.java create mode 100644 src/jcifs/ntlmssp/NtlmMessage.java create mode 100644 src/jcifs/ntlmssp/Type1Message.java create mode 100644 src/jcifs/ntlmssp/Type2Message.java create mode 100644 src/jcifs/ntlmssp/Type3Message.java create mode 100644 src/jcifs/smb/SmbComSessionSetupAndX.java0 create mode 100644 src/jcifs/util/HMACT64.java delete mode 100644 src/jcifs/util/URLDecoder.jav create mode 100644 update/Base64.java create mode 100644 update/HMACT64.java create mode 100644 update/NtlmHttpFilter.java create mode 100644 update/NtlmHttpURLConnection.java create mode 100644 update/NtlmMessage.java create mode 100644 update/NtlmPasswordAuthentication.java create mode 100644 update/NtlmServlet.java create mode 100644 update/NtlmSsp.java create mode 100644 update/Type1Message.java create mode 100644 update/Type2Message.java create mode 100644 update/Type3Message.java diff --git a/CHANGES.txt b/CHANGES.txt index 8d50af1..2b77d33 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,107 @@ +Thu Jul 10 22:07:09 EDT 2003 + +Support for LMv2 authentication has been added. See the Overview page in +the API documentation regarding the jcifs.smb.lmCompatibility property. +Some additonal "cleanup" has also been performed. One side-effect that +might be noticed is that the default domain/username/password can no longer +be set at runtime. Once the client is initialized the default credentials +are fixed. Setting default credentials are runtime was always an unsafe +operation. Create an NtlmPasswordAuthentication object instead. + +Thu Jul 3 20:59:25 EDT 2003 + +There have been two small bug fixes in the SMB layer; the count field in +the SMB_COM_WRITE_ANDX response was being ignored. Apparently the count +specified in the request is always honored or we would have seen plenty of +file corruption by now. Second, the list() and listFiles() did not properly +handle Transaction buffering with certain values for the listSize and +listCount properties (if multipart transaction responses are send). Also +removed a few obnoxious warnings getting logged during file transfers if +Log.WARNINGS is set. + +Regarding NTLM HTTP Authentication; there have been additions and updates +to the documentation on the NTLM flags, a fix to NtlmSsp to only provide +the target when requested by the client via the NTLM "request target" flag +(this is the correct behavior), and a bugfix to NtlmSsp to do +Base64.encodeBytes(bytes, false) instead of Base64.encodeBytes(bytes) (a +linewrap in the header can cause an error). Eric has also excellent +documentation detailing his analysis of the NTLM protocol: + + http://davenport.sourceforge.net/ntlm.html + +Thu Jun 12 00:18:05 EDT 2003 + +It was discovered that if a server responds to the NBT session setup but +not to the SMB_COM_NEGOTIATE request the client will hang indefinately. +This is not a likely thing to occur because servers usually respond or not +at all. Regardless it has been fixed. The client will timeout after +jcifs.netbios.soTimeout in this situation. + +Some tweeking of the NTLM http code has been performed. + +Wed May 28 19:09:17 EDT 2003 + +jcifs-0.7.8 released + +A bug in the new ntlm http client code was identified and fixed. + +Tue May 27 21:40:58 EDT 2003 + +jcifs-0.7.7 released + +A deadlock was identified in SmbTransport very similar to the one found +last year. A significant change has been made that greatly simplifies +synchronization in the transport layer. It eliminates this issue as well as +the extra synchronization introduced to fix the previous problem. It is an +elemental change however so proceed with caution. + +Two new packages have been introduced. The jcifs.ntlmssp package now +contains all NTLMSSP base code. This code is used by the NtlmHttpFilter as +well has the new NtlmHttpURLConnection class for transparently enabling +your HTTP and HTTPS client to negotiate NTLM authentication. The other new +package is jcifs.https which just contains the HTTPS protocol handler +necesary for proper protocol handler registration. Please read the document +entitled "Using jCIFS NTLM Authentication for HTTP Connections" for +important details. + +To permit SmbFileOutputStream to be used with unusual named pipe modes (see +5/1/03 message) that will be both read and written the open flags in +SmbFileOutputStream have been changed from O_WRONLY to O_RDWR. + +The attrExpiration period of SmbFile is now reset in +SmbFileOutputStream.write() to prevent jCIFS from reporting incorrect +attributes values after writing the file. Previously it was possible to +write to the file and immediated check the timestamp or file size and get +an old value. This should no longer happen. + +The SmbFileInputStream.skip() method has been implemented in a way that +will not result in any IO to the server. Thus it is possible to resume +large downloads from the point of failure for instance. + +Wed Apr 16 22:46:07 EDT 2003 + +jcifs-0.7.6 released + +The isDirectory method has been changed to return false if the target does +not exist. Previously this would return true which is inconsistent with +java.io.File. + +Wed Apr 2 23:56:26 EST 2003 + +jcifs-0.7.5 released + +More refinement to the NTLM HTTP authentication code has been applied. +There is also a fix for listing hosts from Windows ME/98/95 local master +browsers. + +Wed Mar 26 19:17:24 EST 2003 + +jcifs-0.7.4 released + +Some NtlmHttpFilter issues were reported by several users of Win98 clients. +Eric has provided a new NtlmSsp.java that works correctly with these +clients. + Wed Feb 12 01:23:02 EST 2003 From the beginning jCIFS identified SmbSessions uniquely by diff --git a/README.txt b/README.txt index 17beab8..b16e455 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,59 @@ +Thu Jul 10 22:07:09 EDT 2003 + +Support for LMv2 authentication has been added. + +Thu Jul 3 20:59:25 EDT 2003 + +There have been minor bug fixes in the NTLM code and SMB layer as well as +documentation updates. + +Thu Jun 12 00:22:05 EDT 2003 + +jcifs-0.7.9 released + +A bug that could cause a connection to hang indefinately has been fixed and +some NTLM HTTP authentication tweeking has been applied. + +Wed May 28 19:09:17 EDT 2003 + +jcifs-0.7.8 released + +A bug in the new ntlm http client code that would cause it to hang for ~60 +seconds before retreiving the target document has been fixed. + +Tue May 27 21:40:58 EDT 2003 + +jcifs-0.7.7 released + +A deadlock has been identified and fixed, NTLM authentication for HTTP and +HTTPS clients has been added (the inverse of the NtlmHttpFilter), the +SmbFileInputStream.skip() method will skip without reading, and there have +two other small fixes. + +Wed Apr 16 22:46:07 EDT 2003 + +jcifs-0.7.6 released + +The isDirectory method has been changed to return false if the target does +not exist. Previously this would return true which is inconsistent with +java.io.File. + +Wed Apr 2 23:56:26 EST 2003 + +jcifs-0.7.5 released + +More refinement to the NTLM HTTP authentication code has been applied. +There is also a fix for listing hosts from Windows ME/98/95 local master +browsers. + +Wed Mar 26 19:17:24 EST 2003 + +jcifs-0.7.4 released + +Some NtlmHttpFilter issues were reported by several users of Win98 clients. +Eric has provided a new NtlmSsp.java that works correctly with these +clients. + Wed Feb 12 01:23:02 EST 2003 A security issue regarding the SmbSession.logon() method used by NTLM HTTP diff --git a/build.xml b/build.xml index c16e817..b7a2669 100644 --- a/build.xml +++ b/build.xml @@ -45,7 +45,14 @@ includes="jcifs/http/*.java" debug="on"/> - + + + + + - + - + - - - + + + - + - + diff --git a/examples/FileOps.java b/examples/FileOps.java index b582892..931accf 100644 --- a/examples/FileOps.java +++ b/examples/FileOps.java @@ -18,7 +18,7 @@ * false - the opposite of the above * isDirectory * true - the target is a workgroup, server, share, or directory - * false - the target is not one of the above + * false - the target is not one of the above or does not exist * isFile * direct opposite of isDirectory * isHidden @@ -205,6 +205,16 @@ public class FileOps { System.out.println( "fail - isDirectory " + d + " returned false but it really is a directory" ); } + // isDirectory - Test directory that does not exist + + b = new SmbFile( d, "bogus" ); + + if( b.isDirectory() ) { + System.out.println( "fail - isDirectory " + b + " returned true but it does not exist" ); + } else { + System.out.println( "okay - isDirectory " + b + " does not exist" ); + } + // isFile - Test file as a file if( f.isFile() ) { diff --git a/examples/LogonTest.java b/examples/LogonTest.java deleted file mode 100644 index 57b9bd7..0000000 --- a/examples/LogonTest.java +++ /dev/null @@ -1,17 +0,0 @@ -import jcifs.UniAddress; -import jcifs.smb.SmbSession; -import jcifs.smb.NtlmPasswordAuthentication; - -public class LogonTest { - - public static void main( String argv[] ) throws Exception { - UniAddress dc = UniAddress.getByName( "miallen2" ); - NtlmPasswordAuthentication good = new NtlmPasswordAuthentication( "mlamrs", "miallen", "ru_RUhrd" ); - NtlmPasswordAuthentication bad = new NtlmPasswordAuthentication( "mlamrs", "miallen", "bogus" ); - SmbSession.logon( dc, good ); - System.out.println( "good" ); - SmbSession.logon( dc, bad ); - System.out.println( "bad" ); - } -} - diff --git a/examples/NtlmHttpClient.java b/examples/NtlmHttpClient.java new file mode 100644 index 0000000..98d8914 --- /dev/null +++ b/examples/NtlmHttpClient.java @@ -0,0 +1,49 @@ +import java.io.*; + +import java.net.*; + +import jcifs.*; + +public class NtlmHttpClient { + + public static void main(String[] args) throws Exception { + // Normally set this outside application. + // Note that as a side effect due to the way handlers are located, + // you can also achieve this by simply doing: + Config.registerSmbURLHandler(); + // which we already do to register the smb handler. + //String pkgs = System.getProperty("java.protocol.handler.pkgs"); + //pkgs = (pkgs != null) ? "jcifs|" + pkgs : "jcifs"; + //System.setProperty("java.protocol.handler.pkgs", pkgs); + // + + if (args == null || args.length < 4) { + System.out.println("NtlmHttpClient "); + System.exit(1); + } + String location = args[0]; + String domain = args[1]; + String user = args[2]; + String password = args[3]; + // can also specify these in the URL, i.e. + // http://DOMAIN%5cuser:password@host/dir/file.html + // which will override these properties + Config.setProperty("jcifs.smb.client.domain", domain); + Config.setProperty("jcifs.smb.client.username", user); + Config.setProperty("jcifs.smb.client.password", password); + + try { + Config.setProperty("jcifs.netbios.hostname", + Config.getProperty("jcifs.netbios.hostname", + InetAddress.getLocalHost().getHostName())); + } catch (Exception ex) { } + URL url = new URL(location); + BufferedReader reader = new BufferedReader( + new InputStreamReader(url.openStream())); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + +} diff --git a/examples/ThreadedSmbCrawler.java b/examples/ThreadedSmbCrawler.java index 97d271e..0b515ad 100644 --- a/examples/ThreadedSmbCrawler.java +++ b/examples/ThreadedSmbCrawler.java @@ -48,7 +48,7 @@ public class ThreadedSmbCrawler { synchronized( dirList ) { while( dirList.isEmpty() ) { -System.err.println( "workingThreads=" + workingThreads ); +//System.err.println( "workingThreads=" + workingThreads ); if( workingThreads == 0 ) { return; // done } @@ -61,7 +61,7 @@ System.err.println( "workingThreads=" + workingThreads ); workingThreads++; } - String[] l = e.dir.list(); + SmbFile[] l = e.dir.listFiles(); int n = maxDepth - e.depth; @@ -71,7 +71,7 @@ System.err.println( "workingThreads=" + workingThreads ); for( int k = 0; k < n; k++ ) { sb.append( " " ); } - SmbFile d = new SmbFile( e.dir, l[i] ); + SmbFile d = l[i]; System.err.println( sb.append( d )); if( d.isDirectory() ) { synchronized( dirList ) { @@ -132,6 +132,6 @@ System.err.println( "workingThreads=" + workingThreads ); } tsc = new ThreadedSmbCrawler( argv[0], Integer.parseInt( argv[1] ), Integer.parseInt( argv[2] )); - System.err.println( "Crawling Complete: " + (tsc.go() / 1000 / 60) + "min" ); + System.err.println( "Crawling Complete: " + (tsc.go() / 1000) + "sec" ); } } diff --git a/examples/VerifyReads.java b/examples/VerifyReads.java index cfa42fa..7d8b6da 100644 --- a/examples/VerifyReads.java +++ b/examples/VerifyReads.java @@ -59,7 +59,7 @@ public class VerifyReads { int depth; if( argv.length < 2 ) { - System.err.println( "Must specify ini directory location (e.g. smb://mlamrs\\;rschprod:grispass@rsch-nyc-19b9/vendorapps) followd by the maximum traversal depth"); + System.err.println( "Must specify ini directory location (e.g. smb://mydom\\;user:pass@nyc-19b9/apps) followd by the maximum traversal depth"); System.exit( 1 ); } diff --git a/src/jcifs/http/Handler.java b/src/jcifs/http/Handler.java new file mode 100644 index 0000000..d915faa --- /dev/null +++ b/src/jcifs/http/Handler.java @@ -0,0 +1,159 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.http; + +import java.io.IOException; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * A URLStreamHandler used to provide NTLM authentication + * capabilities to the default HTTP handler. This acts as a wrapper, + * handling authentication and passing control to the underlying + * stream handler. + */ +public class Handler extends URLStreamHandler { + + /** + * The default HTTP port (80). + */ + public static final int DEFAULT_HTTP_PORT = 80; + + private static final Map PROTOCOL_HANDLERS = new HashMap(); + + private static final String HANDLER_PKGS_PROPERTY = + "java.protocol.handler.pkgs"; + + /** + * Vendor-specific default packages. If no packages are specified in + * "java.protocol.handler.pkgs", the VM uses one or more default + * packages, which are vendor specific. Sun's is included below + * for convenience; others could be as well. If a particular vendor's + * package isn't listed, it can be specified in + * "java.protocol.handler.pkgs". + */ + private static final String[] JVM_VENDOR_DEFAULT_PKGS = new String[] { + "sun.net.www.protocol" + }; + + private static URLStreamHandlerFactory factory; + + /** + * Sets the URL stream handler factory for the environment. This + * allows specification of the factory used in creating underlying + * stream handlers. This can be called once per JVM instance. + * + * @param factory The URL stream handler factory. + */ + public static void setURLStreamHandlerFactory( + URLStreamHandlerFactory factory) { + synchronized (PROTOCOL_HANDLERS) { + if (Handler.factory != null) { + throw new IllegalStateException( + "URLStreamHandlerFactory already set."); + } + PROTOCOL_HANDLERS.clear(); + Handler.factory = factory; + } + } + + /** + * Returns the default HTTP port. + * + * @return An int containing the default HTTP port. + */ + protected int getDefaultPort() { + return DEFAULT_HTTP_PORT; + } + + protected URLConnection openConnection(URL url) throws IOException { + url = new URL(url, url.toExternalForm(), + getDefaultStreamHandler(url.getProtocol())); + return new NtlmHttpURLConnection((HttpURLConnection) + url.openConnection()); + } + + private static URLStreamHandler getDefaultStreamHandler(String protocol) + throws IOException { + synchronized (PROTOCOL_HANDLERS) { + URLStreamHandler handler = (URLStreamHandler) + PROTOCOL_HANDLERS.get(protocol); + if (handler != null) return handler; + if (factory != null) { + handler = factory.createURLStreamHandler(protocol); + } + if (handler == null) { + String path = System.getProperty(HANDLER_PKGS_PROPERTY); + StringTokenizer tokenizer = new StringTokenizer(path, "|"); + while (tokenizer.hasMoreTokens()) { + String provider = tokenizer.nextToken().trim(); + if (provider.equals("jcifs")) continue; + String className = provider + "." + protocol + ".Handler"; + try { + Class handlerClass = null; + try { + handlerClass = Class.forName(className); + } catch (Exception ex) { } + if (handlerClass == null) { + handlerClass = ClassLoader.getSystemClassLoader( + ).loadClass(className); + } + handler = (URLStreamHandler) handlerClass.newInstance(); + break; + } catch (Exception ex) { } + } + } + if (handler == null) { + for (int i = 0; i < JVM_VENDOR_DEFAULT_PKGS.length; i++) { + String className = JVM_VENDOR_DEFAULT_PKGS[i] + "." + + protocol + ".Handler"; + try { + Class handlerClass = null; + try { + handlerClass = Class.forName(className); + } catch (Exception ex) { } + if (handlerClass == null) { + handlerClass = ClassLoader.getSystemClassLoader( + ).loadClass(className); + } + handler = (URLStreamHandler) handlerClass.newInstance(); + } catch (Exception ex) { } + if (handler != null) break; + } + } + if (handler == null) { + throw new IOException( + "Unable to find default handler for protocol: " + + protocol); + } + PROTOCOL_HANDLERS.put(protocol, handler); + return handler; + } + } + +} diff --git a/src/jcifs/http/NtlmHttpFilter.java b/src/jcifs/http/NtlmHttpFilter.java index 8b70e7d..350fec3 100644 --- a/src/jcifs/http/NtlmHttpFilter.java +++ b/src/jcifs/http/NtlmHttpFilter.java @@ -1,19 +1,19 @@ /* jcifs smb client library in Java * Copyright (C) 2002 "Michael B. Allen" - * "Jason Pugsley" - * "skeetz" - * "Eric Glass" - * + * "Jason Pugsley" + * "skeetz" + * "Eric Glass" + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -30,6 +30,7 @@ import jcifs.*; import jcifs.smb.SmbSession; import jcifs.smb.NtlmPasswordAuthentication; import jcifs.smb.SmbAuthException; +import jcifs.util.Base64; /** * This servlet Filter can be used to negotiate password hashes with @@ -40,10 +41,18 @@ import jcifs.smb.SmbAuthException; * Read jCIFS NTLM HTTP Authentication and the Network Explorer Servlet for complete details. */ -public class NtlmHttpFilter extends NtlmSsp implements Filter { +public class NtlmHttpFilter implements Filter { + + private String defaultDomain; private String domainController; + private boolean enableBasic; + + private boolean insecureBasic; + + private String realm; + public void init( FilterConfig filterConfig ) throws ServletException { String name; @@ -59,11 +68,15 @@ public class NtlmHttpFilter extends NtlmSsp implements Filter { Config.setProperty( name, filterConfig.getInitParameter( name )); } } - + defaultDomain = Config.getProperty("jcifs.smb.client.domain"); domainController = Config.getProperty( "jcifs.http.domainController" ); - if( domainController == null ) { - domainController = Config.getProperty( "jcifs.smb.client.domain" ); - } + if( domainController == null ) domainController = defaultDomain; + enableBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.enableBasic")).booleanValue(); + insecureBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.insecureBasic")).booleanValue(); + realm = Config.getProperty("jcifs.http.basicRealm"); + if (realm == null) realm = "jCIFS"; } public void destroy() { } @@ -72,39 +85,65 @@ public class NtlmHttpFilter extends NtlmSsp implements Filter { FilterChain chain ) throws IOException, ServletException { HttpServletRequest req; HttpServletResponse resp; - NtlmPasswordAuthentication ntlm; UniAddress dc; - byte[] challenge; String msg; - HttpSession ssn; + NtlmPasswordAuthentication ntlm = null; req = (HttpServletRequest)request; resp = (HttpServletResponse)response; - ssn = req.getSession(); msg = req.getHeader( "Authorization" ); + boolean offerBasic = enableBasic && (insecureBasic || req.isSecure()); - if( msg != null && msg.startsWith( "NTLM " )) { + if( msg != null && (msg.startsWith( "NTLM " ) || + (offerBasic && msg.startsWith("Basic ")))) { dc = UniAddress.getByName( domainController, true ); - challenge = SmbSession.getChallenge( dc ); - if(( ntlm = doAuthentication( req, resp, challenge )) == null ) { - return; + if (msg.startsWith("NTLM ")) { + byte[] challenge = SmbSession.getChallenge( dc ); + if(( ntlm = NtlmSsp.authenticate( req, resp, challenge )) == null ) { + return; + } + } else { + String auth = new String(Base64.decode(msg.substring(6)), + "US-ASCII"); + int index = auth.indexOf(':'); + String user = (index != -1) ? auth.substring(0, index) : auth; + String password = (index != -1) ? auth.substring(index + 1) : + ""; + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + String domain = (index != -1) ? user.substring(0, index) : + defaultDomain; + user = (index != -1) ? user.substring(index + 1) : user; + ntlm = new NtlmPasswordAuthentication(domain, user, password); } try { SmbSession.logon( dc, ntlm ); } catch( SmbAuthException sae ) { resp.setHeader( "WWW-Authenticate", "NTLM" ); + if (offerBasic) { + resp.addHeader( "WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + resp.setHeader( "Connection", "close" ); + resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); + resp.flushBuffer(); + return; + } + req.getSession().setAttribute( "NtlmHttpAuth", ntlm ); + } else { + HttpSession ssn = req.getSession(false); + if (ssn == null || (ntlm = (NtlmPasswordAuthentication) + ssn.getAttribute("NtlmHttpAuth")) == null) { + resp.setHeader( "WWW-Authenticate", "NTLM" ); + if (offerBasic) { + resp.addHeader( "WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } resp.setHeader( "Connection", "close" ); resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); resp.flushBuffer(); return; } - ssn.setAttribute( "NtlmHttpAuth", ntlm ); - } else if(( ntlm = (NtlmPasswordAuthentication)ssn.getAttribute( "NtlmHttpAuth" )) == null ) { - resp.setHeader( "WWW-Authenticate", "NTLM" ); - resp.setHeader( "Connection", "close" ); - resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); - resp.flushBuffer(); - return; } chain.doFilter( new NtlmHttpServletRequest( req, ntlm ), response ); diff --git a/src/jcifs/http/NtlmHttpServletRequest.java b/src/jcifs/http/NtlmHttpServletRequest.java index f8526ad..47629a9 100644 --- a/src/jcifs/http/NtlmHttpServletRequest.java +++ b/src/jcifs/http/NtlmHttpServletRequest.java @@ -1,6 +1,6 @@ /* jcifs smb client library in Java * Copyright (C) 2002 "Michael B. Allen" - * "Eric Glass" + * "Eric Glass" * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,5 +37,8 @@ class NtlmHttpServletRequest extends HttpServletRequestWrapper { public Principal getUserPrincipal() { return principal; } + public String getAuthType() { + return "NTLM"; + } } diff --git a/src/jcifs/http/NtlmHttpURLConnection.java b/src/jcifs/http/NtlmHttpURLConnection.java new file mode 100644 index 0000000..5dd30f4 --- /dev/null +++ b/src/jcifs/http/NtlmHttpURLConnection.java @@ -0,0 +1,563 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.http; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.PasswordAuthentication; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLDecoder; + +import java.security.Permission; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import jcifs.Config; + +import jcifs.ntlmssp.NtlmFlags; +import jcifs.ntlmssp.NtlmMessage; +import jcifs.ntlmssp.Type1Message; +import jcifs.ntlmssp.Type2Message; +import jcifs.ntlmssp.Type3Message; + +import jcifs.util.Base64; + +/** + * Wraps an HttpURLConnection to provide NTLM authentication + * services. + * + * Please read Using jCIFS NTLM Authentication for HTTP Connections. + */ +public class NtlmHttpURLConnection extends HttpURLConnection { + + private static final int MAX_REDIRECTS = + Integer.parseInt(System.getProperty("http.maxRedirects", "20")); + + private static final int LM_COMPATIBILITY = + Config.getInt("jcifs.smb.lmCompatibility", 0); + + private static final String DEFAULT_DOMAIN; + + private HttpURLConnection connection; + + private Map requestProperties; + + private Map headerFields; + + private String authProperty; + + private String method; + + static { + String domain = System.getProperty("http.auth.ntlm.domain"); + if (domain == null) domain = Type3Message.getDefaultDomain(); + DEFAULT_DOMAIN = domain; + } + + public NtlmHttpURLConnection(HttpURLConnection connection) { + super(connection.getURL()); + this.connection = connection; + requestProperties = new HashMap(); + } + + public void connect() throws IOException { + if (connected) return; + doConnect(); + connected = true; + } + + public URL getURL() { + return connection.getURL(); + } + + public int getContentLength() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentLength(); + } + + public String getContentType() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentType(); + } + + public String getContentEncoding() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentEncoding(); + } + + public long getExpiration() { + try { + connect(); + } catch (IOException ex) { } + return connection.getExpiration(); + } + + public long getDate() { + try { + connect(); + } catch (IOException ex) { } + return connection.getDate(); + } + + public long getLastModified() { + try { + connect(); + } catch (IOException ex) { } + return connection.getLastModified(); + } + + public String getHeaderField(String header) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderField(header); + } + + private synchronized Map getHeaderFields0() { + if (headerFields != null) return headerFields; + Map map = new HashMap(); + String key = connection.getHeaderFieldKey(0); + String value = connection.getHeaderField(0); + for (int i = 1; key != null || value != null; i++) { + List values = (List) map.get(key); + if (values == null) { + values = new ArrayList(); + map.put(key, values); + } + values.add(value); + key = connection.getHeaderFieldKey(i); + value = connection.getHeaderField(i); + } + Iterator entries = map.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + entry.setValue(Collections.unmodifiableList((List) + entry.getValue())); + } + return (headerFields = Collections.unmodifiableMap(map)); + } + + public Map getHeaderFields() { + synchronized (this) { + if (headerFields != null) return headerFields; + } + try { + connect(); + } catch (IOException ex) { } + return getHeaderFields0(); + } + + public int getHeaderFieldInt(String header, int def) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldInt(header, def); + } + + public long getHeaderFieldDate(String header, long def) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldDate(header, def); + } + + public String getHeaderFieldKey(int index) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldKey(index); + } + + public String getHeaderField(int index) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderField(index); + } + + public Object getContent() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getContent(); + } + + public Object getContent(Class[] classes) throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getContent(classes); + } + + public Permission getPermission() throws IOException { + return connection.getPermission(); + } + + public InputStream getInputStream() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getOutputStream(); + } + + public String toString() { + return connection.toString(); + } + + public void setDoInput(boolean doInput) { + connection.setDoInput(doInput); + this.doInput = doInput; + } + + public boolean getDoInput() { + return connection.getDoInput(); + } + + public void setDoOutput(boolean doOutput) { + connection.setDoOutput(doOutput); + this.doOutput = doOutput; + } + + public boolean getDoOutput() { + return connection.getDoOutput(); + } + + public void setAllowUserInteraction(boolean allowUserInteraction) { + connection.setAllowUserInteraction(allowUserInteraction); + this.allowUserInteraction = allowUserInteraction; + } + + public boolean getAllowUserInteraction() { + return connection.getAllowUserInteraction(); + } + + public void setUseCaches(boolean useCaches) { + connection.setUseCaches(useCaches); + this.useCaches = useCaches; + } + + public boolean getUseCaches() { + return connection.getUseCaches(); + } + + public void setIfModifiedSince(long ifModifiedSince) { + connection.setIfModifiedSince(ifModifiedSince); + this.ifModifiedSince = ifModifiedSince; + } + + public long getIfModifiedSince() { + return connection.getIfModifiedSince(); + } + + public boolean getDefaultUseCaches() { + return connection.getDefaultUseCaches(); + } + + public void setDefaultUseCaches(boolean defaultUseCaches) { + connection.setDefaultUseCaches(defaultUseCaches); + } + + public void setRequestProperty(String key, String value) { + if (key == null) throw new NullPointerException(); + List values = new ArrayList(); + values.add(value); + boolean found = false; + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + if (key.equalsIgnoreCase((String) entry.getKey())) { + entry.setValue(value); + found = true; + break; + } + } + if (!found) requestProperties.put(key, values); + } + connection.setRequestProperty(key, value); + } + + public void addRequestProperty(String key, String value) { + if (key == null) throw new NullPointerException(); + List values = null; + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + if (key.equalsIgnoreCase((String) entry.getKey())) { + values = (List) entry.getValue(); + values.add(value); + break; + } + } + if (values == null) { + values = new ArrayList(); + values.add(value); + requestProperties.put(key, values); + } + } + // 1.3-compatible. + StringBuffer buffer = new StringBuffer(); + Iterator propertyValues = values.iterator(); + while (propertyValues.hasNext()) { + buffer.append(propertyValues.next()); + if (propertyValues.hasNext()) { + buffer.append(", "); + } + } + connection.setRequestProperty(key, buffer.toString()); + } + + public String getRequestProperty(String key) { + return connection.getRequestProperty(key); + } + + public Map getRequestProperties() { + Map map = new HashMap(); + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + map.put(entry.getKey(), + Collections.unmodifiableList((List) entry.getValue())); + } + } + return Collections.unmodifiableMap(map); + } + + public void setInstanceFollowRedirects(boolean instanceFollowRedirects) { + connection.setInstanceFollowRedirects(instanceFollowRedirects); + } + + public boolean getInstanceFollowRedirects() { + return connection.getInstanceFollowRedirects(); + } + + public void setRequestMethod(String requestMethod) + throws ProtocolException { + connection.setRequestMethod(requestMethod); + } + + public String getRequestMethod() { + return connection.getRequestMethod(); + } + + public int getResponseCode() throws IOException { + return connection.getResponseCode(); + } + + public String getResponseMessage() throws IOException { + return connection.getResponseMessage(); + } + + public void disconnect() { + connection.disconnect(); + connected = false; + } + + public boolean usingProxy() { + return connection.usingProxy(); + } + + public InputStream getErrorStream() { + return connection.getErrorStream(); + } + + private int parseResponseCode() throws IOException { + try { + String response = connection.getHeaderField(0); + int index = response.indexOf(' '); + while (response.charAt(index) == ' ') index++; + return Integer.parseInt(response.substring(index, index + 3)); + } catch (Exception ex) { + throw new IOException(ex.getMessage()); + } + } + + private synchronized void doConnect() throws IOException { + connection.connect(); + int response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + Type1Message type1 = (Type1Message) attemptNegotiation(response); + if (type1 == null) return; // no NTLM + int attempt = 0; + while (attempt < MAX_REDIRECTS) { + connection.setRequestProperty(authProperty, method + ' ' + + Base64.encode(type1.toByteArray())); + connection.connect(); // send type 1 + response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + Type3Message type3 = (Type3Message) attemptNegotiation(response); + if (type3 == null) return; + connection.setRequestProperty(authProperty, method + ' ' + + Base64.encode(type3.toByteArray())); + connection.connect(); // send type 3 + response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + attempt++; + if (attempt < MAX_REDIRECTS) reconnect(); + } + throw new IOException("Unable to negotiate NTLM authentication."); + } + + private NtlmMessage attemptNegotiation(int response) throws IOException { + authProperty = null; + method = null; + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null && errorStream.available() != 0) { + int count; + byte[] buf = new byte[1024]; + while ((count = errorStream.read(buf, 0, 1024)) != -1); + } + String authHeader; + if (response == HTTP_UNAUTHORIZED) { + authHeader = "WWW-Authenticate"; + authProperty = "Authorization"; + } else { + authHeader = "Proxy-Authenticate"; + authProperty = "Proxy-Authorization"; + } + String authorization = null; + List methods = (List) getHeaderFields0().get(authHeader); + if (methods == null) return null; + Iterator iterator = methods.iterator(); + while (iterator.hasNext()) { + String authMethod = (String) iterator.next(); + if (authMethod.startsWith("NTLM")) { + if (authMethod.length() == 4) { + method = "NTLM"; + break; + } + if (authMethod.indexOf(' ') != 4) continue; + method = "NTLM"; + authorization = authMethod.substring(5).trim(); + break; + } else if (authMethod.startsWith("Negotiate")) { + if (authMethod.length() == 9) { + method = "Negotiate"; + break; + } + if (authMethod.indexOf(' ') != 9) continue; + method = "Negotiate"; + authorization = authMethod.substring(10).trim(); + break; + } + } + if (method == null) return null; + NtlmMessage message = (authorization != null) ? + new Type2Message(Base64.decode(authorization)) : null; + reconnect(); + if (message == null) { + message = new Type1Message(); + if (LM_COMPATIBILITY > 2) { + message.setFlag(NtlmFlags.NTLMSSP_REQUEST_TARGET, true); + } + } else { + String domain = DEFAULT_DOMAIN; + String user = Type3Message.getDefaultUser(); + String password = Type3Message.getDefaultPassword(); + String userInfo = url.getUserInfo(); + if (userInfo != null) { + userInfo = URLDecoder.decode(userInfo); + int index = userInfo.indexOf(':'); + user = (index != -1) ? userInfo.substring(0, index) : userInfo; + if (index != -1) password = userInfo.substring(index + 1); + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + domain = (index != -1) ? user.substring(0, index) : domain; + user = (index != -1) ? user.substring(index + 1) : user; + } + if (user == null) { + try { + URL url = getURL(); + String protocol = url.getProtocol(); + int port = url.getPort(); + if (port == -1) { + port = "https".equalsIgnoreCase(protocol) ? 443 : 80; + } + PasswordAuthentication auth = + Authenticator.requestPasswordAuthentication(null, + port, protocol, "", method); + if (auth != null) { + user = auth.getUserName(); + password = new String(auth.getPassword()); + } + } catch (Exception ex) { } + } + Type2Message type2 = (Type2Message) message; + message = new Type3Message(type2, password, domain, user, + Type3Message.getDefaultWorkstation()); + } + return message; + } + + private void reconnect() throws IOException { + connection = (HttpURLConnection) connection.getURL().openConnection(); + headerFields = null; + synchronized (requestProperties) { + Iterator properties = requestProperties.entrySet().iterator(); + while (properties.hasNext()) { + Map.Entry property = (Map.Entry) properties.next(); + String key = (String) property.getKey(); + StringBuffer value = new StringBuffer(); + Iterator values = ((List) property.getValue()).iterator(); + while (values.hasNext()) { + value.append(values.next()); + if (values.hasNext()) value.append(", "); + } + connection.setRequestProperty(key, value.toString()); + } + } + connection.setAllowUserInteraction(allowUserInteraction); + connection.setDoInput(doInput); + connection.setDoOutput(doOutput); + connection.setIfModifiedSince(ifModifiedSince); + connection.setUseCaches(useCaches); + } +} diff --git a/src/jcifs/http/NtlmServlet.java b/src/jcifs/http/NtlmServlet.java index 14e47db..96fe897 100644 --- a/src/jcifs/http/NtlmServlet.java +++ b/src/jcifs/http/NtlmServlet.java @@ -1,17 +1,17 @@ /* jcifs smb client library in Java * Copyright (C) 2002 "Michael B. Allen" - * "Eric Glass" - * + * "Eric Glass" + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -38,8 +38,11 @@ import jcifs.Config; import jcifs.UniAddress; import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.smb.SmbAuthException; import jcifs.smb.SmbSession; +import jcifs.util.Base64; + /** * This servlet may be used with pre-2.3 servlet containers * to protect content with NTLM HTTP Authentication. Servlets that @@ -54,9 +57,15 @@ import jcifs.smb.SmbSession; public abstract class NtlmServlet extends HttpServlet { - private static final NtlmSsp AUTH = new NtlmSsp(); + private String defaultDomain; + + private String domainController; + + private boolean enableBasic; - private UniAddress domainController; + private boolean insecureBasic; + + private String realm; public void init(ServletConfig config) throws ServletException { super.init(config); @@ -74,37 +83,74 @@ public abstract class NtlmServlet extends HttpServlet { Config.setProperty(name, config.getInitParameter(name)); } } - String dc = Config.getProperty("jcifs.http.domainController"); - if (dc == null && (dc = Config.getProperty( "jcifs.smb.client.domain" )) == null) { - throw new UnavailableException("No domain controller specified."); - } - try { - domainController = UniAddress.getByName(dc); - } catch (UnknownHostException ex) { - throw new UnavailableException("Specified DC unreachable."); - } + defaultDomain = Config.getProperty("jcifs.smb.client.domain"); + domainController = Config.getProperty("jcifs.http.domainController"); + if (domainController == null) domainController = defaultDomain; + enableBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.enableBasic")).booleanValue(); + insecureBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.insecureBasic")).booleanValue(); + realm = Config.getProperty("jcifs.http.basicRealm"); + if (realm == null) realm = "jCIFS"; } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + boolean offerBasic = enableBasic && + (insecureBasic || request.isSecure()); String msg = request.getHeader("Authorization"); - if (msg != null && msg.startsWith("NTLM")) { - HttpSession ssn; - byte[] challenge = SmbSession.getChallenge(domainController); - NtlmPasswordAuthentication ntlm = AUTH.doAuthentication(request, - response, challenge); - if (ntlm == null) return; - SmbSession.logon(domainController, ntlm); - ssn = request.getSession(); + if (msg != null && (msg.startsWith("NTLM ") || + (offerBasic && msg.startsWith("Basic ")))) { + UniAddress dc = UniAddress.getByName(domainController, true); + NtlmPasswordAuthentication ntlm; + if (msg.startsWith("NTLM ")) { + byte[] challenge = SmbSession.getChallenge(dc); + ntlm = NtlmSsp.authenticate(request, response, challenge); + if (ntlm == null) return; + } else { + String auth = new String(Base64.decode(msg.substring(6)), + "US-ASCII"); + int index = auth.indexOf(':'); + String user = (index != -1) ? auth.substring(0, index) : auth; + String password = (index != -1) ? auth.substring(index + 1) : + ""; + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + String domain = (index != -1) ? user.substring(0, index) : + defaultDomain; + user = (index != -1) ? user.substring(index + 1) : user; + ntlm = new NtlmPasswordAuthentication(domain, user, password); + } + try { + SmbSession.logon(dc, ntlm); + } catch (SmbAuthException sae) { + response.setHeader("WWW-Authenticate", "NTLM"); + if (offerBasic) { + response.addHeader("WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + response.setHeader("Connection", "close"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + return; + } + HttpSession ssn = request.getSession(); ssn.setAttribute("NtlmHttpAuth", ntlm); ssn.setAttribute( "ntlmdomain", ntlm.getDomain() ); ssn.setAttribute( "ntlmuser", ntlm.getUsername() ); - } else if (request.getSession().getAttribute("NtlmHttpAuth") == null) { - response.setHeader("WWW-Authenticate", "NTLM"); - response.setHeader("Connection", "close"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.flushBuffer(); - return; + } else { + HttpSession ssn = request.getSession(false); + if (ssn == null || ssn.getAttribute("NtlmHttpAuth") == null) { + response.setHeader("WWW-Authenticate", "NTLM"); + if (offerBasic) { + response.addHeader("WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + response.setHeader("Connection", "close"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + return; + } } super.service(request, response); } diff --git a/src/jcifs/http/NtlmSsp.java b/src/jcifs/http/NtlmSsp.java index d6a02d1..5cab7f2 100644 --- a/src/jcifs/http/NtlmSsp.java +++ b/src/jcifs/http/NtlmSsp.java @@ -1,18 +1,19 @@ /* jcifs smb client library in Java * Copyright (C) 2002 "Michael B. Allen" - * "Jason Pugsley" - * "skeetz" - * + * "Eric Glass" + * "Jason Pugsley" + * "skeetz" + * * 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 @@ -20,12 +21,22 @@ package jcifs.http; -import java.io.*; -import javax.servlet.*; -import javax.servlet.http.*; +import java.io.IOException; + +import javax.servlet.ServletException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import jcifs.smb.NtlmPasswordAuthentication; + import jcifs.util.Base64; +import jcifs.ntlmssp.NtlmFlags; +import jcifs.ntlmssp.Type1Message; +import jcifs.ntlmssp.Type2Message; +import jcifs.ntlmssp.Type3Message; + /** * This class is used internally by NtlmHttpFilter, * NtlmServlet, and NetworkExplorer to negiotiate password @@ -39,93 +50,63 @@ import jcifs.util.Base64; * the Network Explorer Servlet related information. */ -public class NtlmSsp { - - static final byte[] msg2 = { - (byte)0x4E, (byte)0x54, (byte)0x4C, (byte)0x4D, - (byte)0x53, (byte)0x53, (byte)0x50, (byte)0x00, - (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x28, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x01, (byte)0x82, (byte)0x00, (byte)0x00, - (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, - (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, - (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 - }; - - int readInt2( byte[] src, int srcIndex ) { - return ( src[srcIndex] & 0xFF ) + - (( src[srcIndex + 1] & 0xFF ) << 8 ); - } - - /* Decode the type-1-message and encode the type-2-message. Note the "domain" - * in the type-1-message isn't necessarily - * the authentication domain. There is a good possibilty it is only the - * workgroup used for browsing. The 3rd message with have the authenticating - * domain. - */ - int encodeType2Message( byte[] src, byte[] dst, byte[] challenge ) throws IOException { - -jcifs.util.Log.printHexDump( "type-1-message", src ); - - System.arraycopy( msg2, 0, dst, 0, 40 ); - System.arraycopy( challenge, 0, dst, 24, 8 ); - -jcifs.util.Log.printHexDump( "type-2-message", dst, 0, 40 ); - return 40; - } - /* Decode type-3-message. +public class NtlmSsp implements NtlmFlags { + + /** + * Calls the static {@link #authenticate(HttpServletRequest, + * HttpServletResponse, byte[])} method to perform NTLM authentication + * for the specified servlet request. + * + * @param req The request being serviced. + * @param resp The response. + * @param challenge The domain controller challenge. + * @throws IOException If an IO error occurs. + * @throws ServletException If an error occurs. */ - NtlmPasswordAuthentication decodeType3Message( byte[] src ) throws IOException { - int off, len; - byte[] lm_hash = new byte[24]; - byte[] nt_hash = new byte[24]; - String domain, user; - -jcifs.util.Log.printHexDump( "type-3-message", src ); - - off = readInt2( src, 16 ); - System.arraycopy( src, off, lm_hash, 0, 24 ); - off = readInt2( src, 24 ); - System.arraycopy( src, off, nt_hash, 0, 24 ); - len = readInt2( src, 28 ); - off = readInt2( src, 32 ); - domain = new String( src, off, len, "UnicodeLittleUnmarked" ); - len = readInt2( src, 36 ); - off = readInt2( src, 40 ); - user = new String( src, off, len, "UnicodeLittleUnmarked" ); - - return new NtlmPasswordAuthentication( domain, user, lm_hash, nt_hash ); + public NtlmPasswordAuthentication doAuthentication( + HttpServletRequest req, HttpServletResponse resp, byte[] challenge) + throws IOException, ServletException { + return authenticate(req, resp, challenge); } - public NtlmPasswordAuthentication doAuthentication( HttpServletRequest req, - HttpServletResponse resp, byte[] challenge ) - throws IOException, ServletException { - String msg; - - msg = req.getHeader( "Authorization" ); - - if( msg != null && msg.startsWith( "NTLM " )) { - byte[] src = Base64.decode( msg.substring( 5 )); - - if( src[8] == 1 ) { - byte[] dst = new byte[40]; - - msg = Base64.encodeBytes( dst, 0, encodeType2Message( src, dst, challenge )); + /** + * Performs NTLM authentication for the servlet request. + * + * @param req The request being serviced. + * @param resp The response. + * @param challenge The domain controller challenge. + * @throws IOException If an IO error occurs. + * @throws ServletException If an error occurs. + */ + public static NtlmPasswordAuthentication authenticate( + HttpServletRequest req, HttpServletResponse resp, byte[] challenge) + throws IOException, ServletException { + String msg = req.getHeader("Authorization"); + if (msg != null && msg.startsWith("NTLM ")) { + byte[] src = Base64.decode(msg.substring(5)); + if (src[8] == 1) { + Type1Message type1 = new Type1Message(src); + Type2Message type2 = new Type2Message(type1, challenge, null); + msg = Base64.encode(type2.toByteArray()); resp.setHeader( "WWW-Authenticate", "NTLM " + msg ); - resp.setContentLength( 0 ); - } else if( src[8] == 3 ) { - return decodeType3Message( src ); + } else if (src[8] == 3) { + Type3Message type3 = new Type3Message(src); + byte[] lmResponse = type3.getLMResponse(); + if (lmResponse == null) lmResponse = new byte[0]; + byte[] ntResponse = type3.getNTResponse(); + if (ntResponse == null) ntResponse = new byte[0]; + return new NtlmPasswordAuthentication(type3.getDomain(), + type3.getUser(), lmResponse, ntResponse); } } else { - resp.setHeader( "WWW-Authenticate", "NTLM" ); - resp.setHeader( "Connection", "close" ); + resp.setHeader("WWW-Authenticate", "NTLM"); + resp.setHeader("Connection", "close"); } - resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + resp.setContentLength( 0 ); resp.flushBuffer(); - return null; } + } diff --git a/src/jcifs/https/Handler.java b/src/jcifs/https/Handler.java new file mode 100644 index 0000000..041b008 --- /dev/null +++ b/src/jcifs/https/Handler.java @@ -0,0 +1,44 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.https; + +/** + * A URLStreamHandler used to provide NTLM authentication + * capabilities to the default HTTPS handler. This acts as a wrapper, + * handling authentication and passing control to the underlying + * stream handler. + */ +public class Handler extends jcifs.http.Handler { + + /** + * The default HTTPS port (443). + */ + public static final int DEFAULT_HTTPS_PORT = 443; + + /** + * Returns the default HTTPS port. + * + * @return An int containing the default HTTPS port. + */ + protected int getDefaultPort() { + return DEFAULT_HTTPS_PORT; + } + +} diff --git a/src/jcifs/netbios/NbtAddress.java b/src/jcifs/netbios/NbtAddress.java index b93409e..fdbc005 100644 --- a/src/jcifs/netbios/NbtAddress.java +++ b/src/jcifs/netbios/NbtAddress.java @@ -584,6 +584,8 @@ public final class NbtAddress { i++; } } + } else if( hostName.hexCode == 0x1D ) { + calledName = SMBSERVER_NAME; } return calledName; @@ -597,7 +599,14 @@ public final class NbtAddress { try { addrs = client.getNodeStatus( this ); - if( isDataFromNodeStatus ) { + if( hostName.hexCode == 0x1D ) { + for( int i = 0; i < addrs.length; i++ ) { + if( addrs[i].hostName.hexCode == 0x20 ) { + return addrs[i].hostName.name; + } + } + return null; + } else if( isDataFromNodeStatus ) { /* 'this' has been updated and should now * have a real NetBIOS name */ diff --git a/src/jcifs/netbios/NbtSocket.java b/src/jcifs/netbios/NbtSocket.java index 2a2b5bd..f9c4714 100644 --- a/src/jcifs/netbios/NbtSocket.java +++ b/src/jcifs/netbios/NbtSocket.java @@ -23,6 +23,7 @@ import java.net.InetAddress; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; +import jcifs.Config; /** * This class represents a netbios TCP/IP socket. Under no @@ -35,9 +36,11 @@ public class NbtSocket extends Socket { private static final int SSN_SRVC_PORT = 139; private static final int BUFFER_SIZE = 512; + private static final int DEFAULT_SO_TIMEOUT = 5000; NbtAddress address; Name calledName; + int soTimeout; public NbtSocket() { super(); @@ -59,6 +62,7 @@ public class NbtSocket extends Socket { } else { this.calledName = new Name( calledName, 0x20, null ); } + soTimeout = Config.getInt( "jcifs.netbios.soTimeout", DEFAULT_SO_TIMEOUT ); connect(); } @@ -100,6 +104,7 @@ public class NbtSocket extends Socket { SessionServicePacket ssp0 = new SessionRequestPacket( calledName, NbtAddress.localhost.hostName ); out.write( buffer, 0, ssp0.writeWireFormat( buffer, 0 )); + setSoTimeout( soTimeout ); switch( ssp0.readPacketType( in, buffer, 0 )) { case SessionServicePacket.POSITIVE_SESSION_RESPONSE: Log.println( Log.WARNINGS, "session service warning", " session established ok with " + address ); diff --git a/src/jcifs/ntlmssp/NtlmFlags.java b/src/jcifs/ntlmssp/NtlmFlags.java new file mode 100644 index 0000000..9cd8d50 --- /dev/null +++ b/src/jcifs/ntlmssp/NtlmFlags.java @@ -0,0 +1,155 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +/** + * Flags used during negotiation of NTLMSSP authentication. + */ +public interface NtlmFlags { + + /** + * Indicates whether Unicode strings are supported or used. + */ + public static final int NTLMSSP_NEGOTIATE_UNICODE = 0x00000001; + + /** + * Indicates whether OEM strings are supported or used. + */ + public static final int NTLMSSP_NEGOTIATE_OEM = 0x00000002; + + /** + * Indicates whether the authentication target is requested from + * the server. + */ + public static final int NTLMSSP_REQUEST_TARGET = 0x00000004; + + /** + * Specifies that communication across the authenticated channel + * should carry a digital signature (message integrity). + */ + public static final int NTLMSSP_NEGOTIATE_SIGN = 0x00000010; + + /** + * Specifies that communication across the authenticated channel + * should be encrypted (message confidentiality). + */ + public static final int NTLMSSP_NEGOTIATE_SEAL = 0x00000020; + + /** + * Indicates datagram authentication. + */ + public static final int NTLMSSP_NEGOTIATE_DATAGRAM_STYLE = 0x00000040; + + /** + * Indicates that the LAN Manager session key should be used for + * signing and sealing authenticated communication. + */ + public static final int NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080; + + public static final int NTLMSSP_NEGOTIATE_NETWARE = 0x00000100; + + /** + * Indicates support for NTLM authentication. + */ + public static final int NTLMSSP_NEGOTIATE_NTLM = 0x00000200; + + /** + * Indicates whether the OEM-formatted domain name in which the + * client workstation has membership is supplied in the Type-1 message. + * This is used in the negotation of local authentication. + */ + public static final int NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = + 0x00001000; + + /** + * Indicates whether the OEM-formatted workstation name is supplied + * in the Type-1 message. This is used in the negotiation of local + * authentication. + */ + public static final int NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = + 0x00002000; + + /** + * Sent by the server to indicate that the server and client are + * on the same machine. This implies that the server will include + * a local security context handle in the Type 2 message, for + * use in local authentication. + */ + public static final int NTLMSSP_NEGOTIATE_LOCAL_CALL = 0x00004000; + + /** + * Indicates that authenticated communication between the client + * and server should carry a "dummy" digital signature. + */ + public static final int NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000; + + /** + * Sent by the server in the Type 2 message to indicate that the + * target authentication realm is a domain. + */ + public static final int NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000; + + /** + * Sent by the server in the Type 2 message to indicate that the + * target authentication realm is a server. + */ + public static final int NTLMSSP_TARGET_TYPE_SERVER = 0x00020000; + + /** + * Sent by the server in the Type 2 message to indicate that the + * target authentication realm is a share (presumably for share-level + * authentication). + */ + public static final int NTLMSSP_TARGET_TYPE_SHARE = 0x00040000; + + /** + * Indicates that the NTLM2 signing and sealing scheme should be used + * for protecting authenticated communications. This refers to a + * particular session security scheme, and is not related to the use + * of NTLMv2 authentication. + */ + public static final int NTLMSSP_NEGOTIATE_NTLM2 = 0x00080000; + + public static final int NTLMSSP_REQUEST_INIT_RESPONSE = 0x00100000; + + public static final int NTLMSSP_REQUEST_ACCEPT_RESPONSE = 0x00200000; + + public static final int NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000; + + /** + * Sent by the server in the Type 2 message to indicate that it is + * including a Target Information block in the message. The Target + * Information block is used in the calculation of the NTLMv2 response. + */ + public static final int NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000; + + /** + * Indicates that 128-bit encryption is supported. + */ + public static final int NTLMSSP_NEGOTIATE_128 = 0x20000000; + + public static final int NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000; + + /** + * Indicates that 56-bit encryption is supported. + */ + public static final int NTLMSSP_NEGOTIATE_56 = 0x80000000; + +} diff --git a/src/jcifs/ntlmssp/NtlmMessage.java b/src/jcifs/ntlmssp/NtlmMessage.java new file mode 100644 index 0000000..5689a71 --- /dev/null +++ b/src/jcifs/ntlmssp/NtlmMessage.java @@ -0,0 +1,138 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import jcifs.Config; + +/** + * Abstract superclass for all NTLMSSP messages. + */ +public abstract class NtlmMessage implements NtlmFlags { + + /** + * The NTLMSSP "preamble". + */ + protected static final byte[] NTLMSSP_SIGNATURE = new byte[] { + (byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', + (byte) 'S', (byte) 'S', (byte) 'P', (byte) 0 + }; + + private static final String OEM_ENCODING = + Config.getProperty("jcifs.smb.client.codepage", + Config.getProperty("jcifs.encoding", + System.getProperty("file.encoding"))); + + private int flags; + + /** + * Returns the flags currently in use for this message. + * + * @return An int containing the flags in use for this + * message. + */ + public int getFlags() { + return flags; + } + + /** + * Sets the flags for this message. + * + * @param flags The flags for this message. + */ + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * Returns the status of the specified flag. + * + * @param flag The flag to test (i.e., NTLMSSP_NEGOTIATE_OEM). + * @return A boolean indicating whether the flag is set. + */ + public boolean getFlag(int flag) { + return (getFlags() & flag) != 0; + } + + /** + * Sets or clears the specified flag. + * + * @param flag The flag to set/clear (i.e., + * NTLMSSP_NEGOTIATE_OEM). + * @param value Indicates whether to set (true) or + * clear (false) the specified flag. + */ + public void setFlag(int flag, boolean value) { + setFlags(value ? (getFlags() | flag) : + (getFlags() & (0xffffffff ^ flag))); + } + + static int readULong(byte[] src, int index) { + return (src[index] & 0xff) | + ((src[index + 1] & 0xff) << 8) | + ((src[index + 2] & 0xff) << 16) | + ((src[index + 3] & 0xff) << 24); + } + + static int readUShort(byte[] src, int index) { + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); + } + + static byte[] readSecurityBuffer(byte[] src, int index) { + int length = readUShort(src, index); + int offset = readULong(src, index + 4); + byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + static void writeULong(byte[] dest, int offset, int ulong) { + dest[offset] = (byte) (ulong & 0xff); + dest[offset + 1] = (byte) (ulong >> 8 & 0xff); + dest[offset + 2] = (byte) (ulong >> 16 & 0xff); + dest[offset + 3] = (byte) (ulong >> 24 & 0xff); + } + + static void writeUShort(byte[] dest, int offset, int ushort) { + dest[offset] = (byte) (ushort & 0xff); + dest[offset + 1] = (byte) (ushort >> 8 & 0xff); + } + + static void writeSecurityBuffer(byte[] dest, int offset, int bodyOffset, + byte[] src) { + int length = (src != null) ? src.length : 0; + if (length == 0) return; + writeUShort(dest, offset, length); + writeUShort(dest, offset + 2, length); + writeULong(dest, offset + 4, bodyOffset); + System.arraycopy(src, 0, dest, bodyOffset, length); + } + + static String getOEMEncoding() { + return OEM_ENCODING; + } + + /** + * Returns the raw byte representation of this message. + * + * @return A byte[] containing the raw message material. + */ + public abstract byte[] toByteArray(); + +} diff --git a/src/jcifs/ntlmssp/Type1Message.java b/src/jcifs/ntlmssp/Type1Message.java new file mode 100644 index 0000000..42d3610 --- /dev/null +++ b/src/jcifs/ntlmssp/Type1Message.java @@ -0,0 +1,248 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import jcifs.netbios.NbtAddress; + +import jcifs.Config; + +/** + * Represents an NTLMSSP Type-1 message. + */ +public class Type1Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final String DEFAULT_WORKSTATION; + + private String suppliedDomain; + + private String suppliedWorkstation; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + String defaultWorkstation = null; + try { + defaultWorkstation = NbtAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ex) { } + DEFAULT_WORKSTATION = defaultWorkstation; + } + + /** + * Creates a Type-1 message using default values from the current + * environment. + */ + public Type1Message() { + this(getDefaultFlags(), getDefaultDomain(), getDefaultWorkstation()); + } + + /** + * Creates a Type-1 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param suppliedDomain The supplied authentication domain. + * @param suppliedWorkstation The supplied workstation name. + */ + public Type1Message(int flags, String suppliedDomain, + String suppliedWorkstation) { + setFlags(flags); + setSuppliedDomain(suppliedDomain); + setSuppliedWorkstation(suppliedWorkstation); + } + + /** + * Creates a Type-1 message using the given raw Type-1 material. + * + * @param material The raw Type-1 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type1Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the supplied authentication domain. + * + * @return A String containing the supplied domain. + */ + public String getSuppliedDomain() { + return suppliedDomain; + } + + /** + * Sets the supplied authentication domain for this message. + * + * @param suppliedDomain The supplied domain for this message. + */ + public void setSuppliedDomain(String suppliedDomain) { + this.suppliedDomain = suppliedDomain; + } + + /** + * Returns the supplied workstation name. + * + * @return A String containing the supplied workstation name. + */ + public String getSuppliedWorkstation() { + return suppliedWorkstation; + } + + /** + * Sets the supplied workstation name for this message. + * + * @param suppliedWorkstation The supplied workstation for this message. + */ + public void setSuppliedWorkstation(String suppliedWorkstation) { + this.suppliedWorkstation = suppliedWorkstation; + } + + public byte[] toByteArray() { + try { + String suppliedDomain = getSuppliedDomain(); + String suppliedWorkstation = getSuppliedWorkstation(); + int flags = getFlags(); + boolean hostInfo = false; + byte[] domain = new byte[0]; + if (suppliedDomain != null && suppliedDomain.length() != 0) { + hostInfo = true; + flags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED; + domain = suppliedDomain.toUpperCase().getBytes( + getOEMEncoding()); + } else { + flags &= (NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED ^ 0xffffffff); + } + byte[] workstation = new byte[0]; + if (suppliedWorkstation != null && + suppliedWorkstation.length() != 0) { + hostInfo = true; + flags |= NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED; + workstation = + suppliedWorkstation.toUpperCase().getBytes( + getOEMEncoding()); + } else { + flags &= (NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED ^ + 0xffffffff); + } + byte[] type1 = new byte[hostInfo ? + (32 + domain.length + workstation.length) : 16]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type1, 0, 8); + writeULong(type1, 8, 1); + writeULong(type1, 12, flags); + if (hostInfo) { + writeSecurityBuffer(type1, 16, 32, domain); + writeSecurityBuffer(type1, 24, 32 + domain.length, workstation); + } + return type1; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String suppliedDomain = getSuppliedDomain(); + String suppliedWorkstation = getSuppliedWorkstation(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (suppliedDomain != null) { + buffer.append("suppliedDomain: ").append(suppliedDomain); + } + if (suppliedWorkstation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("suppliedWorkstation: ").append(suppliedWorkstation); + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-1 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default domain from the current environment. + * + * @return A String containing the default domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + /** + * Returns the default workstation from the current environment. + * + * @return A String containing the default workstation. + */ + public static String getDefaultWorkstation() { + return DEFAULT_WORKSTATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 1) { + throw new IOException("Not a Type 1 message."); + } + int flags = readULong(material, 12); + String suppliedDomain = null; + if ((flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) != 0) { + byte[] domain = readSecurityBuffer(material, 16); + suppliedDomain = new String(domain, getOEMEncoding()); + } + String suppliedWorkstation = null; + if ((flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) != 0) { + byte[] workstation = readSecurityBuffer(material, 24); + suppliedWorkstation = new String(workstation, getOEMEncoding()); + } + setFlags(flags); + setSuppliedDomain(suppliedDomain); + setSuppliedWorkstation(suppliedWorkstation); + } + +} diff --git a/src/jcifs/ntlmssp/Type2Message.java b/src/jcifs/ntlmssp/Type2Message.java new file mode 100644 index 0000000..bb1de85 --- /dev/null +++ b/src/jcifs/ntlmssp/Type2Message.java @@ -0,0 +1,414 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import jcifs.Config; + +import jcifs.netbios.NbtAddress; + +/** + * Represents an NTLMSSP Type-2 message. + */ +public class Type2Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final byte[] DEFAULT_TARGET_INFORMATION; + + private byte[] challenge; + + private String target; + + private byte[] context; + + private byte[] targetInformation; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + byte[] domain = new byte[0]; + if (DEFAULT_DOMAIN != null) { + try { + domain = DEFAULT_DOMAIN.getBytes("UnicodeLittleUnmarked"); + } catch (IOException ex) { } + } + int domainLength = domain.length; + byte[] server = new byte[0]; + try { + String host = NbtAddress.getLocalHost().getHostName(); + if (host != null) { + try { + server = host.getBytes("UnicodeLittleUnmarked"); + } catch (IOException ex) { } + } + } catch (UnknownHostException ex) { } + int serverLength = server.length; + byte[] targetInfo = new byte[(domainLength > 0 ? domainLength + 4 : 0) + + (serverLength > 0 ? serverLength + 4 : 0) + 4]; + int offset = 0; + if (domainLength > 0) { + writeUShort(targetInfo, offset, 2); + offset += 2; + writeUShort(targetInfo, offset, domainLength); + offset += 2; + System.arraycopy(domain, 0, targetInfo, offset, domainLength); + offset += domainLength; + } + if (serverLength > 0) { + writeUShort(targetInfo, offset, 1); + offset += 2; + writeUShort(targetInfo, offset, serverLength); + offset += 2; + System.arraycopy(server, 0, targetInfo, offset, serverLength); + } + DEFAULT_TARGET_INFORMATION = targetInfo; + } + + /** + * Creates a Type-2 message using default values from the current + * environment. + */ + public Type2Message() { + this(getDefaultFlags(), null, null); + } + + /** + * Creates a Type-2 message in response to the given Type-1 message + * using default values from the current environment. + * + * @param type1 The Type-1 message which this represents a response to. + */ + public Type2Message(Type1Message type1) { + this(type1, null, null); + } + + /** + * Creates a Type-2 message in response to the given Type-1 message. + * + * @param type1 The Type-1 message which this represents a response to. + * @param challenge The challenge from the domain controller/server. + * @param target The authentication target. + */ + public Type2Message(Type1Message type1, byte[] challenge, String target) { + this(getDefaultFlags(type1), challenge, (type1 != null && + target == null && type1.getFlag(NTLMSSP_REQUEST_TARGET)) ? + getDefaultDomain() : target); + } + + /** + * Creates a Type-2 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param challenge The challenge from the domain controller/server. + * @param target The authentication target. + */ + public Type2Message(int flags, byte[] challenge, String target) { + setFlags(flags); + setChallenge(challenge); + setTarget(target); + if (target != null) setTargetInformation(getDefaultTargetInformation()); + } + + /** + * Creates a Type-2 message using the given raw Type-2 material. + * + * @param material The raw Type-2 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type2Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the challenge for this message. + * + * @return A byte[] containing the challenge. + */ + public byte[] getChallenge() { + return challenge; + } + + /** + * Sets the challenge for this message. + * + * @param challenge The challenge from the domain controller/server. + */ + public void setChallenge(byte[] challenge) { + this.challenge = challenge; + } + + /** + * Returns the authentication target. + * + * @return A String containing the authentication target. + */ + public String getTarget() { + return target; + } + + /** + * Sets the authentication target. + * + * @param target The authentication target. + */ + public void setTarget(String target) { + this.target = target; + } + + /** + * Returns the target information block. + * + * @return A byte[] containing the target information block. + * The target information block is used by the client to create an + * NTLMv2 response. + */ + public byte[] getTargetInformation() { + return targetInformation; + } + + /** + * Sets the target information block. + * The target information block is used by the client to create + * an NTLMv2 response. + * + * @param targetInformation The target information block. + */ + public void setTargetInformation(byte[] targetInformation) { + this.targetInformation = targetInformation; + } + + /** + * Returns the local security context. + * + * @return A byte[] containing the local security + * context. This is used by the client to negotiate local + * authentication. + */ + public byte[] getContext() { + return context; + } + + /** + * Sets the local security context. This is used by the client + * to negotiate local authentication. + * + * @param context The local security context. + */ + public void setContext(byte[] context) { + this.context = context; + } + + public byte[] toByteArray() { + try { + String targetName = getTarget(); + byte[] challenge = getChallenge(); + byte[] context = getContext(); + byte[] targetInformation = getTargetInformation(); + int flags = getFlags(); + byte[] target = new byte[0]; + if ((flags & (NTLMSSP_TARGET_TYPE_DOMAIN | + NTLMSSP_TARGET_TYPE_SERVER | + NTLMSSP_TARGET_TYPE_SHARE)) != 0) { + if (targetName != null && targetName.length() != 0) { + target = (flags & NTLMSSP_NEGOTIATE_UNICODE) != 0 ? + targetName.getBytes("UnicodeLittleUnmarked") : + targetName.toUpperCase().getBytes(getOEMEncoding()); + } else { + flags &= (0xffffffff ^ (NTLMSSP_TARGET_TYPE_DOMAIN | + NTLMSSP_TARGET_TYPE_SERVER | + NTLMSSP_TARGET_TYPE_SHARE)); + } + } + if (targetInformation != null) { + flags ^= NTLMSSP_NEGOTIATE_TARGET_INFO; + // empty context is needed for padding when t.i. is supplied. + if (context == null) context = new byte[8]; + } + int data = 32; + if (context != null) data += 8; + if (targetInformation != null) data += 8; + byte[] type2 = new byte[data + target.length + + (targetInformation != null ? targetInformation.length : 0)]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type2, 0, 8); + writeULong(type2, 8, 2); + writeSecurityBuffer(type2, 12, data, target); + writeULong(type2, 20, flags); + System.arraycopy(challenge != null ? challenge : new byte[8], 0, + type2, 24, 8); + if (context != null) System.arraycopy(context, 0, type2, 32, 8); + if (targetInformation != null) { + writeSecurityBuffer(type2, 40, data + target.length, + targetInformation); + } + return type2; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String target = getTarget(); + byte[] challenge = getChallenge(); + byte[] context = getContext(); + byte[] targetInformation = getTargetInformation(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (target != null) { + buffer.append("target: ").append(target); + } + if (challenge != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("challenge: "); + buffer.append("0x"); + for (int i = 0; i < challenge.length; i++) { + buffer.append(Integer.toHexString((challenge[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(challenge[i] & 0x0f)); + } + } + if (context != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("context: "); + buffer.append("0x"); + for (int i = 0; i < context.length; i++) { + buffer.append(Integer.toHexString((context[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(context[i] & 0x0f)); + } + } + if (targetInformation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("targetInformation: "); + buffer.append("0x"); + for (int i = 0; i < targetInformation.length; i++) { + buffer.append(Integer.toHexString((targetInformation[i] >> 4) & + 0x0f)); + buffer.append(Integer.toHexString(targetInformation[i] & 0x0f)); + } + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-2 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default flags for a Type-2 message created in response + * to the given Type-1 message in the current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags(Type1Message type1) { + if (type1 == null) return DEFAULT_FLAGS; + int flags = NTLMSSP_NEGOTIATE_NTLM; + int type1Flags = type1.getFlags(); + flags |= ((type1Flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM; + if ((type1Flags & NTLMSSP_REQUEST_TARGET) != 0) { + String domain = getDefaultDomain(); + if (domain != null) { + flags |= NTLMSSP_REQUEST_TARGET | NTLMSSP_TARGET_TYPE_DOMAIN; + } + } + return flags; + } + + /** + * Returns the default domain from the current environment. + * + * @return A String containing the domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + public static byte[] getDefaultTargetInformation() { + return DEFAULT_TARGET_INFORMATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 2) { + throw new IOException("Not a Type 2 message."); + } + int flags = readULong(material, 20); + setFlags(flags); + String target = null; + byte[] bytes = readSecurityBuffer(material, 12); + if (bytes.length != 0) { + target = new String(bytes, + ((flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + "UnicodeLittleUnmarked" : getOEMEncoding()); + } + setTarget(target); + for (int i = 24; i < 32; i++) { + if (material[i] != 0) { + byte[] challenge = new byte[8]; + System.arraycopy(material, 24, challenge, 0, 8); + setChallenge(challenge); + break; + } + } + int offset = readULong(material, 16); // offset of targetname start + if (offset == 32 || material.length == 32) return; + for (int i = 32; i < 40; i++) { + if (material[i] != 0) { + byte[] context = new byte[8]; + System.arraycopy(material, 32, context, 0, 8); + setContext(context); + break; + } + } + if (offset == 40 || material.length == 40) return; + bytes = readSecurityBuffer(material, 40); + if (bytes.length != 0) setTargetInformation(bytes); + } + +} diff --git a/src/jcifs/ntlmssp/Type3Message.java b/src/jcifs/ntlmssp/Type3Message.java new file mode 100644 index 0000000..c11f1fc --- /dev/null +++ b/src/jcifs/ntlmssp/Type3Message.java @@ -0,0 +1,580 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import java.security.SecureRandom; + +import jcifs.Config; + +import jcifs.netbios.NbtAddress; + +import jcifs.smb.NtlmPasswordAuthentication; + +/** + * Represents an NTLMSSP Type-3 message. + */ +public class Type3Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final String DEFAULT_USER; + + private static final String DEFAULT_PASSWORD; + + private static final String DEFAULT_WORKSTATION; + + private static final int LM_COMPATIBILITY; + + private static final SecureRandom RANDOM = new SecureRandom(); + + private byte[] lmResponse; + + private byte[] ntResponse; + + private String domain; + + private String user; + + private String workstation; + + private byte[] sessionKey; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + DEFAULT_USER = Config.getProperty("jcifs.smb.client.username", null); + DEFAULT_PASSWORD = Config.getProperty("jcifs.smb.client.password", + null); + String defaultWorkstation = null; + try { + defaultWorkstation = NbtAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ex) { } + DEFAULT_WORKSTATION = defaultWorkstation; + LM_COMPATIBILITY = Config.getInt("jcifs.smb.lmCompatibility", 0); + } + + /** + * Creates a Type-3 message using default values from the current + * environment. + */ + public Type3Message() { + setFlags(getDefaultFlags()); + setDomain(getDefaultDomain()); + setUser(getDefaultUser()); + setWorkstation(getDefaultWorkstation()); + } + + /** + * Creates a Type-3 message in response to the given Type-2 message + * using default values from the current environment. + * + * @param type2 The Type-2 message which this represents a response to. + */ + public Type3Message(Type2Message type2) { + setFlags(getDefaultFlags(type2)); + setWorkstation(getDefaultWorkstation()); + String domain = getDefaultDomain(); + setDomain(domain); + String user = getDefaultUser(); + setUser(user); + String password = getDefaultPassword(); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + break; + case 2: + byte[] nt = getNTResponse(type2, password); + setLMResponse(nt); + setNTResponse(nt); + break; + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + RANDOM.nextBytes(clientChallenge); + setLMResponse(getLMv2Response(type2, domain, user, password, + clientChallenge)); + /* + setNTResponse(getNTLMv2Response(type2, domain, user, password, + clientChallenge)); + */ + break; + default: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + } + } + + /** + * Creates a Type-3 message in response to the given Type-2 message. + * + * @param type2 The Type-2 message which this represents a response to. + * @param password The password to use when constructing the response. + * @param domain The domain in which the user has an account. + * @param user The username for the authenticating user. + * @param workstation The workstation from which authentication is + * taking place. + */ + public Type3Message(Type2Message type2, String password, String domain, + String user, String workstation) { + setFlags(getDefaultFlags(type2)); + setDomain(domain); + setUser(user); + setWorkstation(workstation); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + break; + case 2: + byte[] nt = getNTResponse(type2, password); + setLMResponse(nt); + setNTResponse(nt); + break; + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + RANDOM.nextBytes(clientChallenge); + setLMResponse(getLMv2Response(type2, domain, user, password, + clientChallenge)); + /* + setNTResponse(getNTLMv2Response(type2, domain, user, password, + clientChallenge)); + */ + break; + default: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + } + } + + /** + * Creates a Type-3 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param lmResponse The LanManager/LMv2 response. + * @param domain The NT/NTLMv2 response. + * @param domain The domain in which the user has an account. + * @param user The username for the authenticating user. + * @param workstation The workstation from which authentication is + * taking place. + */ + public Type3Message(int flags, byte[] lmResponse, byte[] ntResponse, + String domain, String user, String workstation) { + setFlags(flags); + setLMResponse(lmResponse); + setNTResponse(ntResponse); + setDomain(domain); + setUser(user); + setWorkstation(workstation); + } + + /** + * Creates a Type-3 message using the given raw Type-3 material. + * + * @param material The raw Type-3 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type3Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the LanManager/LMv2 response. + * + * @return A byte[] containing the LanManager response. + */ + public byte[] getLMResponse() { + return lmResponse; + } + + /** + * Sets the LanManager/LMv2 response for this message. + * + * @param lmResponse The LanManager response. + */ + public void setLMResponse(byte[] lmResponse) { + this.lmResponse = lmResponse; + } + + /** + * Returns the NT/NTLMv2 response. + * + * @return A byte[] containing the NT/NTLMv2 response. + */ + public byte[] getNTResponse() { + return ntResponse; + } + + /** + * Sets the NT/NTLMv2 response for this message. + * + * @param lmResponse The NT/NTLMv2 response. + */ + public void setNTResponse(byte[] ntResponse) { + this.ntResponse = ntResponse; + } + + /** + * Returns the domain in which the user has an account. + * + * @return A String containing the domain for the user. + */ + public String getDomain() { + return domain; + } + + /** + * Sets the domain for this message. + * + * @param domain The domain. + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * Returns the username for the authenticating user. + * + * @return A String containing the user for this message. + */ + public String getUser() { + return user; + } + + /** + * Sets the user for this message. + * + * @param user The user. + */ + public void setUser(String user) { + this.user = user; + } + + /** + * Returns the workstation from which authentication is being performed. + * + * @return A String containing the workstation. + */ + public String getWorkstation() { + return workstation; + } + + /** + * Sets the workstation for this message. + * + * @param workstation The workstation. + */ + public void setWorkstation(String workstation) { + this.workstation = workstation; + } + + /** + * Returns the session key. + * + * @return A byte[] containing the session key. + */ + public byte[] getSessionKey() { + return sessionKey; + } + + /** + * Sets the session key. + * + * @param sessionKey The session key. + */ + public void setSessionKey(byte[] sessionKey) { + this.sessionKey = sessionKey; + } + + public byte[] toByteArray() { + try { + int flags = getFlags(); + boolean unicode = (flags & NTLMSSP_NEGOTIATE_UNICODE) != 0; + String oem = unicode ? null : getOEMEncoding(); + String domainName = getDomain(); + byte[] domain = null; + if (domainName != null && domainName.length() != 0) { + domain = unicode ? + domainName.getBytes("UnicodeLittleUnmarked") : + domainName.toUpperCase().getBytes(oem); + } + int domainLength = (domain != null) ? domain.length : 0; + String userName = getUser(); + byte[] user = null; + if (userName != null && userName.length() != 0) { + user = unicode ? userName.getBytes("UnicodeLittleUnmarked") : + userName.toUpperCase().getBytes(oem); + } + int userLength = (user != null) ? user.length : 0; + String workstationName = getWorkstation(); + byte[] workstation = null; + if (workstationName != null && workstationName.length() != 0) { + workstation = unicode ? + workstationName.getBytes("UnicodeLittleUnmarked") : + workstationName.toUpperCase().getBytes(oem); + } + int workstationLength = (workstation != null) ? + workstation.length : 0; + byte[] lmResponse = getLMResponse(); + int lmLength = (lmResponse != null) ? lmResponse.length : 0; + byte[] ntResponse = getNTResponse(); + int ntLength = (ntResponse != null) ? ntResponse.length : 0; + byte[] sessionKey = getSessionKey(); + int keyLength = (sessionKey != null) ? sessionKey.length : 0; + byte[] type3 = new byte[64 + domainLength + userLength + + workstationLength + lmLength + ntLength + keyLength]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type3, 0, 8); + writeULong(type3, 8, 3); + int offset = 64; + writeSecurityBuffer(type3, 12, offset, lmResponse); + offset += lmLength; + writeSecurityBuffer(type3, 20, offset, ntResponse); + offset += ntLength; + writeSecurityBuffer(type3, 28, offset, domain); + offset += domainLength; + writeSecurityBuffer(type3, 36, offset, user); + offset += userLength; + writeSecurityBuffer(type3, 44, offset, workstation); + offset += workstationLength; + writeSecurityBuffer(type3, 52, offset, sessionKey); + writeULong(type3, 60, flags); + return type3; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String user = getUser(); + String domain = getDomain(); + String workstation = getWorkstation(); + byte[] lmResponse = getLMResponse(); + byte[] ntResponse = getNTResponse(); + byte[] sessionKey = getSessionKey(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (domain != null) { + buffer.append("domain: ").append(domain); + } + if (user != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("user: ").append(user); + } + if (workstation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("workstation: ").append(workstation); + } + if (lmResponse != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("lmResponse: "); + buffer.append("0x"); + for (int i = 0; i < lmResponse.length; i++) { + buffer.append(Integer.toHexString((lmResponse[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(lmResponse[i] & 0x0f)); + } + } + if (ntResponse != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("ntResponse: "); + buffer.append("0x"); + for (int i = 0; i < ntResponse.length; i++) { + buffer.append(Integer.toHexString((ntResponse[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(ntResponse[i] & 0x0f)); + } + } + if (sessionKey != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("sessionKey: "); + buffer.append("0x"); + for (int i = 0; i < sessionKey.length; i++) { + buffer.append(Integer.toHexString((sessionKey[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(sessionKey[i] & 0x0f)); + } + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-3 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default flags for a Type-3 message created in response + * to the given Type-2 message in the current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags(Type2Message type2) { + if (type2 == null) return DEFAULT_FLAGS; + int flags = NTLMSSP_NEGOTIATE_NTLM; + flags |= ((type2.getFlags() & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM; + return flags; + } + + /** + * Constructs the LanManager response to the given Type-2 message using + * the supplied password. + * + * @param type2 The Type-2 message. + * @param password The password. + * @return A byte[] containing the LanManager response. + */ + public static byte[] getLMResponse(Type2Message type2, String password) { + if (type2 == null || password == null) return null; + return NtlmPasswordAuthentication.getPreNTLMResponse(password, + type2.getChallenge()); + } + + public static byte[] getLMv2Response(Type2Message type2, + String domain, String user, String password, + byte[] clientChallenge) { + if (type2 == null || domain == null || user == null || + password == null || clientChallenge == null) { + return null; + } + return NtlmPasswordAuthentication.getLMv2Response(domain, user, + password, type2.getChallenge(), clientChallenge); + } + + /** + * Constructs the NT response to the given Type-2 message using + * the supplied password. + * + * @param type2 The Type-2 message. + * @param password The password. + * @return A byte[] containing the NT response. + */ + public static byte[] getNTResponse(Type2Message type2, String password) { + if (type2 == null || password == null) return null; + return NtlmPasswordAuthentication.getNTLMResponse(password, + type2.getChallenge()); + } + + /** + * Returns the default domain from the current environment. + * + * @return The default domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + /** + * Returns the default user from the current environment. + * + * @return The default user. + */ + public static String getDefaultUser() { + return DEFAULT_USER; + } + + /** + * Returns the default password from the current environment. + * + * @return The default password. + */ + public static String getDefaultPassword() { + return DEFAULT_PASSWORD; + } + + /** + * Returns the default workstation from the current environment. + * + * @return The default workstation. + */ + public static String getDefaultWorkstation() { + return DEFAULT_WORKSTATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 3) { + throw new IOException("Not a Type 3 message."); + } + byte[] lmResponse = readSecurityBuffer(material, 12); + int lmResponseOffset = readULong(material, 16); + byte[] ntResponse = readSecurityBuffer(material, 20); + int ntResponseOffset = readULong(material, 24); + byte[] domain = readSecurityBuffer(material, 28); + int domainOffset = readULong(material, 32); + byte[] user = readSecurityBuffer(material, 36); + int userOffset = readULong(material, 40); + byte[] workstation = readSecurityBuffer(material, 44); + int workstationOffset = readULong(material, 48); + int flags; + String charset; + if (lmResponseOffset == 52 || ntResponseOffset == 52 || + domainOffset == 52 || userOffset == 52 || + workstationOffset == 52) { + flags = NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_OEM; + charset = getOEMEncoding(); + } else { + setSessionKey(readSecurityBuffer(material, 52)); + flags = readULong(material, 60); + charset = ((flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + "UnicodeLittleUnmarked" : getOEMEncoding(); + } + setFlags(flags); + setLMResponse(lmResponse); + // NTLMv2 issues w/cross-domain authentication; leave NT empty if >= 3 + if (LM_COMPATIBILITY < 3) setNTResponse(ntResponse); + setDomain(new String(domain, charset)); + setUser(new String(user, charset)); + setWorkstation(new String(workstation, charset)); + } + +} diff --git a/src/jcifs/smb/NtlmPasswordAuthentication.java b/src/jcifs/smb/NtlmPasswordAuthentication.java index 529fc89..c076ab8 100644 --- a/src/jcifs/smb/NtlmPasswordAuthentication.java +++ b/src/jcifs/smb/NtlmPasswordAuthentication.java @@ -1,16 +1,17 @@ /* jcifs smb client library in Java * Copyright (C) 2002 "Michael B. Allen" - * + * "Eric Glass" + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -20,8 +21,10 @@ package jcifs.smb; import jcifs.util.DES; import jcifs.util.MD4; +import jcifs.util.HMACT64; import java.io.UnsupportedEncodingException; import java.security.Principal; +import java.security.SecureRandom; import java.util.Arrays; import jcifs.Config; @@ -37,8 +40,22 @@ import jcifs.Config; public final class NtlmPasswordAuthentication implements Principal { - // KGS!@#$% - static final byte[] S8 = { + private static final int LM_COMPATIBILITY = + Config.getInt("jcifs.smb.lmCompatibility", 0); + + private static final String DEFAULT_DOMAIN = + Config.getProperty("jcifs.smb.client.domain", "?"); + + private static final String DEFAULT_USERNAME = + Config.getProperty("jcifs.smb.client.username", "GUEST"); + + private static final String DEFAULT_PASSWORD = + Config.getProperty("jcifs.smb.client.password", ""); + + private static final SecureRandom random = new SecureRandom(); + + // KGS!@#$% + static final byte[] S8 = { (byte)0x4b, (byte)0x47, (byte)0x53, (byte)0x21, (byte)0x40, (byte)0x23, (byte)0x24, (byte)0x25 }; @@ -53,10 +70,13 @@ public final class NtlmPasswordAuthentication implements Principal { System.arraycopy( e8, 0, e, i * 8, 8 ); } } - static byte[] getPreNTLMResponse( String password, byte[] challenge ) { +/** + * Generate the ANSI DES hash for the password associated with these credentials. + */ + static public byte[] getPreNTLMResponse( String password, byte[] challenge ) { byte[] p14 = new byte[14]; - byte[] p21 = new byte[21]; - byte[] p24 = new byte[24]; + byte[] p21 = new byte[21]; + byte[] p24 = new byte[24]; byte[] passwordBytes; try { passwordBytes = password.toUpperCase().getBytes( ServerMessageBlock.encoding ); @@ -69,28 +89,66 @@ public final class NtlmPasswordAuthentication implements Principal { if( passwordLength > 14) { passwordLength = 14; } - System.arraycopy( passwordBytes, 0, p14, 0, passwordLength ); - E( p14, S8, p21); + System.arraycopy( passwordBytes, 0, p14, 0, passwordLength ); + E( p14, S8, p21); E( p21, challenge, p24); - return p24; + return p24; } - static byte[] getNTLMResponse( String password, byte[] challenge ) { +/** + * Generate the Unicode MD4 hash for the password associated with these credentials. + */ + static public byte[] getNTLMResponse( String password, byte[] challenge ) { byte[] uni = null; byte[] p21 = new byte[21]; byte[] p24 = new byte[24]; try { - uni = password.getBytes( "UnicodeLittleUnmarked" ); + uni = password.getBytes( "UnicodeLittleUnmarked" ); } catch( UnsupportedEncodingException uee ) { Log.printStackTrace( "password encryption exception", uee ); } MD4 md4 = new MD4(); md4.update( uni ); - System.arraycopy( md4.digest(), 0, p21, 0, 16 ); + try { + md4.digest(p21, 0, 16); + } catch (Exception ex) { + Log.printStackTrace( "digest exception", ex ); + } E( p21, challenge, p24 ); return p24; } + /** + * Creates the LMv2 response for the supplied information. + * + * @param domain The domain in which the username exists. + * @param user The username. + * @param password The user's password. + * @param challenge The server challenge. + * @param clientChallenge The client challenge (nonce). + */ + public static byte[] getLMv2Response(String domain, String user, + String password, byte[] challenge, byte[] clientChallenge) { + try { + byte[] hash = new byte[16]; + byte[] response = new byte[24]; + MD4 md4 = new MD4(); + md4.update(password.getBytes("UnicodeLittleUnmarked")); + HMACT64 hmac = new HMACT64(md4.digest()); + hmac.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmac.update(domain.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmac = new HMACT64(hmac.digest()); + hmac.update(challenge); + hmac.update(clientChallenge); + hmac.digest(response, 0, 16); + System.arraycopy(clientChallenge, 0, response, 16, 8); + return response; + } catch (Exception ex) { + Log.printStackTrace("Error creating LMv2 response", ex); + return null; + } + } + static final NtlmPasswordAuthentication NULL = new NtlmPasswordAuthentication( "", "", "" ); static final NtlmPasswordAuthentication GUEST = @@ -117,12 +175,12 @@ public final class NtlmPasswordAuthentication implements Principal { char c; end = userInfo.length(); - for( i = 0, u = 0; i < end; i++ ) { + for( i = 0, u = 0; i < end; i++ ) { c = userInfo.charAt( i ); - if( c == ';' ) { - domain = userInfo.substring( 0, i ); + if( c == ';' ) { + domain = userInfo.substring( 0, i ); u = i + 1; - } else if( c == ':' ) { + } else if( c == ':' ) { password = userInfo.substring( i + 1 ); break; } @@ -130,15 +188,9 @@ public final class NtlmPasswordAuthentication implements Principal { username = userInfo.substring( u, i ); } - if( domain == null ) { - this.domain = Config.getProperty( "jcifs.smb.client.domain", "?" ); - } - if( username == null ) { - this.username = Config.getProperty( "jcifs.smb.client.username", "GUEST" ); - } - if( password == null ) { - this.password = Config.getProperty( "jcifs.smb.client.password", "" ); - } + if( domain == null ) this.domain = DEFAULT_DOMAIN; + if( username == null ) this.username = DEFAULT_USERNAME; + if( password == null ) this.password = DEFAULT_PASSWORD; } /** * Create an NtlmPasswordAuthentication object from a @@ -151,15 +203,9 @@ public final class NtlmPasswordAuthentication implements Principal { this.domain = domain; this.username = username; this.password = password; - if( domain == null ) { - this.domain = Config.getProperty( "jcifs.smb.client.domain", "?" ); - } - if( username == null ) { - this.username = Config.getProperty( "jcifs.smb.client.username", "GUEST" ); - } - if( password == null ) { - this.password = Config.getProperty( "jcifs.smb.client.password", "" ); - } + if( domain == null ) this.domain = DEFAULT_DOMAIN; + if( username == null ) this.username = DEFAULT_USERNAME; + if( password == null ) this.password = DEFAULT_PASSWORD; } /** * Create an NtlmPasswordAuthentication object with raw password @@ -217,7 +263,22 @@ public final class NtlmPasswordAuthentication implements Principal { if( hashesExternal ) { return ansiHash; } - return getPreNTLMResponse( password, challenge ); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + return getPreNTLMResponse( password, challenge ); + case 2: + return getNTLMResponse( password, challenge ); + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + random.nextBytes(clientChallenge); + return getLMv2Response(domain, username, password, challenge, + clientChallenge); + default: + return getPreNTLMResponse( password, challenge ); + } } /** * Computes the 24 byte Unicode password hash given the 8 byte server challenge. @@ -226,7 +287,24 @@ public final class NtlmPasswordAuthentication implements Principal { if( hashesExternal ) { return unicodeHash; } - return getNTLMResponse( password, challenge ); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + case 2: + return getNTLMResponse( password, challenge ); + case 3: + case 4: + case 5: + /* + byte[] clientChallenge = new byte[8]; + random.nextBytes(clientChallenge); + return getNTLMv2Response(domain, username, password, null, + challenge, clientChallenge); + */ + return new byte[0]; + default: + return getNTLMResponse( password, challenge ); + } } /** * Compares two NtlmPasswordAuthentication objects for diff --git a/src/jcifs/smb/SmbComSessionSetupAndX.java b/src/jcifs/smb/SmbComSessionSetupAndX.java index 8beed59..90918e0 100644 --- a/src/jcifs/smb/SmbComSessionSetupAndX.java +++ b/src/jcifs/smb/SmbComSessionSetupAndX.java @@ -1,16 +1,16 @@ /* 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 @@ -60,6 +60,8 @@ class SmbComSessionSetupAndX extends AndXServerMessageBlock { accountPassword = session.auth.getAnsiHash( session.transport.server.encryptionKey ); unicodePassword = session.auth.getUnicodeHash( session.transport.server.encryptionKey ); passwordLength = unicodePasswordLength = 24; + // fix for win9x clients + if (unicodePassword.length == 0) unicodePasswordLength = 0; } else if( Config.getBoolean( "jcifs.smb.client.disablePlainTextPasswords", true )) { throw new RuntimeException( "Plain text passwords are disabled" ); } else if( useUnicode ) { diff --git a/src/jcifs/smb/SmbComSessionSetupAndX.java0 b/src/jcifs/smb/SmbComSessionSetupAndX.java0 new file mode 100644 index 0000000..da61a71 --- /dev/null +++ b/src/jcifs/smb/SmbComSessionSetupAndX.java0 @@ -0,0 +1,172 @@ +/* jcifs smb client library in Java + * Copyright (C) 2000 "Michael B. Allen" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.smb; + +import jcifs.Config; +import java.io.IOException; +import java.io.InputStream; + +class SmbComSessionSetupAndX extends AndXServerMessageBlock { + + static final int BATCH_LIMIT = + Config.getInt( "jcifs.smb.client.SessionSetupAndX.TreeConnectAndX", 1 ); + + byte[] accountPassword, unicodePassword; + int passwordLength, unicodePasswordLength; + int negotiatedMaxBufferSize, + negotiatedMaxMpxCount, + vcNumber, + sessionKey; + String accountName, + primaryDomain, + nativeOs, + nativeLanMan; + + SmbSession session; + + SmbComSessionSetupAndX( SmbSession session, ServerMessageBlock andx ) { + super( andx ); + command = SMB_COM_SESSION_SETUP_ANDX; + this.session = session; + } + + int getBatchLimit( byte command ) { + return command == SMB_COM_TREE_CONNECT_ANDX ? BATCH_LIMIT : 0; + } + int writeParameterWordsWireFormat( byte[] dst, int dstIndex ) { + int start = dstIndex; + + if( session.transport.server.security == SECURITY_USER && + ( session.auth.hashesExternal || + session.auth.password.length() > 0 )) { + if( session.transport.server.encryptedPasswords ) { + // encrypted + accountPassword = session.auth.getAnsiHash( session.transport.server.encryptionKey ); + unicodePassword = session.auth.getUnicodeHash( session.transport.server.encryptionKey ); + // fix for NTLMv2 or empty NTLM/LM + passwordLength = accountPassword.length; + unicodePasswordLength = unicodePassword.length; + } else if( Config.getBoolean( "jcifs.smb.client.disablePlainTextPasswords", true )) { + throw new RuntimeException( "Plain text passwords are disabled" ); + } else if( useUnicode ) { + // plain text + String password = session.auth.getPassword(); + accountPassword = new byte[0]; + passwordLength = 0; + unicodePassword = new byte[(password.length() + 1) * 2]; + unicodePasswordLength = writeString( password, unicodePassword, 0 ); + } else { + // plain text + String password = session.auth.getPassword(); + accountPassword = new byte[(password.length() + 1) * 2]; + passwordLength = writeString( password, accountPassword, 0 ); + unicodePassword = new byte[0]; + unicodePasswordLength = 0; + } + } else { + // no password in session setup + passwordLength = unicodePasswordLength = 0; + } + + negotiatedMaxBufferSize = session.transport.negotiatedMaxBufferSize; + negotiatedMaxMpxCount = session.transport.negotiatedMaxMpxCount; + vcNumber = session.transport.client.vcNumber; + sessionKey = session.transport.client.sessionKey; + + writeInt2( negotiatedMaxBufferSize, dst, dstIndex ); + dstIndex += 2; + writeInt2( negotiatedMaxMpxCount, dst, dstIndex ); + dstIndex += 2; + writeInt2( vcNumber, dst, dstIndex ); + dstIndex += 2; + writeInt4( sessionKey, dst, dstIndex ); + dstIndex += 4; + writeInt2( passwordLength, dst, dstIndex ); + dstIndex += 2; + writeInt2( unicodePasswordLength, dst, dstIndex ); + dstIndex += 2; + dst[dstIndex++] = (byte)0x00; + dst[dstIndex++] = (byte)0x00; + dst[dstIndex++] = (byte)0x00; + dst[dstIndex++] = (byte)0x00; + writeInt4( session.transport.client.capabilities, dst, dstIndex ); + dstIndex += 4; + + return dstIndex - start; + } + int writeBytesWireFormat( byte[] dst, int dstIndex ) { + int start = dstIndex; + + accountName = session.auth.username.toUpperCase(); + primaryDomain = session.auth.domain.toUpperCase(); + nativeOs = session.transport.client.nativeOs; + nativeLanMan = session.transport.client.nativeLanMan; + + if( session.transport.server.security == SECURITY_USER && + ( session.auth.hashesExternal || + session.auth.password.length() > 0 )) { + System.arraycopy( accountPassword, 0, dst, dstIndex, passwordLength ); + dstIndex += passwordLength; + System.arraycopy( unicodePassword, 0, dst, dstIndex, unicodePasswordLength ); + dstIndex += unicodePasswordLength; + } + if( useUnicode ) { + // at least NT 4 observed needing this only with unicode + dst[dstIndex++] = (byte)'\0'; + } + + dstIndex += writeString( accountName, dst, dstIndex ); + dstIndex += writeString( primaryDomain, dst, dstIndex ); + dstIndex += writeString( nativeOs, dst, dstIndex ); +/* + // at least NT 4 observed with 2 zero bytes here + dst[dstIndex++] = (byte)0x00; + dst[dstIndex++] = (byte)0x00; +This still isn't quite right. +*/ + dstIndex += writeString( nativeLanMan, dst, dstIndex ); + + return dstIndex - start; + } + int readParameterWordsWireFormat( byte[] buffer, int bufferIndex ) { + return 0; + } + int readBytesWireFormat( byte[] buffer, int bufferIndex ) { + return 0; + } + int readBytesDirectWireFormat( InputStream in, int byteCount ) throws IOException { + return 0; + } + public String toString() { + String result = new String( "SmbComSessionSetupAndX[" + + super.toString() + + ",maxBufferSize=" + negotiatedMaxBufferSize + + ",maxMpxCount=" + negotiatedMaxMpxCount + + ",vcNumber=" + vcNumber + + ",sessionKey=" + sessionKey + + ",passwordLength=" + passwordLength + + ",unicodePasswordLength=" + unicodePasswordLength + + ",capabilities=" + session.transport.client.capabilities + + ",accountName=" + accountName + + ",primaryDomain=" + primaryDomain + + ",nativeOs=" + nativeOs + + ",nativeLanMan=" + nativeLanMan + "]" ); + return result; + } +} diff --git a/src/jcifs/smb/SmbComTransactionResponse.java b/src/jcifs/smb/SmbComTransactionResponse.java index a0384ba..24a16d2 100644 --- a/src/jcifs/smb/SmbComTransactionResponse.java +++ b/src/jcifs/smb/SmbComTransactionResponse.java @@ -53,6 +53,11 @@ abstract class SmbComTransactionResponse extends ServerMessageBlock implements E txn_buf = null; } + public void reset() { + bufDataStart = 0; + isPrimary = hasMore = true; + parametersDone = dataDone = false; + } public boolean hasMoreElements() { return hasMore; } diff --git a/src/jcifs/smb/SmbComTreeConnectAndX.java b/src/jcifs/smb/SmbComTreeConnectAndX.java index 0437116..8a3bd6e 100644 --- a/src/jcifs/smb/SmbComTreeConnectAndX.java +++ b/src/jcifs/smb/SmbComTreeConnectAndX.java @@ -1,16 +1,16 @@ /* 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 @@ -121,11 +121,11 @@ class SmbComTreeConnectAndX extends AndXServerMessageBlock { if( session.transport.server.security == SECURITY_SHARE && ( session.auth.hashesExternal || session.auth.password.length() > 0 )) { - + if( session.transport.server.encryptedPasswords ) { // encrypted password = session.auth.getAnsiHash( session.transport.server.encryptionKey ); - passwordLength = 24; + passwordLength = password.length; } else if( Config.getBoolean( "jcifs.smb.client.disablePlainTextPasswords", true )) { throw new RuntimeException( "Plain text passwords are disabled" ); } else { diff --git a/src/jcifs/smb/SmbComWriteAndXResponse.java b/src/jcifs/smb/SmbComWriteAndXResponse.java index d728bb6..c1219aa 100644 --- a/src/jcifs/smb/SmbComWriteAndXResponse.java +++ b/src/jcifs/smb/SmbComWriteAndXResponse.java @@ -1,52 +1,52 @@ -/* jcifs smb client library in Java - * Copyright (C) 2000 "Michael B. Allen" - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package jcifs.smb; - -import java.io.IOException; -import java.io.InputStream; - -class SmbComWriteAndXResponse extends AndXServerMessageBlock { - - int count; - - SmbComWriteAndXResponse() { - } - - int writeParameterWordsWireFormat( byte[] dst, int dstIndex ) { - return 0; - } - int writeBytesWireFormat( byte[] dst, int dstIndex ) { - return 0; - } - int readParameterWordsWireFormat( byte[] buffer, int bufferIndex ) { - count = readInt2( buffer, bufferIndex ); - return 8; - } - int readBytesWireFormat( byte[] buffer, int bufferIndex ) { - return 0; - } - int readBytesDirectWireFormat( InputStream in, int byteCount ) throws IOException { - return 0; - } - public String toString() { - return new String( "SmbComWriteAndXResponse[" + - super.toString() + - ",count=" + count + "]" ); - } -} +/* jcifs smb client library in Java + * Copyright (C) 2000 "Michael B. Allen" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.smb; + +import java.io.IOException; +import java.io.InputStream; + +class SmbComWriteAndXResponse extends AndXServerMessageBlock { + + long count; + + SmbComWriteAndXResponse() { + } + + int writeParameterWordsWireFormat( byte[] dst, int dstIndex ) { + return 0; + } + int writeBytesWireFormat( byte[] dst, int dstIndex ) { + return 0; + } + int readParameterWordsWireFormat( byte[] buffer, int bufferIndex ) { + count = readInt2( buffer, bufferIndex ) & 0xFFFFL; + return 8; + } + int readBytesWireFormat( byte[] buffer, int bufferIndex ) { + return 0; + } + int readBytesDirectWireFormat( InputStream in, int byteCount ) throws IOException { + return 0; + } + public String toString() { + return new String( "SmbComWriteAndXResponse[" + + super.toString() + + ",count=" + count + "]" ); + } +} diff --git a/src/jcifs/smb/SmbFile.java b/src/jcifs/smb/SmbFile.java index c53f127..e332cb2 100644 --- a/src/jcifs/smb/SmbFile.java +++ b/src/jcifs/smb/SmbFile.java @@ -261,9 +261,21 @@ public class SmbFile extends URLConnection { static final int O_APPEND = 0x040000; // share access +/** + * When specified as the shareAccess constructor parameter, other SMB clients (including other threads macking calls into jCIFS) will not be permitted to access the target file and will receive "The file is being accessed by another process" message. + */ public static final int FILE_NO_SHARE = 0x00; +/** + * When specified as the shareAccess constructor parameter, other SMB clients will be permitted to read from the target file while this file is open. This constant may be logically OR'd with other share access flags. + */ public static final int FILE_SHARE_READ = 0x01; +/** + * When specified as the shareAccess constructor parameter, other SMB clients will be permitted to write to the target file while this file is open. This constant may be logically OR'd with other share access flags. + */ public static final int FILE_SHARE_WRITE = 0x02; +/** + * When specified as the shareAccess constructor parameter, other SMB clients will be permitted to delete the target file while this file is open. This constant may be logically OR'd with other share access flags. + */ public static final int FILE_SHARE_DELETE = 0x04; // Open Function Encoding @@ -391,10 +403,10 @@ public class SmbFile extends URLConnection { * the parent. See the description above for examples of * using the second chile parameter. * - * @param parent A URL string - * @param child A path string relative to the parent paremeter + * @param context A URL string + * @param name A path string relative to the context paremeter * @throws MalformedURLException - * If the parent and child parameters + * If the context and name parameters * do not follow the prescribed syntax */ @@ -402,14 +414,54 @@ public class SmbFile extends URLConnection { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER )); } +/** + * Constructs an SmbFile representing a resource on an SMB network such + * as a file or directory. +The second parameter may be constructed explicitly or retreived with HttpServletRequest.getUserPrincipal() if NTLM HTTP authentication has been successfully negotiated. + * + * @param url A URL string + * @param auth The credentials the client should use for authentication + * @throws MalformedURLException + * If the url parameter does not follow the prescribed syntax + */ public SmbFile( String url, NtlmPasswordAuthentication auth ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER ), auth ); } +/** + * Constructs an SmbFile representing a resource on an SMB network such + * as a file or directory. The second parameter is a relative path from + * the context. See the description above for examples of + * using the second name parameter. +The third parameter may be constructed explicitly or retreived with HttpServletRequest.getUserPrincipal() if NTLM HTTP authentication has been successfully negotiated. + * + * @param context A URL string + * @param name A path string relative to the context paremeter + * @param auth The credentials the client should use for authentication + * @throws MalformedURLException + * If the context and name parameters + * do not follow the prescribed syntax + */ public SmbFile( String context, String name, NtlmPasswordAuthentication auth ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER ), auth ); } +/** + * Constructs an SmbFile representing a resource on an SMB network such + * as a file or directory. The second parameter is a relative path from + * the context. See the description above for examples of + * using the second name parameter. +The third parameter may be constructed explicitly or retreived with HttpServletRequest.getUserPrincipal() if NTLM HTTP authentication has been successfully negotiated. +The shareAccess parameter controls what permissions other clients have when trying to access the same file while this instance is still open. This value is either FILE_NO_SHARE or any combination of FILE_SHARE_READ, FILE_SHARE_WRITE, and FILE_SHARE_DELETE logically OR'd together. + * + * @param context A URL string + * @param name A path string relative to the context paremeter + * @param auth The credentials the client should use for authentication + * @param shareAccess Specifies what access other clients have while this file is open. + * @throws MalformedURLException + * If the context and name parameters + * do not follow the prescribed syntax + */ public SmbFile( String context, String name, NtlmPasswordAuthentication auth, int shareAccess ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER ), auth ); @@ -418,9 +470,22 @@ public class SmbFile extends URLConnection { } this.shareAccess = shareAccess; } +/** + * Constructs an SmbFile representing a resource on an SMB network such + * as a file or directory from a URL object. + * + * @param url The URL of the target resource + */ public SmbFile( URL url ) { this( url, new NtlmPasswordAuthentication( url.getUserInfo() )); } +/** + * Constructs an SmbFile representing a resource on an SMB network such + * as a file or directory from a URL object and an NtlmPasswordAuthentication object which may be constructed explicitly or retreived with HttpServletRequest.getUserPrincipal() if NTLM HTTP authentication has been successfully negotiated. + * + * @param url The URL of the target resource + * @param auth The credentials the client should use for authentication + */ public SmbFile( URL url, NtlmPasswordAuthentication auth ) { super( url ); this.auth = auth == null ? new NtlmPasswordAuthentication( url.getUserInfo() ) : auth; @@ -999,7 +1064,7 @@ public class SmbFile extends URLConnection { if( getUncPath0().length() == 1 ) { return true; } - exists(); + if (!exists()) return false; return ( attributes & ATTR_DIRECTORY ) == ATTR_DIRECTORY; } @@ -1173,6 +1238,7 @@ public class SmbFile extends URLConnection { response.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; while( response.isEndOfSearch == false && response.searchCount > 0 ) { + response.reset(); sendTransaction( new Trans2FindNext2( sid, response.resumeKey, response.lastName ), response ); count += response.searchCount; @@ -1414,6 +1480,7 @@ public class SmbFile extends URLConnection { response.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; while( response.isEndOfSearch == false && response.searchCount > 0 ) { + response.reset(); sendTransaction( new Trans2FindNext2( sid, response.resumeKey, response.lastName ), response ); count += response.searchCount; @@ -1492,6 +1559,7 @@ public class SmbFile extends URLConnection { ",newFileName=" + dest.unc ); attrExpiration = sizeExpiration = 0; + dest.attrExpiration = 0; /* * Rename Request / Response diff --git a/src/jcifs/smb/SmbFileInputStream.java b/src/jcifs/smb/SmbFileInputStream.java index e0cf1a5..b0a04a4 100644 --- a/src/jcifs/smb/SmbFileInputStream.java +++ b/src/jcifs/smb/SmbFileInputStream.java @@ -32,7 +32,7 @@ public class SmbFileInputStream extends InputStream { private SmbFile file; private long fp; - private int off, readSize, openFlags; + private int readSize, openFlags; private byte[] tmp = new byte[1]; /** @@ -120,8 +120,7 @@ public class SmbFileInputStream extends InputStream { // ensure file is open file.open( openFlags ); - Log.println( Log.WARNINGS, "smb read warning", - " fid=" + file.fid + ",off=" + off + ",len=" + len ); +//Log.println( Log.WARNINGS, "smb read warning", " fid=" + file.fid + ",off=" + off + ",len=" + len ); /* * Read AndX Request / Response @@ -180,5 +179,18 @@ public class SmbFileInputStream extends InputStream { } return resp.available; } +/** + * Skip n bytes of data on this stream. This operation will not result + * in any IO with the server. Unlink InputStream value less than + * the one provided will not be returned if it exceeds the end of the file + * (if this is a problem let us know). + */ + public long skip( long n ) throws IOException { + if (n > 0) { + fp += n; + return n; + } + return 0; + } } diff --git a/src/jcifs/smb/SmbFileOutputStream.java b/src/jcifs/smb/SmbFileOutputStream.java index 5f2d285..72a8dc4 100644 --- a/src/jcifs/smb/SmbFileOutputStream.java +++ b/src/jcifs/smb/SmbFileOutputStream.java @@ -1,204 +1,204 @@ -/* jcifs smb client library in Java - * Copyright (C) 2000 "Michael B. Allen" - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package jcifs.smb; - -import java.net.URL; -import java.io.OutputStream; -import java.io.IOException; -import java.net.UnknownHostException; -import java.net.MalformedURLException; - -/** - * This OutputStream can write bytes to a file on an SMB file server. - */ - -public class SmbFileOutputStream extends OutputStream { - - private SmbFile file; - private boolean append; - private int openFlags, writeSize; - private long fp; - private byte[] tmp = new byte[1]; - -/** - * Creates an {@link java.io.OutputStream} for writing to a file - * on an SMB server addressed by the URL parameter. See {@link - * jcifs.smb.SmbFile} for a detailed description and examples of - * the smb URL syntax. - * - * @param url An smb URL string representing the file to write to - * @return A new OutputStream for the specified file - */ - - public SmbFileOutputStream( String url ) throws SmbException, MalformedURLException, UnknownHostException { - this( url, false ); - } - -/** - * Creates an {@link java.io.OutputStream} for writing bytes to a file on - * an SMB server represented by the {@link jcifs.smb.SmbFile} parameter. See - * {@link jcifs.smb.SmbFile} for a detailed description and examples of - * the smb URL syntax. - * - * @param url An SmbFile specifying the file to write to - * @return A new OutputStream for the specified SmbFile - */ - - public SmbFileOutputStream( SmbFile file ) throws SmbException, MalformedURLException, UnknownHostException { - this( file, false ); - } - -/** - * Creates an {@link java.io.OutputStream} for writing bytes to a file on an - * SMB server addressed by the URL parameter. See {@link jcifs.smb.SmbFile} - * for a detailed description and examples of the smb URL syntax. If the - * second argument is true, then bytes will be written to the - * end of the file rather than the beginning. - * - * @param url An smb URL string representing the file to write to - * @return A new OutputStream for the specified url - */ - - public SmbFileOutputStream( String url, boolean append ) throws SmbException, MalformedURLException, UnknownHostException { - this( new SmbFile( url ), append ); - } - -/** - * Creates an {@link java.io.OutputStream} for writing bytes to a file - * on an SMB server addressed by the SmbFile parameter. See - * {@link jcifs.smb.SmbFile} for a detailed description and examples of - * the smb URL syntax. If the second argument is true, then - * bytes will be written to the end of the file rather than the beginning. - * - * @param url An SmbFile representing the file to write to - * @return A new OutputStream for the specified SmbFile - */ - - public SmbFileOutputStream( SmbFile file, boolean append ) throws SmbException, MalformedURLException, UnknownHostException { - this( file, append, append ? SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_APPEND : - SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC ); - } -/** - * Creates an {@link java.io.OutputStream} for writing bytes to a file - * on an SMB server addressed by the SmbFile parameter. See - * {@link jcifs.smb.SmbFile} for a detailed description and examples of - * the smb URL syntax. -

-The second parameter specifies how the file should be shared. If -SmbFile.FILE_NO_SHARE is specified the client will -have exclusive access to the file. An additional open command -from jCIFS or another application will fail with the "file is being -accessed by another process" error. The FILE_SHARE_READ, -FILE_SHARE_WRITE, and FILE_SHARE_DELETE may be -combined with the bitwise OR '|' to specify that other peocesses may read, -write, and/or delete the file while the jCIFS user has the file open. - * - * @param url An SmbFile representing the file to write to - * @return A new OutputStream for the specified SmbFile - */ - - public SmbFileOutputStream( String url, int shareAccess ) throws SmbException, MalformedURLException, UnknownHostException { - this( new SmbFile( url, "", null, shareAccess ), false ); - } - - SmbFileOutputStream( SmbFile file, boolean append, int openFlags ) throws SmbException, MalformedURLException, UnknownHostException { - this.file = file; - this.append = append; - this.openFlags = openFlags; - if( append ) { - try { - fp = file.length(); - } catch( SmbException se ) { - fp = 0L; - } - } - file.open( openFlags ); - writeSize = Math.min( file.tree.session.transport.snd_buf_size - 70, - file.tree.session.transport.server.maxBufferSize - 70 ); - } - -/** - * Closes this output stream and releases any system resources associated - * with it. - * - * @throws IOException if a network error occurs - */ - - public void close() throws IOException { - file.close(); - } - -/** - * Writes the specified byte to this file output stream. - * - * @throws IOException if a network error occurs - */ - - public void write( int b ) throws IOException { - tmp[0] = (byte)b; - write( tmp, 0, 1 ); - } - -/** - * Writes b.length bytes from the specified byte array to this - * file output stream. - * - * @throws IOException if a network error occurs - */ - - public void write( byte[] b ) throws IOException { - write( b, 0, b.length ); - } - -/** - * Writes len bytes from the specified byte array starting at - * offset off to this file output stream. - * - * @param b The array - * @throws IOException if a network error occurs - */ - - public void write( byte[] b, int off, int len ) throws IOException { - if( len <= 0 ) { - return; - } - - // ensure file is open - if( file.isOpen() == false ) { - file.open( openFlags ); - if( append ) { - fp = file.length(); - } - } - -/* - Log.println( Log.WARNINGS, "smb write warning", - " fid=" + file.fid + ",off=" + off + ",len=" + len ); -*/ - int w; - do { - w = len > writeSize ? writeSize : len; - file.send( new SmbComWriteAndX( file.fid, fp, len - w, b, off, w, null ), - new SmbComWriteAndXResponse() ); - fp += w; - len -= w; - off += w; - } while( len > 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.smb; + +import java.net.URL; +import java.io.OutputStream; +import java.io.IOException; +import java.net.UnknownHostException; +import java.net.MalformedURLException; + +/** + * This OutputStream can write bytes to a file on an SMB file server. + */ + +public class SmbFileOutputStream extends OutputStream { + + private SmbFile file; + private boolean append; + private int openFlags, writeSize; + private long fp; + private byte[] tmp = new byte[1]; + +/** + * Creates an {@link java.io.OutputStream} for writing to a file + * on an SMB server addressed by the URL parameter. See {@link + * jcifs.smb.SmbFile} for a detailed description and examples of + * the smb URL syntax. + * + * @param url An smb URL string representing the file to write to + * @return A new OutputStream for the specified file + */ + + public SmbFileOutputStream( String url ) throws SmbException, MalformedURLException, UnknownHostException { + this( url, false ); + } + +/** + * Creates an {@link java.io.OutputStream} for writing bytes to a file on + * an SMB server represented by the {@link jcifs.smb.SmbFile} parameter. See + * {@link jcifs.smb.SmbFile} for a detailed description and examples of + * the smb URL syntax. + * + * @param url An SmbFile specifying the file to write to + * @return A new OutputStream for the specified SmbFile + */ + + public SmbFileOutputStream( SmbFile file ) throws SmbException, MalformedURLException, UnknownHostException { + this( file, false ); + } + +/** + * Creates an {@link java.io.OutputStream} for writing bytes to a file on an + * SMB server addressed by the URL parameter. See {@link jcifs.smb.SmbFile} + * for a detailed description and examples of the smb URL syntax. If the + * second argument is true, then bytes will be written to the + * end of the file rather than the beginning. + * + * @param url An smb URL string representing the file to write to + * @return A new OutputStream for the specified url + */ + + public SmbFileOutputStream( String url, boolean append ) throws SmbException, MalformedURLException, UnknownHostException { + this( new SmbFile( url ), append ); + } + +/** + * Creates an {@link java.io.OutputStream} for writing bytes to a file + * on an SMB server addressed by the SmbFile parameter. See + * {@link jcifs.smb.SmbFile} for a detailed description and examples of + * the smb URL syntax. If the second argument is true, then + * bytes will be written to the end of the file rather than the beginning. + * + * @param url An SmbFile representing the file to write to + * @return A new OutputStream for the specified SmbFile + */ + + public SmbFileOutputStream( SmbFile file, boolean append ) throws SmbException, MalformedURLException, UnknownHostException { + this( file, append, append ? SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_APPEND : + SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC ); + } +/** + * Creates an {@link java.io.OutputStream} for writing bytes to a file + * on an SMB server addressed by the SmbFile parameter. See + * {@link jcifs.smb.SmbFile} for a detailed description and examples of + * the smb URL syntax. +

+The second parameter specifies how the file should be shared. If +SmbFile.FILE_NO_SHARE is specified the client will +have exclusive access to the file. An additional open command +from jCIFS or another application will fail with the "file is being +accessed by another process" error. The FILE_SHARE_READ, +FILE_SHARE_WRITE, and FILE_SHARE_DELETE may be +combined with the bitwise OR '|' to specify that other peocesses may read, +write, and/or delete the file while the jCIFS user has the file open. + * + * @param url An SmbFile representing the file to write to + * @return A new OutputStream for the specified SmbFile + */ + + public SmbFileOutputStream( String url, int shareAccess ) throws SmbException, MalformedURLException, UnknownHostException { + this( new SmbFile( url, "", null, shareAccess ), false ); + } + + SmbFileOutputStream( SmbFile file, boolean append, int openFlags ) throws SmbException, MalformedURLException, UnknownHostException { + this.file = file; + this.append = append; + this.openFlags = openFlags; + if( append ) { + try { + fp = file.length(); + } catch( SmbException se ) { + fp = 0L; + } + } + file.open( openFlags ); + writeSize = Math.min( file.tree.session.transport.snd_buf_size - 70, + file.tree.session.transport.server.maxBufferSize - 70 ); + } + +/** + * Closes this output stream and releases any system resources associated + * with it. + * + * @throws IOException if a network error occurs + */ + + public void close() throws IOException { + file.close(); + } + +/** + * Writes the specified byte to this file output stream. + * + * @throws IOException if a network error occurs + */ + + public void write( int b ) throws IOException { + tmp[0] = (byte)b; + write( tmp, 0, 1 ); + } + +/** + * Writes b.length bytes from the specified byte array to this + * file output stream. + * + * @throws IOException if a network error occurs + */ + + public void write( byte[] b ) throws IOException { + write( b, 0, b.length ); + } + +/** + * Writes len bytes from the specified byte array starting at + * offset off to this file output stream. + * + * @param b The array + * @throws IOException if a network error occurs + */ + + public void write( byte[] b, int off, int len ) throws IOException { + if( len <= 0 ) { + return; + } + + // ensure file is open + if( file.isOpen() == false ) { + file.open( openFlags ); + if( append ) { + fp = file.length(); + } + } + +/* + Log.println( Log.WARNINGS, "smb write warning", + " fid=" + file.fid + ",off=" + off + ",len=" + len ); +*/ + int w; + SmbComWriteAndXResponse rsp = new SmbComWriteAndXResponse(); + do { + w = len > writeSize ? writeSize : len; + file.send( new SmbComWriteAndX( file.fid, fp, len - w, b, off, w, null ), rsp ); + fp += rsp.count; + len -= rsp.count; + off += rsp.count; + } while( len > 0 ); + } +} diff --git a/src/jcifs/smb/SmbSession.java b/src/jcifs/smb/SmbSession.java index 9981daf..a9fa6d5 100644 --- a/src/jcifs/smb/SmbSession.java +++ b/src/jcifs/smb/SmbSession.java @@ -106,8 +106,9 @@ public final class SmbSession { request.uid = uid; transport.send( request, response ); } - synchronized void sessionSetup( ServerMessageBlock andx, + void sessionSetup( ServerMessageBlock andx, ServerMessageBlock andxResponse ) throws SmbException { +synchronized( transport ) { SmbComSessionSetupAndXResponse response = null; if( sessionSetup ) { @@ -127,8 +128,10 @@ public final class SmbSession { uid = response.uid; sessionSetup = true; +} } - synchronized void logoff( boolean inError ) { + void logoff( boolean inError ) { +synchronized( transport ) { if( sessionSetup == false ) { return; } @@ -156,6 +159,7 @@ public final class SmbSession { } } sessionSetup = false; +} } public String toString() { return "SmbSession[accountName=" + auth.username + diff --git a/src/jcifs/smb/SmbTransport.java b/src/jcifs/smb/SmbTransport.java index cf47e66..af7f2db 100644 --- a/src/jcifs/smb/SmbTransport.java +++ b/src/jcifs/smb/SmbTransport.java @@ -348,7 +348,7 @@ synchronized( rcv_buf ) { } synchronized( response ) { response.useUnicode = useUnicode; - Log.println( Log.WARNINGS, "smb transport warning", + Log.println( Log.DEBUGGING, "smb transport warning", " new data read from socket" ); if( response instanceof SmbComTransactionResponse ) { @@ -507,6 +507,7 @@ synchronized( snd_buf ) { default: throw new SmbException( response.errorCode ); } + } void sendTransaction( SmbComTransaction request, SmbComTransactionResponse response ) throws SmbException { diff --git a/src/jcifs/smb/SmbTree.java b/src/jcifs/smb/SmbTree.java index 63f1179..de27bab 100644 --- a/src/jcifs/smb/SmbTree.java +++ b/src/jcifs/smb/SmbTree.java @@ -94,15 +94,7 @@ class SmbTree { void treeConnect( ServerMessageBlock andx, ServerMessageBlock andxResponse ) throws SmbException { String unc; -/* - * We must lock the session and *then* this tree because there is a small - * possibilty that the session could be trying to disconnect the tree at - * the same time. In this case it will have the session locked and wait - * indefinately trying to acquire the tree which would be locked trying to - * aquire the session resulting in deadlock. - */ -synchronized( session ) { - synchronized( this ) { +synchronized( session.transport ) { if( treeConnected ) { return; @@ -134,10 +126,10 @@ synchronized( session ) { tid = response.tid; service = response.service; treeConnected = true; - } } } - synchronized void treeDisconnect( boolean inError ) { + void treeDisconnect( boolean inError ) { +synchronized( session.transport ) { if( treeConnected == false ) { return; } @@ -148,6 +140,7 @@ synchronized( session ) { } } treeConnected = false; +} } public String toString() { diff --git a/src/jcifs/util/Base64.java b/src/jcifs/util/Base64.java index 297604d..a539146 100644 --- a/src/jcifs/util/Base64.java +++ b/src/jcifs/util/Base64.java @@ -1,549 +1,94 @@ -package jcifs.util; - -import java.io.UnsupportedEncodingException; - -/** - * Encodes and decodes to and from Base64 notation. - * - *

- * Change Log: - *

- *
    - *
  • Trimmed object and stream releated methods for inclusion into jcifs.util package. - *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
  • - *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • - *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • - *
- * - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/xmlizable - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 1.3.4 - */ -public class Base64 -{ - - /** Specify encoding (value is true). */ - public final static boolean ENCODE = true; - - - /** Specify decoding (value is false). */ - public final static boolean DECODE = false; - - - /** Maximum line length (76) of Base64 output. */ - private final static int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte)'\n'; - - - /** The 64 valid Base64 values. */ - private final static byte[] ALPHABET = - { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = - { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - private final static byte BAD_ENCODING = -9; // Indicates error in encoding - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - - /** Defeats instantiation. */ - private Base64(){} - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * - * @param threeBytes the array to convert - * @return four byte array in Base64 notation. - * @since 1.3 - */ - private static byte[] encode3to4( byte[] threeBytes ) - { return encode3to4( threeBytes, 3 ); - } // end encodeToBytes - - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.3 - */ - private static byte[] encode3to4( byte[] threeBytes, int numSigBytes ) - { byte[] dest = new byte[4]; - encode3to4( threeBytes, 0, numSigBytes, dest, 0 ); - return dest; - } - - - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset ) - { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); - - switch( numSigBytes ) - { - case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; - return destination; - - case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalen to calling - * encodeBytes( source, 0, source.length ) - * - * @param source The data to convert - * @since 1.4 - */ - public static String encodeBytes( byte[] source ) - { - return encodeBytes( source, true ); - } // end encodeBytes - - /** - * Encodes a byte array into Base64 notation. - * Equivalen to calling - * encodeBytes( source, 0, source.length ) - * - * @param source The data to convert - * @param breakLines Break lines at 80 characters or less. - * @since 1.4 - */ - public static String encodeBytes( byte[] source, boolean breakLines ) - { - return encodeBytes( source, 0, source.length, breakLines ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len ) - { - return encodeBytes( source, off, len, true ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param breakLines Break lines at 80 characters or less. - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len, boolean breakLines ) - { - int len43 = len * 4 / 3; - byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) - { - encode3to4( source, d+off, 3, outBuff, e ); - - lineLength += 4; - if( breakLines && lineLength == MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if( d < len ) - { - encode3to4( source, d+off, len - d, outBuff, e ); - e += 4; - } // end if: some padding needed - - try { - return new String( outBuff, 0, e, "ASCII" ); - } catch( UnsupportedEncodingException uee ) { - } - return null; - } // end encodeBytes - - - /** - * Encodes a string in Base64 notation with line breaks - * after every 75 Base64 characters. - * - * @param s the string to encode - * @return the encoded string - * @since 1.3 - */ - public static String encodeString( String s ) - { - return encodeString( s, true ); - } // end encodeString - - /** - * Encodes a string in Base64 notation with line breaks - * after every 75 Base64 characters. - * - * @param s the string to encode - * @param breakLines Break lines at 80 characters or less. - * @return the encoded string - * @since 1.3 - */ - public static String encodeString( String s, boolean breakLines ) - { - try { - return encodeBytes( s.getBytes( "ASCII" ), breakLines ); - } catch( UnsupportedEncodingException uee ) { - } - return null; - } // end encodeString - - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes the first four bytes of array fourBytes - * and returns an array up to three bytes long with the - * decoded values. - * - * @param fourBytes the array with Base64 content - * @return array with decoded values - * @since 1.3 - */ - private static byte[] decode4to3( byte[] fourBytes ) - { - byte[] outBuff1 = new byte[3]; - int count = decode4to3( fourBytes, 0, outBuff1, 0 ); - byte[] outBuff2 = new byte[ count ]; - - for( int i = 0; i < count; i++ ) - outBuff2[i] = outBuff1[i]; - - return outBuff2; - } - - - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset ) - { - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else - { - try{ - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - }catch( Exception e){ - jcifs.util.Log.println( jcifs.util.Log.WARNINGS, "Base64", ""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); - jcifs.util.Log.println( jcifs.util.Log.WARNINGS, "Base64", ""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); - jcifs.util.Log.println( jcifs.util.Log.WARNINGS, "Base64", ""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); - jcifs.util.Log.println( jcifs.util.Log.WARNINGS, "Base64", ""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); - return -1; - } //e nd catch - } - } // end decodeToBytes - - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode( String s ) - { - try { - byte[] bytes = s.getBytes( "ASCII" ); - return decode( bytes, 0, bytes.length ); - } catch( UnsupportedEncodingException uee ) { - } - return null; - } // end decode - - - /** - * Decodes data from Base64 notation and - * returns it as a string. - * Equivlaent to calling - * new String( decode( s ) ) - * - * @param s the strind to decode - * @return The data as a string - * @since 1.4 - */ - public static String decodeToString( String s ) - { - try { - return new String( decode( s ), "ISO8859_1" ); - } catch( UnsupportedEncodingException uee ) { - } - return null; - } // end decodeToString - - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - * @since 1.3 - */ - public static byte[] decode( byte[] source, int off, int len ) - { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for( i = 0; i < len; i++ ) - { - sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[ sbiCrop ]; - - if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better - { - if( sbiDecode >= EQUALS_SIGN_ENC ) - { - b4[ b4Posn++ ] = sbiCrop; - if( b4Posn > 3 ) - { - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( sbiCrop == EQUALS_SIGN ) - break; - } // end if: quartet built - - } // end if: equals sign or better - - } // end if: white space, equals sign or better - else - { - System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); - return null; - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode -} // end class Base64 +/* Encodes and decodes to and from Base64 notation. + * Copyright (C) 2003 "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.util; + +public class Base64 { + + private static final String ALPHABET = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * Base-64 encodes the supplied block of data. Line wrapping is not + * applied on output. + * + * @param bytes The block of data that is to be Base-64 encoded. + * @return A String containing the encoded data. + */ + public static String encode(byte[] bytes) { + int length = bytes.length; + if (length == 0) return ""; + StringBuffer buffer = + new StringBuffer((int) Math.ceil((double) length / 3d) * 4); + int remainder = length % 3; + length -= remainder; + int block; + int i = 0; + while (i < length) { + block = ((bytes[i++] & 0xff) << 16) | ((bytes[i++] & 0xff) << 8) | + (bytes[i++] & 0xff); + buffer.append(ALPHABET.charAt(block >>> 18)); + buffer.append(ALPHABET.charAt((block >>> 12) & 0x3f)); + buffer.append(ALPHABET.charAt((block >>> 6) & 0x3f)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + } + if (remainder == 0) return buffer.toString(); + if (remainder == 1) { + block = (bytes[i] & 0xff) << 4; + buffer.append(ALPHABET.charAt(block >>> 6)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + buffer.append("=="); + return buffer.toString(); + } + block = (((bytes[i++] & 0xff) << 8) | ((bytes[i]) & 0xff)) << 2; + buffer.append(ALPHABET.charAt(block >>> 12)); + buffer.append(ALPHABET.charAt((block >>> 6) & 0x3f)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + buffer.append("="); + return buffer.toString(); + } + + /** + * Decodes the supplied Base-64 encoded string. + * + * @param string The Base-64 encoded string that is to be decoded. + * @return A byte[] containing the decoded data block. + */ + public static byte[] decode(String string) { + int length = string.length(); + if (length == 0) return new byte[0]; + int pad = (string.charAt(length - 2) == '=') ? 2 : + (string.charAt(length - 1) == '=') ? 1 : 0; + int size = length * 3 / 4 - pad; + byte[] buffer = new byte[size]; + int block; + int i = 0; + int index = 0; + while (i < length) { + block = (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 18 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 12 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 6 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff); + buffer[index++] = (byte) (block >>> 16); + if (index < size) buffer[index++] = (byte) ((block >>> 8) & 0xff); + if (index < size) buffer[index++] = (byte) (block & 0xff); + } + return buffer; + } + +} diff --git a/src/jcifs/util/HMACT64.java b/src/jcifs/util/HMACT64.java new file mode 100644 index 0000000..11c6d38 --- /dev/null +++ b/src/jcifs/util/HMACT64.java @@ -0,0 +1,116 @@ +/* HMACT64 keyed hashing algorithm + * Copyright (C) 2003 "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.util; + +import java.security.MessageDigest; + +/** + * This is an implementation of the HMACT64 keyed hashing algorithm. + * HMACT64 is defined by Luke Leighton as a modified HMAC-MD5 (RFC 2104) + * in which the key is truncated at 64 bytes (rather than being hashed + * via MD5). + */ +public class HMACT64 extends MessageDigest implements Cloneable { + + private static final int BLOCK_LENGTH = 64; + + private static final byte IPAD = (byte) 0x36; + + private static final byte OPAD = (byte) 0x5c; + + private MessageDigest md5; + + private byte[] ipad = new byte[BLOCK_LENGTH]; + + private byte[] opad = new byte[BLOCK_LENGTH]; + + /** + * Creates an HMACT64 instance which uses the given secret key material. + * + * @param key The key material to use in hashing. + */ + public HMACT64(byte[] key) { + super("HMACT64"); + int length = Math.min(key.length, BLOCK_LENGTH); + for (int i = 0; i < length; i++) { + ipad[i] = (byte) (key[i] ^ IPAD); + opad[i] = (byte) (key[i] ^ OPAD); + } + for (int i = length; i < BLOCK_LENGTH; i++) { + ipad[i] = IPAD; + opad[i] = OPAD; + } + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (Exception ex) { + throw new IllegalStateException(ex.getMessage()); + } + engineReset(); + } + + private HMACT64(HMACT64 hmac) throws CloneNotSupportedException { + super("HMACT64"); + this.ipad = hmac.ipad; + this.opad = hmac.opad; + this.md5 = (MessageDigest) hmac.md5.clone(); + } + + public Object clone() { + try { + return new HMACT64(this); + } catch (CloneNotSupportedException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + protected byte[] engineDigest() { + byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + protected int engineDigest(byte[] buf, int offset, int len) { + byte[] digest = md5.digest(); + md5.update(opad); + md5.update(digest); + try { + return md5.digest(buf, offset, len); + } catch (Exception ex) { + throw new IllegalStateException(); + } + } + + protected int engineGetDigestLength() { + return md5.getDigestLength(); + } + + protected void engineReset() { + md5.reset(); + md5.update(ipad); + } + + protected void engineUpdate(byte b) { + md5.update(b); + } + + protected void engineUpdate(byte[] input, int offset, int len) { + md5.update(input, offset, len); + } + +} diff --git a/src/jcifs/util/URLDecoder.jav b/src/jcifs/util/URLDecoder.jav deleted file mode 100644 index 3c4e719..0000000 --- a/src/jcifs/util/URLDecoder.jav +++ /dev/null @@ -1,81 +0,0 @@ -/* - * @(#)URLDecoder.java 1.9 00/02/02 - * - * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved. - * - * This software is the proprietary information of Sun Microsystems, Inc. - * Use is subject to license terms. - * - */ - -package jcifs.util; - -import java.io.*; - -/** - * The class contains a utility method for converting from - * a MIME format called "x-www-form-urlencoded" - * to a String - *

- * To convert to a String, each character is examined in turn: - *

    - *
  • The ASCII characters 'a' through 'z', - * 'A' through 'Z', and '0' - * through '9' remain the same. - *
  • The plus sign '+'is converted into a - * space character ' '. - *
  • The remaining characters are represented by 3-character - * strings which begin with the percent sign, - * "%xy", where xy is the two-digit - * hexadecimal representation of the lower 8-bits of the character. - *
- * - * @author Mark Chamness - * @author Michael McCloskey - * @version 1.9, 02/02/00 - * @since 1.2 - */ - -public class URLDecoder { - -/** - * Decodes a "x-www-form-urlencoded" - * to a String. - * @param s the String to decode - * @return the newly decoded String - */ - public static String decode(String s) { - StringBuffer sb = new StringBuffer(); - for(int i=0; i + * + * 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.util; + +public class Base64 { + + private static final String ALPHABET = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * Base-64 encodes the supplied block of data. Line wrapping is not + * applied on output. + * + * @param bytes The block of data that is to be Base-64 encoded. + * @return A String containing the encoded data. + */ + public static String encode(byte[] bytes) { + int length = bytes.length; + if (length == 0) return ""; + StringBuffer buffer = + new StringBuffer((int) Math.ceil((double) length / 3d) * 4); + int remainder = length % 3; + length -= remainder; + int block; + int i = 0; + while (i < length) { + block = ((bytes[i++] & 0xff) << 16) | ((bytes[i++] & 0xff) << 8) | + (bytes[i++] & 0xff); + buffer.append(ALPHABET.charAt(block >>> 18)); + buffer.append(ALPHABET.charAt((block >>> 12) & 0x3f)); + buffer.append(ALPHABET.charAt((block >>> 6) & 0x3f)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + } + if (remainder == 0) return buffer.toString(); + if (remainder == 1) { + block = (bytes[i] & 0xff) << 4; + buffer.append(ALPHABET.charAt(block >>> 6)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + buffer.append("=="); + return buffer.toString(); + } + block = (((bytes[i++] & 0xff) << 8) | ((bytes[i]) & 0xff)) << 2; + buffer.append(ALPHABET.charAt(block >>> 12)); + buffer.append(ALPHABET.charAt((block >>> 6) & 0x3f)); + buffer.append(ALPHABET.charAt(block & 0x3f)); + buffer.append("="); + return buffer.toString(); + } + + /** + * Decodes the supplied Base-64 encoded string. + * + * @param string The Base-64 encoded string that is to be decoded. + * @return A byte[] containing the decoded data block. + */ + public static byte[] decode(String string) { + int length = string.length(); + if (length == 0) return new byte[0]; + int pad = (string.charAt(length - 2) == '=') ? 2 : + (string.charAt(length - 1) == '=') ? 1 : 0; + int size = length * 3 / 4 - pad; + byte[] buffer = new byte[size]; + int block; + int i = 0; + int index = 0; + while (i < length) { + block = (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 18 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 12 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff) << 6 | + (ALPHABET.indexOf(string.charAt(i++)) & 0xff); + buffer[index++] = (byte) (block >>> 16); + if (index < size) buffer[index++] = (byte) ((block >>> 8) & 0xff); + if (index < size) buffer[index++] = (byte) (block & 0xff); + } + return buffer; + } + +} diff --git a/update/HMACT64.java b/update/HMACT64.java new file mode 100644 index 0000000..8420fb9 --- /dev/null +++ b/update/HMACT64.java @@ -0,0 +1,116 @@ +/* HMACT64 keyed hashing algorithm + * Copyright (C) 2003 "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.util; + +import java.security.MessageDigest; + +/** + * This is an implementation of the HMACT64 keyed hashing algorithm. + * HMACT64 is defined by Luke Leighton as a modified HMAC-MD5 (RFC 2104) + * in which the key is truncated at 64 bytes (rather than being hashed + * via MD5). + */ +public class HMACT64 extends MessageDigest implements Cloneable { + + private static final int BLOCK_LENGTH = 64; + + private static final byte IPAD = (byte) 0x36; + + private static final byte OPAD = (byte) 0x5c; + + private MessageDigest md5; + + private byte[] ipad = new byte[BLOCK_LENGTH]; + + private byte[] opad = new byte[BLOCK_LENGTH]; + + /** + * Creates an HMACT64 instance which uses the given secret key material. + * + * @param key The key material to use in hashing. + */ + public HMACT64(byte[] key) { + super("HMACT64"); + int length = Math.min(key.length, BLOCK_LENGTH); + for (int i = 0; i < length; i++) { + ipad[i] = (byte) (key[i] ^ IPAD); + opad[i] = (byte) (key[i] ^ OPAD); + } + for (int i = length; i < BLOCK_LENGTH; i++) { + ipad[i] = IPAD; + opad[i] = OPAD; + } + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (Exception ex) { + throw new IllegalStateException(ex.getMessage()); + } + engineReset(); + } + + private HMACT64(HMACT64 hmac) throws CloneNotSupportedException { + super("HMACT64"); + this.ipad = hmac.ipad; + this.opad = hmac.opad; + this.md5 = (MessageDigest) hmac.md5.clone(); + } + + public Object clone() { + try { + return new HMACT64(this); + } catch (CloneNotSupportedException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + protected byte[] engineDigest() { + byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + protected int engineDigest(byte[] buf, int offset, int len) { + byte[] digest = md5.digest(); + md5.update(opad); + md5.update(digest); + try { + return md5.digest(buf, offset, len); + } catch (Exception ex) { + throw new IllegalStateException(); + } + } + + protected int engineGetDigestLength() { + return md5.getDigestLength(); + } + + protected void engineReset() { + md5.reset(); + md5.update(ipad); + } + + protected void engineUpdate(byte b) { + md5.update(b); + } + + protected void engineUpdate(byte[] input, int offset, int len) { + md5.update(input, offset, len); + } + +} diff --git a/update/NtlmHttpFilter.java b/update/NtlmHttpFilter.java new file mode 100644 index 0000000..d160e65 --- /dev/null +++ b/update/NtlmHttpFilter.java @@ -0,0 +1,164 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Jason Pugsley" + * "skeetz" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.http; + +import java.io.*; +import java.util.Enumeration; +import java.net.UnknownHostException; +import javax.servlet.*; +import javax.servlet.http.*; +import jcifs.*; +import jcifs.smb.SmbSession; +import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.smb.SmbAuthException; +import jcifs.util.Base64; + +/** + * This servlet Filter can be used to negotiate password hashes with + * MSIE clients using NTLM SSP. This is similar to Authentication: + * BASIC but weakly encrypted and without requiring the user to re-supply + * authentication credentials. + *

+ * Read jCIFS NTLM HTTP Authentication and the Network Explorer Servlet for complete details. + */ + +public class NtlmHttpFilter implements Filter { + + private String defaultDomain; + + private String domainController; + + private boolean enableBasic; + + private boolean insecureBasic; + + private String realm; + + public void init( FilterConfig filterConfig ) throws ServletException { + String name; + + /* Set jcifs properties we know we want; soTimeout and cachePolicy to 10min. + */ + Config.setProperty( "jcifs.smb.client.soTimeout", "300000" ); + Config.setProperty( "jcifs.netbios.cachePolicy", "600" ); + + Enumeration e = filterConfig.getInitParameterNames(); + while( e.hasMoreElements() ) { + name = (String)e.nextElement(); + if( name.startsWith( "jcifs." )) { + Config.setProperty( name, filterConfig.getInitParameter( name )); + } + } + defaultDomain = Config.getProperty("jcifs.smb.client.domain"); + domainController = Config.getProperty( "jcifs.http.domainController" ); + if( domainController == null ) domainController = defaultDomain; + enableBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.enableBasic")).booleanValue(); + insecureBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.insecureBasic")).booleanValue(); + realm = Config.getProperty("jcifs.http.basicRealm"); + if (realm == null) realm = "jCIFS"; + } + public void destroy() { + } + public void doFilter( ServletRequest request, + ServletResponse response, + FilterChain chain ) throws IOException, ServletException { + HttpServletRequest req; + HttpServletResponse resp; + UniAddress dc; + String msg; + + NtlmPasswordAuthentication ntlm = null; + req = (HttpServletRequest)request; + resp = (HttpServletResponse)response; + msg = req.getHeader( "Authorization" ); + boolean offerBasic = enableBasic && (insecureBasic || req.isSecure()); + + if( msg != null && (msg.startsWith( "NTLM " ) || + (offerBasic && msg.startsWith("Basic ")))) { + dc = UniAddress.getByName( domainController, true ); + if (msg.startsWith("NTLM ")) { + byte[] challenge = SmbSession.getChallenge( dc ); + if(( ntlm = NtlmSsp.authenticate( req, resp, challenge )) == null ) { + return; + } + } else { + String auth = new String(Base64.decode(msg.substring(6)), + "US-ASCII"); + int index = auth.indexOf(':'); + String user = (index != -1) ? auth.substring(0, index) : auth; + String password = (index != -1) ? auth.substring(index + 1) : + ""; + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + String domain = (index != -1) ? user.substring(0, index) : + defaultDomain; + user = (index != -1) ? user.substring(index + 1) : user; + ntlm = new NtlmPasswordAuthentication(domain, user, password); + } + try { + SmbSession.logon( dc, ntlm ); + } catch( SmbAuthException sae ) { + resp.setHeader( "WWW-Authenticate", "NTLM" ); + if (offerBasic) { + resp.addHeader( "WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + resp.setHeader( "Connection", "close" ); + resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); + resp.flushBuffer(); + return; + } + req.getSession().setAttribute( "NtlmHttpAuth", ntlm ); + } else { + HttpSession ssn = req.getSession(false); + if (ssn == null || (ntlm = (NtlmPasswordAuthentication) + ssn.getAttribute("NtlmHttpAuth")) == null) { + resp.setHeader( "WWW-Authenticate", "NTLM" ); + if (offerBasic) { + resp.addHeader( "WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + resp.setHeader( "Connection", "close" ); + resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); + resp.flushBuffer(); + return; + } + } + + chain.doFilter( new NtlmHttpServletRequest( req, ntlm ), response ); + } + + // Added by cgross to work with weblogic 6.1. + public void setFilterConfig( FilterConfig f ) { + try { + init( f ); + } catch( Exception e ) { + e.printStackTrace(); + } + } + public FilterConfig getFilterConfig() { + return null; + } +} + diff --git a/update/NtlmHttpURLConnection.java b/update/NtlmHttpURLConnection.java new file mode 100644 index 0000000..2af8f50 --- /dev/null +++ b/update/NtlmHttpURLConnection.java @@ -0,0 +1,563 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.http; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.PasswordAuthentication; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLDecoder; + +import java.security.Permission; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import jcifs.Config; + +import jcifs.ntlmssp.NtlmFlags; +import jcifs.ntlmssp.NtlmMessage; +import jcifs.ntlmssp.Type1Message; +import jcifs.ntlmssp.Type2Message; +import jcifs.ntlmssp.Type3Message; + +import jcifs.util.Base64; + +/** + * Wraps an HttpURLConnection to provide NTLM authentication + * services. + * + * Please read Using jCIFS NTLM Authentication for HTTP Connections. + */ +public class NtlmHttpURLConnection extends HttpURLConnection { + + private static final int MAX_REDIRECTS = + Integer.parseInt(System.getProperty("http.maxRedirects", "20")); + + private static final int LM_COMPATIBILITY = + Config.getInt("jcifs.smb.lmCompatibility", 0); + + private static final String DEFAULT_DOMAIN; + + private HttpURLConnection connection; + + private Map requestProperties; + + private Map headerFields; + + private String authProperty; + + private String method; + + static { + String domain = System.getProperty("http.auth.ntlm.domain"); + if (domain == null) domain = Type3Message.getDefaultDomain(); + DEFAULT_DOMAIN = domain; + } + + public NtlmHttpURLConnection(HttpURLConnection connection) { + super(connection.getURL()); + this.connection = connection; + requestProperties = new HashMap(); + } + + public void connect() throws IOException { + if (connected) return; + doConnect(); + connected = true; + } + + public URL getURL() { + return connection.getURL(); + } + + public int getContentLength() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentLength(); + } + + public String getContentType() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentType(); + } + + public String getContentEncoding() { + try { + connect(); + } catch (IOException ex) { } + return connection.getContentEncoding(); + } + + public long getExpiration() { + try { + connect(); + } catch (IOException ex) { } + return connection.getExpiration(); + } + + public long getDate() { + try { + connect(); + } catch (IOException ex) { } + return connection.getDate(); + } + + public long getLastModified() { + try { + connect(); + } catch (IOException ex) { } + return connection.getLastModified(); + } + + public String getHeaderField(String header) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderField(header); + } + + private synchronized Map getHeaderFields0() { + if (headerFields != null) return headerFields; + Map map = new HashMap(); + String key = connection.getHeaderFieldKey(0); + String value = connection.getHeaderField(0); + for (int i = 1; key != null || value != null; i++) { + List values = (List) map.get(key); + if (values == null) { + values = new ArrayList(); + map.put(key, values); + } + values.add(value); + key = connection.getHeaderFieldKey(i); + value = connection.getHeaderField(i); + } + Iterator entries = map.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + entry.setValue(Collections.unmodifiableList((List) + entry.getValue())); + } + return (headerFields = Collections.unmodifiableMap(map)); + } + + public Map getHeaderFields() { + synchronized (this) { + if (headerFields != null) return headerFields; + } + try { + connect(); + } catch (IOException ex) { } + return getHeaderFields0(); + } + + public int getHeaderFieldInt(String header, int def) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldInt(header, def); + } + + public long getHeaderFieldDate(String header, long def) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldDate(header, def); + } + + public String getHeaderFieldKey(int index) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderFieldKey(index); + } + + public String getHeaderField(int index) { + try { + connect(); + } catch (IOException ex) { } + return connection.getHeaderField(index); + } + + public Object getContent() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getContent(); + } + + public Object getContent(Class[] classes) throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getContent(classes); + } + + public Permission getPermission() throws IOException { + return connection.getPermission(); + } + + public InputStream getInputStream() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + try { + connect(); + } catch (IOException ex) { } + return connection.getOutputStream(); + } + + public String toString() { + return connection.toString(); + } + + public void setDoInput(boolean doInput) { + connection.setDoInput(doInput); + this.doInput = doInput; + } + + public boolean getDoInput() { + return connection.getDoInput(); + } + + public void setDoOutput(boolean doOutput) { + connection.setDoOutput(doOutput); + this.doOutput = doOutput; + } + + public boolean getDoOutput() { + return connection.getDoOutput(); + } + + public void setAllowUserInteraction(boolean allowUserInteraction) { + connection.setAllowUserInteraction(allowUserInteraction); + this.allowUserInteraction = allowUserInteraction; + } + + public boolean getAllowUserInteraction() { + return connection.getAllowUserInteraction(); + } + + public void setUseCaches(boolean useCaches) { + connection.setUseCaches(useCaches); + this.useCaches = useCaches; + } + + public boolean getUseCaches() { + return connection.getUseCaches(); + } + + public void setIfModifiedSince(long ifModifiedSince) { + connection.setIfModifiedSince(ifModifiedSince); + this.ifModifiedSince = ifModifiedSince; + } + + public long getIfModifiedSince() { + return connection.getIfModifiedSince(); + } + + public boolean getDefaultUseCaches() { + return connection.getDefaultUseCaches(); + } + + public void setDefaultUseCaches(boolean defaultUseCaches) { + connection.setDefaultUseCaches(defaultUseCaches); + } + + public void setRequestProperty(String key, String value) { + if (key == null) throw new NullPointerException(); + List values = new ArrayList(); + values.add(value); + boolean found = false; + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + if (key.equalsIgnoreCase((String) entry.getKey())) { + entry.setValue(value); + found = true; + break; + } + } + if (!found) requestProperties.put(key, values); + } + connection.setRequestProperty(key, value); + } + + public void addRequestProperty(String key, String value) { + if (key == null) throw new NullPointerException(); + List values = null; + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + if (key.equalsIgnoreCase((String) entry.getKey())) { + values = (List) entry.getValue(); + values.add(value); + break; + } + } + if (values == null) { + values = new ArrayList(); + values.add(value); + requestProperties.put(key, values); + } + } + // 1.3-compatible. + StringBuffer buffer = new StringBuffer(); + Iterator propertyValues = values.iterator(); + while (propertyValues.hasNext()) { + buffer.append(propertyValues.next()); + if (propertyValues.hasNext()) { + buffer.append(", "); + } + } + connection.setRequestProperty(key, buffer.toString()); + } + + public String getRequestProperty(String key) { + return connection.getRequestProperty(key); + } + + public Map getRequestProperties() { + Map map = new HashMap(); + synchronized (requestProperties) { + Iterator entries = requestProperties.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + map.put(entry.getKey(), + Collections.unmodifiableList((List) entry.getValue())); + } + } + return Collections.unmodifiableMap(map); + } + + public void setInstanceFollowRedirects(boolean instanceFollowRedirects) { + connection.setInstanceFollowRedirects(instanceFollowRedirects); + } + + public boolean getInstanceFollowRedirects() { + return connection.getInstanceFollowRedirects(); + } + + public void setRequestMethod(String requestMethod) + throws ProtocolException { + connection.setRequestMethod(requestMethod); + } + + public String getRequestMethod() { + return connection.getRequestMethod(); + } + + public int getResponseCode() throws IOException { + return connection.getResponseCode(); + } + + public String getResponseMessage() throws IOException { + return connection.getResponseMessage(); + } + + public void disconnect() { + connection.disconnect(); + connected = false; + } + + public boolean usingProxy() { + return connection.usingProxy(); + } + + public InputStream getErrorStream() { + return connection.getErrorStream(); + } + + private int parseResponseCode() throws IOException { + try { + String response = connection.getHeaderField(0); + int index = response.indexOf(' '); + while (response.charAt(index) == ' ') index++; + return Integer.parseInt(response.substring(index, index + 3)); + } catch (Exception ex) { + throw new IOException(ex.getMessage()); + } + } + + private synchronized void doConnect() throws IOException { + connection.connect(); + int response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + Type1Message type1 = (Type1Message) attemptNegotiation(response); + if (type1 == null) return; // no NTLM + int attempt = 0; + while (attempt < MAX_REDIRECTS) { + connection.setRequestProperty(authProperty, method + ' ' + + Base64.encode(type1.toByteArray())); + connection.connect(); // send type 1 + response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + Type3Message type3 = (Type3Message) attemptNegotiation(response); + if (type3 == null) return; + connection.setRequestProperty(authProperty, method + ' ' + + Base64.encode(type3.toByteArray())); + connection.connect(); // send type 3 + response = parseResponseCode(); + if (response != HTTP_UNAUTHORIZED && response != HTTP_PROXY_AUTH) { + return; + } + attempt++; + if (attempt < MAX_REDIRECTS) reconnect(); + } + throw new IOException("Unable to negotiate NTLM authentication."); + } + + private NtlmMessage attemptNegotiation(int response) throws IOException { + authProperty = null; + method = null; + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null && errorStream.available() != 0) { + int count; + byte[] buf = new byte[1024]; + while ((count = errorStream.read(buf, 0, 1024)) != -1); + } + String authHeader; + if (response == HTTP_UNAUTHORIZED) { + authHeader = "WWW-Authenticate"; + authProperty = "Authorization"; + } else { + authHeader = "Proxy-Authenticate"; + authProperty = "Proxy-Authorization"; + } + String authorization = null; + List methods = (List) getHeaderFields0().get(authHeader); + if (methods == null) return null; + Iterator iterator = methods.iterator(); + while (iterator.hasNext()) { + String authMethod = (String) iterator.next(); + if (authMethod.startsWith("NTLM")) { + if (authMethod.length() == 4) { + method = "NTLM"; + break; + } + if (authMethod.indexOf(' ') != 4) continue; + method = "NTLM"; + authorization = authMethod.substring(5).trim(); + break; + } else if (authMethod.startsWith("Negotiate")) { + if (authMethod.length() == 9) { + method = "Negotiate"; + break; + } + if (authMethod.indexOf(' ') != 9) continue; + method = "Negotiate"; + authorization = authMethod.substring(10).trim(); + break; + } + } + if (method == null) return null; + NtlmMessage message = (authorization != null) ? + new Type2Message(Base64.decode(authorization)) : null; + reconnect(); + if (message == null) { + message = new Type1Message(); + if (LM_COMPATIBILITY > 2) { + message.setFlag(NtlmFlags.NTLMSSP_REQUEST_TARGET, true); + } + } else { + String domain = DEFAULT_DOMAIN; + String user = Type3Message.getDefaultUser(); + String password = Type3Message.getDefaultPassword(); + String userInfo = url.getUserInfo(); + if (userInfo != null) { + userInfo = URLDecoder.decode(userInfo); + int index = userInfo.indexOf(':'); + user = (index != -1) ? userInfo.substring(0, index) : userInfo; + if (index != -1) password = userInfo.substring(index + 1); + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + domain = (index != -1) ? user.substring(0, index) : domain; + user = (index != -1) ? user.substring(index + 1) : user; + } + if (user == null) { + try { + URL url = getURL(); + String protocol = url.getProtocol(); + int port = url.getPort(); + if (port == -1) { + port = "https".equalsIgnoreCase(protocol) ? 443 : 80; + } + PasswordAuthentication auth = + Authenticator.requestPasswordAuthentication(null, + port, protocol, "", method); + if (auth != null) { + user = auth.getUserName(); + password = new String(auth.getPassword()); + } + } catch (Exception ex) { } + } + Type2Message type2 = (Type2Message) message; + message = new Type3Message(type2, password, domain, user, + Type3Message.getDefaultWorkstation()); + } + return message; + } + + private void reconnect() throws IOException { + connection = (HttpURLConnection) connection.getURL().openConnection(); + headerFields = null; + synchronized (requestProperties) { + Iterator properties = requestProperties.entrySet().iterator(); + while (properties.hasNext()) { + Map.Entry property = (Map.Entry) properties.next(); + String key = (String) property.getKey(); + StringBuffer value = new StringBuffer(); + Iterator values = ((List) property.getValue()).iterator(); + while (values.hasNext()) { + value.append(values.next()); + if (values.hasNext()) value.append(", "); + } + connection.setRequestProperty(key, value.toString()); + } + } + connection.setAllowUserInteraction(allowUserInteraction); + connection.setDoInput(doInput); + connection.setDoOutput(doOutput); + connection.setIfModifiedSince(ifModifiedSince); + connection.setUseCaches(useCaches); + } +} diff --git a/update/NtlmMessage.java b/update/NtlmMessage.java new file mode 100644 index 0000000..3b8c692 --- /dev/null +++ b/update/NtlmMessage.java @@ -0,0 +1,138 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import jcifs.Config; + +/** + * Abstract superclass for all NTLMSSP messages. + */ +public abstract class NtlmMessage implements NtlmFlags { + + /** + * The NTLMSSP "preamble". + */ + protected static final byte[] NTLMSSP_SIGNATURE = new byte[] { + (byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', + (byte) 'S', (byte) 'S', (byte) 'P', (byte) 0 + }; + + private static final String OEM_ENCODING = + Config.getProperty("jcifs.smb.client.codepage", + Config.getProperty("jcifs.encoding", + System.getProperty("file.encoding"))); + + private int flags; + + /** + * Returns the flags currently in use for this message. + * + * @return An int containing the flags in use for this + * message. + */ + public int getFlags() { + return flags; + } + + /** + * Sets the flags for this message. + * + * @param flags The flags for this message. + */ + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * Returns the status of the specified flag. + * + * @param flag The flag to test (i.e., NTLMSSP_NEGOTIATE_OEM). + * @return A boolean indicating whether the flag is set. + */ + public boolean getFlag(int flag) { + return (getFlags() & flag) != 0; + } + + /** + * Sets or clears the specified flag. + * + * @param flag The flag to set/clear (i.e., + * NTLMSSP_NEGOTIATE_OEM). + * @param value Indicates whether to set (true) or + * clear (false) the specified flag. + */ + public void setFlag(int flag, boolean value) { + setFlags(value ? (getFlags() | flag) : + (getFlags() & (0xffffffff ^ flag))); + } + + static int readULong(byte[] src, int index) { + return (src[index] & 0xff) | + ((src[index + 1] & 0xff) << 8) | + ((src[index + 2] & 0xff) << 16) | + ((src[index + 3] & 0xff) << 24); + } + + static int readUShort(byte[] src, int index) { + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); + } + + static byte[] readSecurityBuffer(byte[] src, int index) { + int length = readUShort(src, index); + int offset = readULong(src, index + 4); + byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + static void writeULong(byte[] dest, int offset, int ulong) { + dest[offset] = (byte) (ulong & 0xff); + dest[offset + 1] = (byte) (ulong >> 8 & 0xff); + dest[offset + 2] = (byte) (ulong >> 16 & 0xff); + dest[offset + 3] = (byte) (ulong >> 24 & 0xff); + } + + static void writeUShort(byte[] dest, int offset, int ushort) { + dest[offset] = (byte) (ushort & 0xff); + dest[offset + 1] = (byte) (ushort >> 8 & 0xff); + } + + static void writeSecurityBuffer(byte[] dest, int offset, int bodyOffset, + byte[] src) { + int length = (src != null) ? src.length : 0; + if (length == 0) return; + writeUShort(dest, offset, length); + writeUShort(dest, offset + 2, length); + writeULong(dest, offset + 4, bodyOffset); + System.arraycopy(src, 0, dest, bodyOffset, length); + } + + static String getOEMEncoding() { + return OEM_ENCODING; + } + + /** + * Returns the raw byte representation of this message. + * + * @return A byte[] containing the raw message material. + */ + public abstract byte[] toByteArray(); + +} diff --git a/update/NtlmPasswordAuthentication.java b/update/NtlmPasswordAuthentication.java new file mode 100644 index 0000000..a180c0c --- /dev/null +++ b/update/NtlmPasswordAuthentication.java @@ -0,0 +1,348 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "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.util.DES; +import jcifs.util.MD4; +import jcifs.util.HMACT64; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.security.SecureRandom; +import java.util.Arrays; +import jcifs.Config; + +/** + * This class stores and encrypts NTLM user credentials. The default + * credentials are retrieved from the jcifs.smb.client.domain, + * jcifs.smb.client.username, and jcifs.smb.client.password + * properties. + *

+ * Read jCIFS Exceptions and + * NtlmAuthenticator for related information. + */ + +public final class NtlmPasswordAuthentication implements Principal { + + private static final int LM_COMPATIBILITY = + Config.getInt("jcifs.smb.lmCompatibility", 0); + + private static final String DEFAULT_DOMAIN = + Config.getProperty("jcifs.smb.client.domain", "?"); + + private static final String DEFAULT_USERNAME = + Config.getProperty("jcifs.smb.client.username", "GUEST"); + + private static final String DEFAULT_PASSWORD = + Config.getProperty("jcifs.smb.client.password", ""); + + private static final SecureRandom random = new SecureRandom(); + + // KGS!@#$% + static final byte[] S8 = { + (byte)0x4b, (byte)0x47, (byte)0x53, (byte)0x21, + (byte)0x40, (byte)0x23, (byte)0x24, (byte)0x25 + }; + static void E( byte[] key, byte[] data, byte[] e ) { + byte[] key7 = new byte[7]; + byte[] e8 = new byte[8]; + + for( int i = 0; i < key.length / 7; i++ ) { + System.arraycopy( key, i * 7, key7, 0, 7 ); + DES des = new DES( key7 ); + des.encrypt( data, e8 ); + System.arraycopy( e8, 0, e, i * 8, 8 ); + } + } +/** + * Generate the ANSI DES hash for the password associated with these credentials. + */ + static public byte[] getPreNTLMResponse( String password, byte[] challenge ) { + byte[] p14 = new byte[14]; + byte[] p21 = new byte[21]; + byte[] p24 = new byte[24]; + byte[] passwordBytes; + try { + passwordBytes = password.toUpperCase().getBytes( ServerMessageBlock.encoding ); + } catch( UnsupportedEncodingException uee ) { + return null; + } + int passwordLength = passwordBytes.length; + + // Only encrypt the first 14 bytes of the password for Pre 0.12 NT LM + if( passwordLength > 14) { + passwordLength = 14; + } + System.arraycopy( passwordBytes, 0, p14, 0, passwordLength ); + E( p14, S8, p21); + E( p21, challenge, p24); + return p24; + } +/** + * Generate the Unicode MD4 hash for the password associated with these credentials. + */ + static public byte[] getNTLMResponse( String password, byte[] challenge ) { + byte[] uni = null; + byte[] p21 = new byte[21]; + byte[] p24 = new byte[24]; + + try { + uni = password.getBytes( "UnicodeLittleUnmarked" ); + } catch( UnsupportedEncodingException uee ) { + Log.printStackTrace( "password encryption exception", uee ); + } + MD4 md4 = new MD4(); + md4.update( uni ); + try { + md4.digest(p21, 0, 16); + } catch (Exception ex) { + Log.printStackTrace( "digest exception", ex ); + } + E( p21, challenge, p24 ); + return p24; + } + + /** + * Creates the LMv2 response for the supplied information. + * + * @param domain The domain in which the username exists. + * @param user The username. + * @param password The user's password. + * @param challenge The server challenge. + * @param clientChallenge The client challenge (nonce). + */ + public static byte[] getLMv2Response(String domain, String user, + String password, byte[] challenge, byte[] clientChallenge) { + try { + byte[] hash = new byte[16]; + byte[] response = new byte[24]; + MD4 md4 = new MD4(); + md4.update(password.getBytes("UnicodeLittleUnmarked")); + HMACT64 hmac = new HMACT64(md4.digest()); + hmac.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmac.update(domain.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmac = new HMACT64(hmac.digest()); + hmac.update(challenge); + hmac.update(clientChallenge); + hmac.digest(response, 0, 16); + System.arraycopy(clientChallenge, 0, response, 16, 8); + return response; + } catch (Exception ex) { + Log.printStackTrace("Error creating LMv2 response", ex); + return null; + } + } + + static final NtlmPasswordAuthentication NULL = + new NtlmPasswordAuthentication( "", "", "" ); + static final NtlmPasswordAuthentication GUEST = + new NtlmPasswordAuthentication( "?", "GUEST", "" ); + + String domain; + String username; + String password; + byte[] ansiHash; + byte[] unicodeHash; + boolean hashesExternal = false; + +/** + * Create an NtlmPasswordAuthentication object from the userinfo + * component of an SMB URL like "domain;user:pass". This constructor + * is used internally be jCIFS when parsing SMB URLs. + */ + + public NtlmPasswordAuthentication( String userInfo ) { + domain = username = password = null; + + if( userInfo != null ) { + int i, u, end; + char c; + + end = userInfo.length(); + for( i = 0, u = 0; i < end; i++ ) { + c = userInfo.charAt( i ); + if( c == ';' ) { + domain = userInfo.substring( 0, i ); + u = i + 1; + } else if( c == ':' ) { + password = userInfo.substring( i + 1 ); + break; + } + } + username = userInfo.substring( u, i ); + } + + if( domain == null ) this.domain = DEFAULT_DOMAIN; + if( username == null ) this.username = DEFAULT_USERNAME; + if( password == null ) this.password = DEFAULT_PASSWORD; + } +/** + * Create an NtlmPasswordAuthentication object from a + * domain, username, and password. Parameters that are null + * will be substituted with jcifs.smb.client.domain, + * jcifs.smb.client.username, jcifs.smb.client.password + * property values. + */ + public NtlmPasswordAuthentication( String domain, String username, String password ) { + this.domain = domain; + this.username = username; + this.password = password; + if( domain == null ) this.domain = DEFAULT_DOMAIN; + if( username == null ) this.username = DEFAULT_USERNAME; + if( password == null ) this.password = DEFAULT_PASSWORD; + } +/** + * Create an NtlmPasswordAuthentication object with raw password + * hashes. This is used exclusively by the jcifs.http.NtlmSsp + * class which is in turn used by NTLM HTTP authentication functionality. + */ + public NtlmPasswordAuthentication( String domain, String username, + byte[] ansiHash, byte[] unicodeHash ) { + if( domain == null || username == null || + ansiHash == null || unicodeHash == null ) { + throw new IllegalArgumentException( "External credentials cannot null" ); + } + this.domain = domain; + this.username = username; + this.password = null; + this.ansiHash = ansiHash; + this.unicodeHash = unicodeHash; + hashesExternal = true; + } + +/** + * Returns the domain. + */ + public String getDomain() { + return domain; + } +/** + * Returns the username. + */ + public String getUsername() { + return username; + } +/** + * Returns the password in plain text or null if the raw password + * hashes were used to construct this NtlmPasswordAuthentication + * object which will be the case when NTLM HTTP Authentication is + * used. There is no way to retrieve a users password in plain text unless + * it is supplied by the user at runtime. + */ + public String getPassword() { + return password; + } +/** + * Return the domain and username in the format: + * domain\\username. This is equivalent to toString(). + */ + public String getName() { + boolean d = domain.length() > 0 && domain.equals( "?" ) == false; + return d ? domain + "\\" + username : username; + } +/** + * Computes the 24 byte ANSI password hash given the 8 byte server challenge. + */ + public byte[] getAnsiHash( byte[] challenge ) { + if( hashesExternal ) { + return ansiHash; + } + switch (LM_COMPATIBILITY) { + case 0: + case 1: + return getPreNTLMResponse( password, challenge ); + case 2: + return getNTLMResponse( password, challenge ); + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + random.nextBytes(clientChallenge); + return getLMv2Response(domain, username, password, challenge, + clientChallenge); + default: + return getPreNTLMResponse( password, challenge ); + } + } +/** + * Computes the 24 byte Unicode password hash given the 8 byte server challenge. + */ + public byte[] getUnicodeHash( byte[] challenge ) { + if( hashesExternal ) { + return unicodeHash; + } + switch (LM_COMPATIBILITY) { + case 0: + case 1: + case 2: + return getNTLMResponse( password, challenge ); + case 3: + case 4: + case 5: + /* + byte[] clientChallenge = new byte[8]; + random.nextBytes(clientChallenge); + return getNTLMv2Response(domain, username, password, null, + challenge, clientChallenge); + */ + return new byte[0]; + default: + return getNTLMResponse( password, challenge ); + } + } +/** + * Compares two NtlmPasswordAuthentication objects for + * equality. Two NtlmPasswordAuthentication objects are equal if + * their caseless domain and username fields are equal and either both hashes are external and they are equal or both internally supplied passwords are equal. If one NtlmPasswordAuthentication object has external hashes (meaning negotiated via NTLM HTTP Authentication) and the other does not they will not be equal. This is technically not correct however the server 8 byte challage would be required to compute and compare the password hashes but that it not available with this method. + */ + public boolean equals( Object obj ) { + if( obj instanceof NtlmPasswordAuthentication ) { + NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication)obj; + if( ntlm.domain.toUpperCase().equals( domain.toUpperCase() ) && + ntlm.username.toUpperCase().equals( username.toUpperCase() )) { + if( hashesExternal && ntlm.hashesExternal ) { + return Arrays.equals( ansiHash, ntlm.ansiHash ) && + Arrays.equals( unicodeHash, ntlm.unicodeHash ); + /* This still isn't quite right. If one npa object does not have external + * hashes and the other does then they will not be considered equal even + * though they may be. + */ + } else if( !hashesExternal && password.equals( ntlm.password )) { + return true; + } + } + } + return false; + } + + +/** + * Return the upcased username hash code. + */ + public int hashCode() { + return getName().toUpperCase().hashCode(); + } +/** + * Return the domain and username in the format: + * domain\\username. This is equivalent to getName(). + */ + public String toString() { + return getName(); + } +} + diff --git a/update/NtlmServlet.java b/update/NtlmServlet.java new file mode 100644 index 0000000..1304051 --- /dev/null +++ b/update/NtlmServlet.java @@ -0,0 +1,158 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.http; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import jcifs.Config; +import jcifs.UniAddress; + +import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.smb.SmbAuthException; +import jcifs.smb.SmbSession; + +import jcifs.util.Base64; + +/** + * This servlet may be used with pre-2.3 servlet containers + * to protect content with NTLM HTTP Authentication. Servlets that + * extend this abstract base class may be authenticatied against an SMB + * server or domain controller depending on how the + * jcifs.smb.client.domain or jcifs.http.domainController + * properties are be specified. With later containers the + * NtlmHttpFilter should be used/b>. For custom NTLM HTTP Authentication schemes the NtlmSsp may be used. + *

+ * Read jCIFS NTLM HTTP Authentication and the Network Explorer Servlet related information. + */ + +public abstract class NtlmServlet extends HttpServlet { + + private String defaultDomain; + + private String domainController; + + private boolean enableBasic; + + private boolean insecureBasic; + + private String realm; + + public void init(ServletConfig config) throws ServletException { + super.init(config); + + /* Set jcifs properties we know we want; soTimeout and cachePolicy to 10min. + */ + Config.setProperty( "jcifs.smb.client.soTimeout", "300000" ); + Config.setProperty( "jcifs.netbios.cachePolicy", "600" ); + + Enumeration e = config.getInitParameterNames(); + String name; + while (e.hasMoreElements()) { + name = (String) e.nextElement(); + if (name.startsWith("jcifs.")) { + Config.setProperty(name, config.getInitParameter(name)); + } + } + defaultDomain = Config.getProperty("jcifs.smb.client.domain"); + domainController = Config.getProperty("jcifs.http.domainController"); + if (domainController == null) domainController = defaultDomain; + enableBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.enableBasic")).booleanValue(); + insecureBasic = Boolean.valueOf( + Config.getProperty("jcifs.http.insecureBasic")).booleanValue(); + realm = Config.getProperty("jcifs.http.basicRealm"); + if (realm == null) realm = "jCIFS"; + } + + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + boolean offerBasic = enableBasic && + (insecureBasic || request.isSecure()); + String msg = request.getHeader("Authorization"); + if (msg != null && (msg.startsWith("NTLM ") || + (offerBasic && msg.startsWith("Basic ")))) { + UniAddress dc = UniAddress.getByName(domainController, true); + NtlmPasswordAuthentication ntlm; + if (msg.startsWith("NTLM ")) { + byte[] challenge = SmbSession.getChallenge(dc); + ntlm = NtlmSsp.authenticate(request, response, challenge); + if (ntlm == null) return; + } else { + String auth = new String(Base64.decode(msg.substring(6)), + "US-ASCII"); + int index = auth.indexOf(':'); + String user = (index != -1) ? auth.substring(0, index) : auth; + String password = (index != -1) ? auth.substring(index + 1) : + ""; + index = user.indexOf('\\'); + if (index == -1) index = user.indexOf('/'); + String domain = (index != -1) ? user.substring(0, index) : + defaultDomain; + user = (index != -1) ? user.substring(index + 1) : user; + ntlm = new NtlmPasswordAuthentication(domain, user, password); + } + try { + SmbSession.logon(dc, ntlm); + } catch (SmbAuthException sae) { + response.setHeader("WWW-Authenticate", "NTLM"); + if (offerBasic) { + response.addHeader("WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + response.setHeader("Connection", "close"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + return; + } + HttpSession ssn = request.getSession(); + ssn.setAttribute("NtlmHttpAuth", ntlm); + ssn.setAttribute( "ntlmdomain", ntlm.getDomain() ); + ssn.setAttribute( "ntlmuser", ntlm.getUsername() ); + } else { + HttpSession ssn = request.getSession(false); + if (ssn == null || ssn.getAttribute("NtlmHttpAuth") == null) { + response.setHeader("WWW-Authenticate", "NTLM"); + if (offerBasic) { + response.addHeader("WWW-Authenticate", "Basic realm=\"" + + realm + "\""); + } + response.setHeader("Connection", "close"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + return; + } + } + super.service(request, response); + } +} + diff --git a/update/NtlmSsp.java b/update/NtlmSsp.java new file mode 100644 index 0000000..f781f1c --- /dev/null +++ b/update/NtlmSsp.java @@ -0,0 +1,112 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * "Jason Pugsley" + * "skeetz" + * + * 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.http; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import jcifs.smb.NtlmPasswordAuthentication; + +import jcifs.util.Base64; + +import jcifs.ntlmssp.NtlmFlags; +import jcifs.ntlmssp.Type1Message; +import jcifs.ntlmssp.Type2Message; +import jcifs.ntlmssp.Type3Message; + +/** + * This class is used internally by NtlmHttpFilter, + * NtlmServlet, and NetworkExplorer to negiotiate password + * hashes via NTLM SSP with MSIE. It might also be used directly by servlet + * containers to incorporate similar functionality. + *

+ * How NTLMSSP is used in conjunction with HTTP and MSIE clients is + * described in an NTLM + * Authentication Scheme for HTTP.

Also, read jCIFS NTLM HTTP Authentication and + * the Network Explorer Servlet related information. + */ + +public class NtlmSsp implements NtlmFlags { + + /** + * Calls the static {@link #authenticate(HttpServletRequest, + * HttpServletResponse, byte[])} method to perform NTLM authentication + * for the specified servlet request. + * + * @param req The request being serviced. + * @param resp The response. + * @param challenge The domain controller challenge. + * @throws IOException If an IO error occurs. + * @throws ServletException If an error occurs. + */ + public NtlmPasswordAuthentication doAuthentication( + HttpServletRequest req, HttpServletResponse resp, byte[] challenge) + throws IOException, ServletException { + return authenticate(req, resp, challenge); + } + + /** + * Performs NTLM authentication for the servlet request. + * + * @param req The request being serviced. + * @param resp The response. + * @param challenge The domain controller challenge. + * @throws IOException If an IO error occurs. + * @throws ServletException If an error occurs. + */ + public static NtlmPasswordAuthentication authenticate( + HttpServletRequest req, HttpServletResponse resp, byte[] challenge) + throws IOException, ServletException { + String msg = req.getHeader("Authorization"); + if (msg != null && msg.startsWith("NTLM ")) { + byte[] src = Base64.decode(msg.substring(5)); + if (src[8] == 1) { + Type1Message type1 = new Type1Message(src); + Type2Message type2 = new Type2Message(type1, challenge, null); + msg = Base64.encode(type2.toByteArray()); + resp.setHeader( "WWW-Authenticate", "NTLM " + msg ); + } else if (src[8] == 3) { + Type3Message type3 = new Type3Message(src); + byte[] lmResponse = type3.getLMResponse(); + if (lmResponse == null) lmResponse = new byte[0]; + byte[] ntResponse = type3.getNTResponse(); + if (ntResponse == null) ntResponse = new byte[0]; + return new NtlmPasswordAuthentication(type3.getDomain(), + type3.getUser(), lmResponse, ntResponse); + } + } else { + resp.setHeader("WWW-Authenticate", "NTLM"); + resp.setHeader("Connection", "close"); + } + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + resp.setContentLength( 0 ); + resp.flushBuffer(); + return null; + } + +} + diff --git a/update/Type1Message.java b/update/Type1Message.java new file mode 100644 index 0000000..ce31ec2 --- /dev/null +++ b/update/Type1Message.java @@ -0,0 +1,248 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import jcifs.netbios.NbtAddress; + +import jcifs.Config; + +/** + * Represents an NTLMSSP Type-1 message. + */ +public class Type1Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final String DEFAULT_WORKSTATION; + + private String suppliedDomain; + + private String suppliedWorkstation; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + String defaultWorkstation = null; + try { + defaultWorkstation = NbtAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ex) { } + DEFAULT_WORKSTATION = defaultWorkstation; + } + + /** + * Creates a Type-1 message using default values from the current + * environment. + */ + public Type1Message() { + this(getDefaultFlags(), getDefaultDomain(), getDefaultWorkstation()); + } + + /** + * Creates a Type-1 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param suppliedDomain The supplied authentication domain. + * @param suppliedWorkstation The supplied workstation name. + */ + public Type1Message(int flags, String suppliedDomain, + String suppliedWorkstation) { + setFlags(flags); + setSuppliedDomain(suppliedDomain); + setSuppliedWorkstation(suppliedWorkstation); + } + + /** + * Creates a Type-1 message using the given raw Type-1 material. + * + * @param material The raw Type-1 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type1Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the supplied authentication domain. + * + * @return A String containing the supplied domain. + */ + public String getSuppliedDomain() { + return suppliedDomain; + } + + /** + * Sets the supplied authentication domain for this message. + * + * @param suppliedDomain The supplied domain for this message. + */ + public void setSuppliedDomain(String suppliedDomain) { + this.suppliedDomain = suppliedDomain; + } + + /** + * Returns the supplied workstation name. + * + * @return A String containing the supplied workstation name. + */ + public String getSuppliedWorkstation() { + return suppliedWorkstation; + } + + /** + * Sets the supplied workstation name for this message. + * + * @param suppliedWorkstation The supplied workstation for this message. + */ + public void setSuppliedWorkstation(String suppliedWorkstation) { + this.suppliedWorkstation = suppliedWorkstation; + } + + public byte[] toByteArray() { + try { + String suppliedDomain = getSuppliedDomain(); + String suppliedWorkstation = getSuppliedWorkstation(); + int flags = getFlags(); + boolean hostInfo = false; + byte[] domain = new byte[0]; + if (suppliedDomain != null && suppliedDomain.length() != 0) { + hostInfo = true; + flags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED; + domain = suppliedDomain.toUpperCase().getBytes( + getOEMEncoding()); + } else { + flags &= (NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED ^ 0xffffffff); + } + byte[] workstation = new byte[0]; + if (suppliedWorkstation != null && + suppliedWorkstation.length() != 0) { + hostInfo = true; + flags |= NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED; + workstation = + suppliedWorkstation.toUpperCase().getBytes( + getOEMEncoding()); + } else { + flags &= (NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED ^ + 0xffffffff); + } + byte[] type1 = new byte[hostInfo ? + (32 + domain.length + workstation.length) : 16]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type1, 0, 8); + writeULong(type1, 8, 1); + writeULong(type1, 12, flags); + if (hostInfo) { + writeSecurityBuffer(type1, 16, 32, domain); + writeSecurityBuffer(type1, 24, 32 + domain.length, workstation); + } + return type1; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String suppliedDomain = getSuppliedDomain(); + String suppliedWorkstation = getSuppliedWorkstation(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (suppliedDomain != null) { + buffer.append("suppliedDomain: ").append(suppliedDomain); + } + if (suppliedWorkstation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("suppliedWorkstation: ").append(suppliedWorkstation); + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-1 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default domain from the current environment. + * + * @return A String containing the default domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + /** + * Returns the default workstation from the current environment. + * + * @return A String containing the default workstation. + */ + public static String getDefaultWorkstation() { + return DEFAULT_WORKSTATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 1) { + throw new IOException("Not a Type 1 message."); + } + int flags = readULong(material, 12); + String suppliedDomain = null; + if ((flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) != 0) { + byte[] domain = readSecurityBuffer(material, 16); + suppliedDomain = new String(domain, getOEMEncoding()); + } + String suppliedWorkstation = null; + if ((flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) != 0) { + byte[] workstation = readSecurityBuffer(material, 24); + suppliedWorkstation = new String(workstation, getOEMEncoding()); + } + setFlags(flags); + setSuppliedDomain(suppliedDomain); + setSuppliedWorkstation(suppliedWorkstation); + } + +} diff --git a/update/Type2Message.java b/update/Type2Message.java new file mode 100644 index 0000000..a27db1f --- /dev/null +++ b/update/Type2Message.java @@ -0,0 +1,414 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import jcifs.Config; + +import jcifs.netbios.NbtAddress; + +/** + * Represents an NTLMSSP Type-2 message. + */ +public class Type2Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final byte[] DEFAULT_TARGET_INFORMATION; + + private byte[] challenge; + + private String target; + + private byte[] context; + + private byte[] targetInformation; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + byte[] domain = new byte[0]; + if (DEFAULT_DOMAIN != null) { + try { + domain = DEFAULT_DOMAIN.getBytes("UnicodeLittleUnmarked"); + } catch (IOException ex) { } + } + int domainLength = domain.length; + byte[] server = new byte[0]; + try { + String host = NbtAddress.getLocalHost().getHostName(); + if (host != null) { + try { + server = host.getBytes("UnicodeLittleUnmarked"); + } catch (IOException ex) { } + } + } catch (UnknownHostException ex) { } + int serverLength = server.length; + byte[] targetInfo = new byte[(domainLength > 0 ? domainLength + 4 : 0) + + (serverLength > 0 ? serverLength + 4 : 0) + 4]; + int offset = 0; + if (domainLength > 0) { + writeUShort(targetInfo, offset, 2); + offset += 2; + writeUShort(targetInfo, offset, domainLength); + offset += 2; + System.arraycopy(domain, 0, targetInfo, offset, domainLength); + offset += domainLength; + } + if (serverLength > 0) { + writeUShort(targetInfo, offset, 1); + offset += 2; + writeUShort(targetInfo, offset, serverLength); + offset += 2; + System.arraycopy(server, 0, targetInfo, offset, serverLength); + } + DEFAULT_TARGET_INFORMATION = targetInfo; + } + + /** + * Creates a Type-2 message using default values from the current + * environment. + */ + public Type2Message() { + this(getDefaultFlags(), null, null); + } + + /** + * Creates a Type-2 message in response to the given Type-1 message + * using default values from the current environment. + * + * @param type1 The Type-1 message which this represents a response to. + */ + public Type2Message(Type1Message type1) { + this(type1, null, null); + } + + /** + * Creates a Type-2 message in response to the given Type-1 message. + * + * @param type1 The Type-1 message which this represents a response to. + * @param challenge The challenge from the domain controller/server. + * @param target The authentication target. + */ + public Type2Message(Type1Message type1, byte[] challenge, String target) { + this(getDefaultFlags(type1), challenge, (type1 != null && + target == null && type1.getFlag(NTLMSSP_REQUEST_TARGET)) ? + getDefaultDomain() : target); + } + + /** + * Creates a Type-2 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param challenge The challenge from the domain controller/server. + * @param target The authentication target. + */ + public Type2Message(int flags, byte[] challenge, String target) { + setFlags(flags); + setChallenge(challenge); + setTarget(target); + if (target != null) setTargetInformation(getDefaultTargetInformation()); + } + + /** + * Creates a Type-2 message using the given raw Type-2 material. + * + * @param material The raw Type-2 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type2Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the challenge for this message. + * + * @return A byte[] containing the challenge. + */ + public byte[] getChallenge() { + return challenge; + } + + /** + * Sets the challenge for this message. + * + * @param challenge The challenge from the domain controller/server. + */ + public void setChallenge(byte[] challenge) { + this.challenge = challenge; + } + + /** + * Returns the authentication target. + * + * @return A String containing the authentication target. + */ + public String getTarget() { + return target; + } + + /** + * Sets the authentication target. + * + * @param target The authentication target. + */ + public void setTarget(String target) { + this.target = target; + } + + /** + * Returns the target information block. + * + * @return A byte[] containing the target information block. + * The target information block is used by the client to create an + * NTLMv2 response. + */ + public byte[] getTargetInformation() { + return targetInformation; + } + + /** + * Sets the target information block. + * The target information block is used by the client to create + * an NTLMv2 response. + * + * @param targetInformation The target information block. + */ + public void setTargetInformation(byte[] targetInformation) { + this.targetInformation = targetInformation; + } + + /** + * Returns the local security context. + * + * @return A byte[] containing the local security + * context. This is used by the client to negotiate local + * authentication. + */ + public byte[] getContext() { + return context; + } + + /** + * Sets the local security context. This is used by the client + * to negotiate local authentication. + * + * @param context The local security context. + */ + public void setContext(byte[] context) { + this.context = context; + } + + public byte[] toByteArray() { + try { + String targetName = getTarget(); + byte[] challenge = getChallenge(); + byte[] context = getContext(); + byte[] targetInformation = getTargetInformation(); + int flags = getFlags(); + byte[] target = new byte[0]; + if ((flags & (NTLMSSP_TARGET_TYPE_DOMAIN | + NTLMSSP_TARGET_TYPE_SERVER | + NTLMSSP_TARGET_TYPE_SHARE)) != 0) { + if (targetName != null && targetName.length() != 0) { + target = (flags & NTLMSSP_NEGOTIATE_UNICODE) != 0 ? + targetName.getBytes("UnicodeLittleUnmarked") : + targetName.toUpperCase().getBytes(getOEMEncoding()); + } else { + flags &= (0xffffffff ^ (NTLMSSP_TARGET_TYPE_DOMAIN | + NTLMSSP_TARGET_TYPE_SERVER | + NTLMSSP_TARGET_TYPE_SHARE)); + } + } + if (targetInformation != null) { + flags ^= NTLMSSP_NEGOTIATE_TARGET_INFO; + // empty context is needed for padding when t.i. is supplied. + if (context == null) context = new byte[8]; + } + int data = 32; + if (context != null) data += 8; + if (targetInformation != null) data += 8; + byte[] type2 = new byte[data + target.length + + (targetInformation != null ? targetInformation.length : 0)]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type2, 0, 8); + writeULong(type2, 8, 2); + writeSecurityBuffer(type2, 12, data, target); + writeULong(type2, 20, flags); + System.arraycopy(challenge != null ? challenge : new byte[8], 0, + type2, 24, 8); + if (context != null) System.arraycopy(context, 0, type2, 32, 8); + if (targetInformation != null) { + writeSecurityBuffer(type2, 40, data + target.length, + targetInformation); + } + return type2; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String target = getTarget(); + byte[] challenge = getChallenge(); + byte[] context = getContext(); + byte[] targetInformation = getTargetInformation(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (target != null) { + buffer.append("target: ").append(target); + } + if (challenge != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("challenge: "); + buffer.append("0x"); + for (int i = 0; i < challenge.length; i++) { + buffer.append(Integer.toHexString((challenge[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(challenge[i] & 0x0f)); + } + } + if (context != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("context: "); + buffer.append("0x"); + for (int i = 0; i < context.length; i++) { + buffer.append(Integer.toHexString((context[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(context[i] & 0x0f)); + } + } + if (targetInformation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("targetInformation: "); + buffer.append("0x"); + for (int i = 0; i < targetInformation.length; i++) { + buffer.append(Integer.toHexString((targetInformation[i] >> 4) & + 0x0f)); + buffer.append(Integer.toHexString(targetInformation[i] & 0x0f)); + } + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-2 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default flags for a Type-2 message created in response + * to the given Type-1 message in the current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags(Type1Message type1) { + if (type1 == null) return DEFAULT_FLAGS; + int flags = NTLMSSP_NEGOTIATE_NTLM; + int type1Flags = type1.getFlags(); + flags |= ((type1Flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM; + if ((type1Flags & NTLMSSP_REQUEST_TARGET) != 0) { + String domain = getDefaultDomain(); + if (domain != null) { + flags |= NTLMSSP_REQUEST_TARGET | NTLMSSP_TARGET_TYPE_DOMAIN; + } + } + return flags; + } + + /** + * Returns the default domain from the current environment. + * + * @return A String containing the domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + public static byte[] getDefaultTargetInformation() { + return DEFAULT_TARGET_INFORMATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 2) { + throw new IOException("Not a Type 2 message."); + } + int flags = readULong(material, 20); + setFlags(flags); + String target = null; + byte[] bytes = readSecurityBuffer(material, 12); + if (bytes.length != 0) { + target = new String(bytes, + ((flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + "UnicodeLittleUnmarked" : getOEMEncoding()); + } + setTarget(target); + for (int i = 24; i < 32; i++) { + if (material[i] != 0) { + byte[] challenge = new byte[8]; + System.arraycopy(material, 24, challenge, 0, 8); + setChallenge(challenge); + break; + } + } + int offset = readULong(material, 16); // offset of targetname start + if (offset == 32 || material.length == 32) return; + for (int i = 32; i < 40; i++) { + if (material[i] != 0) { + byte[] context = new byte[8]; + System.arraycopy(material, 32, context, 0, 8); + setContext(context); + break; + } + } + if (offset == 40 || material.length == 40) return; + bytes = readSecurityBuffer(material, 40); + if (bytes.length != 0) setTargetInformation(bytes); + } + +} diff --git a/update/Type3Message.java b/update/Type3Message.java new file mode 100644 index 0000000..123b703 --- /dev/null +++ b/update/Type3Message.java @@ -0,0 +1,580 @@ +/* jcifs smb client library in Java + * Copyright (C) 2002 "Michael B. Allen" + * "Eric Glass" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package jcifs.ntlmssp; + +import java.io.IOException; + +import java.net.UnknownHostException; + +import java.security.SecureRandom; + +import jcifs.Config; + +import jcifs.netbios.NbtAddress; + +import jcifs.smb.NtlmPasswordAuthentication; + +/** + * Represents an NTLMSSP Type-3 message. + */ +public class Type3Message extends NtlmMessage { + + private static final int DEFAULT_FLAGS; + + private static final String DEFAULT_DOMAIN; + + private static final String DEFAULT_USER; + + private static final String DEFAULT_PASSWORD; + + private static final String DEFAULT_WORKSTATION; + + private static final int LM_COMPATIBILITY; + + private static final SecureRandom RANDOM = new SecureRandom(); + + private byte[] lmResponse; + + private byte[] ntResponse; + + private String domain; + + private String user; + + private String workstation; + + private byte[] sessionKey; + + static { + DEFAULT_FLAGS = NTLMSSP_NEGOTIATE_NTLM | + (Config.getBoolean("jcifs.smb.client.useUnicode", true) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM); + DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain", null); + DEFAULT_USER = Config.getProperty("jcifs.smb.client.username", null); + DEFAULT_PASSWORD = Config.getProperty("jcifs.smb.client.password", + null); + String defaultWorkstation = null; + try { + defaultWorkstation = NbtAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ex) { } + DEFAULT_WORKSTATION = defaultWorkstation; + LM_COMPATIBILITY = Config.getInt("jcifs.smb.lmCompatibility", 0); + } + + /** + * Creates a Type-3 message using default values from the current + * environment. + */ + public Type3Message() { + setFlags(getDefaultFlags()); + setDomain(getDefaultDomain()); + setUser(getDefaultUser()); + setWorkstation(getDefaultWorkstation()); + } + + /** + * Creates a Type-3 message in response to the given Type-2 message + * using default values from the current environment. + * + * @param type2 The Type-2 message which this represents a response to. + */ + public Type3Message(Type2Message type2) { + setFlags(getDefaultFlags(type2)); + setWorkstation(getDefaultWorkstation()); + String domain = getDefaultDomain(); + setDomain(domain); + String user = getDefaultUser(); + setUser(user); + String password = getDefaultPassword(); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + break; + case 2: + byte[] nt = getNTResponse(type2, password); + setLMResponse(nt); + setNTResponse(nt); + break; + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + RANDOM.nextBytes(clientChallenge); + setLMResponse(getLMv2Response(type2, domain, user, password, + clientChallenge)); + /* + setNTResponse(getNTLMv2Response(type2, domain, user, password, + clientChallenge)); + */ + break; + default: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + } + } + + /** + * Creates a Type-3 message in response to the given Type-2 message. + * + * @param type2 The Type-2 message which this represents a response to. + * @param password The password to use when constructing the response. + * @param domain The domain in which the user has an account. + * @param user The username for the authenticating user. + * @param workstation The workstation from which authentication is + * taking place. + */ + public Type3Message(Type2Message type2, String password, String domain, + String user, String workstation) { + setFlags(getDefaultFlags(type2)); + setDomain(domain); + setUser(user); + setWorkstation(workstation); + switch (LM_COMPATIBILITY) { + case 0: + case 1: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + break; + case 2: + byte[] nt = getNTResponse(type2, password); + setLMResponse(nt); + setNTResponse(nt); + break; + case 3: + case 4: + case 5: + byte[] clientChallenge = new byte[8]; + RANDOM.nextBytes(clientChallenge); + setLMResponse(getLMv2Response(type2, domain, user, password, + clientChallenge)); + /* + setNTResponse(getNTLMv2Response(type2, domain, user, password, + clientChallenge)); + */ + break; + default: + setLMResponse(getLMResponse(type2, password)); + setNTResponse(getNTResponse(type2, password)); + } + } + + /** + * Creates a Type-3 message with the specified parameters. + * + * @param flags The flags to apply to this message. + * @param lmResponse The LanManager/LMv2 response. + * @param domain The NT/NTLMv2 response. + * @param domain The domain in which the user has an account. + * @param user The username for the authenticating user. + * @param workstation The workstation from which authentication is + * taking place. + */ + public Type3Message(int flags, byte[] lmResponse, byte[] ntResponse, + String domain, String user, String workstation) { + setFlags(flags); + setLMResponse(lmResponse); + setNTResponse(ntResponse); + setDomain(domain); + setUser(user); + setWorkstation(workstation); + } + + /** + * Creates a Type-3 message using the given raw Type-3 material. + * + * @param material The raw Type-3 material used to construct this message. + * @throws IOException If an error occurs while parsing the material. + */ + public Type3Message(byte[] material) throws IOException { + parse(material); + } + + /** + * Returns the LanManager/LMv2 response. + * + * @return A byte[] containing the LanManager response. + */ + public byte[] getLMResponse() { + return lmResponse; + } + + /** + * Sets the LanManager/LMv2 response for this message. + * + * @param lmResponse The LanManager response. + */ + public void setLMResponse(byte[] lmResponse) { + this.lmResponse = lmResponse; + } + + /** + * Returns the NT/NTLMv2 response. + * + * @return A byte[] containing the NT/NTLMv2 response. + */ + public byte[] getNTResponse() { + return ntResponse; + } + + /** + * Sets the NT/NTLMv2 response for this message. + * + * @param lmResponse The NT/NTLMv2 response. + */ + public void setNTResponse(byte[] ntResponse) { + this.ntResponse = ntResponse; + } + + /** + * Returns the domain in which the user has an account. + * + * @return A String containing the domain for the user. + */ + public String getDomain() { + return domain; + } + + /** + * Sets the domain for this message. + * + * @param domain The domain. + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * Returns the username for the authenticating user. + * + * @return A String containing the user for this message. + */ + public String getUser() { + return user; + } + + /** + * Sets the user for this message. + * + * @param user The user. + */ + public void setUser(String user) { + this.user = user; + } + + /** + * Returns the workstation from which authentication is being performed. + * + * @return A String containing the workstation. + */ + public String getWorkstation() { + return workstation; + } + + /** + * Sets the workstation for this message. + * + * @param workstation The workstation. + */ + public void setWorkstation(String workstation) { + this.workstation = workstation; + } + + /** + * Returns the session key. + * + * @return A byte[] containing the session key. + */ + public byte[] getSessionKey() { + return sessionKey; + } + + /** + * Sets the session key. + * + * @param sessionKey The session key. + */ + public void setSessionKey(byte[] sessionKey) { + this.sessionKey = sessionKey; + } + + public byte[] toByteArray() { + try { + int flags = getFlags(); + boolean unicode = (flags & NTLMSSP_NEGOTIATE_UNICODE) != 0; + String oem = unicode ? null : getOEMEncoding(); + String domainName = getDomain(); + byte[] domain = null; + if (domainName != null && domainName.length() != 0) { + domain = unicode ? + domainName.getBytes("UnicodeLittleUnmarked") : + domainName.toUpperCase().getBytes(oem); + } + int domainLength = (domain != null) ? domain.length : 0; + String userName = getUser(); + byte[] user = null; + if (userName != null && userName.length() != 0) { + user = unicode ? userName.getBytes("UnicodeLittleUnmarked") : + userName.toUpperCase().getBytes(oem); + } + int userLength = (user != null) ? user.length : 0; + String workstationName = getWorkstation(); + byte[] workstation = null; + if (workstationName != null && workstationName.length() != 0) { + workstation = unicode ? + workstationName.getBytes("UnicodeLittleUnmarked") : + workstationName.toUpperCase().getBytes(oem); + } + int workstationLength = (workstation != null) ? + workstation.length : 0; + byte[] lmResponse = getLMResponse(); + int lmLength = (lmResponse != null) ? lmResponse.length : 0; + byte[] ntResponse = getNTResponse(); + int ntLength = (ntResponse != null) ? ntResponse.length : 0; + byte[] sessionKey = getSessionKey(); + int keyLength = (sessionKey != null) ? sessionKey.length : 0; + byte[] type3 = new byte[64 + domainLength + userLength + + workstationLength + lmLength + ntLength + keyLength]; + System.arraycopy(NTLMSSP_SIGNATURE, 0, type3, 0, 8); + writeULong(type3, 8, 3); + int offset = 64; + writeSecurityBuffer(type3, 12, offset, lmResponse); + offset += lmLength; + writeSecurityBuffer(type3, 20, offset, ntResponse); + offset += ntLength; + writeSecurityBuffer(type3, 28, offset, domain); + offset += domainLength; + writeSecurityBuffer(type3, 36, offset, user); + offset += userLength; + writeSecurityBuffer(type3, 44, offset, workstation); + offset += workstationLength; + writeSecurityBuffer(type3, 52, offset, sessionKey); + writeULong(type3, 60, flags); + return type3; + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + + public String toString() { + String user = getUser(); + String domain = getDomain(); + String workstation = getWorkstation(); + byte[] lmResponse = getLMResponse(); + byte[] ntResponse = getNTResponse(); + byte[] sessionKey = getSessionKey(); + int flags = getFlags(); + StringBuffer buffer = new StringBuffer(); + if (domain != null) { + buffer.append("domain: ").append(domain); + } + if (user != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("user: ").append(user); + } + if (workstation != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("workstation: ").append(workstation); + } + if (lmResponse != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("lmResponse: "); + buffer.append("0x"); + for (int i = 0; i < lmResponse.length; i++) { + buffer.append(Integer.toHexString((lmResponse[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(lmResponse[i] & 0x0f)); + } + } + if (ntResponse != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("ntResponse: "); + buffer.append("0x"); + for (int i = 0; i < ntResponse.length; i++) { + buffer.append(Integer.toHexString((ntResponse[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(ntResponse[i] & 0x0f)); + } + } + if (sessionKey != null) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("sessionKey: "); + buffer.append("0x"); + for (int i = 0; i < sessionKey.length; i++) { + buffer.append(Integer.toHexString((sessionKey[i] >> 4) & 0x0f)); + buffer.append(Integer.toHexString(sessionKey[i] & 0x0f)); + } + } + if (flags != 0) { + if (buffer.length() > 0) buffer.append("; "); + buffer.append("flags: "); + buffer.append("0x"); + buffer.append(Integer.toHexString((flags >> 28) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 24) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 20) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 16) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 12) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 8) & 0x0f)); + buffer.append(Integer.toHexString((flags >> 4) & 0x0f)); + buffer.append(Integer.toHexString(flags & 0x0f)); + } + return buffer.toString(); + } + + /** + * Returns the default flags for a generic Type-3 message in the + * current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags() { + return DEFAULT_FLAGS; + } + + /** + * Returns the default flags for a Type-3 message created in response + * to the given Type-2 message in the current environment. + * + * @return An int containing the default flags. + */ + public static int getDefaultFlags(Type2Message type2) { + if (type2 == null) return DEFAULT_FLAGS; + int flags = NTLMSSP_NEGOTIATE_NTLM; + flags |= ((type2.getFlags() & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + NTLMSSP_NEGOTIATE_UNICODE : NTLMSSP_NEGOTIATE_OEM; + return flags; + } + + /** + * Constructs the LanManager response to the given Type-2 message using + * the supplied password. + * + * @param type2 The Type-2 message. + * @param password The password. + * @return A byte[] containing the LanManager response. + */ + public static byte[] getLMResponse(Type2Message type2, String password) { + if (type2 == null || password == null) return null; + return NtlmPasswordAuthentication.getPreNTLMResponse(password, + type2.getChallenge()); + } + + public static byte[] getLMv2Response(Type2Message type2, + String domain, String user, String password, + byte[] clientChallenge) { + if (type2 == null || domain == null || user == null || + password == null || clientChallenge == null) { + return null; + } + return NtlmPasswordAuthentication.getLMv2Response(domain, user, + password, type2.getChallenge(), clientChallenge); + } + + /** + * Constructs the NT response to the given Type-2 message using + * the supplied password. + * + * @param type2 The Type-2 message. + * @param password The password. + * @return A byte[] containing the NT response. + */ + public static byte[] getNTResponse(Type2Message type2, String password) { + if (type2 == null || password == null) return null; + return NtlmPasswordAuthentication.getNTLMResponse(password, + type2.getChallenge()); + } + + /** + * Returns the default domain from the current environment. + * + * @return The default domain. + */ + public static String getDefaultDomain() { + return DEFAULT_DOMAIN; + } + + /** + * Returns the default user from the current environment. + * + * @return The default user. + */ + public static String getDefaultUser() { + return DEFAULT_USER; + } + + /** + * Returns the default password from the current environment. + * + * @return The default password. + */ + public static String getDefaultPassword() { + return DEFAULT_PASSWORD; + } + + /** + * Returns the default workstation from the current environment. + * + * @return The default workstation. + */ + public static String getDefaultWorkstation() { + return DEFAULT_WORKSTATION; + } + + private void parse(byte[] material) throws IOException { + for (int i = 0; i < 8; i++) { + if (material[i] != NTLMSSP_SIGNATURE[i]) { + throw new IOException("Not an NTLMSSP message."); + } + } + if (readULong(material, 8) != 3) { + throw new IOException("Not a Type 3 message."); + } + byte[] lmResponse = readSecurityBuffer(material, 12); + int lmResponseOffset = readULong(material, 16); + byte[] ntResponse = readSecurityBuffer(material, 20); + int ntResponseOffset = readULong(material, 24); + byte[] domain = readSecurityBuffer(material, 28); + int domainOffset = readULong(material, 32); + byte[] user = readSecurityBuffer(material, 36); + int userOffset = readULong(material, 40); + byte[] workstation = readSecurityBuffer(material, 44); + int workstationOffset = readULong(material, 48); + int flags; + String charset; + if (lmResponseOffset == 52 || ntResponseOffset == 52 || + domainOffset == 52 || userOffset == 52 || + workstationOffset == 52) { + flags = NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_OEM; + charset = getOEMEncoding(); + } else { + setSessionKey(readSecurityBuffer(material, 52)); + flags = readULong(material, 60); + charset = ((flags & NTLMSSP_NEGOTIATE_UNICODE) != 0) ? + "UnicodeLittleUnmarked" : getOEMEncoding(); + } + setFlags(flags); + setLMResponse(lmResponse); + // NTLMv2 issues w/cross-domain authentication; leave NT empty if >= 3 + if (LM_COMPATIBILITY < 3) setNTResponse(ntResponse); + setDomain(new String(domain, charset)); + setUser(new String(user, charset)); + setWorkstation(new String(workstation, charset)); + } + +} -- 2.11.0