001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.proxy.handlers.http.ntlm;
021
022import java.io.BufferedReader;
023import java.io.ByteArrayOutputStream;
024import java.io.IOException;
025import java.io.InputStreamReader;
026import java.io.PrintWriter;
027import java.io.UnsupportedEncodingException;
028import java.util.StringTokenizer;
029
030import org.apache.mina.proxy.utils.ByteUtilities;
031
032/**
033 * NTLMUtilities.java - NTLM functions used for authentication and unit testing.
034 * 
035 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
036 * @since MINA 2.0.0-M3
037 */
038public class NTLMUtilities implements NTLMConstants {
039    /**
040     * @see #writeSecurityBuffer(short, short, int, byte[], int)
041     * 
042     * @param length The length of the security buffer
043     * @param bufferOffset The offset in the security buffer
044     * @return Th created buffer
045     */
046    public final static byte[] writeSecurityBuffer(short length, int bufferOffset) {
047        byte[] b = new byte[8];
048        writeSecurityBuffer(length, length, bufferOffset, b, 0);
049
050        return b;
051    }
052
053    /**
054     * Writes a security buffer to the given array <code>b</code> at offset
055     * <code>offset</code>. A security buffer defines a pointer to an area
056     * in the data that defines some data with a variable length. This allows
057     * to have a semi-fixed length header thus making a little bit easier
058     * the decoding process in the NTLM protocol.
059     * 
060     * @param length the length of the security buffer
061     * @param allocated the allocated space for the security buffer (should be
062     * greater or equal to <code>length</code>
063     * @param bufferOffset the offset from the main array where the currently
064     * defined security buffer will be written
065     * @param b the buffer in which we write the security buffer
066     * @param offset the offset at which to write to the b buffer
067     */
068    public final static void writeSecurityBuffer(short length, short allocated, int bufferOffset, byte[] b, int offset) {
069        ByteUtilities.writeShort(length, b, offset);
070        ByteUtilities.writeShort(allocated, b, offset + 2);
071        ByteUtilities.writeInt(bufferOffset, b, offset + 4);
072    }
073
074    /**
075     * Writes the Windows OS version passed in as three byte values
076     * (majorVersion.minorVersion.buildNumber) to the given byte array
077     * at <code>offset</code>.
078     * 
079     * @param majorVersion the major version number
080     * @param minorVersion the minor version number
081     * @param buildNumber the build number
082     * @param b the target byte array
083     * @param offset the offset at which to write in the array
084     */
085    public final static void writeOSVersion(byte majorVersion, byte minorVersion, short buildNumber, byte[] b,
086            int offset) {
087        b[offset] = majorVersion;
088        b[offset + 1] = minorVersion;
089        b[offset + 2] = (byte) buildNumber;
090        b[offset + 3] = (byte) (buildNumber >> 8);
091        b[offset + 4] = 0;
092        b[offset + 5] = 0;
093        b[offset + 6] = 0;
094        b[offset + 7] = 0x0F;
095    }
096
097    /**
098     * Tries to return a valid OS version on Windows systems. If it fails to
099     * do so or if we're running on another OS then a fake Windows XP OS
100     * version is returned because the protocol uses it.
101     * 
102     * @return a NTLM OS version byte buffer
103     */
104    public final static byte[] getOsVersion() {
105        String os = System.getProperty("os.name");
106
107        if ((os == null) || !os.toUpperCase().contains("WINDOWS")) {
108            return DEFAULT_OS_VERSION;
109        }
110
111        byte[] osVer = new byte[8];
112
113        // Let's enclose the code by a try...catch in order to
114        // manage incorrect strings. In this case, we will generate
115        // an exception and deal with the special cases.
116        try {
117            Process pr = Runtime.getRuntime().exec("cmd /C ver");
118            BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()));
119            pr.waitFor();
120
121            String line;
122
123            // We loop as we may have blank lines.
124            do {
125                line = reader.readLine();
126            } while ((line != null) && (line.length() != 0));
127
128            reader.close();
129
130            // If line is null, we must not go any farther
131            if (line == null) {
132                // Throw an exception to jump into the catch() part
133                throw new Exception();
134            }
135
136            // The command line should return a response like :
137            // Microsoft Windows XP [version 5.1.2600]
138            int pos = line.toLowerCase().indexOf("version");
139
140            if (pos == -1) {
141                // Throw an exception to jump into the catch() part
142                throw new Exception();
143            }
144
145            pos += 8;
146            line = line.substring(pos, line.indexOf(']'));
147            StringTokenizer tk = new StringTokenizer(line, ".");
148
149            if (tk.countTokens() != 3) {
150                // Throw an exception to jump into the catch() part
151                throw new Exception();
152            }
153
154            writeOSVersion(Byte.parseByte(tk.nextToken()), Byte.parseByte(tk.nextToken()),
155                    Short.parseShort(tk.nextToken()), osVer, 0);
156        } catch (Exception ex) {
157            try {
158                String version = System.getProperty("os.version");
159                writeOSVersion(Byte.parseByte(version.substring(0, 1)), Byte.parseByte(version.substring(2, 3)),
160                        (short) 0, 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, String domain, Integer customFlags,
181            byte[] osVersion) {
182        byte[] msg = null;
183
184        if (osVersion != null && osVersion.length != 8) {
185            throw new IllegalArgumentException("osVersion parameter should be a 8 byte wide array");
186        }
187
188        if (workStation == null || domain == null) {
189            throw new IllegalArgumentException("workStation and domain must be non null");
190        }
191
192        int flags = customFlags != null ? customFlags | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
193                | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;
194
195        ByteArrayOutputStream baos = new ByteArrayOutputStream();
196
197        try {
198            baos.write(NTLM_SIGNATURE);
199            baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_1));
200            baos.write(ByteUtilities.writeInt(flags));
201
202            byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain);
203            byte[] workStationData = ByteUtilities.getOEMStringAsByteArray(workStation);
204
205            int pos = (osVersion != null) ? 40 : 32;
206            baos.write(writeSecurityBuffer((short) domainData.length, pos + workStationData.length));
207            baos.write(writeSecurityBuffer((short) workStationData.length, pos));
208
209            if (osVersion != null) {
210                baos.write(osVersion);
211            }
212
213            // Order is not mandatory since a pointer is given in the security buffers
214            baos.write(workStationData);
215            baos.write(domainData);
216
217            msg = baos.toByteArray();
218            baos.close();
219        } catch (IOException e) {
220            return null;
221        }
222
223        return msg;
224    }
225
226    /**
227     * Writes a security buffer and returns the pointer of the position
228     * where to write the next security buffer.
229     * 
230     * @param baos the stream where the security buffer is written
231     * @param len the length of the security buffer
232     * @param pointer the position where the security buffer can be written
233     * @return the position where the next security buffer will be written
234     * @throws IOException if writing to the ByteArrayOutputStream fails
235     */
236    public final static int writeSecurityBufferAndUpdatePointer(ByteArrayOutputStream baos, short len, int pointer)
237            throws IOException {
238        baos.write(writeSecurityBuffer(len, pointer));
239
240        return pointer + len;
241    }
242
243    /**
244     * Extracts the NTLM challenge from the type 2 message as an 8 byte array.
245     * 
246     * @param msg the type 2 message byte array
247     * @return the challenge
248     */
249    public final static byte[] extractChallengeFromType2Message(byte[] msg) {
250        byte[] challenge = new byte[8];
251        System.arraycopy(msg, 24, challenge, 0, 8);
252
253        return challenge;
254    }
255
256    /**
257     * Extracts the NTLM flags from the type 2 message.
258     * 
259     * @param msg the type 2 message byte array
260     * @return the proxy flags as an int
261     */
262    public final static int extractFlagsFromType2Message(byte[] msg) {
263        byte[] flagsBytes = new byte[4];
264
265        System.arraycopy(msg, 20, flagsBytes, 0, 4);
266        ByteUtilities.changeWordEndianess(flagsBytes, 0, 4);
267
268        return ByteUtilities.makeIntFromByte4(flagsBytes);
269    }
270
271    /**
272     * Reads the byte array described by the security buffer stored at the
273     * <code>securityBufferOffset</code> offset.
274     * 
275     * @param msg the message where to read the security buffer and it's value
276     * @param securityBufferOffset the offset at which to read the security buffer
277     * @return a new byte array holding the data pointed by the security buffer
278     */
279    public final static byte[] readSecurityBufferTarget(byte[] msg, int securityBufferOffset) {
280        byte[] securityBuffer = new byte[8];
281
282        System.arraycopy(msg, securityBufferOffset, securityBuffer, 0, 8);
283        ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
284        int length = ByteUtilities.makeIntFromByte2(securityBuffer);
285        int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
286
287        byte[] secBufValue = new byte[length];
288        System.arraycopy(msg, offset, secBufValue, 0, length);
289
290        return secBufValue;
291    }
292
293    /**
294     * Extracts the target name from the type 2 message.
295     * 
296     * @param msg the type 2 message byte array
297     * @param msgFlags the flags if null then flags are extracted from the
298     * type 2 message
299     * @return the target name
300     * @throws UnsupportedEncodingException if unable to use the
301     * needed UTF-16LE or ASCII charsets
302     */
303    public final static String extractTargetNameFromType2Message(byte[] msg, Integer msgFlags)
304            throws UnsupportedEncodingException {
305        // Read the security buffer to determine where the target name
306        // is stored and what it's length is
307        byte[] targetName = readSecurityBufferTarget(msg, 12);
308
309        // now we convert it to a string
310        int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
311
312        if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
313            return new String(targetName, "UTF-16LE");
314        }
315
316        return new String(targetName, "ASCII");
317    }
318
319    /**
320     * Extracts the target information block from the type 2 message.
321     * 
322     * @param msg the type 2 message byte array
323     * @param msgFlags the flags if null then flags are extracted from the
324     * type 2 message
325     * @return the target info
326     */
327    public final static byte[] extractTargetInfoFromType2Message(byte[] msg, Integer msgFlags) {
328        int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
329
330        if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) {
331            return null;
332        }
333
334        int pos = 40;
335
336        return readSecurityBufferTarget(msg, pos);
337    }
338
339    /**
340     * Prints to the {@link PrintWriter} the target information block extracted
341     * from the type 2 message.
342     * 
343     * @param msg the type 2 message
344     * @param msgFlags the flags if null then flags are extracted from the
345     * type 2 message
346     * @param out the output target for the information
347     * @throws UnsupportedEncodingException if unable to use the
348     * needed UTF-16LE or ASCII charsets
349     */
350    public final static void printTargetInformationBlockFromType2Message(byte[] msg, Integer msgFlags, PrintWriter out)
351            throws UnsupportedEncodingException {
352        int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
353
354        byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags);
355
356        if (infoBlock == null) {
357            out.println("No target information block found !");
358        } else {
359            int pos = 0;
360
361            while (infoBlock[pos] != 0) {
362                out.print("---\nType " + infoBlock[pos] + ": ");
363
364                switch (infoBlock[pos]) {
365                case 1:
366                    out.println("Server name");
367                    break;
368                case 2:
369                    out.println("Domain name");
370                    break;
371                case 3:
372                    out.println("Fully qualified DNS hostname");
373                    break;
374                case 4:
375                    out.println("DNS domain name");
376                    break;
377                case 5:
378                    out.println("Parent DNS domain name");
379                    break;
380                }
381
382                byte[] len = new byte[2];
383                System.arraycopy(infoBlock, pos + 2, len, 0, 2);
384                ByteUtilities.changeByteEndianess(len, 0, 2);
385
386                int length = ByteUtilities.makeIntFromByte2(len, 0);
387                out.println("Length: " + length + " bytes");
388                out.print("Data: ");
389
390                if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
391                    out.println(new String(infoBlock, pos + 4, length, "UTF-16LE"));
392                } else {
393                    out.println(new String(infoBlock, pos + 4, length, "ASCII"));
394                }
395
396                pos += 4 + length;
397                out.flush();
398            }
399        }
400    }
401
402    /**
403     * @see <a
404     *      href="http://davenport.sourceforge.net/ntlm.html#theType3Message">NTLM
405     *      message type</a>
406     * 
407     * @param user
408     *            the user name
409     * @param password
410     *            the user password
411     * @param challenge
412     *            the challenge response
413     * @param target
414     *            the target name
415     * @param workstation
416     *            the client workstation's name
417     * @param serverFlags
418     *            the flags set by the client
419     * @param osVersion
420     *            the os version of the client
421     * @return the type 3 message
422     */
423    public final static byte[] createType3Message(String user, String password, byte[] challenge, String target,
424            String workstation, Integer serverFlags, byte[] osVersion) {
425        byte[] msg = null;
426
427        if (challenge == null || challenge.length != 8) {
428            throw new IllegalArgumentException("challenge[] should be a 8 byte wide array");
429        }
430
431        if (osVersion != null && osVersion.length != 8) {
432            throw new IllegalArgumentException("osVersion should be a 8 byte wide array");
433        }
434
435        int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS;
436
437        ByteArrayOutputStream baos = new ByteArrayOutputStream();
438
439        try {
440            baos.write(NTLM_SIGNATURE);
441            baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3));
442
443            byte[] dataLMResponse = NTLMResponses.getLMResponse(password, challenge);
444            byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, challenge);
445
446            boolean useUnicode = ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE);
447            byte[] targetName = ByteUtilities.encodeString(target, useUnicode);
448            byte[] userName = ByteUtilities.encodeString(user, useUnicode);
449            byte[] workstationName = ByteUtilities.encodeString(workstation, useUnicode);
450
451            int pos = osVersion != null ? 72 : 64;
452            int responsePos = pos + targetName.length + userName.length + workstationName.length;
453            responsePos = writeSecurityBufferAndUpdatePointer(baos, (short) dataLMResponse.length, responsePos);
454            writeSecurityBufferAndUpdatePointer(baos, (short) dataNTLMResponse.length, responsePos);
455            pos = writeSecurityBufferAndUpdatePointer(baos, (short) targetName.length, pos);
456            pos = writeSecurityBufferAndUpdatePointer(baos, (short) userName.length, pos);
457            writeSecurityBufferAndUpdatePointer(baos, (short) workstationName.length, pos);
458
459            /**
460            LM/LMv2 Response security buffer
461            20 NTLM/NTLMv2 Response security buffer
462            28 Target Name security buffer
463            36 User Name security buffer
464            44 Workstation Name security buffer
465            (52) Session Key (optional) security buffer
466            (60) Flags (optional) long
467            (64) OS Version Structure (Optional) 8 bytes
468             **/
469
470            // Session Key Security Buffer ??!
471            baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 });
472
473            baos.write(ByteUtilities.writeInt(flags));
474
475            if (osVersion != null) {
476                baos.write(osVersion);
477            }
478
479            // Order is not mandatory since a pointer is given in the security buffers
480            baos.write(targetName);
481            baos.write(userName);
482            baos.write(workstationName);
483
484            baos.write(dataLMResponse);
485            baos.write(dataNTLMResponse);
486
487            msg = baos.toByteArray();
488            baos.close();
489        } catch (Exception e) {
490            e.printStackTrace();
491            return null;
492        }
493
494        return msg;
495    }
496}