Blob Blame History Raw
/*
   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/>.
*/

#include <endian.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "gssapi_ntlmssp.h"
#include "gss_ntlmssp.h"

/* each integer in the export format is a little endian integer */
#pragma pack(push, 1)
struct relmem {
    uint16_t ptr;
    uint16_t len;
};

struct export_name {
    uint8_t type;
    struct relmem domain;
    struct relmem name;
};

struct export_keys {
    struct relmem sign_key;
    struct relmem seal_key;
    struct relmem rc4_state;
    uint32_t seq_num;
};

#define EXPORT_CTX_VER 0x0003
struct export_ctx {
    uint16_t version;
    uint8_t role;
    uint8_t stage;
    uint8_t sec_req;

    struct relmem workstation;

    struct relmem nego_msg;
    struct relmem chal_msg;
    struct relmem auth_msg;

    struct export_name source;
    struct export_name target;

    uint8_t server_chal[8];

    uint32_t gss_flags;
    uint32_t neg_flags;

    struct relmem exported_session_key;
    struct export_keys send;
    struct export_keys recv;

    uint8_t int_flags;
    uint64_t expration_time;

    uint8_t data[];
};
#pragma pack(pop)

#define EXP_CTX_CLIENT 1
#define EXP_CTX_SERVER 2
#define EXP_CTX_DOMSRV 3
#define EXP_CTX_DOMCTR 4
#define EXP_STG_INIT 1
#define EXP_STG_NEGO 2
#define EXP_STG_CHAL 3
#define EXP_STG_AUTH 4
#define EXP_STG_DONE 5
#define EXP_NAME_NONE 0
#define EXP_NAME_ANON 1
#define EXP_NAME_USER 2
#define EXP_NAME_SERV 3

#define INC_EXP_SIZE 0x001000 /* 4K */
#define MAX_EXP_SIZE 0x100000 /* 1M */

#define NEW_SIZE(s, n) \
    ((((s) + (n) + (INC_EXP_SIZE-1)) / INC_EXP_SIZE) * INC_EXP_SIZE)

struct export_state {
    uint8_t *exp_struct;
    size_t exp_data;
    size_t exp_size;
    size_t exp_len;
    size_t exp_ptr;
};

static int export_data_buffer(struct export_state *state,
                              void *data, size_t length,
                              struct relmem *rm)
{
    void *tmp;
    size_t avail = state->exp_size - state->exp_len;
    size_t new_size;

    if (length > avail) {
        new_size = NEW_SIZE(state->exp_size, (length - avail));
        if ((new_size < state->exp_size) || new_size > MAX_EXP_SIZE) {
            return ENOMEM;
        }
        tmp = realloc(state->exp_struct, new_size);
        if (!tmp) {
            return ENOMEM;
        }
        state->exp_struct = tmp;
        state->exp_size = new_size;
        avail = state->exp_size - state->exp_len;
    }

    memcpy(&state->exp_struct[state->exp_data + state->exp_ptr], data, length);
    rm->ptr = state->exp_ptr;
    rm->len = length;
    state->exp_ptr += length;
    state->exp_len += length;
    return 0;
}

static int export_name(struct export_state *state,
                       struct gssntlm_name *name,
                       struct export_name *exp_name)
{
    int ret;

    switch (name->type) {
    case GSSNTLM_NAME_NULL:
        memset(exp_name, 0, sizeof(struct export_name));
        return 0;
    case GSSNTLM_NAME_ANON:
        memset(exp_name, 0, sizeof(struct export_name));
        exp_name->type = EXP_NAME_ANON;
        return 0;
    case GSSNTLM_NAME_USER:
        exp_name->type = EXP_NAME_USER;
        if (name->data.user.domain) {
            ret = export_data_buffer(state, name->data.user.domain,
                                     strlen(name->data.user.domain) + 1,
                                     &exp_name->domain);
            if (ret) {
                return ret;
            }
        } else {
            exp_name->domain.ptr = 0;
            exp_name->domain.len = 0;
        }
        if (name->data.user.name) {
            ret = export_data_buffer(state, name->data.user.name,
                                     strlen(name->data.user.name) + 1,
                                     &exp_name->name);
            if (ret) {
                return ret;
            }
        } else {
            exp_name->name.ptr = 0;
            exp_name->name.len = 0;
        }
        return 0;
    case GSSNTLM_NAME_SERVER:
        exp_name->type = EXP_NAME_SERV;
        exp_name->domain.ptr = 0;
        exp_name->domain.len = 0;
        if (name->data.server.name) {
            ret = export_data_buffer(state, name->data.server.name,
                                     strlen(name->data.server.name) + 1,
                                     &exp_name->name);
            if (ret) {
                return ret;
            }
        } else {
            exp_name->name.ptr = 0;
            exp_name->name.len = 0;
        }
        return 0;
    }
    return EINVAL;
}

static int export_keys(struct export_state *state,
                       struct ntlm_signseal_handle *keys,
                       struct export_keys *exp_keys)
{
    uint8_t buf[258*sizeof(uint32_t)];
    struct ntlm_buffer out = { .data=buf, .length=sizeof(buf) };
    int ret;

    if (keys->sign_key.length > 0) {
        ret = export_data_buffer(state,
                                 keys->sign_key.data,
                                 keys->sign_key.length,
                                 &exp_keys->sign_key);
        if (ret) return ret;
    } else {
        exp_keys->sign_key.ptr = 0;
        exp_keys->sign_key.len = 0;
    }

    if (keys->seal_key.length > 0) {
        ret = export_data_buffer(state,
                                 keys->seal_key.data,
                                 keys->seal_key.length,
                                 &exp_keys->seal_key);
        if (ret) return ret;
    } else {
        exp_keys->seal_key.ptr = 0;
        exp_keys->seal_key.len = 0;
    }

    if (keys->seal_handle) {
        ret = RC4_EXPORT(keys->seal_handle, &out);
        if (ret) return ret;
        ret = export_data_buffer(state, buf, sizeof(buf),
                                 &exp_keys->rc4_state);
        safezero(buf, sizeof(buf));
        if (ret) return ret;
    } else {
        exp_keys->rc4_state.ptr = 0;
        exp_keys->rc4_state.len = 0;
    }

    exp_keys->seq_num = htole32(keys->seq_num);

    return 0;
}

uint32_t gssntlm_export_sec_context(uint32_t *minor_status,
                                    gss_ctx_id_t *context_handle,
                                    gss_buffer_t interprocess_token)
{
    struct gssntlm_ctx *ctx;
    struct export_state state = { NULL, 0, 0, 0, 0};
    struct export_ctx *ectx;
    uint64_t expiration;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    if (context_handle == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

    if (interprocess_token == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_WRITE);
    }

    ctx = (struct gssntlm_ctx *)*context_handle;
    if (ctx == NULL) return GSSERRS(ERR_BADARG, GSS_S_NO_CONTEXT);

    if (ctx->expiration_time && ctx->expiration_time < time(NULL)) {
        return GSSERRS(ERR_EXPIRED, GSS_S_CONTEXT_EXPIRED);
    }

    /* serialize context */
    state.exp_size = NEW_SIZE(0, sizeof(struct export_ctx));
    state.exp_struct = malloc(state.exp_size);
    if (!state.exp_struct) {
        set_GSSERR(ENOMEM);
        goto done;
    }
    ectx = (struct export_ctx *)state.exp_struct;
    state.exp_data = (char *)ectx->data - (char *)ectx;
    state.exp_len = state.exp_data;
    state.exp_ptr = 0;

    ectx->version = htole16(EXPORT_CTX_VER);

    switch(ctx->role) {
    case GSSNTLM_CLIENT:
        ectx->role = EXP_CTX_CLIENT;
        break;
    case GSSNTLM_SERVER:
        ectx->role = EXP_CTX_SERVER;
        break;
    case GSSNTLM_DOMAIN_SERVER:
        ectx->role = EXP_CTX_DOMSRV;
        break;
    case GSSNTLM_DOMAIN_CONTROLLER:
        ectx->role = EXP_CTX_DOMCTR;
        break;
    }

    switch(ctx->stage) {
    case NTLMSSP_STAGE_INIT:
        ectx->stage = EXP_STG_INIT;
        break;
    case NTLMSSP_STAGE_NEGOTIATE:
        ectx->stage = EXP_STG_NEGO;
        break;
    case NTLMSSP_STAGE_CHALLENGE:
        ectx->stage = EXP_STG_CHAL;
        break;
    case NTLMSSP_STAGE_AUTHENTICATE:
        ectx->stage = EXP_STG_AUTH;
        break;
    case NTLMSSP_STAGE_DONE:
        ectx->stage = EXP_STG_DONE;
        break;
    }

    ectx->sec_req = ctx->sec_req;

    if (!ctx->workstation) {
        ectx->workstation.ptr = 0;
        ectx->workstation.len = 0;
    } else {
        ret = export_data_buffer(&state, ctx->workstation,
                                 strlen(ctx->workstation) + 1,
                                 &ectx->workstation);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
    }

    if (ctx->nego_msg.length > 0) {
        ret = export_data_buffer(&state,
                                 ctx->nego_msg.data,
                                 ctx->nego_msg.length,
                                 &ectx->nego_msg);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
    } else {
        ectx->nego_msg.ptr = 0;
        ectx->nego_msg.len = 0;
    }

    if (ctx->chal_msg.length > 0) {
        ret = export_data_buffer(&state,
                                 ctx->chal_msg.data,
                                 ctx->chal_msg.length,
                                 &ectx->chal_msg);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
    } else {
        ectx->chal_msg.ptr = 0;
        ectx->chal_msg.len = 0;
    }

    if (ctx->auth_msg.length > 0) {
        ret = export_data_buffer(&state,
                                 ctx->auth_msg.data,
                                 ctx->auth_msg.length,
                                 &ectx->auth_msg);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
    } else {
        ectx->auth_msg.ptr = 0;
        ectx->auth_msg.len = 0;
    }

    ret = export_name(&state, &ctx->source_name, &ectx->source);
    if (ret) {
        set_GSSERR(ret);
        goto done;
    }

    ret = export_name(&state, &ctx->target_name, &ectx->target);
    if (ret) {
        set_GSSERR(ret);
        goto done;
    }

    memcpy(ectx->server_chal, ctx->server_chal, 8);

    ectx->gss_flags = htole32(ctx->gss_flags);
    ectx->neg_flags = htole32(ctx->neg_flags);

    ret = export_data_buffer(&state,
                             ctx->exported_session_key.data,
                             ctx->exported_session_key.length,
                             &ectx->exported_session_key);
    if (ret) {
        set_GSSERR(ret);
        goto done;
    }

    ret = export_keys(&state, &ctx->crypto_state.send, &ectx->send);
    if (ret) {
        set_GSSERR(ret);
        goto done;
    }

    ret = export_keys(&state, &ctx->crypto_state.recv, &ectx->recv);
    if (ret) {
        set_GSSERR(ret);
        goto done;
    }

    ectx->int_flags = ctx->int_flags;

    expiration = ctx->expiration_time;
    ectx->expration_time = htole64(expiration);

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        free(state.exp_struct);
    } else {
        uint32_t min;
        interprocess_token->value = state.exp_struct;
        interprocess_token->length = state.exp_len;

        /* Invalidate the current context once successfully exported */
        gssntlm_delete_sec_context(&min, context_handle, NULL);
    }
    return GSSERR();
}

static uint32_t import_data_buffer(uint32_t *minor_status,
                                   struct export_state *state,
                                   uint8_t **dest, size_t *len, bool alloc,
                                   struct relmem *rm, bool str)
{
    uint32_t retmaj;
    uint32_t retmin;
    void *ptr;

    if (rm->ptr + rm->len > state->exp_len) {
        set_GSSERRS(0, GSS_S_DEFECTIVE_TOKEN);
        goto done;
    }
    ptr = state->exp_struct + state->exp_data + rm->ptr;
    if (alloc) {
        if (str) {
            *dest = (uint8_t *)strndup((const char *)ptr, rm->len);
        } else {
            *dest = malloc(rm->len);
            if (*dest) {
                memcpy(*dest, ptr, rm->len);
            }
        }
        if (!*dest) {
            set_GSSERR(ENOMEM);
            goto done;
        }
    } else {
        if (!*len) {
            set_GSSERR(ERR_BADARG);
            goto done;
        }
        if (rm->len > *len) {
            set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
            goto done;
        }
        memcpy(*dest, ptr, rm->len);
    }
    if (len) *len = rm->len;
    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

static uint32_t import_name(uint32_t *minor_status,
                            struct export_state *state,
                            struct export_name *name,
                            struct gssntlm_name *imp_name)
{
    uint32_t retmaj;
    uint32_t retmin;
    uint8_t *dest;

    switch (name->type) {
    case EXP_NAME_NONE:
        memset(imp_name, 0, sizeof(struct gssntlm_name));
        break;

    case EXP_NAME_ANON:
        memset(imp_name, 0, sizeof(struct gssntlm_name));
        imp_name->type = GSSNTLM_NAME_ANON;
        break;

    case EXP_NAME_USER:
        imp_name->type = GSSNTLM_NAME_USER;
        dest = NULL;
        if (name->domain.len > 0) {
            retmaj = import_data_buffer(&retmin, state,
                                     &dest, NULL, true,
                                     &name->domain, true);
            if (retmaj != GSS_S_COMPLETE) {
                set_GSSERRS(retmin, retmaj);
                goto done;
            }
        }
        imp_name->data.user.domain = (char *)dest;
        dest = NULL;
        if (name->name.len > 0) {
            retmaj = import_data_buffer(&retmin, state,
                                     &dest, NULL, true,
                                     &name->name, true);
            if (retmaj != GSS_S_COMPLETE) {
                set_GSSERRS(retmin, retmaj);
                goto done;
            }
        }
        imp_name->data.user.name = (char *)dest;
        break;

    case EXP_NAME_SERV:
        imp_name->type = GSSNTLM_NAME_SERVER;
        dest = NULL;
        if (name->name.len > 0) {
            retmaj = import_data_buffer(&retmin, state,
                                     &dest, NULL, true,
                                     &name->name, true);
            if (retmaj != GSS_S_COMPLETE) {
                set_GSSERRS(retmin, retmaj);
                goto done;
            }
        }
        imp_name->data.server.name = (char *)dest;
        break;

    default:
        set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
        break;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

static uint32_t import_keys(uint32_t *minor_status,
                            struct export_state *state,
                            struct export_keys *keys,
                            struct ntlm_signseal_handle *imp_keys)
{
    struct ntlm_buffer in;
    uint8_t *dest;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    if (keys->sign_key.len > 0) {
        imp_keys->sign_key.length = 16; /* buf max size */
        dest = imp_keys->sign_key.data;
        retmaj = import_data_buffer(&retmin, state,
                                 &dest, &imp_keys->sign_key.length,
                                 false, &keys->sign_key, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        memset(&imp_keys->sign_key, 0, sizeof(struct ntlm_key));
    }

    if (keys->seal_key.len > 0) {
        imp_keys->seal_key.length = 16; /* buf max size */
        dest = imp_keys->seal_key.data;
        retmaj = import_data_buffer(&retmin, state,
                                 &dest, &imp_keys->seal_key.length,
                                 false, &keys->seal_key, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        memset(&imp_keys->seal_key, 0, sizeof(struct ntlm_key));
    }

    if (keys->rc4_state.len > 0) {
        retmaj = import_data_buffer(&retmin, state,
                                 &in.data, &in.length, true,
                                 &keys->rc4_state, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
        ret = RC4_IMPORT(&imp_keys->seal_handle, &in);
        safezero(in.data, in.length);
        safefree(in.data);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
    } else {
        imp_keys->seal_handle = NULL;
    }

    imp_keys->seq_num = le32toh(keys->seq_num);

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

uint32_t gssntlm_import_sec_context(uint32_t *minor_status,
                                    gss_buffer_t interprocess_token,
                                    gss_ctx_id_t *context_handle)
{
    struct gssntlm_ctx *ctx = NULL;
    struct export_state state;
    struct export_ctx *ectx;
    uint8_t *dest;
    uint64_t time;
    uint32_t retmaj;
    uint32_t retmin;

    if (interprocess_token == NULL) {
        return GSSERRS(0, GSS_S_CALL_INACCESSIBLE_READ);
    }

    if (interprocess_token->length < sizeof(struct export_ctx)) {
        return GSSERRS(0, GSS_S_DEFECTIVE_TOKEN);
    }

    if (context_handle == NULL) {
        return GSSERRS(0, GSS_S_CALL_INACCESSIBLE_WRITE);
    }

    ctx = calloc(1, sizeof(struct gssntlm_ctx));
    if (!ctx) {
        set_GSSERR(ENOMEM);
        goto done;
    }
    retmin = ntlm_init_ctx(&ctx->ntlm);
    if (retmin) {
        set_GSSERR(retmin);
        goto done;
    }

    state.exp_struct = interprocess_token->value;
    state.exp_len = interprocess_token->length;
    ectx = (struct export_ctx *)state.exp_struct;
    state.exp_data = (char *)ectx->data - (char *)ectx;
    state.exp_ptr = 0;

    if (ectx->version != le16toh(EXPORT_CTX_VER)) {
        set_GSSERRS(0, GSS_S_DEFECTIVE_TOKEN);
        goto done;
    }

    switch (ectx->role) {
    case EXP_CTX_CLIENT:
        ctx->role = GSSNTLM_CLIENT;
        break;
    case EXP_CTX_SERVER:
        ctx->role = GSSNTLM_SERVER;
        break;
    case EXP_CTX_DOMSRV:
        ctx->role = GSSNTLM_DOMAIN_SERVER;
        break;
    case EXP_CTX_DOMCTR:
        ctx->role = GSSNTLM_DOMAIN_CONTROLLER;
        break;
    default:
        set_GSSERRS(0, GSS_S_DEFECTIVE_TOKEN);
        goto done;
    }

    switch (ectx->stage) {
    case EXP_STG_INIT:
        ctx->stage = NTLMSSP_STAGE_INIT;
        break;
    case EXP_STG_NEGO:
        ctx->stage = NTLMSSP_STAGE_NEGOTIATE;
        break;
    case EXP_STG_CHAL:
        ctx->stage = NTLMSSP_STAGE_CHALLENGE;
        break;
    case EXP_STG_AUTH:
        ctx->stage = NTLMSSP_STAGE_AUTHENTICATE;
        break;
    case EXP_STG_DONE:
        ctx->stage = NTLMSSP_STAGE_DONE;
        break;
    default:
        set_GSSERRS(0, GSS_S_DEFECTIVE_TOKEN);
        goto done;
    }

    ctx->sec_req = ectx->sec_req;

    dest = NULL;
    if (ectx->workstation.len > 0) {
        retmaj = import_data_buffer(&retmin, &state, &dest, NULL,
                                 true, &ectx->workstation, true);
        if (retmaj != GSS_S_COMPLETE) goto done;
    }
    ctx->workstation = (char *)dest;

    if (ectx->nego_msg.len > 0) {
        retmaj = import_data_buffer(&retmin, &state,
                                 &ctx->nego_msg.data, &ctx->nego_msg.length,
                                 true, &ectx->nego_msg, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        ctx->nego_msg.data = NULL;
        ctx->nego_msg.length = 0;
    }

    if (ectx->chal_msg.len > 0) {
        retmaj = import_data_buffer(&retmin, &state,
                                 &ctx->chal_msg.data, &ctx->chal_msg.length,
                                 true, &ectx->chal_msg, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        ctx->chal_msg.data = NULL;
        ctx->chal_msg.length = 0;
    }

    if (ectx->auth_msg.len > 0) {
        retmaj = import_data_buffer(&retmin, &state,
                                 &ctx->auth_msg.data, &ctx->auth_msg.length,
                                 true, &ectx->auth_msg, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        ctx->auth_msg.data = NULL;
        ctx->auth_msg.length = 0;
    }

    retmaj = import_name(&retmin, &state,
                      &ectx->source, &ctx->source_name);
    if (retmaj != GSS_S_COMPLETE) goto done;

    retmaj = import_name(&retmin, &state,
                      &ectx->target, &ctx->target_name);
    if (retmaj != GSS_S_COMPLETE) goto done;

    memcpy(ctx->server_chal, ectx->server_chal, 8);

    ctx->gss_flags = le32toh(ectx->gss_flags);
    ctx->neg_flags = le32toh(ectx->neg_flags);

    if (ectx->exported_session_key.len > 0) {
        ctx->exported_session_key.length = 16; /* buf max size */
        dest = ctx->exported_session_key.data;
        retmaj = import_data_buffer(&retmin, &state, &dest,
                                 &ctx->exported_session_key.length,
                                 false, &ectx->exported_session_key, true);
        if (retmaj != GSS_S_COMPLETE) goto done;
    } else {
        memset(&ctx->exported_session_key, 0, sizeof(struct ntlm_key));
    }

    retmaj = import_keys(&retmin, &state,
                      &ectx->send, &ctx->crypto_state.send);
    if (retmaj != GSS_S_COMPLETE) goto done;

    retmaj = import_keys(&retmin, &state,
                      &ectx->recv, &ctx->crypto_state.recv);
    if (retmaj != GSS_S_COMPLETE) goto done;

    /* We need to restoer also the general crypto status flags */
    ctx->crypto_state.ext_sec =
        (ctx->neg_flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY);
    ctx->crypto_state.datagram =
        (ctx->neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM);

    ctx->int_flags = ectx->int_flags;

    time = le64toh(ectx->expration_time);
    ctx->expiration_time = time;

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj == GSS_S_COMPLETE) {
        *context_handle = (gss_ctx_id_t)ctx;
    } else {
        uint32_t min;
        gssntlm_delete_sec_context(&min, (gss_ctx_id_t *)&ctx, NULL);
    }
    return GSSERR();
}

#pragma pack(push, 1)
struct export_cred {
    uint16_t version;    /* 0x00 0x01 */
    uint16_t type;

    struct export_name name;    /* user or server name */
    struct relmem nt_hash;      /* empty for dummy or server */
    struct relmem lm_hash;      /* empty for dummy or server */

    uint8_t data[];
};
#pragma pack(pop)

#define EXP_CRED_NONE 0
#define EXP_CRED_ANON 1
#define EXP_CRED_USER 2
#define EXP_CRED_SERVER 3
#define EXP_CRED_EXTERNAL 4

uint32_t gssntlm_export_cred(uint32_t *minor_status,
                             gss_cred_id_t cred_handle,
                             gss_buffer_t token)
{
    struct gssntlm_cred *cred;
    struct export_state state = { NULL, 0, 0, 0, 0 };
    struct export_cred *ecred;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    if (token == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_WRITE);
    }

    cred = (struct gssntlm_cred *)cred_handle;
    if (cred_handle == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_NO_CRED);
    }

    state.exp_size = NEW_SIZE(0, sizeof(struct export_cred));
    state.exp_struct = calloc(1, state.exp_size);
    if (!state.exp_struct) {
        set_GSSERR(ENOMEM);
        goto done;
    }
    ecred = (struct export_cred *)state.exp_struct;
    state.exp_data = (char *)ecred->data - (char *)ecred;
    state.exp_len = state.exp_data;
    state.exp_ptr = 0;

    ecred->version = htole16(1);

    switch (cred->type) {
    case GSSNTLM_CRED_NONE:
        ecred->type = EXP_CRED_NONE;
        break;
    case GSSNTLM_CRED_ANON:
        ecred->type = EXP_CRED_ANON;
        break;
    case GSSNTLM_CRED_USER:
        ecred->type = EXP_CRED_USER;

        ret = export_name(&state, &cred->cred.user.user, &ecred->name);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }

        ret = export_data_buffer(&state,
                                 cred->cred.user.nt_hash.data,
                                 cred->cred.user.nt_hash.length,
                                 &ecred->nt_hash);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }

        ret = export_data_buffer(&state,
                                 cred->cred.user.lm_hash.data,
                                 cred->cred.user.lm_hash.length,
                                 &ecred->lm_hash);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
        break;
    case GSSNTLM_CRED_SERVER:
        ecred->type = EXP_CRED_SERVER;

        ret = export_name(&state, &cred->cred.server.name, &ecred->name);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
        break;
    case GSSNTLM_CRED_EXTERNAL:
        ecred->type = EXP_CRED_EXTERNAL;

        ret = export_name(&state, &cred->cred.external.user, &ecred->name);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
        break;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        free(state.exp_struct);
    } else {
        token->value = state.exp_struct;
        token->length = state.exp_len;
    }
    return GSSERR();
}

uint32_t gssntlm_import_cred(uint32_t *minor_status,
                             gss_buffer_t token,
                             gss_cred_id_t *cred_handle)
{
    struct gssntlm_cred *cred;
    struct export_state state = { NULL, 0, 0, 0, 0 };
    struct export_cred *ecred;
    uint32_t retmaj;
    uint32_t retmin;

    if (token == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

    if (token->length < sizeof(struct export_cred)) {
        return GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
    }

    if (cred_handle == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_WRITE);
    }

    cred = calloc(1, sizeof(struct gssntlm_cred));
    if (!cred) {
        set_GSSERR(ENOMEM);
        goto done;
    }

    state.exp_struct = token->value;
    state.exp_len = token->length;
    ecred = (struct export_cred *)state.exp_struct;
    state.exp_data = (char *)ecred->data - (char *)ecred;
    state.exp_ptr = 0;

    if (ecred->version != le16toh(1)) {
        set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
        goto done;
    }

    switch (ecred->type) {
    case EXP_CRED_NONE:
        cred->type = GSSNTLM_CRED_NONE;
        break;
    case EXP_CRED_ANON:
        cred->type = GSSNTLM_CRED_ANON;
        break;
    case EXP_CRED_USER:
        cred->type = GSSNTLM_CRED_USER;
        retmaj = import_name(&retmin, &state, &ecred->name,
                          &cred->cred.user.user);
        if (retmaj != GSS_S_COMPLETE) goto done;

        if (ecred->nt_hash.len > 16 || ecred->lm_hash.len > 16) {
            set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
            goto done;
        }

        retmaj = import_data_buffer(&retmin, &state,
                                 (uint8_t **)&cred->cred.user.nt_hash.data,
                                 &cred->cred.user.nt_hash.length,
                                 false, &ecred->nt_hash, false);
        if (retmaj != GSS_S_COMPLETE) goto done;

        retmaj = import_data_buffer(&retmin, &state,
                                 (uint8_t **)&cred->cred.user.lm_hash.data,
                                 &cred->cred.user.lm_hash.length,
                                 false, &ecred->lm_hash, false);
        if (retmaj != GSS_S_COMPLETE) goto done;
        break;
    case EXP_CRED_SERVER:
        cred->type = GSSNTLM_CRED_SERVER;
        retmaj = import_name(&retmin, &state, &ecred->name,
                          &cred->cred.server.name);
        if (retmaj != GSS_S_COMPLETE) goto done;
        break;
    case EXP_CRED_EXTERNAL:
        cred->type = GSSNTLM_CRED_EXTERNAL;
        retmaj = import_name(&retmin, &state, &ecred->name,
                          &cred->cred.external.user);
        if (retmaj != GSS_S_COMPLETE) goto done;
        break;
    default:
        set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN);
        break;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj == GSS_S_COMPLETE) {
        *cred_handle = (gss_cred_id_t)cred;
    } else {
        uint32_t min;
        gssntlm_release_cred(&min, (gss_cred_id_t *)&cred);
    }
    return GSSERR();
}