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  @SuppressWarnings({"unused", "SpellCheckingInspection"})
30  public class NTLMMessageDecoder {
31      static final int NTLMSSP_KEY_56 = 0x80000000;
32      static final int NTLMSSP_KEY_EXCHANGE = 0x40000000;
33      static final int NTLMSSP_KEY_128 = 0x20000000;
34  
35      static final int NTLMSSP_TARGET_INFO = 0x00800000;
36  
37      static final int NTLMSSP_NTLM2_KEY = 0x00080000;
38      static final int NTLMSSP_CHALL_NOT_NT = 0x00040000;
39      static final int NTLMSSP_CHALL_ACCEPT = 0x00020000;
40      static final int NTLMSSP_CHALL_INIT = 0x00010000;
41      static final int NTLMSSP_ALWAYS_SIGN = 0x00008000;
42  
43      static final long NTLMSSP_NEGOTIATE_UNICODE = 0x00000001;
44      static final long NTLMSSP_REQUEST_TARGET = 0x00000004;
45      static final long NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000;
46      static final long NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000;
47      static final long NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000;
48      static final long NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
49      static final long NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000;
50  
51      static final int NTLMSSP_LOCAL_CALL = 0x00004000;
52      static final int NTLMSSP_WORKSTATION = 0x00002000;
53      static final int NTLMSSP_DOMAIN = 0x00001000;
54  
55      static final int NTLMSSP_NTLM_KEY = 0x00000200;
56      static final int NTLMSSP_NETWARE = 0x00000100;
57      static final int NTLMSSP_LM_KEY = 0x00000080;
58      static final int NTLMSSP_DATAGRAM = 0x00000040;
59      static final int NTLMSSP_SEAL = 0x00000020;
60      static final int NTLMSSP_SIGN = 0x00000010;
61  
62      static final int NTLMSSP_TARGET = 0x00000004;
63      static final int NTLMSSP_OEM = 0x00000002;
64      static final int NTLMSSP_UNICODE = 0x00000001;
65  
66      private final String base64Message;
67  
68      public static String decode(String message) {
69          return new NTLMMessageDecoder(message).decodeMessage();
70      }
71  
72      NTLMMessageDecoder(String base64Message) {
73          this.base64Message = base64Message;
74      }
75  
76      public String decodeMessage() {
77          StringBuilder buffer = new StringBuilder();
78          byte[] message = Base64.decodeBase64(base64Message.getBytes());
79          String prefix = new String(message, 0, 7);
80          if (!"NTLMSSP".equals(prefix) || message[7] != 0) {
81              buffer.append("Invalid Prefix: ");
82          } else {
83              int messageType = getInt(message, 8);
84              buffer.append("NTLM type: ").append(messageType);
85  
86              if (messageType == 1) {
87                  buffer.append(getFlags(message, 12));
88                  buffer.append(" domain: ").append(getStringValue(message, 16, "ASCII"));
89                  buffer.append(" host: ").append(getStringValue(message, 24, "ASCII"));
90              } else if (messageType == 2) {
91                  buffer.append(" target: ").append(getStringValue(message, 12, "UnicodeLittleUnmarked"));
92                  buffer.append(getFlags(message, 20));
93                  byte[] challenge = getBytes(message, 24, 8);
94                  buffer.append(" challenge: ").append(toHexString(challenge));
95                  if (message.length > 32) {
96                      buffer.append(" context: 0x").append(Integer.toHexString(getInt(message, 32))).append(" 0x").append(Integer.toHexString(getInt(message, 36)));
97                  }
98                  if (message.length > 40) {
99                      buffer.append(" target info: ").append(getValues(message, 40, "UnicodeLittleUnmarked"));
100                 }
101                 if (message.length > 48) {
102                     buffer.append(getOSVersion(message, 48));
103                 }
104 
105             } else if (messageType == 3) {
106                 buffer.append(" lm response: ").append(getByteValue(message, 12));
107                 buffer.append(" ntlm response: ").append(getByteValue(message, 20));
108                 buffer.append(" target name: ").append(getStringValue(message, 28, "UnicodeLittleUnmarked"));
109                 buffer.append(" user name: ").append(getStringValue(message, 36, "UnicodeLittleUnmarked"));
110                 buffer.append(" workstation name: ").append(getStringValue(message, 44, "UnicodeLittleUnmarked"));
111                 // optional
112                 if (getShort(message, 32) > 52) {
113                     buffer.append(" session key: ").append(getByteValue(message, 52));
114                 }
115                 if (getShort(message, 32) > 60) {
116                     buffer.append(getFlags(message, 60));
117                 }
118                 if (getShort(message, 32) > 64) {
119                     buffer.append(getOSVersion(message, 64));
120                 }
121             }
122         }
123         return buffer.toString();
124     }
125 
126 
127     public int getShort(byte[] data, int offset) {
128         return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8);
129     }
130 
131     public int getInt(byte[] data, int offset) {
132         return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8) + ((data[offset + 2] & 0xFF) << 16) + ((data[offset + 3] & 0xFF) << 24);
133     }
134 
135     public String getStringValue(byte[] data, int offset, String encoding) {
136         int length = getShort(data, offset);
137         int maxLength = getShort(data, offset + 2);
138         int valueOffset = getInt(data, offset + 4);
139         String value = "";
140         if (valueOffset + length <= data.length) {
141             try {
142                 value = new String(data, valueOffset, length, encoding);
143             } catch (UnsupportedEncodingException e) {
144                 value = "[Invalid encoding " + encoding + "]";
145             }
146         }
147         return "(length: " + length + " maxLength: " + maxLength + " offset: " + valueOffset + " value:" + value + ")";
148     }
149 
150     public String getValueType(int valueType) {
151         if (valueType == 0) {
152             return "EOL";
153         } else if (valueType == 1) {
154             return "NBCOMPUTERNAME";
155         } else if (valueType == 2) {
156             return "NBDOMAINNAME";
157         } else if (valueType == 3) {
158             return "DNSCOMPUTERNAME";
159         } else if (valueType == 4) {
160             return "DNSDOMAINNAME";
161         } else if (valueType == 5) {
162             return "DNSTREENAME";
163         } else if (valueType == 6) {
164             return "FLAGS";
165         } else if (valueType == 7) {
166             return "TIMESTAMP";
167         } else if (valueType == 8) {
168             return "SINGLEHOST";
169         } else if (valueType == 9) {
170             return "TARGETNAME";
171         } else if (valueType == 10) {
172             return "CHANNELBINDING";
173         } else {
174             return String.valueOf(valueType);
175         }
176     }
177 
178     public String getValues(byte[] data, int offset, String encoding) {
179         StringBuilder buffer = new StringBuilder();
180         int length = getShort(data, offset);
181         int maxLength = getShort(data, offset + 2);
182         int valueOffset = getInt(data, offset + 4);
183         // String array available at valueOffset
184         int valueType = getShort(data, valueOffset);
185         int valueLength = getShort(data, valueOffset + 2);
186         while (valueLength > 0) {
187             String value;
188             if (valueType == 7) {
189                 long timestamp = ByteBuffer.wrap(data, valueOffset + 4, valueLength).order(ByteOrder.LITTLE_ENDIAN).getLong();
190                 value = timestamp + " " + new Date((timestamp - 116444736000000000L) / 10000);
191             } else {
192                 try {
193                     value = new String(data, valueOffset + 4, valueLength, encoding);
194                 } catch (UnsupportedEncodingException e) {
195                     value = "[Invalid encoding " + encoding + "]";
196                 }
197             }
198             buffer.append("(").append(getValueType(valueType)).append(": ").append(value).append(")");
199             valueOffset += valueLength + 4;
200             valueType = getShort(data, valueOffset);
201             valueLength = getShort(data, valueOffset + 2);
202         }
203 
204         return buffer.toString();
205     }
206 
207     public String getByteValue(byte[] data, int offset) {
208         int length = getShort(data, offset);
209         int maxLength = getShort(data, offset + 2);
210         int valueOffset = getInt(data, offset + 4);
211         byte[] value = getBytes(data, valueOffset, length);
212         return "(length: " + length + " maxLength: " + maxLength + " offset: " + valueOffset + " value:" + toHexString(value) + ")";
213     }
214 
215     public byte[] getBytes(byte[] data, int offset, int length) {
216         byte[] value = null;
217         if (offset + length <= data.length) {
218             value = new byte[length];
219             System.arraycopy(data, offset, value, 0, length);
220         }
221         return value;
222     }
223 
224     public String getFlags(byte[] data, int offset) {
225         StringBuilder buffer = new StringBuilder();
226         int flags = getInt(data, offset);
227         buffer.append(" flags: 0x").append(Integer.toHexString(flags));
228 
229         if ((flags & NTLMSSP_KEY_56) != 0) {
230             buffer.append(" NTLMSSP_KEY_56");
231         }
232         if ((flags & NTLMSSP_KEY_EXCHANGE) != 0) {
233             buffer.append(" NTLMSSP_KEY_EXCHANGE");
234         }
235         if ((flags & NTLMSSP_KEY_128) != 0) {
236             buffer.append(" NTLMSSP_KEY_128");
237         }
238 
239         if ((flags & NTLMSSP_TARGET_INFO) != 0) {
240             buffer.append(" NTLMSSP_TARGET_INFO");
241         }
242 
243         if ((flags & NTLMSSP_NTLM2_KEY) != 0) {
244             buffer.append(" NTLMSSP_NTLM2_KEY");
245         }
246         if ((flags & NTLMSSP_CHALL_NOT_NT) != 0) {
247             buffer.append(" NTLMSSP_CHALL_NOT_NT");
248         }
249         if ((flags & NTLMSSP_CHALL_ACCEPT) != 0) {
250             buffer.append(" NTLMSSP_CHALL_ACCEPT");
251         }
252         if ((flags & NTLMSSP_CHALL_INIT) != 0) {
253             buffer.append(" NTLMSSP_CHALL_INIT");
254         }
255         if ((flags & NTLMSSP_ALWAYS_SIGN) != 0) {
256             buffer.append(" NTLMSSP_ALWAYS_SIGN");
257         }
258         if ((flags & NTLMSSP_LOCAL_CALL) != 0) {
259             buffer.append(" NTLMSSP_LOCAL_CALL");
260         }
261         if ((flags & NTLMSSP_WORKSTATION) != 0) {
262             buffer.append(" NTLMSSP_WORKSTATION");
263         }
264         if ((flags & NTLMSSP_DOMAIN) != 0) {
265             buffer.append(" NTLMSSP_DOMAIN");
266         }
267 
268         if ((flags & NTLMSSP_NTLM_KEY) != 0) {
269             buffer.append(" NTLMSSP_NTLM_KEY");
270         }
271         if ((flags & NTLMSSP_NETWARE) != 0) {
272             buffer.append(" NTLMSSP_NETWARE");
273         }
274         if ((flags & NTLMSSP_LM_KEY) != 0) {
275             buffer.append(" NTLMSSP_LM_KEY");
276         }
277         if ((flags & NTLMSSP_DATAGRAM) != 0) {
278             buffer.append(" NTLMSSP_DATAGRAM");
279         }
280         if ((flags & NTLMSSP_SEAL) != 0) {
281             buffer.append(" NTLMSSP_SEAL");
282         }
283         if ((flags & NTLMSSP_SIGN) != 0) {
284             buffer.append(" NTLMSSP_SIGN");
285         }
286 
287         if ((flags & NTLMSSP_TARGET) != 0) {
288             buffer.append(" NTLMSSP_TARGET");
289         }
290         if ((flags & NTLMSSP_OEM) != 0) {
291             buffer.append(" NTLMSSP_OEM");
292         }
293         if ((flags & NTLMSSP_UNICODE) != 0) {
294             buffer.append(" NTLMSSP_UNICODE");
295         }
296         return buffer.toString();
297     }
298 
299     public String getOSVersion(byte[] data, int offset) {
300         return " os version: " + data[offset] + '.' + data[offset + 1] + ' ' + getShort(data, offset + 2);
301     }
302 
303 
304     public String toHexString(byte[] data) {
305         StringBuilder buffer = new StringBuilder();
306         if (data != null) {
307             for (byte b : data) {
308                 if ((b & 0xF0) == 0) {
309                     buffer.append('0');
310                 }
311                 buffer.append(Integer.toHexString(b & 0xFF));
312             }
313         }
314         return buffer.toString();
315     }
316 
317 }