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