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