View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.proxy.handlers.http.ntlm;
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.InputStreamReader;
26  import java.io.PrintWriter;
27  import java.io.UnsupportedEncodingException;
28  import java.util.StringTokenizer;
29  
30  import org.apache.mina.proxy.utils.ByteUtilities;
31  
32  /**
33   * NTLMUtilities.java - NTLM functions used for authentication and unit testing.
34   * 
35   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
36   * @since MINA 2.0.0-M3
37   */
38  public class NTLMUtilities implements NTLMConstants {
39      /**
40       * @see #writeSecurityBuffer(short, short, int, byte[], int)
41       */
42      public final static byte[] writeSecurityBuffer(short length, int bufferOffset) {
43          byte[] b = new byte[8];
44          writeSecurityBuffer(length, length, bufferOffset, b, 0);
45  
46          return b;
47      }
48  
49      /**
50       * Writes a security buffer to the given array <code>b</code> at offset
51       * <code>offset</code>. A security buffer defines a pointer to an area
52       * in the data that defines some data with a variable length. This allows
53       * to have a semi-fixed length header thus making a little bit easier
54       * the decoding process in the NTLM protocol.
55       * 
56       * @param length the length of the security buffer
57       * @param allocated the allocated space for the security buffer (should be
58       * greater or equal to <code>length</code>
59       * @param bufferOffset the offset from the main array where the currently
60       * defined security buffer will be written
61       * @param b the buffer in which we write the security buffer
62       * @param offset the offset at which to write to the b buffer
63       */
64      public final static void writeSecurityBuffer(short length, short allocated, int bufferOffset, byte[] b, int offset) {
65          ByteUtilities.writeShort(length, b, offset);
66          ByteUtilities.writeShort(allocated, b, offset + 2);
67          ByteUtilities.writeInt(bufferOffset, b, offset + 4);
68      }
69  
70      /**
71       * Writes the Windows OS version passed in as three byte values
72       * (majorVersion.minorVersion.buildNumber) to the given byte array
73       * at <code>offset</code>.
74       * 
75       * @param majorVersion the major version number
76       * @param minorVersion the minor version number
77       * @param buildNumber the build number
78       * @param b the target byte array
79       * @param offset the offset at which to write in the array
80       */
81      public final static void writeOSVersion(byte majorVersion, byte minorVersion, short buildNumber, byte[] b,
82              int offset) {
83          b[offset] = majorVersion;
84          b[offset + 1] = minorVersion;
85          b[offset + 2] = (byte) buildNumber;
86          b[offset + 3] = (byte) (buildNumber >> 8);
87          b[offset + 4] = 0;
88          b[offset + 5] = 0;
89          b[offset + 6] = 0;
90          b[offset + 7] = 0x0F;
91      }
92  
93      /**
94       * Tries to return a valid OS version on Windows systems. If it fails to
95       * do so or if we're running on another OS then a fake Windows XP OS
96       * version is returned because the protocol uses it.
97       * 
98       * @return a NTLM OS version byte buffer
99       */
100     public final static byte[] getOsVersion() {
101         String os = System.getProperty("os.name");
102 
103         if ((os == null) || !os.toUpperCase().contains("WINDOWS")) {
104             return DEFAULT_OS_VERSION;
105         }
106 
107         byte[] osVer = new byte[8];
108 
109         // Let's enclose the code by a try...catch in order to
110         // manage incorrect strings. In this case, we will generate
111         // an exception and deal with the special cases.
112         try {
113             Process pr = Runtime.getRuntime().exec("cmd /C ver");
114             BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()));
115             pr.waitFor();
116 
117             String line;
118 
119             // We loop as we may have blank lines.
120             do {
121                 line = reader.readLine();
122             } while ((line != null) && (line.length() != 0));
123 
124             reader.close();
125 
126             // If line is null, we must not go any farther
127             if (line == null) {
128                 // Throw an exception to jump into the catch() part
129                 throw new Exception();
130             }
131 
132             // The command line should return a response like :
133             // Microsoft Windows XP [version 5.1.2600]
134             int pos = line.toLowerCase().indexOf("version");
135 
136             if (pos == -1) {
137                 // Throw an exception to jump into the catch() part
138                 throw new Exception();
139             }
140 
141             pos += 8;
142             line = line.substring(pos, line.indexOf(']'));
143             StringTokenizer tk = new StringTokenizer(line, ".");
144 
145             if (tk.countTokens() != 3) {
146                 // Throw an exception to jump into the catch() part
147                 throw new Exception();
148             }
149 
150             writeOSVersion(Byte.parseByte(tk.nextToken()), Byte.parseByte(tk.nextToken()),
151                     Short.parseShort(tk.nextToken()), osVer, 0);
152         } catch (Exception ex) {
153             try {
154                 String version = System.getProperty("os.version");
155                 writeOSVersion(Byte.parseByte(version.substring(0, 1)), Byte.parseByte(version.substring(2, 3)),
156                         (short) 0, osVer, 0);
157             } catch (Exception ex2) {
158                 return DEFAULT_OS_VERSION;
159             }
160         }
161 
162         return osVer;
163     }
164 
165     /**
166      * see http://davenport.sourceforge.net/ntlm.html#theType1Message
167      * 
168      * @param workStation the workstation name
169      * @param domain the domain name
170      * @param customFlags custom flags, if null then
171      * <code>NTLMConstants.DEFAULT_CONSTANTS</code> is used
172      * @param osVersion the os version of the client, if null then
173      * <code>NTLMConstants.DEFAULT_OS_VERSION</code> is used
174      * @return the type 1 message
175      */
176     public final static byte[] createType1Message(String workStation, String domain, Integer customFlags,
177             byte[] osVersion) {
178         byte[] msg = null;
179 
180         if (osVersion != null && osVersion.length != 8) {
181             throw new IllegalArgumentException("osVersion parameter should be a 8 byte wide array");
182         }
183 
184         if (workStation == null || domain == null) {
185             throw new IllegalArgumentException("workStation and domain must be non null");
186         }
187 
188         int flags = customFlags != null ? customFlags | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
189                 | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;
190 
191         ByteArrayOutputStream baos = new ByteArrayOutputStream();
192 
193         try {
194             baos.write(NTLM_SIGNATURE);
195             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_1));
196             baos.write(ByteUtilities.writeInt(flags));
197 
198             byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain);
199             byte[] workStationData = ByteUtilities.getOEMStringAsByteArray(workStation);
200 
201             int pos = (osVersion != null) ? 40 : 32;
202             baos.write(writeSecurityBuffer((short) domainData.length, pos + workStationData.length));
203             baos.write(writeSecurityBuffer((short) workStationData.length, pos));
204 
205             if (osVersion != null) {
206                 baos.write(osVersion);
207             }
208 
209             // Order is not mandatory since a pointer is given in the security buffers
210             baos.write(workStationData);
211             baos.write(domainData);
212 
213             msg = baos.toByteArray();
214             baos.close();
215         } catch (IOException e) {
216             return null;
217         }
218 
219         return msg;
220     }
221 
222     /**
223      * Writes a security buffer and returns the pointer of the position
224      * where to write the next security buffer.
225      * 
226      * @param baos the stream where the security buffer is written
227      * @param len the length of the security buffer
228      * @param pointer the position where the security buffer can be written
229      * @return the position where the next security buffer will be written
230      * @throws IOException if writing to the ByteArrayOutputStream fails
231      */
232     public final static int writeSecurityBufferAndUpdatePointer(ByteArrayOutputStream baos, short len, int pointer)
233             throws IOException {
234         baos.write(writeSecurityBuffer(len, pointer));
235 
236         return pointer + len;
237     }
238 
239     /**
240      * Extracts the NTLM challenge from the type 2 message as an 8 byte array.
241      * 
242      * @param msg the type 2 message byte array
243      * @return the challenge
244      */
245     public final static byte[] extractChallengeFromType2Message(byte[] msg) {
246         byte[] challenge = new byte[8];
247         System.arraycopy(msg, 24, challenge, 0, 8);
248 
249         return challenge;
250     }
251 
252     /**
253      * Extracts the NTLM flags from the type 2 message.
254      * 
255      * @param msg the type 2 message byte array
256      * @return the proxy flags as an int
257      */
258     public final static int extractFlagsFromType2Message(byte[] msg) {
259         byte[] flagsBytes = new byte[4];
260 
261         System.arraycopy(msg, 20, flagsBytes, 0, 4);
262         ByteUtilities.changeWordEndianess(flagsBytes, 0, 4);
263 
264         return ByteUtilities.makeIntFromByte4(flagsBytes);
265     }
266 
267     /**
268      * Reads the byte array described by the security buffer stored at the
269      * <code>securityBufferOffset</code> offset.
270      * 
271      * @param msg the message where to read the security buffer and it's value
272      * @param securityBufferOffset the offset at which to read the security buffer
273      * @return a new byte array holding the data pointed by the security buffer
274      */
275     public final static byte[] readSecurityBufferTarget(byte[] msg, int securityBufferOffset) {
276         byte[] securityBuffer = new byte[8];
277 
278         System.arraycopy(msg, securityBufferOffset, securityBuffer, 0, 8);
279         ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
280         int length = ByteUtilities.makeIntFromByte2(securityBuffer);
281         int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
282 
283         byte[] secBufValue = new byte[length];
284         System.arraycopy(msg, offset, secBufValue, 0, length);
285 
286         return secBufValue;
287     }
288 
289     /**
290      * Extracts the target name from the type 2 message.
291      * 
292      * @param msg the type 2 message byte array
293      * @param msgFlags the flags if null then flags are extracted from the
294      * type 2 message
295      * @return the target name
296      * @throws UnsupportedEncodingException if unable to use the
297      * needed UTF-16LE or ASCII charsets
298      */
299     public final static String extractTargetNameFromType2Message(byte[] msg, Integer msgFlags)
300             throws UnsupportedEncodingException {
301         // Read the security buffer to determine where the target name
302         // is stored and what it's length is
303         byte[] targetName = readSecurityBufferTarget(msg, 12);
304 
305         // now we convert it to a string
306         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
307 
308         if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
309             return new String(targetName, "UTF-16LE");
310         }
311 
312         return new String(targetName, "ASCII");
313     }
314 
315     /**
316      * Extracts the target information block from the type 2 message.
317      * 
318      * @param msg the type 2 message byte array
319      * @param msgFlags the flags if null then flags are extracted from the
320      * type 2 message
321      * @return the target info
322      */
323     public final static byte[] extractTargetInfoFromType2Message(byte[] msg, Integer msgFlags) {
324         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
325 
326         if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) {
327             return null;
328         }
329 
330         int pos = 40;
331 
332         return readSecurityBufferTarget(msg, pos);
333     }
334 
335     /**
336      * Prints to the {@link PrintWriter} the target information block extracted
337      * from the type 2 message.
338      * 
339      * @param msg the type 2 message
340      * @param msgFlags the flags if null then flags are extracted from the
341      * type 2 message
342      * @param out the output target for the information
343      * @throws UnsupportedEncodingException if unable to use the
344      * needed UTF-16LE or ASCII charsets
345      */
346     public final static void printTargetInformationBlockFromType2Message(byte[] msg, Integer msgFlags, PrintWriter out)
347             throws UnsupportedEncodingException {
348         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
349 
350         byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags);
351 
352         if (infoBlock == null) {
353             out.println("No target information block found !");
354         } else {
355             int pos = 0;
356 
357             while (infoBlock[pos] != 0) {
358                 out.print("---\nType " + infoBlock[pos] + ": ");
359 
360                 switch (infoBlock[pos]) {
361                 case 1:
362                     out.println("Server name");
363                     break;
364                 case 2:
365                     out.println("Domain name");
366                     break;
367                 case 3:
368                     out.println("Fully qualified DNS hostname");
369                     break;
370                 case 4:
371                     out.println("DNS domain name");
372                     break;
373                 case 5:
374                     out.println("Parent DNS domain name");
375                     break;
376                 }
377 
378                 byte[] len = new byte[2];
379                 System.arraycopy(infoBlock, pos + 2, len, 0, 2);
380                 ByteUtilities.changeByteEndianess(len, 0, 2);
381 
382                 int length = ByteUtilities.makeIntFromByte2(len, 0);
383                 out.println("Length: " + length + " bytes");
384                 out.print("Data: ");
385 
386                 if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
387                     out.println(new String(infoBlock, pos + 4, length, "UTF-16LE"));
388                 } else {
389                     out.println(new String(infoBlock, pos + 4, length, "ASCII"));
390                 }
391 
392                 pos += 4 + length;
393                 out.flush();
394             }
395         }
396     }
397 
398     /**
399      * @see <a
400      *      href="http://davenport.sourceforge.net/ntlm.html#theType3Message">NTLM
401      *      message type</a>
402      * 
403      * @param user
404      *            the user name
405      * @param password
406      *            the user password
407      * @param challenge
408      *            the challenge response
409      * @param target
410      *            the target name
411      * @param workstation
412      *            the client workstation's name
413      * @param serverFlags
414      *            the flags set by the client
415      * @param osVersion
416      *            the os version of the client
417      * @return the type 3 message
418      */
419     public final static byte[] createType3Message(String user, String password, byte[] challenge, String target,
420             String workstation, Integer serverFlags, byte[] osVersion) {
421         byte[] msg = null;
422 
423         if (challenge == null || challenge.length != 8) {
424             throw new IllegalArgumentException("challenge[] should be a 8 byte wide array");
425         }
426 
427         if (osVersion != null && osVersion.length != 8) {
428             throw new IllegalArgumentException("osVersion should be a 8 byte wide array");
429         }
430 
431         int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS;
432 
433         ByteArrayOutputStream baos = new ByteArrayOutputStream();
434 
435         try {
436             baos.write(NTLM_SIGNATURE);
437             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3));
438 
439             byte[] dataLMResponse = NTLMResponses.getLMResponse(password, challenge);
440             byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, challenge);
441 
442             boolean useUnicode = ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE);
443             byte[] targetName = ByteUtilities.encodeString(target, useUnicode);
444             byte[] userName = ByteUtilities.encodeString(user, useUnicode);
445             byte[] workstationName = ByteUtilities.encodeString(workstation, useUnicode);
446 
447             int pos = osVersion != null ? 72 : 64;
448             int responsePos = pos + targetName.length + userName.length + workstationName.length;
449             responsePos = writeSecurityBufferAndUpdatePointer(baos, (short) dataLMResponse.length, responsePos);
450             writeSecurityBufferAndUpdatePointer(baos, (short) dataNTLMResponse.length, responsePos);
451             pos = writeSecurityBufferAndUpdatePointer(baos, (short) targetName.length, pos);
452             pos = writeSecurityBufferAndUpdatePointer(baos, (short) userName.length, pos);
453             writeSecurityBufferAndUpdatePointer(baos, (short) workstationName.length, pos);
454 
455             /**
456             LM/LMv2 Response security buffer
457             20 NTLM/NTLMv2 Response security buffer
458             28 Target Name security buffer
459             36 User Name security buffer
460             44 Workstation Name security buffer
461             (52) Session Key (optional) security buffer
462             (60) Flags (optional) long
463             (64) OS Version Structure (Optional) 8 bytes
464              **/
465 
466             // Session Key Security Buffer ??!
467             baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 });
468 
469             baos.write(ByteUtilities.writeInt(flags));
470 
471             if (osVersion != null) {
472                 baos.write(osVersion);
473             }
474 
475             // Order is not mandatory since a pointer is given in the security buffers
476             baos.write(targetName);
477             baos.write(userName);
478             baos.write(workstationName);
479 
480             baos.write(dataLMResponse);
481             baos.write(dataNTLMResponse);
482 
483             msg = baos.toByteArray();
484             baos.close();
485         } catch (Exception e) {
486             e.printStackTrace();
487             return null;
488         }
489 
490         return msg;
491     }
492 }