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 }