Initial NTLM message parsing library
authorSimo Sorce <idra@samba.org>
Sun, 23 Jun 2013 16:20:44 +0000 (12:20 -0400)
committerSimo Sorce <simo@redhat.com>
Tue, 16 Jul 2013 06:02:42 +0000 (02:02 -0400)
Implements functions to encode/decode NTLMSSP packets

Makefile.am
src/ntlm.c [new file with mode: 0644]
src/ntlm.h [new file with mode: 0644]

index ca89f7dfad6df23cd9c886250ca47b69d3f754f0..cb75e1e3f654e38412c5bc0fa28f47115d6756b6 100644 (file)
@@ -69,9 +69,11 @@ EXTRA_DIST = build/config.rpath
 GSS_NTLM_LIBS = $(GSSAPI_LIBS)
 
 GN_MECHGLUE_OBJ = \
+    src/ntlm.c \
     src/gss_ntlmssp.c
 
 dist_noinst_HEADERS = \
+    src/ntlm.h \
     src/gss_ntlmssp.h
 
 ####################
diff --git a/src/ntlm.c b/src/ntlm.c
new file mode 100644 (file)
index 0000000..0c4824d
--- /dev/null
@@ -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 *)&timestamp;
+        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(&timestamp, 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;
+}
diff --git a/src/ntlm.h b/src/ntlm.h
new file mode 100644 (file)
index 0000000..5f3a8d8
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+   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/>.
+*/
+
+#ifndef _NTLM_H_
+#define _NTLM_H
+
+/* Negotiate Flags */
+#define NTLMSSP_NEGOTIATE_56                        (1 << 31)
+#define NTLMSSP_NEGOTIATE_KEY_EXCH                  (1 << 30)
+#define NTLMSSP_NEGOTIATE_128                       (1 << 29)
+#define UNUSED_R1                                   (1 << 28)
+#define UNUSED_R2                                   (1 << 27)
+#define UNUSED_R3                                   (1 << 26)
+#define NTLMSSP_NEGOTIATE_VERSION                   (1 << 25)
+#define UNUSED_R4                                   (1 << 24)
+#define NTLMSSP_NEGOTIATE_TARGET_INFO               (1 << 23)
+#define NTLMSSP_REQUEST_NON_NT_SESSION_KEY          (1 << 22)
+#define UNUSED_R5 /* Davenport: NEGOTIATE_ACCEPT */ (1 << 21)
+#define NTLMSSP_NEGOTIATE_IDENTIFY                  (1 << 20)
+#define NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY  (1 << 19)
+#define UNUSED_R6 /* Davenport:TARGET_TYPE_SHARE */ (1 << 18)
+#define NTLMSSP_TARGET_TYPE_SERVER                  (1 << 17)
+#define NTLMSSP_TARGET_TYPE_DOMAIN                  (1 << 16)
+#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN               (1 << 15)
+#define UNUSED_R7 /* Davenport:LOCAL_CALL */        (1 << 14)
+#define NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED  (1 << 13)
+#define NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED       (1 << 12)
+#define NTLMSSP_ANONYMOUS                           (1 << 11)
+#define UNUSED_R8                                   (1 << 10)
+#define NTLMSSP_NEGOTIATE_NTLM                      (1 << 9)
+#define UNUSED_R9                                   (1 << 8)
+#define NTLMSSP_NEGOTIATE_LM_KEY                    (1 << 7)
+#define NTLMSSP_NEGOTIATE_DATAGRAM                  (1 << 6)
+#define NTLMSSP_NEGOTIATE_SEAL                      (1 << 5)
+#define NTLMSSP_NEGOTIATE_SIGN                      (1 << 4)
+#define UNUSED_R10                                  (1 << 3)
+#define NTLMSSP_REQUEST_TARGET                      (1 << 2)
+#define NTLMSSP_NEGOTIATE_OEM                       (1 << 1)
+#define NTLMSSP_NEGOTIATE_UNICODE                   (1 << 0)
+
+/* (2.2.2.10 VERSION) */
+#define WINDOWS_MAJOR_VERSION_5 0x05
+#define WINDOWS_MAJOR_VERSION_6 0x06
+#define WINDOWS_MINOR_VERSION_0 0x00
+#define WINDOWS_MINOR_VERSION_1 0x01
+#define WINDOWS_MINOR_VERSION_2 0x02
+#define NTLMSSP_REVISION_W2K3 0x0F
+
+#define NTLMSSP_VERSION_MAJOR WINDOWS_MAJOR_VERSION_6
+#define NTLMSSP_VERSION_MINOR WINDOWS_MINOR_VERSION_2
+#define NTLMSSP_VERSION_BUILD 0
+#define NTLMSSP_VERSION_REV NTLMSSP_REVISION_W2K3
+
+enum ntlm_err_code {
+    ERR_BASE = 0x4E540000, /* base error space at 'NT00' */
+    ERR_DECODE,
+    ERR_ENCODE,
+};
+#define NTLM_ERR_MASK 0x4E54FFFF
+#define IS_NTLM_ERR_CODE(x) (((x) & NTLM_ERR_MASK) ? true : false)
+
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#define safefree(x) do { free(x); x = NULL; } while(0)
+
+struct ntlm_buffer {
+    uint8_t *data;
+    size_t length;
+};
+
+void ntlm_free_buffer_data(struct ntlm_buffer *buf);
+
+uint64_t ntlm_timestamp_now(void);
+
+
+enum ntlm_role {
+    NTLM_CLIENT,
+    NTLM_SERVER,
+    NTLM_DOMAIN_SERVER,
+    NTLM_DOMAIN_CONTROLLER
+};
+
+struct ntlm_ctx;
+
+/**
+ * @brief           Create a ntlm_ctx initialized to the initial state
+ *
+ * @param ctx       The returned context
+ *
+ * @return 0 if successful, an error otherwise
+ */
+int ntlm_init_ctx(struct ntlm_ctx **ctx);
+
+/**
+ * @brief           Frees a ntlm_ctx
+ *
+ * @param ctx       Pointer to a context to be freed
+ *
+ * @return 0 if successful, an error otherwise
+ * NOTE: even if an error is returned the contetx is freed and NULLed
+ */
+int ntlm_free_ctx(struct ntlm_ctx **ctx);
+
+/**
+ * @brief   A utility function to construct a target_info structure
+ *
+ * @param ctx                   The ntlm context
+ * @param nb_computer_name      The NetBIOS Computer Name
+ * @param nb_domain_name        The NetBIOS Domain Name
+ * @param dns_computer_name     The DNS Fully Qualified Computer Name
+ * @param dns_domain_name       The DNS Fully Qualified Domain Name
+ * @param dns_tree_name         The DNS Tree Name
+ * @param av_flags              The av flags
+ * @param av_timestamp          A 64 bit FILETIME timestamp
+ * @param av_single_host        A ntlm_buffer with the single host data
+ * @param av_target_name        The target name
+ * @param av_cb                 A ntlm_buffer with channel binding data
+ * @param target_info           The buffer in which target_info is returned.
+ *
+ * NOTE: The caller is responsible for free()ing the buffer
+ *
+ * @return      0 if everyting parses correctly, or an error code
+ */
+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);
+
+
+/**
+ * @brief   A utility function to parse a target_info structure
+ *
+ * @param ctx                   The ntlm context
+ * @param buffer                A ntlm_buffer containing the info to be parsed
+ * @param nb_computer_name      The NetBIOS Computer Name
+ * @param nb_domain_name        The NetBIOS Domain Name
+ * @param dns_computer_name     The DNS Fully Qualified Computer Name
+ * @param dns_domain_name       The DNS Fully Qualified Domain Name
+ * @param dns_tree_name         The DNS Tree Name
+ * @param av_flags              The av flags
+ * @param av_timestamp          A 64 bit FILETIME timestamp
+ * @param av_single_host        A ntlm_buffer with the single host data
+ * @param av_target_name        The target name
+ * @param av_cb                 A ntlm_buffer with channel binding data
+ *
+ * NOTE: The caller is responsible for free()ing all strings, while the
+ *       ntlm_buffer types point directly at data in the provided buffer.
+ *
+ * @return      0 if everyting parses correctly, or an error code
+ */
+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);
+
+/**
+ * @brief Verifies the message signature is valid and the message
+ * in sequence with the expected state
+ *
+ * @param ctx           The conversation context.
+ * @param buffer        A ntlm_buffer containing the raw NTLMSSP packet
+ *
+ * @return      0 if everyting parses correctly, or an error code
+ *
+ * NOTE: Always use ntlm_detect_msg_type before calling other functions,
+ * so that the signature and message type are checked, and the state is
+ * validated.
+ */
+int ntlm_decode_msg_type(struct ntlm_ctx *ctx,
+                         struct ntlm_buffer *buffer,
+                         uint32_t *type);
+
+/**
+ * @brief This function encodes a NEGTIATE_MESSAGE which is the first message
+ * a client will send to a server. It also updates the stage in the context.
+ *
+ * @param ctx           A fresh ntlm context.
+ * @param flags         Requested flags
+ * @param domain        Optional Domain Name
+ * @param workstation   Optional Workstation Name
+ * @param message       A ntlm_buffer containing the returned message
+ *
+ * NOTE: the caller is responsible for free()ing the message buffer.
+ *
+ * @return      0 if everyting encodes correctly, or an error code
+ */
+int ntlm_encode_neg_msg(struct ntlm_ctx *ctx, uint32_t flags,
+                        const char *domain, const char *workstation,
+                        struct ntlm_buffer *message);
+
+/**
+ * @brief This function decodes a NTLMSSP NEGTIATE_MESSAGE.
+ *
+ * @param ctx           A fresh ntlm context
+ * @param buffer        A ntlm_buffer containing the raw NTLMSSP packet
+ * @param flags         Returns the flags requested by the client
+ * @param domain        Returns the domain provided by the client if any
+ * @param workstation   Returns the workstation provided by the client if any
+ *
+ * @return      0 if everyting parses correctly, or an error code
+ *
+ */
+int ntlm_decode_neg_msg(struct ntlm_ctx *ctx,
+                        struct ntlm_buffer *buffer, uint32_t *flags,
+                        char **domain, char **workstation);
+
+/**
+ * @brief This function encodes a CHALLENGE_MESSAGE which is the first message
+ * a server will send to a client. It also updates the stage in the context.
+ *
+ * @param ctx           The ntlm context
+ * @param flags         The challenge flags
+ * @param target_name   The target name
+ * @param challenge     A 64 bit value with a challenge
+ * @param target_info   A buffer containing target_info data
+ * @param message       A ntlm_buffer containing the encoded message
+ *
+ * NOTE: the caller is responsible for free()ing the message buffer
+ *
+ * @return      0 if everyting encodes correctly, or an error code
+ */
+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);
+
+
+/**
+ * @brief This function decodes a NTLMSSP CHALLENGE_MESSAGE.
+ *
+ * @param ctx           The ntlm context
+ * @param buffer        A ntlm_buffer containing the raw NTLMSSP packet
+ * @param flags         The challenge flags
+ * @param target_name   The target name
+ * @param challenge     A 64 bit value with the server challenge
+ *                      The caller MUST provide a preallocated buffer of
+ *                      appropriate length (8 bytes)
+ * @param target_info   A buffer containing returned target_info data
+ *
+ * @return      0 if everyting encodes correctly, or an error code
+ */
+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);
+
+
+/**
+ * @brief This function encodes a AUTHENTICATE_MESSAGE which is the second
+ * message a client will send to a serve.
+ * It also updates the stage in the context.
+ *
+ * @param ctx           The ntlm context
+ * @param flags         The flags
+ * @param lm_chalresp   A LM or LMv2 response
+ * @param nt_chalresp   A NTLM or NTLMv2 response
+ * @param domain_name   The Domain name
+ * @param user_name     The User name
+ * @param workstation   The Workstation name
+ * @param enc_sess_key  The session key
+ * @param mic           A MIC of the messages
+ * @param message       A ntlm_buffer containing the encoded message
+ *
+ * @return      0 if everyting encodes correctly, or an error code
+ */
+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);
+
+/**
+ * @brief This function decodes a NTLMSSP AUTHENTICATE_MESSAGE.
+ *
+ * @param ctx           The ntlm context
+ * @param buffer        A ntlm_buffer containing the raw NTLMSSP packet
+ * @param flags         The negotiated flags
+ * @param lm_chalresp   A LM or LMv2 response
+ * @param nt_chalresp   A NTLM or NTLMv2 response
+ * @param domain_name   The Domain name
+ * @param user_name     The User name
+ * @param workstation   The Workstation name
+ * @param enc_sess_key  The session key
+ * @param mic           A MIC of the messages
+ *                      Passing a pointer to a mic means the caller has
+ *                      previously requested the presence of a MIC field from
+ *                      the peer. If a MIC is not returned by the peer the
+ *                      secoding will fail. If not MIC ha sbeen previously
+ *                      requested set this pointer to NULL.
+ *                      The caller must provide a preallocated buffer of
+ *                      appropriate length (16 bytes)
+ *
+ * NOTE: the caller is reponsible for freeing all allocated buffers
+ * on success.
+ *
+ * @return      0 if everyting encodes correctly, or an error code
+ */
+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);
+
+#endif /* _NTLM_H_ */