diff options
author | Simo Sorce <idra@samba.org> | 2013-06-23 12:20:44 -0400 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2013-07-16 02:02:42 -0400 |
commit | 399c16829ceb8dad8f3f6e25e626212e4fca332e (patch) | |
tree | 13106f28d5c3cfbced23d47b2cf58a5d09bac4ee /src/ntlm.c | |
parent | 46f02be9a4c16b615df7ec8f5d9c08ec653e74c9 (diff) | |
download | gss-ntlmssp-399c16829ceb8dad8f3f6e25e626212e4fca332e.tar.gz gss-ntlmssp-399c16829ceb8dad8f3f6e25e626212e4fca332e.tar.xz gss-ntlmssp-399c16829ceb8dad8f3f6e25e626212e4fca332e.zip |
Initial NTLM message parsing library
Implements functions to encode/decode NTLMSSP packets
Diffstat (limited to 'src/ntlm.c')
-rw-r--r-- | src/ntlm.c | 1387 |
1 files changed, 1387 insertions, 0 deletions
diff --git a/src/ntlm.c b/src/ntlm.c new file mode 100644 index 0000000..0c4824d --- /dev/null +++ b/src/ntlm.c @@ -0,0 +1,1387 @@ +/* + Copyright (C) 2013 Simo Sorce <simo@samba.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + + +/* This File implements the NTLM protocol as specified by: + * [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol + * + * Additional cross checking with: + * http://davenport.sourceforge.net/ntlm.html + */ + +#include <alloca.h> +#include <endian.h> +#include <errno.h> +#include <iconv.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "ntlm.h" + +#pragma pack(push, 1) +struct wire_msg_hdr { + uint8_t signature[8]; + uint32_t msg_type; +}; +#pragma pack(pop) + +/* A wire string, the offset is relative to the mesage and must fall into the + * payload section. + * max_len should be set equal to len and ignored by servers. + */ +#pragma pack(push, 1) +struct wire_field_hdr { + uint16_t len; + uint16_t max_len; + uint32_t offset; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_neg_msg { + struct wire_msg_hdr header; + uint32_t neg_flags; + struct wire_field_hdr domain_name; + struct wire_field_hdr workstation_name; + uint8_t payload[]; /* variable */ +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_chal_msg { + struct wire_msg_hdr header; + struct wire_field_hdr target_name; + uint32_t neg_flags; + uint8_t server_challenge[8]; + uint8_t reserved[8]; + struct wire_field_hdr target_info; + uint8_t payload[]; /* variable */ +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_auth_msg { + struct wire_msg_hdr header; + struct wire_field_hdr lm_chalresp; + struct wire_field_hdr nt_chalresp; + struct wire_field_hdr domain_name; + struct wire_field_hdr user_name; + struct wire_field_hdr workstation; + struct wire_field_hdr enc_sess_key; + uint32_t neg_flags; + uint8_t payload[]; /* variable */ +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_av_pair { + uint16_t av_id; + uint16_t av_len; + uint8_t value[]; /* variable */ +}; +#pragma pack(pop) + +enum msv_av_ids { + MSV_AV_EOL = 0, + MSV_AV_NB_COMPUTER_NAME, + MSV_AV_NB_DOMAIN_NAME, + MSV_AV_DNS_COMPUTER_NAME, + MSV_AV_DNS_DOMAIN_NAME, + MSV_AV_DNS_TREE_NAME, + MSV_AV_FLAGS, + MSV_AV_TIMESTAMP, + MSV_AV_SINGLE_HOST, + MSV_AV_TARGET_NAME, + MSV_AV_CHANNEL_BINDINGS +}; + +/* Used only on the same host */ +#pragma pack(push, 1) +struct wire_single_host_data { + uint32_t size; + uint32_t Z4; + uint32_t data_present; + uint32_t custom_data; + uint8_t machine_id[32]; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_channel_binding { + uint8_t md5_hash[16]; +}; +#pragma pack(pop) + +/* lm response, v1 or v2 */ +#pragma pack(push, 1) +union wire_lm_response { + struct { + uint8_t resp[24]; + } v1; + struct { + uint8_t resp[16]; + uint8_t cli_chal[8]; + } v2; +}; +#pragma pack(pop) + +/* ntlm response, v1 or v2 */ +#pragma pack(push, 1) +union wire_ntlm_response { + struct { + uint8_t resp[24]; + } v1; + struct { + uint8_t resp[16]; + uint8_t cli_chal[8]; + } v2; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct wire_ntlm_cli_chal { + uint8_t resp_type; + uint8_t hi_resp_type; + uint16_t reserved1; + uint32_t reserved2; + uint64_t timestamp; + uint8_t cli_chal[8]; + uint32_t reserved3; + uint8_t av_pairs[]; /* variable */ +}; +#pragma pack(pop) + +/* signature structure, v1 or v2 */ +#pragma pack(push, 1) +union wire_msg_signature { + struct { + uint32_t version; + uint8_t random_pad[4]; + uint8_t checksum[4]; + uint32_t seq_num; + } v1; + struct { + uint32_t version; + uint8_t checksum[8]; + uint32_t seq_num; + } v2; +}; +#pragma pack(pop) + +/* Version information. + * Used only for debugging and usually placed as the head of the payload when + * used */ +#pragma pack(push, 1) +struct wire_version { + uint8_t major; + uint8_t minor; + uint16_t build; + uint8_t reserved[3]; + uint8_t revision; +}; +#pragma pack(pop) + +struct ntlm_ctx { + iconv_t from_oem; + iconv_t to_oem; +}; + +int ntlm_init_ctx(struct ntlm_ctx **ctx) +{ + struct ntlm_ctx *_ctx; + int ret = 0; + + _ctx = calloc(1, sizeof(struct ntlm_ctx)); + if (!_ctx) return ENOMEM; + + _ctx->from_oem = iconv_open("UCS-2LE", "UTF-8"); + if (_ctx->from_oem == (iconv_t) -1) { + ret = errno; + } + + _ctx->to_oem = iconv_open("UTF-8", "UCS-2LE"); + if (_ctx->to_oem == (iconv_t) -1) { + iconv_close(_ctx->from_oem); + ret = errno; + } + + if (ret) { + safefree(_ctx); + } else { + *ctx = _ctx; + } + return ret; +} + +int ntlm_free_ctx(struct ntlm_ctx **ctx) +{ + int ret; + + if (!ctx || !*ctx) return 0; + + ret = iconv_close((*ctx)->from_oem); + if (ret) ret = errno; + + ret = iconv_close((*ctx)->to_oem); + if (ret) ret = errno; + + safefree(*ctx); + return ret; +} + +void ntlm_free_buffer_data(struct ntlm_buffer *buf) +{ + if (!buf) return; + + safefree(buf->data); + buf->length = 0; +} + +/* A FILETIME structure is effectively a little endian 64 bit integer + * with the time from January 1, 1601 UTC with 10s of microsecond resolution. + */ +#define FILETIME_EPOCH_VALUE 11644473600000000LL +uint64_t ntlm_timestamp_now(void) +{ + struct timeval tv; + uint64_t filetime; + + gettimeofday(&tv, NULL); + + /* set filetime to the time representing the eopch */ + filetime = FILETIME_EPOCH_VALUE; + /* add the number of seconds since the epoch */ + filetime += (uint64_t)tv.tv_sec * 10000000; + /* add the number of microseconds since the epoch */ + filetime += tv.tv_usec * 10; + + return filetime; +} + +/** + * @brief Converts a string using the provided iconv context. + * This function is ok only to convert utf8<->ucs2 + * + * @param cd The iconv context + * @param in Input buffer + * @param out Output buffer + * @param baselen Input length + * @param outlen Returned length of out buffer + * + * NOTE: out must be preallocated to a size of baselen * 2 + * + * @return 0 on success or a standard error value on error. + */ +static int ntlm_str_convert(iconv_t cd, + const char *in, char *out, + size_t baselen, size_t *outlen) +{ + char *_in; + size_t inleft, outleft; + size_t ret; + + ret = iconv(cd, NULL, NULL, NULL, NULL); + if (ret == -1) return errno; + + _in = discard_const(in); + inleft = baselen; + /* conservative max_size calculation in case lots of octects end up + * being multiple bytes in length (in both directions) */ + outleft = baselen * 2; + + ret = iconv(cd, &_in, &inleft, &out, &outleft); + if (ret == -1) return errno; + + if (outlen) { + *outlen = baselen * 2 - outleft; + } + return 0; +} + + +#define NEGOTIATE_MESSAGE 0x00000001 +#define CHALLENGE_MESSAGE 0x00000002 +#define AUTHENTICATE_MESSAGE 0x00000003 + +uint8_t ntlmssp_sig[8] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}; + +static void ntlm_encode_header(struct wire_msg_hdr *hdr, uint32_t msg_type) +{ + memcpy(hdr->signature, ntlmssp_sig, 8); + hdr->msg_type = htole32(msg_type); +} + +static int ntlm_decode_header(struct wire_msg_hdr *hdr, uint32_t *msg_type) +{ + if (memcmp(hdr->signature, ntlmssp_sig, 8) != 0) { + return ERR_DECODE; + } + + *msg_type = le32toh(hdr->msg_type); + return 0; +} + +static int ntlm_encode_oem_str(struct wire_field_hdr *hdr, + struct ntlm_buffer *buffer, + size_t *data_offs, + const char *str, int str_len) +{ + if (*data_offs + str_len > buffer->length) { + return ERR_ENCODE; + } + + memcpy(&buffer->data[*data_offs], str, str_len); + hdr->len = htole16(str_len); + hdr->max_len = htole16(str_len); + hdr->offset = htole32(*data_offs); + + *data_offs += str_len; + return 0; +} + +static int ntlm_decode_oem_str(struct wire_field_hdr *str_hdr, + struct ntlm_buffer *buffer, + size_t payload_offs, char **_str) +{ + uint16_t str_len; + uint32_t str_offs; + char *str = NULL; + + str_len = le16toh(str_hdr->len); + if (str_len == 0) goto done; + + str_offs = le32toh(str_hdr->offset); + if ((str_offs < payload_offs) || + (str_offs > buffer->length) || + (str_offs + str_len > buffer->length)) { + return ERR_DECODE; + } + + str = strndup((const char *)&buffer->data[str_offs], str_len); + if (!str) return ENOMEM; + +done: + *_str = str; + return 0; +} + +static int ntlm_encode_ucs2_str_hdr(struct ntlm_ctx *ctx, + struct wire_field_hdr *hdr, + struct ntlm_buffer *buffer, + size_t *data_offs, + const char *str, int str_len) +{ + char *out; + size_t outlen; + int ret; + + out = (char *)&buffer->data[*data_offs]; + + ret = ntlm_str_convert(ctx->from_oem, str, out, str_len, &outlen); + if (ret) return ret; + + hdr->len = htole16(outlen); + hdr->max_len = htole16(outlen); + hdr->offset = htole32(*data_offs); + + *data_offs += outlen; + return 0; +} + +static int ntlm_decode_ucs2_str_hdr(struct ntlm_ctx *ctx, + struct wire_field_hdr *str_hdr, + struct ntlm_buffer *buffer, + size_t payload_offs, char **str) +{ + char *in, *out = NULL; + uint16_t str_len; + uint32_t str_offs; + size_t outlen; + int ret = 0; + + str_len = le16toh(str_hdr->len); + if (str_len == 0) goto done; + + str_offs = le32toh(str_hdr->offset); + if ((str_offs < payload_offs) || + (str_offs > buffer->length) || + (str_offs + str_len > buffer->length)) { + return ERR_DECODE; + } + + in = (char *)&buffer->data[str_offs]; + + out = malloc(str_len * 2 + 1); + if (!out) return ENOMEM; + + ret = ntlm_str_convert(ctx->to_oem, in, out, str_len, &outlen); + + /* make sure to terminate output string */ + out[outlen] = '\0'; + +done: + if (ret) { + safefree(out); + } + *str = out; + return ret; +} + +static int ntlm_encode_version(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, + size_t *data_offs) +{ + struct wire_version *v; + + if (*data_offs + sizeof(struct wire_version) > buffer->length) { + return ERR_ENCODE; + } + + v = (struct wire_version *)&buffer->data[*data_offs]; + v->major = NTLMSSP_VERSION_MAJOR; + v->minor = NTLMSSP_VERSION_MINOR; + v->build = htole16(NTLMSSP_VERSION_BUILD); + v->revision = NTLMSSP_VERSION_REV; + + *data_offs += sizeof(struct wire_version); + return 0; +} + +static int ntlm_encode_field(struct wire_field_hdr *hdr, + struct ntlm_buffer *buffer, + size_t *data_offs, + struct ntlm_buffer *field) +{ + if (*data_offs + field->length > buffer->length) { + return ERR_ENCODE; + } + + memcpy(&buffer->data[*data_offs], field->data, field->length); + hdr->len = htole16(field->length); + hdr->max_len = hdr->len; + hdr->offset = htole32(*data_offs); + + *data_offs += field->length; + return 0; +} + +static int ntlm_decode_field(struct wire_field_hdr *hdr, + struct ntlm_buffer *buffer, + size_t payload_offs, + struct ntlm_buffer *field) +{ + struct ntlm_buffer b = { NULL, 0 }; + uint32_t offs; + uint16_t len; + + len = le16toh(hdr->len); + if (len == 0) goto done; + + offs = le32toh(hdr->offset); + if ((offs < payload_offs) || + (offs > buffer->length) || + (offs + len > buffer->length)) { + return ERR_DECODE; + } + + b.data = malloc(len); + if (!b.data) return ENOMEM; + + b.length = len; + memcpy(b.data, &buffer->data[offs], b.length); + +done: + *field = b; + return 0; +} + +static int ntlm_encode_av_pair_ucs2_str(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, + size_t *data_offs, + enum msv_av_ids av_id, + const char *str, size_t str_len) +{ + struct wire_av_pair *av_pair; + char *out; + size_t outlen; + int ret; + + if (*data_offs + 4 + str_len > buffer->length) { + return ERR_ENCODE; + } + + av_pair = (struct wire_av_pair *)&buffer->data[*data_offs]; + out = (char *)av_pair->value; + + ret = ntlm_str_convert(ctx->from_oem, str, out, str_len, &outlen); + if (ret) return ret; + + av_pair->av_len = htole16(outlen); + av_pair->av_id = htole16(av_id); + + *data_offs += av_pair->av_len + 4; + return 0; +} + +static int ntlm_decode_av_pair_ucs2_str(struct ntlm_ctx *ctx, + struct wire_av_pair *av_pair, + char **str) +{ + char *in, *out; + size_t inlen, outlen; + int ret; + + in = (char *)av_pair->value; + inlen = le16toh(av_pair->av_len); + out = malloc(inlen * 2 + 1); + + ret = ntlm_str_convert(ctx->to_oem, in, out, inlen, &outlen); + if (ret) { + safefree(out); + return ret; + } + /* terminate out string for sure */ + out[outlen] = '\0'; + + *str = out; + return 0; +} + +static int ntlm_encode_av_pair_value(struct ntlm_buffer *buffer, + size_t *data_offs, + enum msv_av_ids av_id, + struct ntlm_buffer *value) +{ + struct wire_av_pair *av_pair; + + if (*data_offs + 4 + value->length > buffer->length) { + return ERR_ENCODE; + } + + av_pair = (struct wire_av_pair *)&buffer->data[*data_offs]; + av_pair->av_id = htole16(av_id); + av_pair->av_len = htole16(value->length); + if (value->length) { + memcpy(av_pair->value, value->data, value->length); + } + + *data_offs += value->length + 4; + return 0; +} + +int ntlm_encode_target_info(struct ntlm_ctx *ctx, char *nb_computer_name, + char *nb_domain_name, char *dns_computer_name, + char *dns_domain_name, char *dns_tree_name, + uint32_t *av_flags, uint64_t *av_timestamp, + struct ntlm_buffer *av_single_host, + char *av_target_name, struct ntlm_buffer *av_cb, + struct ntlm_buffer *target_info) +{ + struct ntlm_buffer buffer; + size_t data_offs; + size_t max_size; + size_t nb_computer_name_len = 0; + size_t nb_domain_name_len = 0; + size_t dns_computer_name_len = 0; + size_t dns_domain_name_len = 0; + size_t dns_tree_name_len = 0; + size_t av_target_name_len = 0; + struct ntlm_buffer value; + int ret = 0; + + max_size = 4; /* MSV_AV_EOL */ + + if (nb_computer_name) { + nb_computer_name_len = strlen(nb_computer_name); + max_size += nb_computer_name_len * 2; + } + if (nb_domain_name) { + nb_domain_name_len = strlen(nb_domain_name); + max_size += nb_domain_name_len * 2; + } + if (dns_computer_name) { + dns_computer_name_len = strlen(dns_computer_name); + max_size += dns_computer_name_len * 2; + } + if (dns_domain_name) { + dns_domain_name_len = strlen(dns_domain_name); + max_size += dns_domain_name_len * 2; + } + if (dns_tree_name) { + dns_tree_name_len = strlen(dns_tree_name); + max_size += dns_tree_name_len * 2; + } + if (av_flags) { + max_size += 8; + } + if (av_timestamp) { + max_size += 12; + } + if (av_single_host) { + max_size += av_single_host->length; + } + if (av_target_name) { + av_target_name_len = strlen(av_target_name); + max_size += av_target_name_len * 2; + } + if (av_cb) { + max_size += av_cb->length; + } + + data_offs = 0; + buffer.length = max_size; + buffer.data = calloc(1, buffer.length); + if (!buffer.data) return ENOMEM; + + if (nb_computer_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_NB_COMPUTER_NAME, + nb_computer_name, + nb_computer_name_len); + if (ret) goto done; + } + if (nb_domain_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_NB_DOMAIN_NAME, + nb_domain_name, + nb_domain_name_len); + if (ret) goto done; + } + if (dns_computer_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_DNS_COMPUTER_NAME, + dns_computer_name, + dns_computer_name_len); + if (ret) goto done; + } + if (dns_domain_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_DNS_DOMAIN_NAME, + dns_domain_name, + dns_domain_name_len); + if (ret) goto done; + } + if (dns_tree_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_DNS_TREE_NAME, + dns_tree_name, + dns_tree_name_len); + if (ret) goto done; + } + if (av_flags) { + uint32_t flags = htole32(*av_flags); + value.data = (uint8_t *)&flags; + value.length = 4; + ret = ntlm_encode_av_pair_value(&buffer, &data_offs, + MSV_AV_FLAGS, &value); + if (ret) goto done; + } + if (av_timestamp) { + uint64_t timestamp = htole64(*av_timestamp); + value.data = (uint8_t *)×tamp; + value.length = 8; + ret = ntlm_encode_av_pair_value(&buffer, &data_offs, + MSV_AV_TIMESTAMP, &value); + if (ret) goto done; + } + if (av_single_host) { + ret = ntlm_encode_av_pair_value(&buffer, &data_offs, + MSV_AV_SINGLE_HOST, av_single_host); + if (ret) goto done; + } + if (av_target_name) { + ret = ntlm_encode_av_pair_ucs2_str(ctx, &buffer, &data_offs, + MSV_AV_TARGET_NAME, + av_target_name, + av_target_name_len); + if (ret) goto done; + } + if (av_cb) { + ret = ntlm_encode_av_pair_value(&buffer, &data_offs, + MSV_AV_CHANNEL_BINDINGS, av_cb); + if (ret) goto done; + } + + value.length = 0; + value.data = NULL; + ret = ntlm_encode_av_pair_value(&buffer, &data_offs, MSV_AV_EOL, &value); + buffer.length = data_offs; + +done: + if (ret) { + safefree(buffer.data); + } else { + *target_info = buffer; + } + return ret; +} + +int ntlm_decode_target_info(struct ntlm_ctx *ctx, struct ntlm_buffer *buffer, + char **nb_computer_name, char **nb_domain_name, + char **dns_computer_name, char **dns_domain_name, + char **dns_tree_name, char **av_target_name, + uint32_t *av_flags, uint64_t *av_timestamp, + struct ntlm_buffer *av_single_host, + struct ntlm_buffer *av_cb) +{ + struct wire_av_pair *av_pair; + uint16_t av_id = (uint16_t)-1; + uint16_t av_len = (uint16_t)-1; + struct ntlm_buffer sh = { NULL, 0 }; + struct ntlm_buffer cb = { NULL, 0 }; + char *nb_computer = NULL; + char *nb_domain = NULL; + char *dns_computer = NULL; + char *dns_domain = NULL; + char *dns_tree = NULL; + char *av_target = NULL; + size_t data_offs = 0; + uint64_t timestamp; + uint32_t flags; + int ret = 0; + + while (data_offs + 4 <= buffer->length) { + av_pair = (struct wire_av_pair *)&buffer->data[data_offs]; + data_offs += 4; + av_id = le16toh(av_pair->av_id); + av_len = le16toh(av_pair->av_len); + if (av_len > buffer->length - data_offs) { + ret = ERR_DECODE; + goto done; + } + data_offs += av_len; + + switch (av_id) { + case MSV_AV_CHANNEL_BINDINGS: + if (!av_cb) continue; + cb.data = av_pair->value; + cb.length = av_len; + break; + case MSV_AV_TARGET_NAME: + if (!av_target_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &av_target); + if (ret) goto done; + break; + case MSV_AV_SINGLE_HOST: + if (!av_single_host) continue; + sh.data = av_pair->value; + sh.length = av_len; + break; + case MSV_AV_TIMESTAMP: + if (!av_timestamp) continue; + memcpy(×tamp, av_pair->value, sizeof(timestamp)); + timestamp = le64toh(timestamp); + break; + case MSV_AV_FLAGS: + if (!av_flags) continue; + memcpy(&flags, av_pair->value, sizeof(flags)); + flags = le32toh(flags); + break; + case MSV_AV_DNS_TREE_NAME: + if (!dns_tree_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &dns_tree); + if (ret) goto done; + break; + case MSV_AV_DNS_DOMAIN_NAME: + if (!dns_domain_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &dns_domain); + if (ret) goto done; + break; + case MSV_AV_DNS_COMPUTER_NAME: + if (!dns_computer_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &dns_computer); + if (ret) goto done; + break; + case MSV_AV_NB_DOMAIN_NAME: + if (!nb_domain_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &nb_domain); + if (ret) goto done; + break; + case MSV_AV_NB_COMPUTER_NAME: + if (!nb_computer_name) continue; + ret = ntlm_decode_av_pair_ucs2_str(ctx, av_pair, &nb_computer); + if (ret) goto done; + break; + default: + /* unknown av_pair, or EOL */ + break; + } + if (av_id == MSV_AV_EOL) break; + } + + if (av_id != MSV_AV_EOL || av_len != 0) { + ret = ERR_DECODE; + } + +done: + if (ret) { + ntlm_free_buffer_data(&sh); + ntlm_free_buffer_data(&cb); + safefree(nb_computer); + safefree(nb_domain); + safefree(dns_computer); + safefree(dns_domain); + safefree(dns_tree); + safefree(av_target); + } else { + if (nb_computer_name) *nb_computer_name = nb_computer; + if (nb_domain_name) *nb_domain_name = nb_domain; + if (dns_computer_name) *dns_computer_name = dns_computer; + if (dns_domain_name) *dns_domain_name = dns_domain; + if (dns_tree_name) *dns_tree_name = dns_tree; + if (av_target_name) *av_target_name = av_target; + if (av_single_host) *av_single_host = sh; + if (av_cb) *av_cb = cb; + } + return ret; +} + +int ntlm_decode_msg_type(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, + uint32_t *type) +{ + struct wire_neg_msg *msg; + uint32_t msg_type; + int ret; + + if (!ctx) return EINVAL; + + if (buffer->length < sizeof(struct wire_msg_hdr)) { + return ERR_DECODE; + } + + msg = (struct wire_neg_msg *)buffer->data; + + ret = ntlm_decode_header(&msg->header, &msg_type); + if (ret) goto done; + + switch (msg_type) { + case NEGOTIATE_MESSAGE: + if (buffer->length < sizeof(struct wire_neg_msg)) { + return ERR_DECODE; + } + break; + case CHALLENGE_MESSAGE: + if (buffer->length < sizeof(struct wire_chal_msg)) { + return ERR_DECODE; + } + break; + case AUTHENTICATE_MESSAGE: + if (buffer->length < sizeof(struct wire_auth_msg)) { + return ERR_DECODE; + } + break; + default: + ret = ERR_DECODE; + break; + } + +done: + if (ret == 0) { + *type = msg_type; + } + return ret; +} + +int ntlm_encode_neg_msg(struct ntlm_ctx *ctx, uint32_t flags, + const char *domain, const char *workstation, + struct ntlm_buffer *message) +{ + struct wire_neg_msg *msg; + struct ntlm_buffer buffer; + size_t data_offs; + size_t dom_len = 0; + size_t wks_len = 0; + int ret = 0; + + if (!ctx) return EINVAL; + + buffer.length = sizeof(struct wire_neg_msg); + + /* Strings MUST use OEM charset in negotiate message */ + if (flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) { + if (!domain) return EINVAL; + dom_len = strlen(domain); + buffer.length += dom_len; + } + if (flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) { + if (!workstation) return EINVAL; + wks_len = strlen(workstation); + buffer.length += wks_len; + } + + buffer.data = calloc(1, buffer.length); + if (!buffer.data) return ENOMEM; + + msg = (struct wire_neg_msg *)buffer.data; + data_offs = (void *)msg->payload - (void *)msg; + + ntlm_encode_header(&msg->header, NEGOTIATE_MESSAGE); + + msg->neg_flags = htole32(flags); + + if (dom_len) { + ret = ntlm_encode_oem_str(&msg->domain_name, &buffer, + &data_offs, domain, dom_len); + if (ret) goto done; + } + + if (wks_len) { + ret = ntlm_encode_oem_str(&msg->workstation_name, &buffer, + &data_offs, workstation, wks_len); + if (ret) goto done; + } + +done: + if (ret) { + safefree(buffer.data); + } else { + *message = buffer; + } + return ret; +} + +int ntlm_decode_neg_msg(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, uint32_t *flags, + char **domain, char **workstation) +{ + struct wire_neg_msg *msg; + size_t payload_offs; + uint32_t neg_flags; + char *dom = NULL; + char *wks = NULL; + int ret = 0; + + if (!ctx) return EINVAL; + + msg = (struct wire_neg_msg *)buffer->data; + payload_offs = (void *)msg->payload - (void *)msg; + + neg_flags = le32toh(msg->neg_flags); + + if (neg_flags & NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED) { + ret = ntlm_decode_oem_str(&msg->domain_name, buffer, + payload_offs, &dom); + if (ret) goto done; + } + if (neg_flags & NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) { + ret = ntlm_decode_oem_str(&msg->workstation_name, buffer, + payload_offs, &wks); + if (ret) goto done; + } + +done: + if (ret) { + safefree(dom); + safefree(wks); + } else { + *flags = neg_flags; + *domain = dom; + *workstation = wks; + } + return ret; +} + +/* TODO: support datagram style */ +int ntlm_encode_chal_msg(struct ntlm_ctx *ctx, + uint32_t flags, + char *target_name, + struct ntlm_buffer *challenge, + struct ntlm_buffer *target_info, + struct ntlm_buffer *message) +{ + struct wire_chal_msg *msg; + struct ntlm_buffer buffer; + size_t data_offs; + size_t target_len = 0; + int ret = 0; + + if (!ctx) return EINVAL; + + if (!challenge || challenge->length != 8) return EINVAL; + + buffer.length = sizeof(struct wire_chal_msg); + + if (flags & NTLMSSP_NEGOTIATE_VERSION) { + buffer.length += sizeof(struct wire_version); + } + + if ((flags & NTLMSSP_TARGET_TYPE_SERVER) + || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { + if (!target_name) return EINVAL; + + target_len = strlen(target_name); + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + buffer.length += target_len * 2; + } else { + buffer.length += target_len; + } + } + + if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + if (!target_info) return EINVAL; + + buffer.length += target_info->length; + } + + buffer.data = calloc(1, buffer.length); + if (!buffer.data) return ENOMEM; + + msg = (struct wire_chal_msg *)buffer.data; + data_offs = (void *)msg->payload - (void *)msg; + + ntlm_encode_header(&msg->header, CHALLENGE_MESSAGE); + + /* this must be first as it pushes the payload further down */ + if (flags & NTLMSSP_NEGOTIATE_VERSION) { + ret = ntlm_encode_version(ctx, &buffer, &data_offs); + if (ret) goto done; + } + + if ((flags & NTLMSSP_TARGET_TYPE_SERVER) + || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_encode_ucs2_str_hdr(ctx, &msg->target_name, &buffer, + &data_offs, target_name, target_len); + } else { + ret = ntlm_encode_oem_str(&msg->target_name, &buffer, + &data_offs, target_name, target_len); + } + if (ret) goto done; + } + + msg->neg_flags = htole32(flags); + memcpy(msg->server_challenge, challenge->data, 8); + + if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + ret = ntlm_encode_field(&msg->target_info, &buffer, + &data_offs, target_info); + if (ret) goto done; + } + +done: + if (ret) { + safefree(buffer.data); + } else { + *message = buffer; + } + return ret; +} + +int ntlm_decode_chal_msg(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, + uint32_t *_flags, char **target_name, + struct ntlm_buffer *challenge, + struct ntlm_buffer *target_info) +{ + struct wire_chal_msg *msg; + size_t payload_offs; + uint32_t flags; + char *trg = NULL; + int ret = 0; + + if (!ctx) return EINVAL; + + if (challenge->length < 8) return EINVAL; + + msg = (struct wire_chal_msg *)buffer->data; + payload_offs = (void *)msg->payload - (void *)msg; + + flags = le32toh(msg->neg_flags); + + if ((flags & NTLMSSP_TARGET_TYPE_SERVER) + || (flags & NTLMSSP_TARGET_TYPE_DOMAIN)) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_decode_ucs2_str_hdr(ctx, &msg->target_name, buffer, + payload_offs, &trg); + } else { + ret = ntlm_decode_oem_str(&msg->target_name, buffer, + payload_offs, &trg); + } + if (ret) goto done; + } + + memcpy(challenge->data, msg->server_challenge, 8); + challenge->length = 8; + + if (flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + ret = ntlm_decode_field(&msg->target_info, buffer, + payload_offs, target_info); + if (ret) goto done; + } + +done: + if (ret) { + safefree(trg); + } else { + *_flags = flags; + *target_name = trg; + } + return ret; +} + +int ntlm_encode_auth_msg(struct ntlm_ctx *ctx, + uint32_t flags, + struct ntlm_buffer *lm_chalresp, + struct ntlm_buffer *nt_chalresp, + char *domain_name, char *user_name, + char *workstation, + struct ntlm_buffer *enc_sess_key, + struct ntlm_buffer *mic, + struct ntlm_buffer *message) +{ + struct wire_auth_msg *msg; + struct ntlm_buffer buffer; + size_t data_offs; + size_t domain_name_len = 0; + size_t user_name_len = 0; + size_t workstation_len = 0; + int ret = 0; + + if (!ctx) return EINVAL; + + buffer.length = sizeof(struct wire_auth_msg); + + if (lm_chalresp) { + buffer.length += lm_chalresp->length; + } + if (nt_chalresp) { + buffer.length += nt_chalresp->length; + } + if (domain_name) { + domain_name_len = strlen(domain_name); + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + buffer.length += domain_name_len * 2; + } else { + buffer.length += domain_name_len; + } + } + if (user_name) { + user_name_len = strlen(user_name); + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + buffer.length += user_name_len * 2; + } else { + buffer.length += user_name_len; + } + } + if (workstation) { + workstation_len = strlen(workstation); + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + buffer.length += workstation_len * 2; + } else { + buffer.length += workstation_len; + } + } + if (enc_sess_key) { + buffer.length += enc_sess_key->length; + } + if (flags & NTLMSSP_NEGOTIATE_VERSION) { + buffer.length += sizeof(struct wire_version); + } + if (mic) { + buffer.length += 16; + } + + buffer.data = calloc(1, buffer.length); + if (!buffer.data) return ENOMEM; + + msg = (struct wire_auth_msg *)buffer.data; + data_offs = (void *)msg->payload - (void *)msg; + + ntlm_encode_header(&msg->header, AUTHENTICATE_MESSAGE); + + /* this must be first as it pushes the payload further down */ + if (flags & NTLMSSP_NEGOTIATE_VERSION) { + ret = ntlm_encode_version(ctx, &buffer, &data_offs); + if (ret) goto done; + } + + /* this must be second as it pushes the payload further down */ + if (mic) { + memcpy(&buffer.data[data_offs], mic->data, mic->length); + data_offs += mic->length; + } + + if (lm_chalresp) { + ret = ntlm_encode_field(&msg->lm_chalresp, &buffer, + &data_offs, lm_chalresp); + if (ret) goto done; + } + if (nt_chalresp) { + ret = ntlm_encode_field(&msg->nt_chalresp, &buffer, + &data_offs, nt_chalresp); + if (ret) goto done; + } + if (domain_name_len) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_encode_ucs2_str_hdr(ctx, &msg->domain_name, + &buffer, &data_offs, + domain_name, domain_name_len); + } else { + ret = ntlm_encode_oem_str(&msg->domain_name, + &buffer, &data_offs, + domain_name, domain_name_len); + } + if (ret) goto done; + } + if (user_name_len) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_encode_ucs2_str_hdr(ctx, &msg->user_name, + &buffer, &data_offs, + user_name, user_name_len); + } else { + ret = ntlm_encode_oem_str(&msg->user_name, + &buffer, &data_offs, + user_name, user_name_len); + } + if (ret) goto done; + } + if (workstation_len) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_encode_ucs2_str_hdr(ctx, &msg->workstation, + &buffer, &data_offs, + workstation, workstation_len); + } else { + ret = ntlm_encode_oem_str(&msg->workstation, + &buffer, &data_offs, + workstation, workstation_len); + } + if (ret) goto done; + } + if (enc_sess_key) { + ret = ntlm_encode_field(&msg->enc_sess_key, &buffer, + &data_offs, enc_sess_key); + if (ret) goto done; + } + + msg->neg_flags = htole32(flags); + +done: + if (ret) { + safefree(buffer.data); + } else { + *message = buffer; + } + return ret; +} + +int ntlm_decode_auth_msg(struct ntlm_ctx *ctx, + struct ntlm_buffer *buffer, + uint32_t flags, + struct ntlm_buffer *lm_chalresp, + struct ntlm_buffer *nt_chalresp, + char **domain_name, char **user_name, + char **workstation, + struct ntlm_buffer *enc_sess_key, + struct ntlm_buffer *mic) +{ + struct wire_auth_msg *msg; + size_t payload_offs; + char *dom = NULL; + char *usr = NULL; + char *wks = NULL; + int ret = 0; + + if (!ctx) return EINVAL; + + if (lm_chalresp) lm_chalresp->data = NULL; + if (nt_chalresp) nt_chalresp->data = NULL; + if (enc_sess_key) enc_sess_key->data = NULL; + + msg = (struct wire_auth_msg *)buffer->data; + payload_offs = (void *)msg->payload - (void *)msg; + + /* this must be first as it pushes the payload further down */ + if (flags & NTLMSSP_NEGOTIATE_VERSION) { + /* skip version for now */ + payload_offs += sizeof(struct wire_version); + } + + /* this must be second as it pushes the payload further down */ + if (mic) { + if (mic->length < 16) return ERR_DECODE; + /* mic is at payload_offs right now */ + if (buffer->length - payload_offs < 16) return ERR_DECODE; + memcpy(mic->data, &buffer->data[payload_offs], 16); + payload_offs += 16; + } + + if (msg->lm_chalresp.len != 0 && lm_chalresp) { + ret = ntlm_decode_field(&msg->lm_chalresp, buffer, + payload_offs, lm_chalresp); + } + if (msg->nt_chalresp.len != 0 && nt_chalresp) { + ret = ntlm_decode_field(&msg->nt_chalresp, buffer, + payload_offs, nt_chalresp); + } + if (msg->domain_name.len != 0 && domain_name) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_decode_ucs2_str_hdr(ctx, &msg->domain_name, buffer, + payload_offs, &dom); + } else { + ret = ntlm_decode_oem_str(&msg->domain_name, buffer, + payload_offs, &dom); + } + if (ret) goto done; + } + if (msg->user_name.len != 0 && user_name) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_decode_ucs2_str_hdr(ctx, &msg->user_name, buffer, + payload_offs, &usr); + } else { + ret = ntlm_decode_oem_str(&msg->user_name, buffer, + payload_offs, &usr); + } + if (ret) goto done; + } + if (msg->workstation.len != 0 && workstation) { + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ret = ntlm_decode_ucs2_str_hdr(ctx, &msg->workstation, buffer, + payload_offs, &wks); + } else { + ret = ntlm_decode_oem_str(&msg->workstation, buffer, + payload_offs, &wks); + } + if (ret) goto done; + } + if (msg->enc_sess_key.len != 0 && enc_sess_key) { + ret = ntlm_decode_field(&msg->enc_sess_key, buffer, + payload_offs, enc_sess_key); + } + + /* ignore returned flags, our flags are authoritative + flags = le32toh(msg->neg_flags); + */ + +done: + if (ret) { + if (lm_chalresp) safefree(lm_chalresp->data); + if (nt_chalresp) safefree(nt_chalresp->data); + if (enc_sess_key) safefree(enc_sess_key->data); + safefree(dom); + safefree(usr); + safefree(wks); + } else { + if (domain_name) *domain_name = dom; + if (user_name) *user_name = usr; + if (workstation) *workstation = wks; + } + return ret; +} |