/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/gssapi/generic/oid_ops.c */
/*
 * Copyright 1995 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */
/*
 * Copyright 1993 by OpenVision Technologies, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of OpenVision not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission. OpenVision makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/* GSS-API V2 interfaces to manipulate OIDs */

#include "gssapiP_generic.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <gssapi/gssapi_generic.h>
#include <errno.h>
#include <ctype.h>

/*
 * The functions for allocating and releasing individual OIDs use malloc and
 * free instead of the gssalloc wrappers, because the mechglue currently mixes
 * generic_gss_copy_oid() with hand-freeing of OIDs.  We do not need to free
 * free OIDs allocated by mechanisms, so this should not be a problem.
 */

OM_uint32
generic_gss_release_oid(OM_uint32 *minor_status, gss_OID *oid)
{
    if (minor_status)
        *minor_status = 0;

    if (oid == NULL || *oid == GSS_C_NO_OID)
        return(GSS_S_COMPLETE);

    /*
     * The V2 API says the following!
     *
     * gss_release_oid[()] will recognize any of the GSSAPI's own OID values,
     * and will silently ignore attempts to free these OIDs; for other OIDs
     * it will call the C free() routine for both the OID data and the
     * descriptor.  This allows applications to freely mix their own heap-
     * allocated OID values with OIDs returned by GSS-API.
     */

    /*
     * We use the official OID definitions instead of the unofficial OID
     * definitions. But we continue to support the unofficial OID
     * gss_nt_service_name just in case if some gss applications use
     * the old OID.
     */

    if ((*oid != GSS_C_NT_USER_NAME) &&
        (*oid != GSS_C_NT_MACHINE_UID_NAME) &&
        (*oid != GSS_C_NT_STRING_UID_NAME) &&
        (*oid != GSS_C_NT_HOSTBASED_SERVICE) &&
        (*oid != GSS_C_NT_ANONYMOUS) &&
        (*oid != GSS_C_NT_EXPORT_NAME) &&
        (*oid != GSS_C_NT_COMPOSITE_EXPORT) &&
        (*oid != gss_nt_service_name)) {
        free((*oid)->elements);
        free(*oid);
    }
    *oid = GSS_C_NO_OID;
    return(GSS_S_COMPLETE);
}

OM_uint32
generic_gss_copy_oid(OM_uint32 *minor_status,
                     const gss_OID_desc * const oid,
                     gss_OID *new_oid)
{
    gss_OID         p;

    *minor_status = 0;

    p = (gss_OID) malloc(sizeof(gss_OID_desc));
    if (!p) {
        *minor_status = ENOMEM;
        return GSS_S_FAILURE;
    }
    p->length = oid->length;
    p->elements = malloc(p->length);
    if (!p->elements) {
        free(p);
        return GSS_S_FAILURE;
    }
    memcpy(p->elements, oid->elements, p->length);
    *new_oid = p;
    return(GSS_S_COMPLETE);
}


OM_uint32
generic_gss_create_empty_oid_set(OM_uint32 *minor_status, gss_OID_set *oid_set)
{
    *minor_status = 0;

    if (oid_set == NULL)
        return GSS_S_CALL_INACCESSIBLE_WRITE;

    if ((*oid_set = (gss_OID_set) gssalloc_malloc(sizeof(gss_OID_set_desc)))) {
        memset(*oid_set, 0, sizeof(gss_OID_set_desc));
        return(GSS_S_COMPLETE);
    }
    else {
        *minor_status = ENOMEM;
        return(GSS_S_FAILURE);
    }
}

OM_uint32
generic_gss_add_oid_set_member(OM_uint32 *minor_status,
                               const gss_OID_desc * const member_oid,
                               gss_OID_set *oid_set)
{
    gss_OID     elist;
    gss_OID     lastel;

    *minor_status = 0;

    if (member_oid == NULL || member_oid->length == 0 ||
        member_oid->elements == NULL)
        return (GSS_S_CALL_INACCESSIBLE_READ);

    if (oid_set == NULL)
        return GSS_S_CALL_INACCESSIBLE_WRITE;

    elist = (*oid_set)->elements;
    /* Get an enlarged copy of the array */
    if (((*oid_set)->elements = (gss_OID) gssalloc_malloc(((*oid_set)->count+1) *
                                                          sizeof(gss_OID_desc)))) {
        /* Copy in the old junk */
        if (elist)
            memcpy((*oid_set)->elements,
                   elist,
                   ((*oid_set)->count * sizeof(gss_OID_desc)));

        /* Duplicate the input element */
        lastel = &(*oid_set)->elements[(*oid_set)->count];
        if ((lastel->elements =
             (void *) gssalloc_malloc((size_t) member_oid->length))) {
            /* Success - copy elements */
            memcpy(lastel->elements, member_oid->elements,
                   (size_t) member_oid->length);
            /* Set length */
            lastel->length = member_oid->length;

            /* Update count */
            (*oid_set)->count++;
            if (elist)
                gssalloc_free(elist);
            *minor_status = 0;
            return(GSS_S_COMPLETE);
        }
        else
            gssalloc_free((*oid_set)->elements);
    }
    /* Failure - restore old contents of list */
    (*oid_set)->elements = elist;
    *minor_status = ENOMEM;
    return(GSS_S_FAILURE);
}

OM_uint32
generic_gss_test_oid_set_member(OM_uint32 *minor_status,
                                const gss_OID_desc * const member,
                                gss_OID_set set,
                                int * present)
{
    OM_uint32   i;
    int         result;

    *minor_status = 0;

    if (member == NULL || set == NULL)
        return (GSS_S_CALL_INACCESSIBLE_READ);

    if (present == NULL)
        return (GSS_S_CALL_INACCESSIBLE_WRITE);

    result = 0;
    for (i=0; i<set->count; i++) {
        if ((set->elements[i].length == member->length) &&
            !memcmp(set->elements[i].elements,
                    member->elements,
                    (size_t) member->length)) {
            result = 1;
            break;
        }
    }
    *present = result;
    return(GSS_S_COMPLETE);
}

OM_uint32
generic_gss_oid_to_str(OM_uint32 *minor_status,
                       const gss_OID_desc * const oid,
                       gss_buffer_t oid_str)
{
    unsigned long       number, n;
    OM_uint32           i;
    int                 first;
    unsigned char       *cp;
    struct k5buf        buf;

    if (minor_status != NULL)
        *minor_status = 0;

    if (oid_str != GSS_C_NO_BUFFER) {
        oid_str->length = 0;
        oid_str->value = NULL;
    }

    if (oid == NULL || oid->length == 0 || oid->elements == NULL)
        return (GSS_S_CALL_INACCESSIBLE_READ);

    if (oid_str == GSS_C_NO_BUFFER)
        return (GSS_S_CALL_INACCESSIBLE_WRITE);

    /* Decoded according to krb5/gssapi_krb5.c */

    cp = (unsigned char *) oid->elements;
    number = (unsigned long) cp[0];
    k5_buf_init_dynamic(&buf);
    k5_buf_add(&buf, "{ ");
    number = 0;
    cp = (unsigned char *) oid->elements;
    first = 1;
    for (i = 0; i < oid->length; i++) {
        number = (number << 7) | (cp[i] & 0x7f);
        if ((cp[i] & 0x80) == 0) {
            if (first) {
                n = (number < 40) ? 0 : (number < 80) ? 1 : 2;
                k5_buf_add_fmt(&buf, "%lu %lu ", n, number - (n * 40));
                first = 0;
            } else {
                k5_buf_add_fmt(&buf, "%lu ", number);
            }
            number = 0;
        }
    }
    k5_buf_add_len(&buf, "}\0", 2);
    return k5buf_to_gss(minor_status, &buf, oid_str);
}

/* Return the length of a DER OID subidentifier encoding. */
static size_t
arc_encoded_length(unsigned long arc)
{
    size_t len = 1;

    for (arc >>= 7; arc; arc >>= 7)
        len++;
    return len;
}

/* Encode a subidentifier into *bufp and advance it to the encoding's end. */
static void
arc_encode(unsigned long arc, unsigned char **bufp)
{
    unsigned char *p;

    /* Advance to the end and encode backwards. */
    p = *bufp = *bufp + arc_encoded_length(arc);
    *--p = arc & 0x7f;
    for (arc >>= 7; arc; arc >>= 7)
        *--p = (arc & 0x7f) | 0x80;
}

/* Fetch an arc value from *bufp and advance past it and any following spaces
 * or periods.  Return 1 on success, 0 if *bufp is not at a valid arc value. */
static int
get_arc(const unsigned char **bufp, const unsigned char *end,
        unsigned long *arc_out)
{
    const unsigned char *p = *bufp;
    unsigned long arc = 0, newval;

    if (p == end || !isdigit(*p))
        return 0;
    for (; p < end && isdigit(*p); p++) {
        newval = arc * 10 + (*p - '0');
        if (newval < arc)
            return 0;
        arc = newval;
    }
    while (p < end && (isspace(*p) || *p == '.'))
        p++;
    *bufp = p;
    *arc_out = arc;
    return 1;
}

/*
 * Convert a sequence of two or more decimal arc values into a DER-encoded OID.
 * The values may be separated by any combination of whitespace and period
 * characters, and may be optionally surrounded with braces.  Leading
 * whitespace and trailing garbage is allowed.  The first arc value must be 0,
 * 1, or 2, and the second value must be less than 40 if the first value is not
 * 2.
 */
OM_uint32
generic_gss_str_to_oid(OM_uint32 *minor_status,
                       gss_buffer_t oid_str,
                       gss_OID *oid_out)
{
    const unsigned char *p, *end, *arc3_start;
    unsigned char *out;
    unsigned long arc, arc1, arc2;
    size_t nbytes;
    int brace = 0;
    gss_OID oid;

    if (minor_status != NULL)
        *minor_status = 0;

    if (oid_out != NULL)
        *oid_out = GSS_C_NO_OID;

    if (GSS_EMPTY_BUFFER(oid_str))
        return (GSS_S_CALL_INACCESSIBLE_READ);

    if (oid_out == NULL)
        return (GSS_S_CALL_INACCESSIBLE_WRITE);

    /* Skip past initial spaces and, optionally, an open brace. */
    brace = 0;
    p = oid_str->value;
    end = p + oid_str->length;
    while (p < end && isspace(*p))
        p++;
    if (p < end && *p == '{') {
        brace = 1;
        p++;
    }
    while (p < end && isspace(*p))
        p++;

    /* Get the first two arc values, to be encoded as one subidentifier. */
    if (!get_arc(&p, end, &arc1) || !get_arc(&p, end, &arc2))
        return (GSS_S_FAILURE);
    if (arc1 > 2 || (arc1 < 2 && arc2 > 39) || arc2 > ULONG_MAX - 80)
        return (GSS_S_FAILURE);
    arc3_start = p;

    /* Compute the total length of the encoding while checking syntax. */
    nbytes = arc_encoded_length(arc1 * 40 + arc2);
    while (get_arc(&p, end, &arc))
        nbytes += arc_encoded_length(arc);
    if (brace && (p == end || *p != '}'))
        return (GSS_S_FAILURE);

    /* Allocate an oid structure. */
    oid = malloc(sizeof(*oid));
    if (oid == NULL)
        return (GSS_S_FAILURE);
    oid->elements = malloc(nbytes);
    if (oid->elements == NULL) {
        free(oid);
        return (GSS_S_FAILURE);
    }
    oid->length = nbytes;

    out = oid->elements;
    arc_encode(arc1 * 40 + arc2, &out);
    p = arc3_start;
    while (get_arc(&p, end, &arc))
        arc_encode(arc, &out);
    assert(out - nbytes == oid->elements);
    *oid_out = oid;
    return(GSS_S_COMPLETE);
}

/* Compose an OID of a prefix and an integer suffix */
OM_uint32
generic_gss_oid_compose(OM_uint32 *minor_status,
                        const char *prefix,
                        size_t prefix_len,
                        int suffix,
                        gss_OID_desc *oid)
{
    int osuffix, i;
    size_t nbytes;
    unsigned char *op;

    if (oid == GSS_C_NO_OID) {
        *minor_status = EINVAL;
        return GSS_S_FAILURE;
    }
    if (oid->length < prefix_len) {
        *minor_status = ERANGE;
        return GSS_S_FAILURE;
    }

    memcpy(oid->elements, prefix, prefix_len);

    nbytes = 0;
    osuffix = suffix;
    while (suffix) {
        nbytes++;
        suffix >>= 7;
    }
    suffix = osuffix;

    if (oid->length < prefix_len + nbytes) {
        *minor_status = ERANGE;
        return GSS_S_FAILURE;
    }

    op = (unsigned char *) oid->elements + prefix_len + nbytes;
    i = -1;
    while (suffix) {
        op[i] = (unsigned char)suffix & 0x7f;
        if (i != -1)
            op[i] |= 0x80;
        i--;
        suffix >>= 7;
    }

    oid->length = prefix_len + nbytes;

    *minor_status = 0;
    return GSS_S_COMPLETE;
}

OM_uint32
generic_gss_oid_decompose(OM_uint32 *minor_status,
                          const char *prefix,
                          size_t prefix_len,
                          gss_OID_desc *oid,
                          int *suffix)
{
    size_t i, slen;
    unsigned char *op;

    if (oid->length < prefix_len ||
        memcmp(oid->elements, prefix, prefix_len) != 0) {
        return GSS_S_BAD_MECH;
    }

    op = (unsigned char *) oid->elements + prefix_len;

    *suffix = 0;

    slen = oid->length - prefix_len;

    for (i = 0; i < slen; i++) {
        *suffix = (*suffix << 7) | (op[i] & 0x7f);
        if (i + 1 != slen && (op[i] & 0x80) == 0) {
            *minor_status = EINVAL;
            return GSS_S_FAILURE;
        }
    }

    return GSS_S_COMPLETE;
}

OM_uint32
generic_gss_copy_oid_set(OM_uint32 *minor_status,
                         const gss_OID_set_desc * const oidset,
                         gss_OID_set *new_oidset)
{
    gss_OID_set_desc *copy;
    OM_uint32 minor = 0;
    OM_uint32 major = GSS_S_COMPLETE;
    OM_uint32 i;

    if (minor_status != NULL)
        *minor_status = 0;

    if (new_oidset != NULL)
        *new_oidset = GSS_C_NO_OID_SET;

    if (oidset == GSS_C_NO_OID_SET)
        return (GSS_S_CALL_INACCESSIBLE_READ);

    if (new_oidset == NULL)
        return (GSS_S_CALL_INACCESSIBLE_WRITE);

    if ((copy = (gss_OID_set_desc *) gssalloc_calloc(1, sizeof (*copy))) == NULL) {
        major = GSS_S_FAILURE;
        goto done;
    }

    if ((copy->elements = (gss_OID_desc *)
         gssalloc_calloc(oidset->count, sizeof (*copy->elements))) == NULL) {
        major = GSS_S_FAILURE;
        goto done;
    }
    copy->count = oidset->count;

    for (i = 0; i < copy->count; i++) {
        gss_OID_desc *out = &copy->elements[i];
        gss_OID_desc *in = &oidset->elements[i];

        if ((out->elements = (void *) gssalloc_malloc(in->length)) == NULL) {
            major = GSS_S_FAILURE;
            goto done;
        }
        (void) memcpy(out->elements, in->elements, in->length);
        out->length = in->length;
    }

    *new_oidset = copy;
done:
    if (major != GSS_S_COMPLETE) {
        (void) generic_gss_release_oid_set(&minor, &copy);
    }

    return (major);
}
