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/>.
*/

#define _GNU_SOURCE

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

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

#include "gss_ntlmssp.h"

static uint32_t string_split(uint32_t *minor_status, char sep,
                             const char *str, size_t len,
                             char **s1, char **s2)
{
    uint32_t retmaj;
    uint32_t retmin;
    char *r1 = NULL;
    char *r2 = NULL;
    const char *p;
    size_t l;

    p = memchr(str, sep, len);
    if (!p) return GSSERRS(0, GSS_S_UNAVAILABLE);

    if (s1) {
        l = p - str;
        r1 = strndup(str, l);
        if (!r1) {
            set_GSSERR(ENOMEM);
            goto done;
        }
    }
    if (s2) {
        p++;
        l = len - (p - str);
        r2 = strndup(p, l);
        if (!r2) {
            set_GSSERR(ENOMEM);
            goto done;
        }
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        free(r1);
        free(r2);
    } else {
        if (s1) *s1 = r1;
        if (s2) *s2 = r2;
    }
    return GSSERR();
}

#define MAX_NAME_LEN 1024
static uint32_t get_enterprise_name(uint32_t *minor_status,
                                    const char *str, size_t len,
                                    char **username)
{
    uint32_t retmaj;
    uint32_t retmin;
    char *buf;
    char *e;

    if (len > MAX_NAME_LEN) {
        return GSSERRS(ERR_NAMETOOLONG, GSS_S_BAD_NAME);
    }
    buf = alloca(len + 1);

    memcpy(buf, str, len);
    buf[len] = '\0';

    e = strstr(buf, "\\@");
    if (e) {
        /* remove escape */
        memmove(e, e + 1, len - (e - buf));
    } else {
        /* check if domain part contains dot */
        e = strchr(buf, '@');
        if (e) {
            e = strchr(e, '.');
        }
    }
    if (!e) return GSSERRS(0, GSS_S_UNAVAILABLE);

    *username = strdup(buf);
    if (NULL == *username) {
        set_GSSERR(ENOMEM);
        goto done;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

static uint32_t uid_to_name(uint32_t *minor_status, uid_t uid, char **name)
{
    uint32_t retmaj;
    uint32_t retmin;
    struct passwd *pw;

    pw = getpwuid(uid);
    if (pw) {
        return GSSERRS(ERR_NOUSRFOUND, GSS_S_FAILURE);
    }
    *name = strdup(pw->pw_name);
    if (!*name) {
        set_GSSERR(ENOMEM);
        goto done;
    }
    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

uint32_t gssntlm_import_name_by_mech(uint32_t *minor_status,
                                     gss_const_OID mech_type,
                                     gss_buffer_t input_name_buffer,
                                     gss_OID input_name_type,
                                     gss_name_t *output_name)
{
    char hostname[HOST_NAME_MAX + 1] = { 0 };
    char struid[12] = { 0 };
    uid_t uid;
    struct gssntlm_name *name = NULL;
    uint32_t retmaj;
    uint32_t retmin;

    /* TODO: check mech_type == gssntlm_oid */
    if (mech_type == GSS_C_NO_OID) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

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

    /* treat null OID like NT_USER_NAME */
    if (input_name_type == GSS_C_NULL_OID) {
        input_name_type = GSS_C_NT_USER_NAME;
    }

    if (gss_oid_equal(input_name_type, GSS_C_NT_HOSTBASED_SERVICE) ||
        gss_oid_equal(input_name_type, GSS_C_NT_HOSTBASED_SERVICE_X)) {

        name->type = GSSNTLM_NAME_SERVER;

        retmaj = string_split(&retmin, '@',
                              input_name_buffer->value,
                              input_name_buffer->length,
                              NULL, &name->data.server.name);
        if ((retmaj == GSS_S_COMPLETE) ||
            (retmaj != GSS_S_UNAVAILABLE)) {
            goto done;
        }

        /* no seprator, assume only service is provided and try to source
         * the local host name */
        retmin = gethostname(hostname, HOST_NAME_MAX);
        if (retmin) {
            set_GSSERR(retmin);
            goto done;
        }
        hostname[HOST_NAME_MAX] = '\0';
        name->data.server.name = strdup(hostname);
        if (!name->data.server.name) {
            set_GSSERR(ENOMEM);
        }
        set_GSSERRS(0, GSS_S_COMPLETE);

    } else if (gss_oid_equal(input_name_type, GSS_C_NT_USER_NAME)) {

        name->type = GSSNTLM_NAME_USER;
        name->data.user.domain = NULL;

        /* Check if enterprise name first */
        retmaj = get_enterprise_name(&retmin,
                                     input_name_buffer->value,
                                     input_name_buffer->length,
                                     &name->data.user.name);
        if ((retmaj == GSS_S_COMPLETE) ||
            (retmaj != GSS_S_UNAVAILABLE)) {
            goto done;
        }
        /* Check if in classic DOMAIN\User windows format */
        retmaj = string_split(&retmin, '\\',
                              input_name_buffer->value,
                              input_name_buffer->length,
                              &name->data.user.domain,
                              &name->data.user.name);
        if ((retmaj == GSS_S_COMPLETE) ||
            (retmaj != GSS_S_UNAVAILABLE)) {
            goto done;
        }
        /* else accept a user@domain format too */
        retmaj = string_split(&retmin, '@',
                              input_name_buffer->value,
                              input_name_buffer->length,
                              &name->data.user.name,
                              &name->data.user.domain);
        if ((retmaj == GSS_S_COMPLETE) ||
            (retmaj != GSS_S_UNAVAILABLE)) {
            goto done;
        }
        /* finally, take string as simple user name */
        name->data.user.name = strndup(input_name_buffer->value,
                                       input_name_buffer->length);
        if (!name->data.user.name) {
            set_GSSERR(ENOMEM);
        }
        retmaj = GSS_S_COMPLETE;
    } else if (gss_oid_equal(input_name_type, GSS_C_NT_MACHINE_UID_NAME)) {

        name->type = GSSNTLM_NAME_USER;
        name->data.user.domain = NULL;

        uid = *(uid_t *)input_name_buffer->value;
        retmaj = uid_to_name(&retmin, uid, &name->data.user.name);
    } else if (gss_oid_equal(input_name_type, GSS_C_NT_STRING_UID_NAME)) {

        name->type = GSSNTLM_NAME_USER;
        name->data.user.domain = NULL;

        if (input_name_buffer->length > 12) {
            set_GSSERR(ERR_BADARG);
            goto done;
        }
        memcpy(struid, input_name_buffer->value, input_name_buffer->length);
        struid[11] = '\0';
        errno = 0;
        uid = strtol(struid, NULL, 10);
        if (errno) {
            set_GSSERR(ERR_BADARG);
            goto done;
        }
        retmaj = uid_to_name(&retmin, uid, &name->data.user.name);
    } else if (gss_oid_equal(input_name_type, GSS_C_NT_ANONYMOUS)) {
        name->type = GSSNTLM_NAME_ANON;
        set_GSSERRS(0, GSS_S_COMPLETE);
    } else if (gss_oid_equal(input_name_type, GSS_C_NT_EXPORT_NAME)) {
        /* TODO */
        set_GSSERRS(ERR_NOTSUPPORTED, GSS_S_BAD_NAMETYPE);
    } else {
        set_GSSERRS(ERR_BADARG, GSS_S_BAD_NAMETYPE);
    }

done:
    if (retmaj != GSS_S_COMPLETE) {
        uint32_t tmpmin;
        gssntlm_release_name(&tmpmin, (gss_name_t *)&name);
    } else {
        *output_name = (gss_name_t)name;
    }
    return GSSERR();
}

uint32_t gssntlm_import_name(uint32_t *minor_status,
                             gss_buffer_t input_name_buffer,
                             gss_OID input_name_type,
                             gss_name_t *output_name)
{
    return gssntlm_import_name_by_mech(minor_status,
                                       discard_const(&gssntlm_oid),
                                       input_name_buffer,
                                       input_name_type,
                                       output_name);
}

int gssntlm_copy_name(struct gssntlm_name *src, struct gssntlm_name *dst)
{
    char *dom = NULL, *usr = NULL, *srv = NULL;
    int ret;
    dst->type = src->type;
    switch (src->type) {
    case GSSNTLM_NAME_NULL:
    case GSSNTLM_NAME_ANON:
        break;
    case GSSNTLM_NAME_USER:
        if (src->data.user.domain) {
            dom = strdup(src->data.user.domain);
            if (!dom) {
                ret = ENOMEM;
                goto done;
            }
        }
        if (src->data.user.name) {
            usr = strdup(src->data.user.name);
            if (!usr) {
                ret = ENOMEM;
                goto done;
            }
        }
        dst->data.user.domain = dom;
        dst->data.user.name = usr;
        break;
    case GSSNTLM_NAME_SERVER:
        if (src->data.server.name) {
            srv = strdup(src->data.server.name);
            if (!srv) {
                ret = ENOMEM;
                goto done;
            }
        }
        dst->data.server.name = srv;
        break;
    }

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

uint32_t gssntlm_duplicate_name(uint32_t *minor_status,
                                const gss_name_t input_name,
                                gss_name_t *dest_name)
{
    struct gssntlm_name *in;
    struct gssntlm_name *out;
    uint32_t retmin;
    uint32_t retmaj;

    if (input_name == GSS_C_NO_NAME || dest_name == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

    in = (struct gssntlm_name *)input_name;

    if (in->type == GSSNTLM_NAME_NULL) {
        *dest_name = GSS_C_NO_NAME;
        return GSSERRS(0, GSS_S_COMPLETE);
    }

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

    retmin = gssntlm_copy_name(in, out);
    if (retmin) {
        set_GSSERR(retmin);
        goto done;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        safefree(out);
    }
    *dest_name = (gss_name_t)out;
    return GSSERR();
}

void gssntlm_int_release_name(struct gssntlm_name *name)
{
    if (!name) return;

    switch (name->type) {
    case GSSNTLM_NAME_NULL:
        return;
    case GSSNTLM_NAME_ANON:
        break;
    case GSSNTLM_NAME_USER:
        safefree(name->data.user.domain);
        safefree(name->data.user.name);
        break;
    case GSSNTLM_NAME_SERVER:
        safefree(name->data.server.name);
        break;
    }
    name->type = GSSNTLM_NAME_NULL;
}

uint32_t gssntlm_release_name(uint32_t *minor_status,
                              gss_name_t *input_name)
{
    uint32_t retmaj;
    uint32_t retmin;

    if (!input_name) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

    gssntlm_int_release_name((struct gssntlm_name *)*input_name);

    safefree(*input_name);
    return GSSERRS(0, GSS_S_COMPLETE);
}

uint32_t gssntlm_display_name(uint32_t *minor_status,
                              gss_name_t input_name,
                              gss_buffer_t output_name_buffer,
                              gss_OID *output_name_type)
{
    struct gssntlm_name *in;
    gss_buffer_t out;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    if (input_name == GSS_C_NO_NAME || output_name_buffer == NULL) {
        return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ);
    }

    in = (struct gssntlm_name *)input_name;
    out = output_name_buffer;

    switch (in->type) {
    case GSSNTLM_NAME_NULL:
        return GSSERRS(ERR_BADARG, GSS_S_BAD_NAME);
    case GSSNTLM_NAME_ANON:
        out->value = strdup("NT AUTHORITY\\ANONYMOUS LOGON");
        if (!out->value) {
            set_GSSERR(ENOMEM);
            goto done;
        }
        out->length = strlen(out->value) + 1;
        if (output_name_type) {
            *output_name_type = GSS_C_NT_ANONYMOUS;
        }
        break;
    case GSSNTLM_NAME_USER:
        if (in->data.user.domain) {
            ret = asprintf((char **)&out->value, "%s\\%s",
                           in->data.user.domain, in->data.user.name);
            if (ret == -1) {
                out->value = NULL;
            }
        } else {
            out->value = strdup(in->data.user.name);
        }
        if (!out->value) {
            set_GSSERR(ENOMEM);
            goto done;
        }
        out->length = strlen(out->value) + 1;
        if (output_name_type) {
            *output_name_type = GSS_C_NT_USER_NAME;
        }
        break;
    case GSSNTLM_NAME_SERVER:
        out->value = strdup(in->data.server.name);
        if (!out->value) {
            set_GSSERR(ENOMEM);
            goto done;
        }
        out->length = strlen(out->value) + 1;
        if (output_name_type) {
            *output_name_type = GSS_C_NT_HOSTBASED_SERVICE;
        }
        break;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    return GSSERR();
}

#define PWBUFLEN 1024

uint32_t gssntlm_localname(uint32_t *minor_status,
	                   const gss_name_t name,
	                   gss_const_OID mech_type,
	                   gss_buffer_t localname)
{
    struct gssntlm_name *in;
    char *uname = NULL;
    char pwbuf[PWBUFLEN];
    struct passwd pw, *res;
    uint32_t retmaj;
    uint32_t retmin;
    int ret;

    in = (struct gssntlm_name *)name;
    if (in->type != GSSNTLM_NAME_USER) {
        set_GSSERRS(ERR_BADARG, GSS_S_BAD_NAME);
        goto done;
    }

    /* TODO: hook up with winbindd/sssd for name resolution ? */

    if (in->data.user.domain) {
        ret = asprintf(&uname, "%s\\%s",
                       in->data.user.domain, in->data.user.name);
        if (ret == -1) {
            set_GSSERR(ENOMEM);
            goto done;
        }
        ret = getpwnam_r(uname, &pw, pwbuf, PWBUFLEN, &res);
        if (ret) {
            set_GSSERR(ret);
            goto done;
        }
        safefree(uname);
        if (res) {
            uname = strdup(res->pw_name);
        }
    }
    if (uname == NULL) {
        ret = getpwnam_r(in->data.user.name, &pw, pwbuf, PWBUFLEN, &res);
        if (ret != 0 || res == NULL) {
            set_GSSERR(ret);
            goto done;
        }
        uname = strdup(res->pw_name);
    }
    if (!uname) {
        set_GSSERR(ENOMEM);
        goto done;
    }

    set_GSSERRS(0, GSS_S_COMPLETE);

done:
    if (retmaj) {
        safefree(uname);
    } else {
        localname->value = uname;
        localname->length = strlen(uname) + 1;
    }
    return GSSERR();
}

uint32_t netbios_get_names(char *computer_name,
                           char **netbios_host, char **netbios_domain)
{
    char *nb_computer_name = NULL;
    char *nb_domain_name = NULL;
    char *env_name;
    uint32_t ret;

    env_name = getenv("NETBIOS_COMPUTER_NAME");
    if (env_name) {
        nb_computer_name = strdup(env_name);
        if (!nb_computer_name) {
            ret = ENOMEM;
            goto done;
        }
    }

    env_name = getenv("NETBIOS_DOMAIN_NAME");
    if (env_name) {
        nb_domain_name = strdup(env_name);
        if (!nb_domain_name) {
            ret = ENOMEM;
            goto done;
        }
    }

    if (!nb_computer_name || !nb_domain_name) {
        /* fetch only mising ones */
        ret = external_netbios_get_names(
                    nb_computer_name ? NULL : &nb_computer_name,
                    nb_domain_name ? NULL : &nb_domain_name);
        if ((ret != 0) &&
            (ret != ENOENT) &&
            (ret != ERR_NOTAVAIL)) {
            goto done;
        }
    }

    if (!nb_computer_name) {
        char *p;
        p = strchr(computer_name, '.');
        if (p) {
            nb_computer_name = strndup(computer_name, p - computer_name);
        } else {
            nb_computer_name = strdup(computer_name);
        }
        for (p = nb_computer_name; p && *p; p++) {
            /* Can only be ASCII, so toupper is safe */
            *p = toupper(*p);
        }
        if (!nb_computer_name) {
            ret = ENOMEM;
            goto done;
        }
    }

    if (!nb_domain_name) {
        nb_domain_name = strdup(DEF_NB_DOMAIN);
        if (!nb_domain_name) {
            ret = ENOMEM;
            goto done;
        }
    }

    ret = 0;

done:
    if (ret) {
        safefree(nb_computer_name);
        safefree(nb_domain_name);
    }

    *netbios_domain = nb_domain_name;
    *netbios_host = nb_computer_name;
    return ret;
}

uint32_t gssntlm_inquire_name(uint32_t *minor_status,
                              gss_name_t name,
                              int *name_is_MN,
                              gss_OID *MN_mech,
                              gss_buffer_set_t *attrs)
{
    return GSS_S_UNAVAILABLE;
}