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