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 @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
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
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 }