/src/gss-ntlmssp/src/ntlm.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright 2013 Simo Sorce <simo@samba.org>, see COPYING for license */ |
2 | | |
3 | | /* This File implements the NTLM protocol as specified by: |
4 | | * [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol |
5 | | * |
6 | | * Additional cross checking with: |
7 | | * http://davenport.sourceforge.net/ntlm.html |
8 | | */ |
9 | | |
10 | | #include <alloca.h> |
11 | | #include <endian.h> |
12 | | #include <errno.h> |
13 | | #include <iconv.h> |
14 | | #include <stddef.h> |
15 | | #include <string.h> |
16 | | #include <sys/time.h> |
17 | | |
18 | | #include <unicase.h> |
19 | | |
20 | | #include "ntlm.h" |
21 | | |
22 | | #pragma pack(push, 1) |
23 | | struct wire_av_pair { |
24 | | uint16_t av_id; |
25 | | uint16_t av_len; |
26 | | uint8_t value[]; /* variable */ |
27 | | }; |
28 | | #pragma pack(pop) |
29 | | |
30 | | enum msv_av_ids { |
31 | | MSV_AV_EOL = 0, |
32 | | MSV_AV_NB_COMPUTER_NAME, |
33 | | MSV_AV_NB_DOMAIN_NAME, |
34 | | MSV_AV_DNS_COMPUTER_NAME, |
35 | | MSV_AV_DNS_DOMAIN_NAME, |
36 | | MSV_AV_DNS_TREE_NAME, |
37 | | MSV_AV_FLAGS, |
38 | | MSV_AV_TIMESTAMP, |
39 | | MSV_AV_SINGLE_HOST, |
40 | | MSV_AV_TARGET_NAME, |
41 | | MSV_AV_CHANNEL_BINDINGS |
42 | | }; |
43 | | |
44 | | /* Used only on the same host */ |
45 | | #pragma pack(push, 1) |
46 | | struct wire_single_host_data { |
47 | | uint32_t size; |
48 | | uint32_t Z4; |
49 | | uint32_t data_present; |
50 | | uint32_t custom_data; |
51 | | uint8_t machine_id[32]; |
52 | | }; |
53 | | #pragma pack(pop) |
54 | | |
55 | | #pragma pack(push, 1) |
56 | | struct wire_ntlm_cli_chal { |
57 | | uint8_t resp_type; |
58 | | uint8_t hi_resp_type; |
59 | | uint16_t reserved1; |
60 | | uint32_t reserved2; |
61 | | uint64_t timestamp; |
62 | | uint8_t cli_chal[8]; |
63 | | uint32_t reserved3; |
64 | | uint8_t av_pairs[]; /* variable */ |
65 | | }; |
66 | | #pragma pack(pop) |
67 | | |
68 | | struct ntlm_ctx { |
69 | | iconv_t from_oem; |
70 | | iconv_t to_oem; |
71 | | }; |
72 | | |
73 | | int ntlm_init_ctx(struct ntlm_ctx **ctx) |
74 | 1.14k | { |
75 | 1.14k | struct ntlm_ctx *_ctx; |
76 | 1.14k | int ret = 0; |
77 | | |
78 | 1.14k | _ctx = calloc(1, sizeof(struct ntlm_ctx)); |
79 | 1.14k | if (!_ctx) return ENOMEM; |
80 | | |
81 | 1.14k | _ctx->from_oem = iconv_open("UTF-16LE", "UTF-8"); |
82 | 1.14k | if (_ctx->from_oem == (iconv_t) -1) { |
83 | 0 | ret = errno; |
84 | 0 | } |
85 | | |
86 | 1.14k | _ctx->to_oem = iconv_open("UTF-8", "UTF-16LE"); |
87 | 1.14k | if (_ctx->to_oem == (iconv_t) -1) { |
88 | 0 | iconv_close(_ctx->from_oem); |
89 | 0 | ret = errno; |
90 | 0 | } |
91 | | |
92 | 1.14k | if (ret) { |
93 | 0 | safefree(_ctx); |
94 | 1.14k | } else { |
95 | 1.14k | *ctx = _ctx; |
96 | 1.14k | } |
97 | 1.14k | return ret; |
98 | 1.14k | } |
99 | | |
100 | | int ntlm_free_ctx(struct ntlm_ctx **ctx) |
101 | 1.14k | { |
102 | 1.14k | int ret; |
103 | | |
104 | 1.14k | if (!ctx || !*ctx) return 0; |
105 | | |
106 | 1.14k | if ((*ctx)->from_oem) { |
107 | 1.14k | ret = iconv_close((*ctx)->from_oem); |
108 | 1.14k | if (ret) goto done; |
109 | 1.14k | } |
110 | | |
111 | 1.14k | if ((*ctx)->to_oem) { |
112 | 1.14k | ret = iconv_close((*ctx)->to_oem); |
113 | 1.14k | } |
114 | | |
115 | 1.14k | done: |
116 | 1.14k | if (ret) ret = errno; |
117 | 1.14k | safefree(*ctx); |
118 | 1.14k | return ret; |
119 | 1.14k | } |
120 | | |
121 | | void ntlm_free_buffer_data(struct ntlm_buffer *buf) |
122 | 8.32k | { |
123 | 8.32k | if (!buf) return; |
124 | | |
125 | 8.32k | safefree(buf->data); |
126 | 8.32k | buf->length = 0; |
127 | 8.32k | } |
128 | | |
129 | | /* A FILETIME structure is effectively a little endian 64 bit integer |
130 | | * with the time from January 1, 1601 UTC with 10s of microsecond resolution. |
131 | | */ |
132 | 957 | #define FILETIME_EPOCH_VALUE 116444736000000000LL |
133 | | uint64_t ntlm_timestamp_now(void) |
134 | 957 | { |
135 | 957 | struct timeval tv; |
136 | 957 | uint64_t filetime; |
137 | | |
138 | 957 | gettimeofday(&tv, NULL); |
139 | | |
140 | | /* set filetime to the time representing the eopch */ |
141 | 957 | filetime = FILETIME_EPOCH_VALUE; |
142 | | /* add the number of seconds since the epoch */ |
143 | 957 | filetime += (uint64_t)tv.tv_sec * 10000000; |
144 | | /* add the number of microseconds since the epoch */ |
145 | 957 | filetime += tv.tv_usec * 10; |
146 | | |
147 | 957 | return filetime; |
148 | 957 | } |
149 | | |
150 | | bool ntlm_casecmp(const char *s1, const char *s2) |
151 | 0 | { |
152 | 0 | size_t s1_len, s2_len; |
153 | 0 | int ret, res; |
154 | |
|
155 | 0 | if (s1 == s2) return true; |
156 | 0 | if (!s1 || !s2) return false; |
157 | | |
158 | 0 | s1_len = strlen(s1); |
159 | 0 | s2_len = strlen(s2); |
160 | |
|
161 | 0 | ret = ulc_casecmp(s1, s1_len, s2, s2_len, |
162 | 0 | uc_locale_language(), NULL, &res); |
163 | 0 | if (ret || res != 0) return false; |
164 | 0 | return true; |
165 | 0 | } |
166 | | |
167 | | |
168 | | /** |
169 | | * @brief Converts a string using the provided iconv context. |
170 | | * This function is ok only to convert utf8<->utf16le |
171 | | * |
172 | | * @param cd The iconv context |
173 | | * @param in Input buffer |
174 | | * @param out Output buffer |
175 | | * @param baselen Input length |
176 | | * @param outlen Returned length of out buffer |
177 | | * |
178 | | * NOTE: out must be preallocated to a size of baselen * 2 |
179 | | * |
180 | | * @return 0 on success or a standard error value on error. |
181 | | */ |
182 | | static int ntlm_str_convert(iconv_t cd, |
183 | | const char *in, char *out, |
184 | | size_t baselen, size_t *outlen) |
185 | 3.40k | { |
186 | 3.40k | char *_in; |
187 | 3.40k | size_t inleft, outleft; |
188 | 3.40k | size_t ret; |
189 | | |
190 | 3.40k | ret = iconv(cd, NULL, NULL, NULL, NULL); |
191 | 3.40k | if (ret == -1) return errno; |
192 | | |
193 | 3.40k | _in = discard_const(in); |
194 | 3.40k | inleft = baselen; |
195 | | /* conservative max_size calculation in case lots of octects end up |
196 | | * being multiple bytes in length (in both directions) */ |
197 | 3.40k | outleft = baselen * 2; |
198 | | |
199 | 3.40k | ret = iconv(cd, &_in, &inleft, &out, &outleft); |
200 | 3.40k | if (ret == -1) return errno; |
201 | | |
202 | 3.38k | if (outlen) { |
203 | 3.38k | *outlen = baselen * 2 - outleft; |
204 | 3.38k | } |
205 | 3.38k | return 0; |
206 | 3.40k | } |
207 | | |
208 | | uint8_t ntlmssp_sig[8] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}; |
209 | | |
210 | | static void ntlm_encode_header(struct wire_msg_hdr *hdr, uint32_t msg_type) |
211 | 957 | { |
212 | 957 | memcpy(hdr->signature, ntlmssp_sig, 8); |
213 | 957 | hdr->msg_type = htole32(msg_type); |
214 | 957 | } |
215 | | |
216 | | static int ntlm_decode_header(struct wire_msg_hdr *hdr, uint32_t *msg_type) |
217 | 2.05k | { |
218 | 2.05k | if (memcmp(hdr->signature, ntlmssp_sig, 8) != 0) { |
219 | 100 | return ERR_DECODE; |
220 | 100 | } |
221 | | |
222 | 1.95k | *msg_type = le32toh(hdr->msg_type); |
223 | 1.95k | return 0; |
224 | 2.05k | } |
225 | | |
226 | | static int ntlm_encode_oem_str(struct wire_field_hdr *hdr, |
227 | | struct ntlm_buffer *buffer, |
228 | | size_t *data_offs, |
229 | | const char *str, int str_len) |
230 | 643 | { |
231 | 643 | if (*data_offs + str_len > buffer->length) { |
232 | 0 | return ERR_ENCODE; |
233 | 0 | } |
234 | | |
235 | 643 | memcpy(&buffer->data[*data_offs], str, str_len); |
236 | 643 | hdr->len = htole16(str_len); |
237 | 643 | hdr->max_len = htole16(str_len); |
238 | 643 | hdr->offset = htole32(*data_offs); |
239 | | |
240 | 643 | *data_offs += str_len; |
241 | 643 | return 0; |
242 | 643 | } |
243 | | |
244 | | static int ntlm_decode_oem_str(struct wire_field_hdr *str_hdr, |
245 | | struct ntlm_buffer *buffer, |
246 | | size_t payload_offs, char **_str) |
247 | 580 | { |
248 | 580 | uint16_t str_len; |
249 | 580 | uint32_t str_offs; |
250 | 580 | char *str = NULL; |
251 | | |
252 | 580 | str_len = le16toh(str_hdr->len); |
253 | 580 | if (str_len == 0) goto done; |
254 | | |
255 | 580 | str_offs = le32toh(str_hdr->offset); |
256 | 580 | if ((str_offs < payload_offs) || |
257 | 580 | (str_offs > buffer->length) || |
258 | 580 | (UINT32_MAX - str_offs < str_len) || |
259 | 580 | (str_offs + str_len > buffer->length)) { |
260 | 211 | return ERR_DECODE; |
261 | 211 | } |
262 | | |
263 | 369 | str = strndup((const char *)&buffer->data[str_offs], str_len); |
264 | 369 | if (!str) return ENOMEM; |
265 | | |
266 | 369 | done: |
267 | 369 | *_str = str; |
268 | 369 | return 0; |
269 | 369 | } |
270 | | |
271 | | static int ntlm_encode_u16l_str_hdr(struct ntlm_ctx *ctx, |
272 | | struct wire_field_hdr *hdr, |
273 | | struct ntlm_buffer *buffer, |
274 | | size_t *data_offs, |
275 | | const char *str, int str_len) |
276 | 314 | { |
277 | 314 | char *out; |
278 | 314 | size_t outlen; |
279 | 314 | int ret; |
280 | | |
281 | 314 | out = (char *)&buffer->data[*data_offs]; |
282 | | |
283 | 314 | ret = ntlm_str_convert(ctx->from_oem, str, out, str_len, &outlen); |
284 | 314 | if (ret) return ret; |
285 | | |
286 | 314 | hdr->len = htole16(outlen); |
287 | 314 | hdr->max_len = htole16(outlen); |
288 | 314 | hdr->offset = htole32(*data_offs); |
289 | | |
290 | 314 | *data_offs += outlen; |
291 | 314 | return 0; |
292 | 314 | } |
293 | | |
294 | | static int ntlm_decode_u16l_str_hdr(struct ntlm_ctx *ctx, |
295 | | struct wire_field_hdr *str_hdr, |
296 | | struct ntlm_buffer *buffer, |
297 | | size_t payload_offs, char **str) |
298 | 305 | { |
299 | 305 | char *in, *out = NULL; |
300 | 305 | uint16_t str_len; |
301 | 305 | uint32_t str_offs; |
302 | 305 | size_t outlen = 0; |
303 | 305 | int ret = 0; |
304 | | |
305 | 305 | str_len = le16toh(str_hdr->len); |
306 | 305 | if (str_len == 0) goto done; |
307 | | |
308 | 305 | str_offs = le32toh(str_hdr->offset); |
309 | 305 | if ((str_offs < payload_offs) || |
310 | 305 | (str_offs > buffer->length) || |
311 | 305 | (UINT32_MAX - str_offs < str_len) || |
312 | 305 | (str_offs + str_len > buffer->length)) { |
313 | 89 | return ERR_DECODE; |
314 | 89 | } |
315 | | |
316 | 216 | in = (char *)&buffer->data[str_offs]; |
317 | | |
318 | 216 | out = malloc(str_len * 2 + 1); |
319 | 216 | if (!out) return ENOMEM; |
320 | | |
321 | 216 | ret = ntlm_str_convert(ctx->to_oem, in, out, str_len, &outlen); |
322 | | |
323 | 216 | done: |
324 | 216 | if (ret) { |
325 | 20 | safefree(out); |
326 | 196 | } else { |
327 | | /* make sure to terminate output string */ |
328 | 196 | if (out) { |
329 | 196 | out[outlen] = '\0'; |
330 | 196 | } |
331 | 196 | } |
332 | | |
333 | 216 | *str = out; |
334 | 216 | return ret; |
335 | 216 | } |
336 | | |
337 | | struct wire_version ntlmssp_version = { |
338 | | NTLMSSP_VERSION_MAJOR, |
339 | | NTLMSSP_VERSION_MINOR, |
340 | | NTLMSSP_VERSION_BUILD, /* 0 is always 0 even in LE */ |
341 | | { 0, 0, 0 }, |
342 | | NTLMSSP_VERSION_REV |
343 | | }; |
344 | | |
345 | | void ntlm_internal_set_version(uint8_t major, uint8_t minor, |
346 | | uint16_t build, uint8_t revision) |
347 | 0 | { |
348 | 0 | ntlmssp_version.major = major; |
349 | 0 | ntlmssp_version.minor = minor; |
350 | 0 | ntlmssp_version.build = htole16(build); |
351 | 0 | ntlmssp_version.revision = revision; |
352 | 0 | } |
353 | | |
354 | | static int ntlm_encode_version(struct ntlm_ctx *ctx, |
355 | | struct ntlm_buffer *buffer, |
356 | | size_t data_offs) |
357 | 843 | { |
358 | 843 | if (data_offs + sizeof(struct wire_version) > buffer->length) { |
359 | 0 | return ERR_ENCODE; |
360 | 0 | } |
361 | | |
362 | 843 | memcpy(&buffer->data[data_offs], &ntlmssp_version, |
363 | 843 | sizeof(struct wire_version)); |
364 | 843 | return 0; |
365 | 843 | } |
366 | | |
367 | | static int ntlm_encode_field(struct wire_field_hdr *hdr, |
368 | | struct ntlm_buffer *buffer, |
369 | | size_t *data_offs, |
370 | | struct ntlm_buffer *field) |
371 | 593 | { |
372 | 593 | if (*data_offs + field->length > buffer->length) { |
373 | 0 | return ERR_ENCODE; |
374 | 0 | } |
375 | | |
376 | 593 | memcpy(&buffer->data[*data_offs], field->data, field->length); |
377 | 593 | hdr->len = htole16(field->length); |
378 | 593 | hdr->max_len = hdr->len; |
379 | 593 | hdr->offset = htole32(*data_offs); |
380 | | |
381 | 593 | *data_offs += field->length; |
382 | 593 | return 0; |
383 | 593 | } |
384 | | |
385 | | static int ntlm_decode_field(struct wire_field_hdr *hdr, |
386 | | struct ntlm_buffer *buffer, |
387 | | size_t payload_offs, |
388 | | struct ntlm_buffer *field) |
389 | 829 | { |
390 | 829 | struct ntlm_buffer b = { NULL, 0 }; |
391 | 829 | uint32_t offs; |
392 | 829 | uint16_t len; |
393 | | |
394 | 829 | len = le16toh(hdr->len); |
395 | 829 | if (len == 0) goto done; |
396 | | |
397 | 829 | offs = le32toh(hdr->offset); |
398 | 829 | if ((offs < payload_offs) || |
399 | 829 | (offs > buffer->length) || |
400 | 829 | (UINT32_MAX - offs < len) || |
401 | 829 | (offs + len > buffer->length)) { |
402 | 215 | return ERR_DECODE; |
403 | 215 | } |
404 | | |
405 | 614 | b.data = malloc(len); |
406 | 614 | if (!b.data) return ENOMEM; |
407 | | |
408 | 614 | b.length = len; |
409 | 614 | memcpy(b.data, &buffer->data[offs], b.length); |
410 | | |
411 | 614 | done: |
412 | 614 | *field = b; |
413 | 614 | return 0; |
414 | 614 | } |
415 | | |
416 | | static int ntlm_encode_av_pair_u16l_str(struct ntlm_ctx *ctx, |
417 | | struct ntlm_buffer *buffer, |
418 | | size_t *data_offs, |
419 | | enum msv_av_ids av_id, |
420 | | const char *str, size_t str_len) |
421 | 2.87k | { |
422 | 2.87k | struct wire_av_pair *av_pair; |
423 | 2.87k | char *out; |
424 | 2.87k | size_t outlen; |
425 | 2.87k | int ret; |
426 | | |
427 | 2.87k | if (*data_offs + 4 + str_len > buffer->length) { |
428 | 0 | return ERR_ENCODE; |
429 | 0 | } |
430 | | |
431 | 2.87k | av_pair = (struct wire_av_pair *)&buffer->data[*data_offs]; |
432 | 2.87k | out = (char *)av_pair->value; |
433 | | |
434 | 2.87k | ret = ntlm_str_convert(ctx->from_oem, str, out, str_len, &outlen); |
435 | 2.87k | if (ret) return ret; |
436 | | |
437 | 2.87k | av_pair->av_len = htole16(outlen); |
438 | 2.87k | av_pair->av_id = htole16(av_id); |
439 | | |
440 | 2.87k | *data_offs += av_pair->av_len + 4; |
441 | 2.87k | return 0; |
442 | 2.87k | } |
443 | | |
444 | | static int ntlm_decode_av_pair_u16l_str(struct ntlm_ctx *ctx, |
445 | | struct wire_av_pair *av_pair, |
446 | | char **str) |
447 | 0 | { |
448 | 0 | char *in, *out; |
449 | 0 | size_t inlen, outlen; |
450 | 0 | int ret; |
451 | |
|
452 | 0 | in = (char *)av_pair->value; |
453 | 0 | inlen = le16toh(av_pair->av_len); |
454 | 0 | out = malloc(inlen * 2 + 1); |
455 | |
|
456 | 0 | ret = ntlm_str_convert(ctx->to_oem, in, out, inlen, &outlen); |
457 | 0 | if (ret) { |
458 | 0 | safefree(out); |
459 | 0 | return ret; |
460 | 0 | } |
461 | | /* terminate out string for sure */ |
462 | 0 | out[outlen] = '\0'; |
463 | |
|
464 | 0 | *str = out; |
465 | 0 | return 0; |
466 | 0 | } |
467 | | |
468 | | static int ntlm_encode_av_pair_value(struct ntlm_buffer *buffer, |
469 | | size_t *data_offs, |
470 | | enum msv_av_ids av_id, |
471 | | struct ntlm_buffer *value) |
472 | 1.91k | { |
473 | 1.91k | struct wire_av_pair *av_pair; |
474 | | |
475 | 1.91k | if (*data_offs + 4 + value->length > buffer->length) { |
476 | 0 | return ERR_ENCODE; |
477 | 0 | } |
478 | | |
479 | 1.91k | av_pair = (struct wire_av_pair *)&buffer->data[*data_offs]; |
480 | 1.91k | av_pair->av_id = htole16(av_id); |
481 | 1.91k | av_pair->av_len = htole16(value->length); |
482 | 1.91k | if (value->length) { |
483 | 957 | memcpy(av_pair->value, value->data, value->length); |
484 | 957 | } |
485 | | |
486 | 1.91k | *data_offs += value->length + 4; |
487 | 1.91k | return 0; |
488 | 1.91k | } |
489 | | |
490 | | int ntlm_encode_target_info(struct ntlm_ctx *ctx, char *nb_computer_name, |
491 | | char *nb_domain_name, char *dns_computer_name, |
492 | | char *dns_domain_name, char *dns_tree_name, |
493 | | uint32_t *av_flags, uint64_t *av_timestamp, |
494 | | struct ntlm_buffer *av_single_host, |
495 | | char *av_target_name, struct ntlm_buffer *av_cb, |
496 | | struct ntlm_buffer *target_info) |
497 | 957 | { |
498 | 957 | struct ntlm_buffer buffer; |
499 | 957 | size_t data_offs; |
500 | 957 | size_t max_size; |
501 | 957 | size_t nb_computer_name_len = 0; |
502 | 957 | size_t nb_domain_name_len = 0; |
503 | 957 | size_t dns_computer_name_len = 0; |
504 | 957 | size_t dns_domain_name_len = 0; |
505 | 957 | size_t dns_tree_name_len = 0; |
506 | 957 | size_t av_target_name_len = 0; |
507 | 957 | struct ntlm_buffer value; |
508 | 957 | int ret = 0; |
509 | | |
510 | 957 | max_size = 4; /* MSV_AV_EOL */ |
511 | | |
512 | 957 | if (nb_computer_name) { |
513 | 957 | nb_computer_name_len = strlen(nb_computer_name); |
514 | 957 | max_size += 4 + nb_computer_name_len * 2; |
515 | 957 | } |
516 | 957 | if (nb_domain_name) { |
517 | 957 | nb_domain_name_len = strlen(nb_domain_name); |
518 | 957 | max_size += 4 + nb_domain_name_len * 2; |
519 | 957 | } |
520 | 957 | if (dns_computer_name) { |
521 | 957 | dns_computer_name_len = strlen(dns_computer_name); |
522 | 957 | max_size += 4 + dns_computer_name_len * 2; |
523 | 957 | } |
524 | 957 | if (dns_domain_name) { |
525 | 0 | dns_domain_name_len = strlen(dns_domain_name); |
526 | 0 | max_size += 4 + dns_domain_name_len * 2; |
527 | 0 | } |
528 | 957 | if (dns_tree_name) { |
529 | 0 | dns_tree_name_len = strlen(dns_tree_name); |
530 | 0 | max_size += 4 + dns_tree_name_len * 2; |
531 | 0 | } |
532 | 957 | if (av_flags) { |
533 | 0 | max_size += 4 + 4; |
534 | 0 | } |
535 | 957 | if (av_timestamp) { |
536 | 957 | max_size += 4 + 8; |
537 | 957 | } |
538 | 957 | if (av_single_host) { |
539 | 0 | max_size += 4 + av_single_host->length; |
540 | 0 | } |
541 | 957 | if (av_target_name) { |
542 | 0 | av_target_name_len = strlen(av_target_name); |
543 | 0 | max_size += 4 + av_target_name_len * 2; |
544 | 0 | } |
545 | 957 | if (av_cb && av_cb->length > 0) { |
546 | 0 | max_size += 4 + av_cb->length; |
547 | 0 | } |
548 | | |
549 | 957 | data_offs = 0; |
550 | 957 | buffer.length = max_size; |
551 | 957 | buffer.data = calloc(1, buffer.length); |
552 | 957 | if (!buffer.data) return ENOMEM; |
553 | | |
554 | 957 | if (nb_computer_name) { |
555 | 957 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
556 | 957 | MSV_AV_NB_COMPUTER_NAME, |
557 | 957 | nb_computer_name, |
558 | 957 | nb_computer_name_len); |
559 | 957 | if (ret) goto done; |
560 | 957 | } |
561 | 957 | if (nb_domain_name) { |
562 | 957 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
563 | 957 | MSV_AV_NB_DOMAIN_NAME, |
564 | 957 | nb_domain_name, |
565 | 957 | nb_domain_name_len); |
566 | 957 | if (ret) goto done; |
567 | 957 | } |
568 | 957 | if (dns_computer_name) { |
569 | 957 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
570 | 957 | MSV_AV_DNS_COMPUTER_NAME, |
571 | 957 | dns_computer_name, |
572 | 957 | dns_computer_name_len); |
573 | 957 | if (ret) goto done; |
574 | 957 | } |
575 | 957 | if (dns_domain_name) { |
576 | 0 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
577 | 0 | MSV_AV_DNS_DOMAIN_NAME, |
578 | 0 | dns_domain_name, |
579 | 0 | dns_domain_name_len); |
580 | 0 | if (ret) goto done; |
581 | 0 | } |
582 | 957 | if (dns_tree_name) { |
583 | 0 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
584 | 0 | MSV_AV_DNS_TREE_NAME, |
585 | 0 | dns_tree_name, |
586 | 0 | dns_tree_name_len); |
587 | 0 | if (ret) goto done; |
588 | 0 | } |
589 | 957 | if (av_flags) { |
590 | 0 | uint32_t flags = htole32(*av_flags); |
591 | 0 | value.data = (uint8_t *)&flags; |
592 | 0 | value.length = 4; |
593 | 0 | ret = ntlm_encode_av_pair_value(&buffer, &data_offs, |
594 | 0 | MSV_AV_FLAGS, &value); |
595 | 0 | if (ret) goto done; |
596 | 0 | } |
597 | 957 | if (av_timestamp) { |
598 | 957 | uint64_t timestamp = htole64(*av_timestamp); |
599 | 957 | value.data = (uint8_t *)×tamp; |
600 | 957 | value.length = 8; |
601 | 957 | ret = ntlm_encode_av_pair_value(&buffer, &data_offs, |
602 | 957 | MSV_AV_TIMESTAMP, &value); |
603 | 957 | if (ret) goto done; |
604 | 957 | } |
605 | 957 | if (av_single_host) { |
606 | 0 | ret = ntlm_encode_av_pair_value(&buffer, &data_offs, |
607 | 0 | MSV_AV_SINGLE_HOST, av_single_host); |
608 | 0 | if (ret) goto done; |
609 | 0 | } |
610 | 957 | if (av_target_name) { |
611 | 0 | ret = ntlm_encode_av_pair_u16l_str(ctx, &buffer, &data_offs, |
612 | 0 | MSV_AV_TARGET_NAME, |
613 | 0 | av_target_name, |
614 | 0 | av_target_name_len); |
615 | 0 | if (ret) goto done; |
616 | 0 | } |
617 | 957 | if (av_cb && av_cb->length > 0) { |
618 | 0 | ret = ntlm_encode_av_pair_value(&buffer, &data_offs, |
619 | 0 | MSV_AV_CHANNEL_BINDINGS, av_cb); |
620 | 0 | if (ret) goto done; |
621 | 0 | } |
622 | | |
623 | 957 | value.length = 0; |
624 | 957 | value.data = NULL; |
625 | 957 | ret = ntlm_encode_av_pair_value(&buffer, &data_offs, MSV_AV_EOL, &value); |
626 | 957 | buffer.length = data_offs; |
627 | | |
628 | 957 | done: |
629 | 957 | if (ret) { |
630 | 0 | safefree(buffer.data); |
631 | 957 | } else { |
632 | 957 | *target_info = buffer; |
633 | 957 | } |
634 | 957 | return ret; |
635 | 957 | } |
636 | | |
637 | | int ntlm_decode_target_info(struct ntlm_ctx *ctx, struct ntlm_buffer *buffer, |
638 | | char **nb_computer_name, char **nb_domain_name, |
639 | | char **dns_computer_name, char **dns_domain_name, |
640 | | char **dns_tree_name, char **av_target_name, |
641 | | uint32_t *av_flags, uint64_t *av_timestamp, |
642 | | struct ntlm_buffer *av_single_host, |
643 | | struct ntlm_buffer *av_cb) |
644 | 209 | { |
645 | 209 | struct wire_av_pair *av_pair; |
646 | 209 | uint16_t av_id = (uint16_t)-1; |
647 | 209 | uint16_t av_len = (uint16_t)-1; |
648 | 209 | struct ntlm_buffer sh = { NULL, 0 }; |
649 | 209 | struct ntlm_buffer cb = { NULL, 0 }; |
650 | 209 | char *nb_computer = NULL; |
651 | 209 | char *nb_domain = NULL; |
652 | 209 | char *dns_computer = NULL; |
653 | 209 | char *dns_domain = NULL; |
654 | 209 | char *dns_tree = NULL; |
655 | 209 | char *av_target = NULL; |
656 | 209 | size_t data_offs = 0; |
657 | 209 | uint64_t timestamp = 0; |
658 | 209 | uint32_t flags = 0; |
659 | 209 | int ret = 0; |
660 | | |
661 | 3.35k | while (data_offs + 4 <= buffer->length) { |
662 | 3.23k | av_pair = (struct wire_av_pair *)&buffer->data[data_offs]; |
663 | 3.23k | data_offs += 4; |
664 | 3.23k | av_id = le16toh(av_pair->av_id); |
665 | 3.23k | av_len = le16toh(av_pair->av_len); |
666 | 3.23k | if (av_len > buffer->length - data_offs) { |
667 | 46 | ret = ERR_DECODE; |
668 | 46 | goto done; |
669 | 46 | } |
670 | 3.18k | data_offs += av_len; |
671 | | |
672 | 3.18k | switch (av_id) { |
673 | 298 | case MSV_AV_CHANNEL_BINDINGS: |
674 | 298 | if (!av_cb) continue; |
675 | 298 | cb.data = av_pair->value; |
676 | 298 | cb.length = av_len; |
677 | 298 | break; |
678 | 238 | case MSV_AV_TARGET_NAME: |
679 | 238 | if (!av_target_name) continue; |
680 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &av_target); |
681 | 0 | if (ret) goto done; |
682 | 0 | break; |
683 | 242 | case MSV_AV_SINGLE_HOST: |
684 | 242 | if (!av_single_host) continue; |
685 | 0 | sh.data = av_pair->value; |
686 | 0 | sh.length = av_len; |
687 | 0 | break; |
688 | 237 | case MSV_AV_TIMESTAMP: |
689 | 237 | if (!av_timestamp) continue; |
690 | 0 | if (av_len < sizeof(timestamp)) { |
691 | 0 | ret = ERR_DECODE; |
692 | 0 | goto done; |
693 | 0 | } |
694 | 0 | memcpy(×tamp, av_pair->value, sizeof(timestamp)); |
695 | 0 | timestamp = le64toh(timestamp); |
696 | 0 | break; |
697 | 417 | case MSV_AV_FLAGS: |
698 | 417 | if (!av_flags) continue; |
699 | 417 | if (av_len < sizeof(flags)) { |
700 | 4 | ret = ERR_DECODE; |
701 | 4 | goto done; |
702 | 4 | } |
703 | 413 | memcpy(&flags, av_pair->value, sizeof(flags)); |
704 | 413 | flags = le32toh(flags); |
705 | 413 | break; |
706 | 200 | case MSV_AV_DNS_TREE_NAME: |
707 | 200 | if (!dns_tree_name) continue; |
708 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &dns_tree); |
709 | 0 | if (ret) goto done; |
710 | 0 | break; |
711 | 243 | case MSV_AV_DNS_DOMAIN_NAME: |
712 | 243 | if (!dns_domain_name) continue; |
713 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &dns_domain); |
714 | 0 | if (ret) goto done; |
715 | 0 | break; |
716 | 222 | case MSV_AV_DNS_COMPUTER_NAME: |
717 | 222 | if (!dns_computer_name) continue; |
718 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &dns_computer); |
719 | 0 | if (ret) goto done; |
720 | 0 | break; |
721 | 220 | case MSV_AV_NB_DOMAIN_NAME: |
722 | 220 | if (!nb_domain_name) continue; |
723 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &nb_domain); |
724 | 0 | if (ret) goto done; |
725 | 0 | break; |
726 | 381 | case MSV_AV_NB_COMPUTER_NAME: |
727 | 381 | if (!nb_computer_name) continue; |
728 | 0 | ret = ntlm_decode_av_pair_u16l_str(ctx, av_pair, &nb_computer); |
729 | 0 | if (ret) goto done; |
730 | 0 | break; |
731 | 491 | default: |
732 | | /* unknown av_pair, or EOL */ |
733 | 491 | break; |
734 | 3.18k | } |
735 | 1.20k | if (av_id == MSV_AV_EOL) break; |
736 | 1.20k | } |
737 | | |
738 | 159 | if (av_id != MSV_AV_EOL || av_len != 0) { |
739 | 144 | ret = ERR_DECODE; |
740 | 144 | } |
741 | | |
742 | 209 | done: |
743 | 209 | if (ret) { |
744 | 194 | safefree(nb_computer); |
745 | 194 | safefree(nb_domain); |
746 | 194 | safefree(dns_computer); |
747 | 194 | safefree(dns_domain); |
748 | 194 | safefree(dns_tree); |
749 | 194 | safefree(av_target); |
750 | 194 | } else { |
751 | 15 | if (nb_computer_name) *nb_computer_name = nb_computer; |
752 | 15 | if (nb_domain_name) *nb_domain_name = nb_domain; |
753 | 15 | if (dns_computer_name) *dns_computer_name = dns_computer; |
754 | 15 | if (dns_domain_name) *dns_domain_name = dns_domain; |
755 | 15 | if (dns_tree_name) *dns_tree_name = dns_tree; |
756 | 15 | if (av_target_name) *av_target_name = av_target; |
757 | 15 | if (av_timestamp) *av_timestamp = timestamp; |
758 | 15 | if (av_single_host) *av_single_host = sh; |
759 | 15 | if (av_flags) *av_flags = flags; |
760 | 15 | if (av_cb) *av_cb = cb; |
761 | 15 | } |
762 | 209 | return ret; |
763 | 159 | } |
764 | | |
765 | | int ntlm_process_target_info(struct ntlm_ctx *ctx, bool protect, |
766 | | struct ntlm_buffer *in, |
767 | | const char *spn, |
768 | | struct ntlm_buffer *unhashed_cb, |
769 | | struct ntlm_buffer *out, |
770 | | uint64_t *out_srv_time, |
771 | | bool *add_mic) |
772 | 0 | { |
773 | 0 | char *nb_computer_name = NULL; |
774 | 0 | char *nb_domain_name = NULL; |
775 | 0 | char *dns_computer_name = NULL; |
776 | 0 | char *dns_domain_name = NULL; |
777 | 0 | char *dns_tree_name = NULL; |
778 | 0 | char *av_target_name = NULL; |
779 | 0 | uint32_t av_flags = 0; |
780 | 0 | uint64_t srv_time = 0; |
781 | 0 | uint8_t cb[16] = { 0 }; |
782 | 0 | struct ntlm_buffer av_cb = { NULL, 0 }; |
783 | 0 | int ret = 0; |
784 | | |
785 | | /* TODO: check that returned netbios/dns names match ? */ |
786 | | /* TODO: support SingleHost buffers */ |
787 | 0 | ret = ntlm_decode_target_info(ctx, in, |
788 | 0 | &nb_computer_name, &nb_domain_name, |
789 | 0 | &dns_computer_name, &dns_domain_name, |
790 | 0 | &dns_tree_name, &av_target_name, |
791 | 0 | &av_flags, &srv_time, NULL, NULL); |
792 | 0 | if (ret) goto done; |
793 | | |
794 | 0 | if (protect && (!nb_computer_name || nb_computer_name[0] == '\0')) { |
795 | 0 | ret = EINVAL; |
796 | 0 | goto done; |
797 | 0 | } |
798 | | |
799 | 0 | if (spn && av_target_name && |
800 | 0 | ((av_flags & MSVAVFLAGS_UNVERIFIED_SPN) == 0)) { |
801 | 0 | if (strcasecmp(spn, av_target_name) != 0) { |
802 | 0 | ret = EINVAL; |
803 | 0 | goto done; |
804 | 0 | } |
805 | 0 | } |
806 | | |
807 | | /* the server did not send the timestamp, use current time */ |
808 | 0 | if (srv_time == 0) { |
809 | 0 | srv_time = ntlm_timestamp_now(); |
810 | 0 | } else if (add_mic) { |
811 | 0 | av_flags |= MSVAVFLAGS_MIC_PRESENT; |
812 | 0 | *add_mic = true; |
813 | 0 | } |
814 | |
|
815 | 0 | if (unhashed_cb->length > 0) { |
816 | 0 | av_cb.data = cb; |
817 | 0 | av_cb.length = 16; |
818 | 0 | ret = ntlm_hash_channel_bindings(unhashed_cb, &av_cb); |
819 | 0 | if (ret) goto done; |
820 | 0 | } |
821 | | |
822 | 0 | if (!av_target_name && spn) { |
823 | 0 | av_target_name = strdup(spn); |
824 | 0 | if (!av_target_name) { |
825 | 0 | ret = ENOMEM; |
826 | 0 | goto done; |
827 | 0 | } |
828 | 0 | } |
829 | | |
830 | 0 | ret = ntlm_encode_target_info(ctx, |
831 | 0 | nb_computer_name, nb_domain_name, |
832 | 0 | dns_computer_name, dns_domain_name, |
833 | 0 | dns_tree_name, &av_flags, &srv_time, |
834 | 0 | NULL, av_target_name, &av_cb, out); |
835 | |
|
836 | 0 | done: |
837 | 0 | safefree(nb_computer_name); |
838 | 0 | safefree(nb_domain_name); |
839 | 0 | safefree(dns_computer_name); |
840 | 0 | safefree(dns_domain_name); |
841 | 0 | safefree(dns_tree_name); |
842 | 0 | safefree(av_target_name); |
843 | 0 | *out_srv_time = srv_time; |
844 | 0 | return ret; |
845 | 0 | } |
846 | | |
847 | | int ntlm_decode_msg_type(struct ntlm_ctx *ctx, |
848 | | struct ntlm_buffer *buffer, |
849 | | uint32_t *type) |
850 | 2.08k | { |
851 | 2.08k | struct wire_neg_msg *msg; |
852 | 2.08k | uint32_t msg_type; |
853 | 2.08k | int ret; |
854 | | |
855 | 2.08k | if (!ctx) return EINVAL; |
856 | | |
857 | 2.08k | if (buffer->length < sizeof(struct wire_msg_hdr)) { |
858 | 30 | return ERR_DECODE; |
859 | 30 | } |
860 | | |
861 | 2.05k | msg = (struct wire_neg_msg *)buffer->data; |
862 | | |
863 | 2.05k | ret = ntlm_decode_header(&msg->header, &msg_type); |
864 | 2.05k | if (ret) goto done; |
865 | | |
866 | 1.95k | switch (msg_type) { |
867 | 975 | case NEGOTIATE_MESSAGE: |
868 | 975 | if (buffer->length < sizeof(struct wire_neg_msg)) { |
869 | 10 | return ERR_DECODE; |
870 | 10 | } |
871 | 965 | break; |
872 | 965 | case CHALLENGE_MESSAGE: |
873 | 29 | if (buffer->length < sizeof(struct wire_chal_msg) && |
874 | 29 | buffer->length != sizeof(struct wire_chal_msg_old)) { |
875 | 14 | return ERR_DECODE; |
876 | 14 | } |
877 | 15 | break; |
878 | 944 | case AUTHENTICATE_MESSAGE: |
879 | 944 | if (buffer->length < sizeof(struct wire_auth_msg)) { |
880 | 12 | return ERR_DECODE; |
881 | 12 | } |
882 | 932 | break; |
883 | 932 | default: |
884 | 3 | ret = ERR_DECODE; |
885 | 3 | break; |
886 | 1.95k | } |
887 | | |
888 | 2.01k | done: |
889 | 2.01k | if (ret == 0) { |
890 | 1.91k | *type = msg_type; |
891 | 1.91k | } |
892 | 2.01k | return ret; |
893 | 1.95k | } |
894 | | |
895 | | int ntlm_encode_neg_msg(struct ntlm_ctx *ctx, uint32_t flags, |
896 | | const char *domain, const char *workstation, |
897 | | struct ntlm_buffer *message) |
898 | 0 | { |
899 | 0 | struct wire_neg_msg *msg; |
900 | 0 | struct ntlm_buffer buffer; |
901 | 0 | size_t data_offs; |
902 | 0 | size_t dom_len = 0; |
903 | 0 | size_t wks_len = 0; |
904 | 0 | int ret = 0; |
905 | |
|
906 | 0 | if (!ctx) return EINVAL; |
907 | | |
908 | 0 | buffer.length = sizeof(struct wire_neg_msg); |
909 | | |
910 | | /* Strings MUST use OEM charset in negotiate message */ |
911 | 0 | if (flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) { |
912 | 0 | if (!domain) return EINVAL; |
913 | 0 | dom_len = strlen(domain); |
914 | 0 | buffer.length += dom_len; |
915 | 0 | } |
916 | 0 | if (flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) { |
917 | 0 | if (!workstation) return EINVAL; |
918 | 0 | wks_len = strlen(workstation); |
919 | 0 | buffer.length += wks_len; |
920 | 0 | } |
921 | | |
922 | 0 | buffer.data = calloc(1, buffer.length); |
923 | 0 | if (!buffer.data) return ENOMEM; |
924 | | |
925 | 0 | msg = (struct wire_neg_msg *)buffer.data; |
926 | 0 | data_offs = (char *)msg->payload - (char *)msg; |
927 | |
|
928 | 0 | ntlm_encode_header(&msg->header, NEGOTIATE_MESSAGE); |
929 | |
|
930 | 0 | msg->neg_flags = htole32(flags); |
931 | |
|
932 | 0 | if (dom_len) { |
933 | 0 | ret = ntlm_encode_oem_str(&msg->domain_name, &buffer, |
934 | 0 | &data_offs, domain, dom_len); |
935 | 0 | if (ret) goto done; |
936 | 0 | } |
937 | | |
938 | 0 | if (wks_len) { |
939 | 0 | ret = ntlm_encode_oem_str(&msg->workstation_name, &buffer, |
940 | 0 | &data_offs, workstation, wks_len); |
941 | 0 | if (ret) goto done; |
942 | 0 | } |
943 | | |
944 | 0 | if (flags & NTLMSSP_NEGOTIATE_VERSION) { |
945 | 0 | ret = ntlm_encode_version(ctx, &buffer, |
946 | 0 | (char *)&msg->version - (char *)msg); |
947 | 0 | if (ret) goto done; |
948 | 0 | } |
949 | | |
950 | 0 | done: |
951 | 0 | if (ret) { |
952 | 0 | safefree(buffer.data); |
953 | 0 | } else { |
954 | 0 | *message = buffer; |
955 | 0 | } |
956 | 0 | return ret; |
957 | 0 | } |
958 | | |
959 | | int ntlm_decode_neg_msg(struct ntlm_ctx *ctx, |
960 | | struct ntlm_buffer *buffer, uint32_t *flags, |
961 | | char **domain, char **workstation) |
962 | 961 | { |
963 | 961 | struct wire_neg_msg *msg; |
964 | 961 | size_t payload_offs; |
965 | 961 | uint32_t neg_flags; |
966 | 961 | char *dom = NULL; |
967 | 961 | char *wks = NULL; |
968 | 961 | int ret = 0; |
969 | | |
970 | 961 | if (!ctx) return EINVAL; |
971 | | |
972 | 961 | msg = (struct wire_neg_msg *)buffer->data; |
973 | 961 | payload_offs = (char *)msg->payload - (char *)msg; |
974 | | |
975 | 961 | neg_flags = le32toh(msg->neg_flags); |
976 | | |
977 | 961 | if ((neg_flags & NTLMSSP_NEGOTIATE_VERSION) == 0) { |
978 | | /* adjust the payload offset to point to the |
979 | | * version field, for compatibility with older |
980 | | * clients that completely omitted the structure |
981 | | * on the wire */ |
982 | 659 | payload_offs -= sizeof(struct wire_version); |
983 | 659 | } |
984 | | |
985 | 961 | if (domain && |
986 | 961 | (neg_flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED)) { |
987 | 0 | ret = ntlm_decode_oem_str(&msg->domain_name, buffer, |
988 | 0 | payload_offs, &dom); |
989 | 0 | if (ret) goto done; |
990 | 0 | } |
991 | 961 | if (workstation && |
992 | 961 | (neg_flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED)) { |
993 | 0 | ret = ntlm_decode_oem_str(&msg->workstation_name, buffer, |
994 | 0 | payload_offs, &wks); |
995 | 0 | if (ret) goto done; |
996 | 0 | } |
997 | | |
998 | 961 | done: |
999 | 961 | if (ret) { |
1000 | 0 | safefree(dom); |
1001 | 0 | safefree(wks); |
1002 | 961 | } else { |
1003 | 961 | *flags = neg_flags; |
1004 | 961 | if (domain) *domain = dom; |
1005 | 961 | if (workstation) *workstation = wks; |
1006 | 961 | } |
1007 | 961 | return ret; |
1008 | 961 | } |
1009 | | |
1010 | | int ntlm_encode_chal_msg(struct ntlm_ctx *ctx, |
1011 | | uint32_t flags, |
1012 | | const char *target_name, |
1013 | | struct ntlm_buffer *challenge, |
1014 | | struct ntlm_buffer *target_info, |
1015 | | struct ntlm_buffer *message) |
1016 | 957 | { |
1017 | 957 | struct wire_chal_msg *msg; |
1018 | 957 | struct ntlm_buffer buffer; |
1019 | 957 | size_t data_offs; |
1020 | 957 | size_t target_len = 0; |
1021 | 957 | int ret = 0; |
1022 | | |
1023 | 957 | if (!ctx) return EINVAL; |
1024 | | |
1025 | 957 | if (!challenge || challenge->length != 8) return EINVAL; |
1026 | | |
1027 | 957 | buffer.length = sizeof(struct wire_chal_msg); |
1028 | | |
1029 | 957 | if ((flags & NTLMSSP_TARGET_TYPE_SERVER) |
1030 | 957 | || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { |
1031 | 957 | if (!target_name) return EINVAL; |
1032 | | |
1033 | 957 | target_len = strlen(target_name); |
1034 | 957 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1035 | 314 | buffer.length += target_len * 2; |
1036 | 643 | } else { |
1037 | 643 | buffer.length += target_len; |
1038 | 643 | } |
1039 | 957 | } |
1040 | | |
1041 | 957 | if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { |
1042 | 593 | if (!target_info) return EINVAL; |
1043 | | |
1044 | 593 | buffer.length += target_info->length; |
1045 | 593 | } |
1046 | | |
1047 | 957 | buffer.data = calloc(1, buffer.length); |
1048 | 957 | if (!buffer.data) return ENOMEM; |
1049 | | |
1050 | 957 | msg = (struct wire_chal_msg *)buffer.data; |
1051 | 957 | data_offs = (char *)msg->payload - (char *)msg; |
1052 | | |
1053 | 957 | ntlm_encode_header(&msg->header, CHALLENGE_MESSAGE); |
1054 | | |
1055 | | /* this must be first as it pushes the payload further down */ |
1056 | 957 | if (flags & NTLMSSP_NEGOTIATE_VERSION) { |
1057 | 843 | ret = ntlm_encode_version(ctx, &buffer, |
1058 | 843 | (char *)&msg->version - (char *)msg); |
1059 | 843 | if (ret) goto done; |
1060 | 843 | } |
1061 | | |
1062 | 957 | if ((flags & NTLMSSP_TARGET_TYPE_SERVER) |
1063 | 957 | || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { |
1064 | 957 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1065 | 314 | ret = ntlm_encode_u16l_str_hdr(ctx, &msg->target_name, &buffer, |
1066 | 314 | &data_offs, target_name, target_len); |
1067 | 643 | } else { |
1068 | 643 | ret = ntlm_encode_oem_str(&msg->target_name, &buffer, |
1069 | 643 | &data_offs, target_name, target_len); |
1070 | 643 | } |
1071 | 957 | if (ret) goto done; |
1072 | 957 | } |
1073 | | |
1074 | 957 | msg->neg_flags = htole32(flags); |
1075 | 957 | memcpy(msg->server_challenge, challenge->data, 8); |
1076 | | |
1077 | 957 | if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { |
1078 | 593 | ret = ntlm_encode_field(&msg->target_info, &buffer, |
1079 | 593 | &data_offs, target_info); |
1080 | 593 | if (ret) goto done; |
1081 | 593 | } |
1082 | | |
1083 | 957 | done: |
1084 | 957 | if (ret) { |
1085 | 0 | safefree(buffer.data); |
1086 | 957 | } else { |
1087 | 957 | *message = buffer; |
1088 | 957 | } |
1089 | 957 | return ret; |
1090 | 957 | } |
1091 | | |
1092 | | int ntlm_decode_chal_msg(struct ntlm_ctx *ctx, |
1093 | | struct ntlm_buffer *buffer, |
1094 | | uint32_t *_flags, char **target_name, |
1095 | | struct ntlm_buffer *challenge, |
1096 | | struct ntlm_buffer *target_info) |
1097 | 0 | { |
1098 | 0 | struct wire_chal_msg *msg; |
1099 | 0 | size_t payload_offs; |
1100 | 0 | size_t base_chal_size; |
1101 | 0 | uint32_t flags; |
1102 | 0 | char *trg = NULL; |
1103 | 0 | int ret = 0; |
1104 | |
|
1105 | 0 | if (!ctx) return EINVAL; |
1106 | | |
1107 | 0 | if (challenge->length < 8) return EINVAL; |
1108 | | |
1109 | 0 | msg = (struct wire_chal_msg *)buffer->data; |
1110 | 0 | payload_offs = (char *)msg->payload - (char *)msg; |
1111 | |
|
1112 | 0 | flags = le32toh(msg->neg_flags); |
1113 | 0 | base_chal_size = sizeof(struct wire_chal_msg); |
1114 | |
|
1115 | 0 | if ((flags & NTLMSSP_NEGOTIATE_VERSION) == 0) { |
1116 | | /* adjust the payload offset to point to the |
1117 | | * version field, for compatibility with older |
1118 | | * clients that completely omitted the structure |
1119 | | * on the wire */ |
1120 | 0 | payload_offs -= sizeof(struct wire_version); |
1121 | 0 | base_chal_size -= sizeof(struct wire_version); |
1122 | 0 | } |
1123 | |
|
1124 | 0 | if ((flags & NTLMSSP_TARGET_TYPE_SERVER) |
1125 | 0 | || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { |
1126 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1127 | 0 | ret = ntlm_decode_u16l_str_hdr(ctx, &msg->target_name, buffer, |
1128 | 0 | payload_offs, &trg); |
1129 | 0 | } else { |
1130 | 0 | ret = ntlm_decode_oem_str(&msg->target_name, buffer, |
1131 | 0 | payload_offs, &trg); |
1132 | 0 | } |
1133 | 0 | if (ret) goto done; |
1134 | 0 | } |
1135 | | |
1136 | 0 | memcpy(challenge->data, msg->server_challenge, 8); |
1137 | 0 | challenge->length = 8; |
1138 | | |
1139 | | /* if we allowed a broken short challenge message from an old |
1140 | | * server we must stop here */ |
1141 | 0 | if (buffer->length < base_chal_size) { |
1142 | 0 | if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { |
1143 | 0 | ret = ERR_DECODE; |
1144 | 0 | } |
1145 | 0 | goto done; |
1146 | 0 | } |
1147 | | |
1148 | 0 | if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { |
1149 | 0 | ret = ntlm_decode_field(&msg->target_info, buffer, |
1150 | 0 | payload_offs, target_info); |
1151 | 0 | if (ret) goto done; |
1152 | 0 | } |
1153 | | |
1154 | 0 | done: |
1155 | 0 | if (ret) { |
1156 | 0 | safefree(trg); |
1157 | 0 | } else { |
1158 | 0 | *_flags = flags; |
1159 | 0 | *target_name = trg; |
1160 | 0 | } |
1161 | 0 | return ret; |
1162 | 0 | } |
1163 | | |
1164 | | int ntlm_encode_auth_msg(struct ntlm_ctx *ctx, |
1165 | | uint32_t flags, |
1166 | | struct ntlm_buffer *lm_chalresp, |
1167 | | struct ntlm_buffer *nt_chalresp, |
1168 | | char *domain_name, char *user_name, |
1169 | | char *workstation, |
1170 | | struct ntlm_buffer *enc_sess_key, |
1171 | | struct ntlm_buffer *mic, |
1172 | | struct ntlm_buffer *message) |
1173 | 0 | { |
1174 | 0 | struct wire_auth_msg *msg; |
1175 | 0 | struct ntlm_buffer buffer; |
1176 | 0 | struct ntlm_buffer empty_chalresp = { 0 }; |
1177 | 0 | size_t data_offs; |
1178 | 0 | size_t domain_name_len = 0; |
1179 | 0 | size_t user_name_len = 0; |
1180 | 0 | size_t workstation_len = 0; |
1181 | 0 | int ret = 0; |
1182 | |
|
1183 | 0 | if (!ctx) return EINVAL; |
1184 | | |
1185 | 0 | buffer.length = sizeof(struct wire_auth_msg); |
1186 | |
|
1187 | 0 | if (lm_chalresp) { |
1188 | 0 | buffer.length += lm_chalresp->length; |
1189 | 0 | } else { |
1190 | 0 | lm_chalresp = &empty_chalresp; |
1191 | 0 | } |
1192 | 0 | if (nt_chalresp) { |
1193 | 0 | buffer.length += nt_chalresp->length; |
1194 | 0 | } else { |
1195 | 0 | nt_chalresp = &empty_chalresp; |
1196 | 0 | } |
1197 | 0 | if (domain_name) { |
1198 | 0 | domain_name_len = strlen(domain_name); |
1199 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1200 | 0 | buffer.length += domain_name_len * 2; |
1201 | 0 | } else { |
1202 | 0 | buffer.length += domain_name_len; |
1203 | 0 | } |
1204 | 0 | } |
1205 | 0 | if (user_name) { |
1206 | 0 | user_name_len = strlen(user_name); |
1207 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1208 | 0 | buffer.length += user_name_len * 2; |
1209 | 0 | } else { |
1210 | 0 | buffer.length += user_name_len; |
1211 | 0 | } |
1212 | 0 | } |
1213 | 0 | if (workstation) { |
1214 | 0 | workstation_len = strlen(workstation); |
1215 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1216 | 0 | buffer.length += workstation_len * 2; |
1217 | 0 | } else { |
1218 | 0 | buffer.length += workstation_len; |
1219 | 0 | } |
1220 | 0 | } |
1221 | 0 | if (enc_sess_key) { |
1222 | 0 | buffer.length += enc_sess_key->length; |
1223 | 0 | } |
1224 | 0 | if (mic) { |
1225 | 0 | buffer.length += 16; |
1226 | 0 | } |
1227 | |
|
1228 | 0 | buffer.data = calloc(1, buffer.length); |
1229 | 0 | if (!buffer.data) return ENOMEM; |
1230 | | |
1231 | 0 | msg = (struct wire_auth_msg *)buffer.data; |
1232 | 0 | data_offs = (char *)msg->payload - (char *)msg; |
1233 | |
|
1234 | 0 | ntlm_encode_header(&msg->header, AUTHENTICATE_MESSAGE); |
1235 | | |
1236 | | /* this must be first as it pushes the payload further down */ |
1237 | 0 | if (flags & NTLMSSP_NEGOTIATE_VERSION) { |
1238 | 0 | ret = ntlm_encode_version(ctx, &buffer, |
1239 | 0 | (char *)&msg->version - (char *)msg); |
1240 | 0 | if (ret) goto done; |
1241 | 0 | } |
1242 | | |
1243 | | /* this pushes the payload further down */ |
1244 | 0 | if (mic) { |
1245 | 0 | memset(&buffer.data[data_offs], 0, mic->length); |
1246 | | /* return the actual pointer back in the mic, as it will |
1247 | | * be backfilled later by the caller */ |
1248 | 0 | mic->data = &buffer.data[data_offs]; |
1249 | 0 | data_offs += mic->length; |
1250 | 0 | } |
1251 | |
|
1252 | 0 | ret = ntlm_encode_field(&msg->lm_chalresp, &buffer, |
1253 | 0 | &data_offs, lm_chalresp); |
1254 | 0 | if (ret) goto done; |
1255 | | |
1256 | 0 | ret = ntlm_encode_field(&msg->nt_chalresp, &buffer, |
1257 | 0 | &data_offs, nt_chalresp); |
1258 | 0 | if (ret) goto done; |
1259 | | |
1260 | 0 | if (domain_name_len) { |
1261 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1262 | 0 | ret = ntlm_encode_u16l_str_hdr(ctx, &msg->domain_name, |
1263 | 0 | &buffer, &data_offs, |
1264 | 0 | domain_name, domain_name_len); |
1265 | 0 | } else { |
1266 | 0 | ret = ntlm_encode_oem_str(&msg->domain_name, |
1267 | 0 | &buffer, &data_offs, |
1268 | 0 | domain_name, domain_name_len); |
1269 | 0 | } |
1270 | 0 | if (ret) goto done; |
1271 | 0 | } |
1272 | 0 | if (user_name_len) { |
1273 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1274 | 0 | ret = ntlm_encode_u16l_str_hdr(ctx, &msg->user_name, |
1275 | 0 | &buffer, &data_offs, |
1276 | 0 | user_name, user_name_len); |
1277 | 0 | } else { |
1278 | 0 | ret = ntlm_encode_oem_str(&msg->user_name, |
1279 | 0 | &buffer, &data_offs, |
1280 | 0 | user_name, user_name_len); |
1281 | 0 | } |
1282 | 0 | if (ret) goto done; |
1283 | 0 | } |
1284 | 0 | if (workstation_len) { |
1285 | 0 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1286 | 0 | ret = ntlm_encode_u16l_str_hdr(ctx, &msg->workstation, |
1287 | 0 | &buffer, &data_offs, |
1288 | 0 | workstation, workstation_len); |
1289 | 0 | } else { |
1290 | 0 | ret = ntlm_encode_oem_str(&msg->workstation, |
1291 | 0 | &buffer, &data_offs, |
1292 | 0 | workstation, workstation_len); |
1293 | 0 | } |
1294 | 0 | if (ret) goto done; |
1295 | 0 | } |
1296 | 0 | if (enc_sess_key) { |
1297 | 0 | ret = ntlm_encode_field(&msg->enc_sess_key, &buffer, |
1298 | 0 | &data_offs, enc_sess_key); |
1299 | 0 | if (ret) goto done; |
1300 | 0 | } |
1301 | | |
1302 | 0 | msg->neg_flags = htole32(flags); |
1303 | |
|
1304 | 0 | done: |
1305 | 0 | if (ret) { |
1306 | 0 | safefree(buffer.data); |
1307 | 0 | } else { |
1308 | 0 | *message = buffer; |
1309 | 0 | } |
1310 | 0 | return ret; |
1311 | 0 | } |
1312 | | |
1313 | | int ntlm_decode_auth_msg(struct ntlm_ctx *ctx, |
1314 | | struct ntlm_buffer *buffer, |
1315 | | uint32_t flags, |
1316 | | struct ntlm_buffer *lm_chalresp, |
1317 | | struct ntlm_buffer *nt_chalresp, |
1318 | | char **domain_name, char **user_name, |
1319 | | char **workstation, |
1320 | | struct ntlm_buffer *enc_sess_key, |
1321 | | struct ntlm_buffer *target_info, |
1322 | | struct ntlm_buffer *mic) |
1323 | 921 | { |
1324 | 921 | struct wire_auth_msg *msg; |
1325 | 921 | uint32_t neg_flags; |
1326 | 921 | size_t payload_offs; |
1327 | 921 | char *dom = NULL; |
1328 | 921 | char *usr = NULL; |
1329 | 921 | char *wks = NULL; |
1330 | 921 | int ret = 0; |
1331 | | |
1332 | 921 | if (!ctx) return EINVAL; |
1333 | | |
1334 | 921 | if (lm_chalresp) lm_chalresp->data = NULL; |
1335 | 921 | if (nt_chalresp) nt_chalresp->data = NULL; |
1336 | 921 | if (enc_sess_key) enc_sess_key->data = NULL; |
1337 | | |
1338 | 921 | msg = (struct wire_auth_msg *)buffer->data; |
1339 | 921 | payload_offs = (char *)msg->payload - (char *)msg; |
1340 | | |
1341 | 921 | neg_flags = le32toh(msg->neg_flags); |
1342 | 921 | if ((neg_flags & NTLMSSP_NEGOTIATE_VERSION) == 0) { |
1343 | | /* adjust the payload offset to point to the |
1344 | | * version field, for compatibility with older |
1345 | | * clients that completely omitted the structure |
1346 | | * on the wire */ |
1347 | 916 | payload_offs -= sizeof(struct wire_version); |
1348 | 916 | } |
1349 | | |
1350 | | /* Unconditionally copy 16 bytes for the MIC, if it was really |
1351 | | * added by the client it will be flagged in the AV_PAIR contained |
1352 | | * in the NT Response, that will be fully decoded later by the caller |
1353 | | * and the MIC checked otherwise these 16 bytes will just be ignored */ |
1354 | 921 | if (mic) { |
1355 | 921 | size_t mic_offs = payload_offs; |
1356 | | |
1357 | 921 | if (mic->length < 16) return ERR_DECODE; |
1358 | | |
1359 | 921 | if ((neg_flags & NTLMSSP_NEGOTIATE_VERSION) == 0) { |
1360 | 916 | struct wire_version zver = {0}; |
1361 | | /* mic is at payload_offs right now, but this offset may be |
1362 | | * wrongly reduced for compatibility with older clients, |
1363 | | * if all bytes are zeroed, then it means there was an actual |
1364 | | * empty version struct */ |
1365 | 916 | if (memcmp(&msg->version, &zver, |
1366 | 916 | sizeof(struct wire_version)) == 0) { |
1367 | 29 | mic_offs += sizeof(struct wire_version); |
1368 | 29 | } |
1369 | 916 | } |
1370 | | |
1371 | 921 | if (buffer->length - mic_offs < 16) return ERR_DECODE; |
1372 | 908 | memcpy(mic->data, &buffer->data[mic_offs], 16); |
1373 | 908 | } |
1374 | | |
1375 | 908 | if (msg->lm_chalresp.len != 0 && lm_chalresp) { |
1376 | 271 | ret = ntlm_decode_field(&msg->lm_chalresp, buffer, |
1377 | 271 | payload_offs, lm_chalresp); |
1378 | 271 | if (ret) goto done; |
1379 | 271 | } |
1380 | 847 | if (msg->nt_chalresp.len != 0 && nt_chalresp) { |
1381 | 344 | ret = ntlm_decode_field(&msg->nt_chalresp, buffer, |
1382 | 344 | payload_offs, nt_chalresp); |
1383 | 344 | if (ret) goto done; |
1384 | | |
1385 | 276 | if (target_info) { |
1386 | 276 | union wire_ntlm_response *resp; |
1387 | 276 | struct wire_ntlmv2_cli_chal *chal; |
1388 | 276 | uint8_t *data; |
1389 | 276 | int len; |
1390 | 276 | resp = (union wire_ntlm_response *)nt_chalresp->data; |
1391 | 276 | chal = (struct wire_ntlmv2_cli_chal *)resp->v2.cli_chal; |
1392 | 276 | len = nt_chalresp->length - sizeof(resp->v2.resp) |
1393 | 276 | - offsetof(struct wire_ntlmv2_cli_chal, target_info); |
1394 | 276 | if (len > 0) { |
1395 | 228 | data = chal->target_info; |
1396 | 228 | target_info->data = malloc(len); |
1397 | 228 | if (!target_info->data) { |
1398 | 0 | ret = ENOMEM; |
1399 | 0 | goto done; |
1400 | 0 | } |
1401 | 228 | memcpy(target_info->data, data, len); |
1402 | 228 | target_info->length = len; |
1403 | 228 | } |
1404 | 276 | } |
1405 | 276 | } |
1406 | 779 | if (msg->domain_name.len != 0 && domain_name) { |
1407 | 355 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1408 | 128 | ret = ntlm_decode_u16l_str_hdr(ctx, &msg->domain_name, buffer, |
1409 | 128 | payload_offs, &dom); |
1410 | 227 | } else { |
1411 | 227 | ret = ntlm_decode_oem_str(&msg->domain_name, buffer, |
1412 | 227 | payload_offs, &dom); |
1413 | 227 | } |
1414 | 355 | if (ret) goto done; |
1415 | 355 | } |
1416 | 661 | if (msg->user_name.len != 0 && user_name) { |
1417 | 330 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1418 | 111 | ret = ntlm_decode_u16l_str_hdr(ctx, &msg->user_name, buffer, |
1419 | 111 | payload_offs, &usr); |
1420 | 219 | } else { |
1421 | 219 | ret = ntlm_decode_oem_str(&msg->user_name, buffer, |
1422 | 219 | payload_offs, &usr); |
1423 | 219 | } |
1424 | 330 | if (ret) goto done; |
1425 | 330 | } |
1426 | 556 | if (msg->workstation.len != 0 && workstation) { |
1427 | 200 | if (flags & NTLMSSP_NEGOTIATE_UNICODE) { |
1428 | 66 | ret = ntlm_decode_u16l_str_hdr(ctx, &msg->workstation, buffer, |
1429 | 66 | payload_offs, &wks); |
1430 | 134 | } else { |
1431 | 134 | ret = ntlm_decode_oem_str(&msg->workstation, buffer, |
1432 | 134 | payload_offs, &wks); |
1433 | 134 | } |
1434 | 200 | if (ret) goto done; |
1435 | 200 | } |
1436 | 459 | if (msg->enc_sess_key.len != 0 && enc_sess_key) { |
1437 | 214 | ret = ntlm_decode_field(&msg->enc_sess_key, buffer, |
1438 | 214 | payload_offs, enc_sess_key); |
1439 | 214 | } |
1440 | | |
1441 | | /* ignore returned flags, our flags are authoritative |
1442 | | flags = le32toh(msg->neg_flags); |
1443 | | */ |
1444 | | |
1445 | 908 | done: |
1446 | 908 | if (ret) { |
1447 | 535 | if (lm_chalresp) safefree(lm_chalresp->data); |
1448 | 535 | if (nt_chalresp) safefree(nt_chalresp->data); |
1449 | 535 | if (enc_sess_key) safefree(enc_sess_key->data); |
1450 | 535 | safefree(dom); |
1451 | 535 | safefree(usr); |
1452 | 535 | safefree(wks); |
1453 | 535 | } else { |
1454 | 373 | if (domain_name) *domain_name = dom; |
1455 | 373 | if (user_name) *user_name = usr; |
1456 | 373 | if (workstation) *workstation = wks; |
1457 | 373 | } |
1458 | 908 | return ret; |
1459 | 459 | } |