1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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 }