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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <gssapi/gssapi.h>
#include <gssapi/gssapi_ext.h>

#include "gss_ntlmssp.h"

static int get_user_file_creds(struct gssntlm_name *name,
                               struct gssntlm_cred *cred)
{
    const char *envvar;
    char line[1024];
    char *dom, *usr, *pwd;
    char *p;
    bool found = false;
    FILE *f;
    int ret;

    /* use the same var used by Heimdal */
    envvar = getenv("NTLM_USER_FILE");
    if (envvar == NULL) return ENOENT;

    /* Use the same file format used by Heimdal in hope to achieve
     * some compatibility between implementations:
     * Each line is one entry like the following:
     * DOMAIN:USERNAME:PASSWORD */
    f = fopen(envvar, "r");
    if (!f) return errno;

    while(fgets(line, 1024, f)) {
        p = line;
        if (*p == '#') continue;
        dom = p;
        p = strchr(dom, ':');
        if (!p) continue;
        *p++ = '\0';
        usr = p;
        p = strchr(usr, ':');
        if (!p) continue;
        *p++ = '\0';
        pwd = p;
        strsep(&p, "\r\n");

        /* if no name is specified use the first found */
        if (name == NULL) {
            found = true;
            break;
        }

        if (name->data.user.domain) {
            if (!ntlm_casecmp(dom, name->data.user.domain)) continue;
        }
        if (name->data.user.name) {
            if (!ntlm_casecmp(usr, name->data.user.name)) continue;
        }
        /* all matched (NULLs in name are wildcards) */
        found = true;
        break;
    }

    fclose(f);

    if (!found) {
        return ENOENT;
    }

    cred->type = GSSNTLM_CRED_USER;
    cred->cred.user.user.type = GSSNTLM_NAME_USER;
    cred->cred.user.user.data.user.domain = strdup(dom);
    if (!cred->cred.user.user.data.user.domain) return ENOMEM;
    cred->cred.user.user.data.user.name = strdup(usr);
    if (!cred->cred.user.user.data.user.name) return ENOMEM;
    cred->cred.user.nt_hash.length = 16;

    ret = NTOWFv1(pwd, &cred->cred.user.nt_hash);
    if (ret) return ret;

    if (gssntlm_get_lm_compatibility_level() < 3) {
        cred->cred.user.lm_hash.length = 16;
        ret = LMOWFv1(pwd, &cred->cred.user.lm_hash);
        if (ret) return ret;
    }

    return 0;
}

static int get_server_creds(struct gssntlm_name *name,
                            struct gssntlm_cred *cred)
{
    gss_name_t gssname = NULL;
    gss_buffer_desc tmpbuf;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    if (name == NULL) {
        tmpbuf.value = discard_const("");
        tmpbuf.length = 0;
        ret = 0;
        retmaj = gssntlm_import_name_by_mech(&retmin,
                                             &gssntlm_oid,
                                             &tmpbuf,
                                             GSS_C_NT_HOSTBASED_SERVICE,
                                             &gssname);
        if (retmaj) return retmin;

        name = (struct gssntlm_name *)gssname;
    }

    cred->type = GSSNTLM_CRED_SERVER;
    ret = gssntlm_copy_name(name, &cred->cred.server.name);
    gssntlm_int_release_name((struct gssntlm_name *)gssname);

    return ret;
}

static int hex_to_key(const char *hex, struct ntlm_key *key)
{
    const char *p;
    uint32_t i, j;
    uint8_t t;
    size_t len;

    len = strlen(hex);
    if (len != 32) return EINVAL;

    for (i = 0; i < 16; i++) {
        for (j = 0; j < 2; j++) {
            p = &hex[j + (i * 2)];
            if (*p >= '0' && *p <= '9') {
                t = (*p - '0');
            } else if (*p >= 'A' && *p <= 'F') {
                t = (*p - 'A' + 10);
            } else {
                return EINVAL;
            }
            if (j == 0) t = t << 4;
            key->data[i] = t;
        }
    }
    key->length = 16;
    return 0;
}

#define GENERIC_CS_PASSWORD "password"
/* To support in future, RC4 Key is NT hash */
#define KRB5_CS_CLI_KEYTAB_URN "client_keytab"
#define KRB5_CS_KEYTAB_URN "keytab"

static int get_creds_from_store(struct gssntlm_name *name,
                                struct gssntlm_cred *cred,
                                gss_const_key_value_set_t cred_store)
{
    uint32_t i;
    int ret;

    cred->type = GSSNTLM_CRED_NONE;

    if (name) {
        switch (name->type) {
        case GSSNTLM_NAME_NULL:
            cred->type = GSSNTLM_CRED_NONE;
            break;
        case GSSNTLM_NAME_ANON:
            cred->type = GSSNTLM_CRED_ANON;
            break;
        case GSSNTLM_NAME_USER:
            cred->type = GSSNTLM_CRED_USER;
            ret = gssntlm_copy_name(name, &cred->cred.user.user);
            break;
        case GSSNTLM_NAME_SERVER:
            cred->type = GSSNTLM_CRED_SERVER;
            ret = gssntlm_copy_name(name, &cred->cred.server.name);
            break;
        default:
            return EINVAL;
        }
    }

    /* so far only user options can be defined in the cred_store */
    if (cred->type != GSSNTLM_CRED_USER) return ENOENT;

    for (i = 0; i < cred_store->count; i++) {
        if (strcmp(cred_store->elements[i].key, GSS_NTLMSSP_CS_DOMAIN) == 0) {
            /* ignore duplicates */
            if (cred->cred.user.user.data.user.domain) continue;
            cred->cred.user.user.data.user.domain =
                                    strdup(cred_store->elements[i].value);
            if (!cred->cred.user.user.data.user.domain) return ENOMEM;
        }
        if (strcmp(cred_store->elements[i].key, GSS_NTLMSSP_CS_NTHASH) == 0) {
            /* ignore duplicates */
            if (cred->cred.user.nt_hash.length) continue;
            ret = hex_to_key(cred_store->elements[i].value,
                             &cred->cred.user.nt_hash);
            if (ret) return ret;
        }
        if ((strcmp(cred_store->elements[i].key, GSS_NTLMSSP_CS_PASSWORD) == 0) ||
            (strcmp(cred_store->elements[i].key, GENERIC_CS_PASSWORD) == 0)) {
            if (cred->cred.user.nt_hash.length) continue;
            cred->cred.user.nt_hash.length = 16;
            ret = NTOWFv1(cred_store->elements[i].value,
                          &cred->cred.user.nt_hash);

            if (gssntlm_get_lm_compatibility_level() < 3) {
                cred->cred.user.lm_hash.length = 16;
                ret = LMOWFv1(cred_store->elements[i].value,
                              &cred->cred.user.lm_hash);
                if (ret) return ret;
            }

            if (ret) return ret;
        }
    }

    /* TODO: should we call get_user_file_creds/get_server_creds if values are
     * not found ?
     */

    return 0;
}

static void gssntlm_copy_key(struct ntlm_key *dest, struct ntlm_key *src)
{
    memcpy(dest->data, src->data, src->length);
    dest->length = src->length;
}

int gssntlm_copy_creds(struct gssntlm_cred *in, struct gssntlm_cred *out)
{
    char *dom = NULL, *usr = NULL, *srv = NULL;
    int ret = 0;

    out->type = GSSNTLM_CRED_NONE;

    switch (in->type) {
    case GSSNTLM_CRED_NONE:
        break;
    case GSSNTLM_CRED_ANON:
        out->cred.anon.dummy = 1;
        break;
    case GSSNTLM_CRED_USER:
        ret = gssntlm_copy_name(&in->cred.user.user,
                                &out->cred.user.user);
        if (ret) goto done;
        gssntlm_copy_key(&out->cred.user.nt_hash,
                         &in->cred.user.nt_hash);
        gssntlm_copy_key(&out->cred.user.lm_hash,
                         &in->cred.user.lm_hash);
        break;
    case GSSNTLM_CRED_SERVER:
        ret = gssntlm_copy_name(&in->cred.server.name,
                                &out->cred.server.name);
        if (ret) goto done;
        break;
    case GSSNTLM_CRED_EXTERNAL:
        ret = gssntlm_copy_name(&in->cred.external.user,
                                &out->cred.external.user);
        if (ret) goto done;
        break;
    }
    out->type = in->type;

done:
    if (ret) {
        safefree(dom);
        safefree(usr);
        safefree(srv);
    }
    return ret;
}

void gssntlm_int_release_cred(struct gssntlm_cred *cred)
{
    if (!cred) return;

    switch (cred->type) {
    case GSSNTLM_CRED_NONE:
        break;
    case GSSNTLM_CRED_ANON:
        cred->cred.anon.dummy = 0;
        break;
    case GSSNTLM_CRED_USER:
        gssntlm_int_release_name(&cred->cred.user.user);
        safezero(cred->cred.user.nt_hash.data, 16);
        cred->cred.user.nt_hash.length = 0;
        safezero(cred->cred.user.lm_hash.data, 16);
        cred->cred.user.lm_hash.length = 0;
        break;
    case GSSNTLM_CRED_SERVER:
        gssntlm_int_release_name(&cred->cred.server.name);
        break;
    case GSSNTLM_CRED_EXTERNAL:
        gssntlm_int_release_name(&cred->cred.external.user);
        break;
    }
}

uint32_t gssntlm_acquire_cred_from(uint32_t *minor_status,
                                   gss_name_t desired_name,
                                   uint32_t time_req,
                                   gss_OID_set desired_mechs,
                                   gss_cred_usage_t cred_usage,
                                   gss_const_key_value_set_t cred_store,
                                   gss_cred_id_t *output_cred_handle,
                                   gss_OID_set *actual_mechs,
                                   uint32_t *time_rec)
{
    struct gssntlm_cred *cred;
    struct gssntlm_name *name;
    uint32_t retmaj;
    uint32_t retmin;

    name = (struct gssntlm_name *)desired_name;

    cred = calloc(1, sizeof(struct gssntlm_cred));
    if (!cred) {
        return GSSERRS(errno, GSS_S_FAILURE);
    }

    /* FIXME: should we split the cred union and allow GSS_C_BOTH ?
     * It may be possible to specify get server name from env and/or
     * user creds from cred store at the same time, etc .. */
    if (cred_usage == GSS_C_BOTH) {
        if (name == NULL) {
            cred_usage = GSS_C_ACCEPT;
        } else {
            switch (name->type) {
            case GSSNTLM_NAME_SERVER:
                cred_usage = GSS_C_ACCEPT;
                break;
            case GSSNTLM_NAME_USER:
            case GSSNTLM_NAME_ANON:
                cred_usage = GSS_C_INITIATE;
                break;
            default:
                set_GSSERRS(ERR_BADCRED, GSS_S_CRED_UNAVAIL);
                goto done;
            }
        }
    }

    if (cred_usage == GSS_C_INITIATE) {
        if (name != NULL && name->type != GSSNTLM_NAME_USER) {
            set_GSSERRS(ERR_NOUSRNAME, GSS_S_BAD_NAMETYPE);
            goto done;
        }

        if (cred_store != GSS_C_NO_CRED_STORE) {
            retmin = get_creds_from_store(name, cred, cred_store);
        } else {
            retmin = get_user_file_creds(name, cred);
            if (retmin) {
                retmin = external_get_creds(name, cred);
            }
        }
        if (retmin) {
            set_GSSERR(retmin);
            goto done;
        }
    } else if (cred_usage == GSS_C_ACCEPT) {
        if (name != NULL && name->type != GSSNTLM_NAME_SERVER) {
            set_GSSERRS(ERR_NOSRVNAME, GSS_S_BAD_NAMETYPE);
            goto done;
        }

        retmin = get_server_creds(name, cred);
        if (retmin) {
            set_GSSERR(retmin);
            goto done;
        }
    } else if (cred_usage == GSS_C_BOTH) {
        set_GSSERRS(ERR_NOTSUPPORTED, GSS_S_CRED_UNAVAIL);
        goto done;
    } else {
        set_GSSERRS(ERR_BADARG, GSS_S_CRED_UNAVAIL);
        goto done;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        uint32_t tmpmin;
        gssntlm_release_cred(&tmpmin, (gss_cred_id_t *)&cred);
    } else {
        *output_cred_handle = (gss_cred_id_t)cred;
        if (time_rec) *time_rec = GSS_C_INDEFINITE;
    }

    return GSSERR();
}

uint32_t gssntlm_acquire_cred(uint32_t *minor_status,
                              gss_name_t desired_name,
                              uint32_t time_req,
                              gss_OID_set desired_mechs,
                              gss_cred_usage_t cred_usage,
                              gss_cred_id_t *output_cred_handle,
                              gss_OID_set *actual_mechs,
                              uint32_t *time_rec)
{
    return gssntlm_acquire_cred_from(minor_status,
                                     desired_name,
                                     time_req,
                                     desired_mechs,
                                     cred_usage,
                                     GSS_C_NO_CRED_STORE,
                                     output_cred_handle,
                                     actual_mechs,
                                     time_rec);
}

uint32_t gssntlm_release_cred(uint32_t *minor_status,
                              gss_cred_id_t *cred_handle)
{
    *minor_status = 0;

    if (!cred_handle) return GSS_S_COMPLETE;

    gssntlm_int_release_cred((struct gssntlm_cred *)*cred_handle);
    safefree(*cred_handle);

    return GSS_S_COMPLETE;
}

uint32_t gssntlm_acquire_cred_with_password(uint32_t *minor_status,
                                            gss_name_t desired_name,
                                            gss_buffer_t password,
                                            uint32_t time_req,
                                            gss_OID_set desired_mechs,
                                            gss_cred_usage_t cred_usage,
                                            gss_cred_id_t *output_cred_handle,
                                            gss_OID_set *actual_mechs,
                                            uint32_t *time_rec)
{
    gss_key_value_element_desc element;
    gss_key_value_set_desc cred_store;

    element.key = GENERIC_CS_PASSWORD;
    element.value = (const char *)password->value;

    cred_store.count = 1;
    cred_store.elements = &element;

    return gssntlm_acquire_cred_from(minor_status,
                                     desired_name,
                                     time_req,
                                     desired_mechs,
                                     cred_usage,
                                     &cred_store,
                                     output_cred_handle,
                                     actual_mechs,
                                     time_rec);
}

uint32_t gssntlm_inquire_cred(uint32_t *minor_status,
                              gss_cred_id_t cred_handle,
                              gss_name_t *name,
                              uint32_t *lifetime,
                              gss_cred_usage_t *cred_usage,
                              gss_OID_set *mechanisms)
{
    struct gssntlm_cred *cred = (struct gssntlm_cred *)GSS_C_NO_CREDENTIAL;
    uint32_t retmin, retmaj;
    uint32_t maj, min;

    if (cred_handle == GSS_C_NO_CREDENTIAL) {
        maj = gssntlm_acquire_cred_from(&min,
                                        NULL, GSS_C_INDEFINITE,
                                        NULL, GSS_C_INITIATE,
                                        GSS_C_NO_CRED_STORE,
                                        (gss_cred_id_t *)&cred,
                                        NULL, NULL);
        if (maj) {
            set_GSSERRS(0, GSS_S_NO_CRED);
            goto done;
        }
    } else {
        cred = (struct gssntlm_cred *)cred_handle;
    }

    if (cred->type == GSSNTLM_CRED_NONE) {
        set_GSSERRS(ERR_BADARG, GSS_S_NO_CRED);
        goto done;
    }

    if (name) {
        switch (cred->type) {
        case GSSNTLM_CRED_NONE:
        case GSSNTLM_CRED_ANON:
            *name = GSS_C_NO_NAME;
            break;
        case GSSNTLM_CRED_USER:
            maj = gssntlm_duplicate_name(&min,
                                         (gss_name_t)&cred->cred.user.user,
                                         name);
            if (maj != GSS_S_COMPLETE) {
                set_GSSERRS(min, maj);
                goto done;
            }
            break;
        case GSSNTLM_CRED_SERVER:
            maj = gssntlm_duplicate_name(&min,
                                         (gss_name_t)&cred->cred.server.name,
                                         name);
            if (maj != GSS_S_COMPLETE) {
                set_GSSERRS(min, maj);
                goto done;
            }
            break;
        case GSSNTLM_CRED_EXTERNAL:
            maj = gssntlm_duplicate_name(&min,
                                        (gss_name_t)&cred->cred.external.user,
                                        name);
            if (maj != GSS_S_COMPLETE) {
                set_GSSERRS(min, maj);
                goto done;
            }
            break;
        }
    }

    if (lifetime) *lifetime = GSS_C_INDEFINITE;
    if (cred_usage) {
        if (cred->type == GSSNTLM_CRED_SERVER) {
            *cred_usage = GSS_C_ACCEPT;
        } else {
            *cred_usage = GSS_C_INITIATE;
        }
    }

    if (mechanisms) {
        maj = gss_create_empty_oid_set(&min, mechanisms);
        if (maj != GSS_S_COMPLETE) {
            set_GSSERRS(min, maj);
            gss_release_name(&min, name);
            goto done;
        }
        maj = gss_add_oid_set_member(&min,
                                     discard_const(&gssntlm_oid),
                                     mechanisms);
        if (maj != GSS_S_COMPLETE) {
            set_GSSERRS(min, maj);
            gss_release_oid_set(&min, mechanisms);
            gss_release_name(&min, name);
            goto done;
        }
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (cred_handle == GSS_C_NO_CREDENTIAL) {
        gssntlm_release_cred(&min, (gss_cred_id_t *)&cred);
    }
    return GSSERR();
}

uint32_t gssntlm_inquire_cred_by_mech(uint32_t *minor_status,
                                      gss_cred_id_t cred_handle,
                                      gss_OID mech_type,
                                      gss_name_t *name,
                                      uint32_t *initiator_lifetime,
                                      uint32_t *acceptor_lifetime,
                                      gss_cred_usage_t *cred_usage)
{
    gss_cred_usage_t usage;
    uint32_t lifetime;
    uint32_t retmaj;
    uint32_t retmin;
    uint32_t maj, min;

    maj = gssntlm_inquire_cred(&min, cred_handle, name,
                               &lifetime, &usage, NULL);
    if (maj != GSS_S_COMPLETE) return GSSERRS(min, maj);

    switch (usage) {
    case GSS_C_INITIATE:
        if (initiator_lifetime) *initiator_lifetime = lifetime;
        if (acceptor_lifetime) *acceptor_lifetime = 0;
        break;
    case GSS_C_ACCEPT:
        if (initiator_lifetime) *initiator_lifetime = 0;
        if (acceptor_lifetime) *acceptor_lifetime = lifetime;
        break;
    case GSS_C_BOTH:
        if (initiator_lifetime) *initiator_lifetime = lifetime;
        if (acceptor_lifetime) *acceptor_lifetime = lifetime;
        break;
    default:
        return GSSERRS(ERR_BADARG, GSS_S_FAILURE);
    }

    if (cred_usage) *cred_usage = usage;
    return GSSERRS(0, GSS_S_COMPLETE);
}