View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  
20  package davmail.http;
21  
22  import org.apache.commons.codec.binary.Base64;
23  
24  import java.io.UnsupportedEncodingException;
25  import java.nio.ByteBuffer;
26  import java.nio.ByteOrder;
27  import java.util.Date;
28  
29  public class NTLMMessageDecoder {
30      static final int NTLMSSP_KEY_56 = 0x80000000;
31      static final int NTLMSSP_KEY_EXCHANGE = 0x40000000;
32      static final int NTLMSSP_KEY_128 = 0x20000000;
33  
34      static final int NTLMSSP_TARGET_INFO = 0x00800000;
35  
36      static final int NTLMSSP_NTLM2_KEY = 0x00080000;
37      static final int NTLMSSP_CHALL_NOT_NT = 0x00040000;
38      static final int NTLMSSP_CHALL_ACCEPT = 0x00020000;
39      static final int NTLMSSP_CHALL_INIT = 0x00010000;
40      static final int NTLMSSP_ALWAYS_SIGN = 0x00008000;
41  
42      static final long NTLMSSP_NEGOTIATE_UNICODE = 0x00000001;
43      static final long NTLMSSP_REQUEST_TARGET = 0x00000004;
44      static final long NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000;
45      static final long NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000;
46      static final long NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000;
47      static final long NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
48      static final long NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000;
49  
50      static final int NTLMSSP_LOCAL_CALL = 0x00004000;
51      static final int NTLMSSP_WORKSTATION = 0x00002000;
52      static final int NTLMSSP_DOMAIN = 0x00001000;
53  
54      static final int NTLMSSP_NTLM_KEY = 0x00000200;
55      static final int NTLMSSP_NETWARE = 0x00000100;
56      static final int NTLMSSP_LM_KEY = 0x00000080;
57      static final int NTLMSSP_DATAGRAM = 0x00000040;
58      static final int NTLMSSP_SEAL = 0x00000020;
59      static final int NTLMSSP_SIGN = 0x00000010;
60  
61      static final int NTLMSSP_TARGET = 0x00000004;
62      static final int NTLMSSP_OEM = 0x00000002;
63      static final int NTLMSSP_UNICODE = 0x00000001;
64  
65      private final String base64Message;
66  
67      public static String decode(String message) {
68          return new NTLMMessageDecoder(message).decodeMessage();
69      }
70  
71      NTLMMessageDecoder(String base64Message) {
72          this.base64Message = base64Message;
73      }
74  
75      public String decodeMessage() {
76          StringBuilder buffer = new StringBuilder();
77          byte[] message = Base64.decodeBase64(base64Message.getBytes());
78          String prefix = new String(message, 0, 7);
79          if (!"NTLMSSP".equals(prefix) || message[7] != 0) {
80              buffer.append("Invalid Prefix: ");
81          } else {
82              int messageType = getInt(message, 8);
83              buffer.append("NTLM type: ").append(messageType);
84  
85              if (messageType == 1) {
86                  buffer.append(getFlags(message, 12));
87                  buffer.append(" domain: ").append(getStringValue(message, 16, "ASCII"));
88                  buffer.append(" host: ").append(getStringValue(message, 24, "ASCII"));
89              } else if (messageType == 2) {
90                  buffer.append(" target: ").append(getStringValue(message, 12, "UnicodeLittleUnmarked"));
91                  buffer.append(getFlags(message, 20));
92                  byte[] challenge = getBytes(message, 24, 8);
93                  buffer.append(" challenge: ").append(toHexString(challenge));
94                  if (message.length > 32) {
95                      buffer.append(" context: 0x").append(Integer.toHexString(getInt(message, 32))).append(" 0x").append(Integer.toHexString(getInt(message, 36)));
96                  }
97                  if (message.length > 40) {
98                      buffer.append(" target info: ").append(getValues(message, 40, "UnicodeLittleUnmarked"));
99                  }
100                 if (message.length > 48) {
101                     buffer.append(getOSVersion(message, 48));
102                 }
103 
104             } else if (messageType == 3) {
105                 buffer.append(" lm response: ").append(getByteValue(message, 12));
106                 buffer.append(" ntlm response: ").append(getByteValue(message, 20));
107                 buffer.append(" target name: ").append(getStringValue(message, 28, "UnicodeLittleUnmarked"));
108                 buffer.append(" user name: ").append(getStringValue(message, 36, "UnicodeLittleUnmarked"));
109                 buffer.append(" workstation name: ").append(getStringValue(message, 44, "UnicodeLittleUnmarked"));
110                 // optional
111                 if (getShort(message, 32) > 52) {
112                     buffer.append(" session key: ").append(getByteValue(message, 52));
113                 }
114                 if (getShort(message, 32) > 60) {
115                     buffer.append(getFlags(message, 60));
116                 }
117                 if (getShort(message, 32) > 64) {
118                     buffer.append(getOSVersion(message, 64));
119                 }
120             }
121         }
122         return buffer.toString();
123     }
124 
125 
126     public int getShort(byte[] data, int offset) {
127         return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8);
128     }
129 
130     public int getInt(byte[] data, int offset) {
131         return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8) + ((data[offset + 2] & 0xFF) << 16) + ((data[offset + 3] & 0xFF) << 24);
132     }
133 
134     public String getStringValue(byte[] data, int offset, String encoding) {
135         int length = getShort(data, offset);
136         int maxLength = getShort(data, offset + 2);
137         int valueOffset = getInt(data, offset + 4);
138         String value = "";
139         if (valueOffset + length <= data.length) {
140             try {
141                 value = new String(data, valueOffset, length, encoding);
142             } catch (UnsupportedEncodingException e) {
143                 value = "[Invalid encoding " + encoding + "]";
144             }
145         }
146         return "(length: " + length + " maxLength: " + maxLength + " offset: " + valueOffset + " value:" + value + ")";
147     }
148 
149     public String getValueType(int valueType) {
150         if (valueType == 0) {
151             return "EOL";
152         } else if (valueType == 1) {
153             return "NBCOMPUTERNAME";
154         } else if (valueType == 2) {
155             return "NBDOMAINNAME";
156         } else if (valueType == 3) {
157             return "DNSCOMPUTERNAME";
158         } else if (valueType == 4) {
159             return "DNSDOMAINNAME";
160         } else if (valueType == 5) {
161             return "DNSTREENAME";
162         } else if (valueType == 6) {
163             return "FLAGS";
164         } else if (valueType == 7) {
165             return "TIMESTAMP";
166         } else if (valueType == 8) {
167             return "SINGLEHOST";
168         } else if (valueType == 9) {
169             return "TARGETNAME";
170         } else if (valueType == 10) {
171             return "CHANNELBINDING";
172         } else {
173             return String.valueOf(valueType);
174         }
175     }
176 
177     public String getValues(byte[] data, int offset, String encoding) {
178         StringBuilder buffer = new StringBuilder();
179         int length = getShort(data, offset);
180         int maxLength = getShort(data, offset + 2);
181         int valueOffset = getInt(data, offset + 4);
182         // String array available at valueOffset
183         int valueType = getShort(data, valueOffset);
184         int valueLength = getShort(data, valueOffset + 2);
185         while (valueLength > 0) {
186             String value;
187             if (valueType == 7) {
188                 long timestamp = ByteBuffer.wrap(data, valueOffset + 4, valueLength).order(ByteOrder.LITTLE_ENDIAN).getLong();
189                 value = timestamp + " " + new Date((timestamp - 116444736000000000L) / 10000);
190             } else {
191                 try {
192                     value = new String(data, valueOffset + 4, valueLength, encoding);
193                 } catch (UnsupportedEncodingException e) {
194                     value = "[Invalid encoding " + encoding + "]";
195                 }
196             }
197             buffer.append("(").append(getValueType(valueType)).append(": ").append(value).append(")");
198             valueOffset += valueLength + 4;
199             valueType = getShort(data, valueOffset);
200             valueLength = getShort(data, valueOffset + 2);
201         }
202 
203         return buffer.toString();
204     }
205 
206     public String getByteValue(byte[] data, int offset) {
207         int length = getShort(data, offset);
208         int maxLength = getShort(data, offset + 2);
209         int valueOffset = getInt(data, offset + 4);
210         byte[] value = getBytes(data, valueOffset, length);
211         return "(length: " + length + " maxLength: " + maxLength + " offset: " + valueOffset + " value:" + toHexString(value) + ")";
212     }
213 
214     public byte[] getBytes(byte[] data, int offset, int length) {
215         byte[] value = null;
216         if (offset + length <= data.length) {
217             value = new byte[length];
218             System.arraycopy(data, offset, value, 0, length);
219         }
220         return value;
221     }
222 
223     public String getFlags(byte[] data, int offset) {
224         StringBuilder buffer = new StringBuilder();
225         int flags = getInt(data, offset);
226         buffer.append(" flags: 0x").append(Integer.toHexString(flags));
227 
228         if ((flags & NTLMSSP_KEY_56) != 0) {
229             buffer.append(" NTLMSSP_KEY_56");
230         }
231         if ((flags & NTLMSSP_KEY_EXCHANGE) != 0) {
232             buffer.append(" NTLMSSP_KEY_EXCHANGE");
233         }
234         if ((flags & NTLMSSP_KEY_128) != 0) {
235             buffer.append(" NTLMSSP_KEY_128");
236         }
237 
238         if ((flags & NTLMSSP_TARGET_INFO) != 0) {
239             buffer.append(" NTLMSSP_TARGET_INFO");
240         }
241 
242         if ((flags & NTLMSSP_NTLM2_KEY) != 0) {
243             buffer.append(" NTLMSSP_NTLM2_KEY");
244         }
245         if ((flags & NTLMSSP_CHALL_NOT_NT) != 0) {
246             buffer.append(" NTLMSSP_CHALL_NOT_NT");
247         }
248         if ((flags & NTLMSSP_CHALL_ACCEPT) != 0) {
249             buffer.append(" NTLMSSP_CHALL_ACCEPT");
250         }
251         if ((flags & NTLMSSP_CHALL_INIT) != 0) {
252             buffer.append(" NTLMSSP_CHALL_INIT");
253         }
254         if ((flags & NTLMSSP_ALWAYS_SIGN) != 0) {
255             buffer.append(" NTLMSSP_ALWAYS_SIGN");
256         }
257         if ((flags & NTLMSSP_LOCAL_CALL) != 0) {
258             buffer.append(" NTLMSSP_LOCAL_CALL");
259         }
260         if ((flags & NTLMSSP_WORKSTATION) != 0) {
261             buffer.append(" NTLMSSP_WORKSTATION");
262         }
263         if ((flags & NTLMSSP_DOMAIN) != 0) {
264             buffer.append(" NTLMSSP_DOMAIN");
265         }
266 
267         if ((flags & NTLMSSP_NTLM_KEY) != 0) {
268             buffer.append(" NTLMSSP_NTLM_KEY");
269         }
270         if ((flags & NTLMSSP_NETWARE) != 0) {
271             buffer.append(" NTLMSSP_NETWARE");
272         }
273         if ((flags & NTLMSSP_LM_KEY) != 0) {
274             buffer.append(" NTLMSSP_LM_KEY");
275         }
276         if ((flags & NTLMSSP_DATAGRAM) != 0) {
277             buffer.append(" NTLMSSP_DATAGRAM");
278         }
279         if ((flags & NTLMSSP_SEAL) != 0) {
280             buffer.append(" NTLMSSP_SEAL");
281         }
282         if ((flags & NTLMSSP_SIGN) != 0) {
283             buffer.append(" NTLMSSP_SIGN");
284         }
285 
286         if ((flags & NTLMSSP_TARGET) != 0) {
287             buffer.append(" NTLMSSP_TARGET");
288         }
289         if ((flags & NTLMSSP_OEM) != 0) {
290             buffer.append(" NTLMSSP_OEM");
291         }
292         if ((flags & NTLMSSP_UNICODE) != 0) {
293             buffer.append(" NTLMSSP_UNICODE");
294         }
295         return buffer.toString();
296     }
297 
298     public String getOSVersion(byte[] data, int offset) {
299         return " os version: " + data[offset] + '.' + data[offset + 1] + ' ' + getShort(data, offset + 2);
300     }
301 
302 
303     public String toHexString(byte[] data) {
304         StringBuilder buffer = new StringBuilder();
305         for (byte b : data) {
306             if ((b & 0xF0) == 0) {
307                 buffer.append('0');
308             }
309             buffer.append(Integer.toHexString(b & 0xFF));
310         }
311         return buffer.toString();
312     }
313 
314 }