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 The Apache MINA Project (dev@mina.apache.org)
36   * @version $Rev: 759662 $, $Date: 2009-03-29 12:43:02 +0200 (Sun, 29 Mar 2009) $
37   * @since MINA 2.0.0-M3
38   */
39  public class NTLMUtilities implements NTLMConstants {
40      /**
41       * @see #writeSecurityBuffer(short, short, int, byte[], int)
42       */
43      public final static byte[] writeSecurityBuffer(short length,
44              int bufferOffset) {
45          byte[] b = new byte[8];
46          writeSecurityBuffer(length, length, bufferOffset, b, 0);
47          return b;
48      }
49  
50      /**
51       * Writes a security buffer to the given array <code>b</code> at offset 
52       * <code>offset</code>.
53       * 
54       * @param length the length of the security buffer
55       * @param allocated the allocated space for the security buffer (should be
56       * greater or equal to <code>length</code>
57       * @param bufferOffset the offset since the beginning of the <code>b</code>
58       * buffer where the buffer iswritten
59       * @param b the buffer in which we write the security buffer
60       * @param offset the offset at which to write to the buffer
61       */
62      public final static void writeSecurityBuffer(short length, short allocated,
63              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      public final static void writeOSVersion(byte majorVersion,
70              byte minorVersion, short buildNumber, byte[] b, int offset) {
71          b[offset] = majorVersion;
72          b[offset + 1] = minorVersion;
73          b[offset + 2] = (byte) buildNumber;
74          b[offset + 3] = (byte) (buildNumber >> 8);
75          b[offset + 4] = 0;
76          b[offset + 5] = 0;
77          b[offset + 6] = 0;
78          b[offset + 7] = 0x0F;
79      }
80  
81      /**
82       * Tries to return a valid Os version on windows systems.
83       */
84      public final static byte[] getOsVersion() {
85          String os = System.getProperty("os.name");
86          if (os == null || !os.toUpperCase().contains("WINDOWS")) {
87              return DEFAULT_OS_VERSION;
88          } else {
89              byte[] osVer = new byte[8];
90              try {
91                  Process pr = Runtime.getRuntime().exec("cmd /C ver");
92                  BufferedReader reader = new BufferedReader(
93                          new InputStreamReader(pr.getInputStream()));
94                  pr.waitFor();
95                  
96                  String line;
97                  do {
98                  	  line = reader.readLine();
99                  } while ((line != null) && (line.length() != 0));
100                 
101                 int pos = line.toLowerCase().indexOf("version");
102 
103                 if (pos == -1) {
104                     throw new NullPointerException();
105                 }
106 
107                 pos += 8;
108                 line = line.substring(pos, line.indexOf(']'));
109                 StringTokenizer tk = new StringTokenizer(line, ".");
110                 if (tk.countTokens() != 3) {
111                     throw new NullPointerException();
112                 }
113 
114                 writeOSVersion(Byte.parseByte(tk.nextToken()), Byte
115                         .parseByte(tk.nextToken()), Short.parseShort(tk
116                         .nextToken()), osVer, 0);
117             } catch (Exception ex) {
118                 try {
119                     String version = System.getProperty("os.version");
120                     writeOSVersion(Byte.parseByte(version.substring(0, 1)),
121                             Byte.parseByte(version.substring(2, 3)), (short) 0,
122                             osVer, 0);
123                 } catch (Exception ex2) {
124                     return DEFAULT_OS_VERSION;
125                 }
126             }
127             return osVer;
128         }
129     }
130 
131     /**
132      * see http://davenport.sourceforge.net/ntlm.html#theType1Message
133      * 
134      * @param workStation the workstation name
135      * @param domain the domain name
136      * @param customFlags custom flags, if null then 
137      * <code>NTLMConstants.DEFAULT_CONSTANTS</code> is used
138      * @param osVersion the os version of the client, if null then 
139      * <code>NTLMConstants.DEFAULT_OS_VERSION</code> is used
140      * @return
141      */
142     public final static byte[] createType1Message(String workStation,
143             String domain, Integer customFlags, byte[] osVersion) {
144         byte[] msg = null;
145 
146         if (osVersion != null && osVersion.length != 8) {
147             throw new IllegalArgumentException(
148                     "osVersion parameter should be a 8 byte wide array");
149         }
150 
151         if (workStation == null || domain == null) {
152             throw new NullPointerException(
153                     "workStation and domain must be non null");
154         }
155 
156         int flags = customFlags != null ? customFlags
157                 | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
158                 | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;
159 
160         ByteArrayOutputStream baos = new ByteArrayOutputStream();
161 
162         try {
163             baos.write(NTLM_SIGNATURE);
164             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_1));
165             baos.write(ByteUtilities.writeInt(flags));
166 
167             byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain);
168             byte[] workStationData = ByteUtilities
169                     .getOEMStringAsByteArray(workStation);
170 
171             int pos = (osVersion != null) ? 40 : 32;
172             baos.write(writeSecurityBuffer((short) domainData.length, pos
173                     + workStationData.length));
174             baos
175                     .write(writeSecurityBuffer((short) workStationData.length,
176                             pos));
177 
178             if (osVersion != null) {
179                 baos.write(osVersion);
180             }
181 
182             // Order is not mandatory since a pointer is given in the security buffers
183             baos.write(workStationData);
184             baos.write(domainData);
185 
186             msg = baos.toByteArray();
187             baos.close();
188         } catch (IOException e) {
189             return null;
190         }
191 
192         return msg;
193     }
194 
195     /**
196      * Write a security buffer and returns the pointer of the position 
197      * where to write the next security buffer.
198      * 
199      * @param baos the stream where the security buffer is written
200      * @param len the length of the security buffer 
201      * @param pointer the position where the security buffer can be written
202      * @return the position where the next security buffer will be written
203      * @throws IOException
204      */
205     public final static int writeSecurityBufferAndUpdatePointer(
206             ByteArrayOutputStream baos, short len, int pointer)
207             throws IOException {
208         baos.write(writeSecurityBuffer(len, pointer));
209         return pointer + len;
210     }
211 
212     public final static byte[] extractChallengeFromType2Message(byte[] msg) {
213         byte[] challenge = new byte[8];
214         System.arraycopy(msg, 24, challenge, 0, 8);
215         return challenge;
216     }
217 
218     public final static int extractFlagsFromType2Message(byte[] msg) {
219         byte[] flagsBytes = new byte[4];
220 
221         System.arraycopy(msg, 20, flagsBytes, 0, 4);
222         ByteUtilities.changeWordEndianess(flagsBytes, 0, 4);
223 
224         return ByteUtilities.makeIntFromByte4(flagsBytes);
225     }
226 
227     public final static String extractTargetNameFromType2Message(byte[] msg,
228             Integer msgFlags) throws UnsupportedEncodingException {
229         byte[] targetName = null;
230 
231         // Read security buffer
232         byte[] securityBuffer = new byte[8];
233 
234         System.arraycopy(msg, 12, securityBuffer, 0, 8);
235         ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
236         int length = ByteUtilities.makeIntFromByte2(securityBuffer);
237         int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
238 
239         targetName = new byte[length];
240         System.arraycopy(msg, offset, targetName, 0, length);
241 
242         int flags = msgFlags == null ? extractFlagsFromType2Message(msg)
243                 : msgFlags;
244         if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
245             return new String(targetName, "UTF-16LE");
246         } else {
247             return new String(targetName, "ASCII");
248         }
249     }
250 
251     public final static byte[] extractTargetInfoFromType2Message(byte[] msg,
252             Integer msgFlags) {
253         int flags = msgFlags == null ? extractFlagsFromType2Message(msg)
254                 : msgFlags;
255         byte[] targetInformationBlock = null;
256 
257         if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO))
258             return null;
259 
260         int pos = 40; //isFlagSet(flags, FLAG_NEGOTIATE_LOCAL_CALL) ? 40 : 32;
261 
262         // Read security buffer
263         byte[] securityBuffer = new byte[8];
264 
265         System.arraycopy(msg, pos, securityBuffer, 0, 8);
266         ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
267         int length = ByteUtilities.makeIntFromByte2(securityBuffer);
268         int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
269 
270         targetInformationBlock = new byte[length];
271         System.arraycopy(msg, offset, targetInformationBlock, 0, length);
272 
273         return targetInformationBlock;
274     }
275 
276     public final static void printTargetInformationBlockFromType2Message(
277             byte[] msg, Integer msgFlags, PrintWriter out)
278             throws UnsupportedEncodingException {
279         int flags = msgFlags == null ? extractFlagsFromType2Message(msg)
280                 : msgFlags;
281 
282         byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags);
283         if (infoBlock == null) {
284             out.println("No target information block found !");
285         } else {
286             int pos = 0;
287             while (infoBlock[pos] != 0) {
288                 out.print("---\nType " + infoBlock[pos] + ": ");
289                 switch (infoBlock[pos]) {
290                 case 1:
291                     out.println("Server name");
292                     break;
293                 case 2:
294                     out.println("Domain name");
295                     break;
296                 case 3:
297                     out.println("Fully qualified DNS hostname");
298                     break;
299                 case 4:
300                     out.println("DNS domain name");
301                     break;
302                 case 5:
303                     out.println("Parent DNS domain name");
304                     break;
305                 }
306                 byte[] len = new byte[2];
307                 System.arraycopy(infoBlock, pos + 2, len, 0, 2);
308                 ByteUtilities.changeByteEndianess(len, 0, 2);
309 
310                 int length = ByteUtilities.makeIntFromByte2(len, 0);
311                 out.println("Length: " + length + " bytes");
312                 out.print("Data: ");
313                 if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
314                     out.println(new String(infoBlock, pos + 4, length,
315                             "UTF-16LE"));
316                 } else {
317                     out
318                             .println(new String(infoBlock, pos + 4, length,
319                                     "ASCII"));
320                 }
321                 pos += 4 + length;
322                 out.flush();
323             }
324         }
325     }
326 
327     /**
328      * http://davenport.sourceforge.net/ntlm.html#theType3Message
329      */
330     public final static byte[] createType3Message(String user, String password,
331             byte[] challenge, String target, String workstation,
332             Integer serverFlags, byte[] osVersion) {
333         byte[] msg = null;
334 
335         if (challenge == null || challenge.length != 8) {
336             throw new IllegalArgumentException(
337                     "challenge[] should be a 8 byte wide array");
338         }
339 
340         if (osVersion != null && osVersion.length != 8) {
341             throw new IllegalArgumentException(
342                     "osVersion should be a 8 byte wide array");
343         }
344 
345         //TOSEE breaks tests
346         /*int flags = serverFlags != null ? serverFlags | 
347         		FLAG_NEGOTIATE_WORKSTATION_SUPPLIED | 
348         		FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;*/
349         int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS;
350 
351         ByteArrayOutputStream baos = new ByteArrayOutputStream();
352 
353         try {
354             baos.write(NTLM_SIGNATURE);
355             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3));
356 
357             byte[] dataLMResponse = NTLMResponses.getLMResponse(password,
358                     challenge);
359             byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password,
360                     challenge);
361 
362             boolean useUnicode = ByteUtilities.isFlagSet(flags,
363                     FLAG_NEGOTIATE_UNICODE);
364             byte[] targetName = ByteUtilities.encodeString(target, useUnicode);
365             byte[] userName = ByteUtilities.encodeString(user, useUnicode);
366             byte[] workstationName = ByteUtilities.encodeString(workstation,
367                     useUnicode);
368 
369             int pos = osVersion != null ? 72 : 64;
370             int responsePos = pos + targetName.length + userName.length
371                     + workstationName.length;
372             responsePos = writeSecurityBufferAndUpdatePointer(baos,
373                     (short) dataLMResponse.length, responsePos);
374             writeSecurityBufferAndUpdatePointer(baos,
375                     (short) dataNTLMResponse.length, responsePos);
376             pos = writeSecurityBufferAndUpdatePointer(baos,
377                     (short) targetName.length, pos);
378             pos = writeSecurityBufferAndUpdatePointer(baos,
379                     (short) userName.length, pos);
380             writeSecurityBufferAndUpdatePointer(baos,
381                     (short) workstationName.length, pos);
382 
383             /**
384             LM/LMv2 Response security buffer 
385             20 NTLM/NTLMv2 Response security buffer 
386             28 Target Name security buffer 
387             36 User Name security buffer 
388             44 Workstation Name security buffer 
389             (52) Session Key (optional) security buffer 
390             (60) Flags (optional) long 
391             (64) OS Version Structure (Optional) 8 bytes
392             **/
393 
394             baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 }); // Session Key Security Buffer ??!
395             baos.write(ByteUtilities.writeInt(flags));
396 
397             if (osVersion != null) {
398                 baos.write(osVersion);
399             }
400             //else
401             //	baos.write(DEFAULT_OS_VERSION);
402 
403             // Order is not mandatory since a pointer is given in the security buffers
404             baos.write(targetName);
405             baos.write(userName);
406             baos.write(workstationName);
407 
408             baos.write(dataLMResponse);
409             baos.write(dataNTLMResponse);
410 
411             msg = baos.toByteArray();
412             baos.close();
413         } catch (Exception e) {
414             e.printStackTrace();
415             return null;
416         }
417 
418         return msg;
419     }
420 }