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