root/usr/src/lib/libkmf/ber_der/common/decode.c
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 * Copyright (c) 1990 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */

/* decode.c - ber input decoding routines */

#include <strings.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <inttypes.h>

#include <ber_der.h>
#include "kmfber_int.h"

static void
ber_svecfree(char **vals)
{
        int     i;

        if (vals == NULL)
                return;
        for (i = 0; vals[i] != NULL; i++)
                free(vals[i]);
        free((char *)vals);
}

/*
 * Note: kmfber_get_tag() only uses the ber_end and ber_ptr elements of ber.
 * If that changes, the kmfber_peek_tag() and/or
 * kmfkmfber_skip_tag() implementations will need to be changed.
 */
/* return the tag - KMFBER_DEFAULT returned means trouble */
static ber_tag_t
kmfber_get_tag(BerElement *ber)
{
        unsigned char   xbyte;
        ber_tag_t       tag;
        char            *tagp;
        int             i;

        if (kmfber_read(ber, (char *)&xbyte, 1) != 1)
                return (KMFBER_DEFAULT);

        if ((xbyte & KMFBER_BIG_TAG_MASK) != KMFBER_BIG_TAG_MASK)
                return ((ber_uint_t)xbyte);

        tagp = (char *)&tag;
        tagp[0] = xbyte;
        for (i = 1; i < sizeof (ber_int_t); i++) {
                if (kmfber_read(ber, (char *)&xbyte, 1) != 1)
                        return (KMFBER_DEFAULT);

                tagp[i] = xbyte;

                if (! (xbyte & KMFBER_MORE_TAG_MASK))
                        break;
        }

        /* tag too big! */
        if (i == sizeof (ber_int_t))
                return (KMFBER_DEFAULT);

        /* want leading, not trailing 0's */
        return (tag >> (sizeof (ber_int_t)- i - 1));
}

/*
 * Note: kmfber_skip_tag() only uses the ber_end and ber_ptr elements of ber.
 * If that changes, the implementation of kmfber_peek_tag() will need to
 * be changed.
 */
ber_tag_t
kmfber_skip_tag(BerElement *ber, ber_len_t *len)
{
        ber_tag_t       tag;
        unsigned char   lc;
        int             noctets, diff;
        uint32_t        netlen;

        /*
         * Any ber element looks like this: tag length contents.
         * Assuming everything's ok, we return the tag byte (we
         * can assume a single byte), and return the length in len.
         *
         * Assumptions:
         *      1) definite lengths
         *      2) primitive encodings used whenever possible
         */

        /*
         * First, we read the tag.
         */

        if ((tag = kmfber_get_tag(ber)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);

        /*
         * Next, read the length.  The first byte contains the length of
         * the length.  If bit 8 is set, the length is the long form,
         * otherwise it's the short form.  We don't allow a length that's
         * greater than what we can hold in an unsigned long.
         */

        *len = 0;
        netlen = 0;
        if (kmfber_read(ber, (char *)&lc, 1) != 1)
                return (KMFBER_DEFAULT);
        if (lc & 0x80) {
                noctets = (lc & 0x7f);
                if (noctets > sizeof (ber_uint_t))
                        return (KMFBER_DEFAULT);
                diff = sizeof (ber_int_t) - noctets;
                if (kmfber_read(ber, (char *)&netlen + diff, noctets)
                    != noctets)
                        return (KMFBER_DEFAULT);
                *len = ntohl(netlen);
        } else {
                *len = lc;
        }

        return (tag);
}


/*
 * Note: Previously, we passed the "ber" parameter directly to
 * kmfber_skip_tag(), saving and restoring the ber_ptr element only.
 * We now take advantage of the fact that the only ber structure
 * elements touched by kmfber_skip_tag() are ber_end and ber_ptr.
 * If that changes, this code must change too.
 */
static ber_tag_t
kmfber_peek_tag(BerElement *ber, ber_len_t *len)
{
        BerElement      bercopy;

        bercopy.ber_end = ber->ber_end;
        bercopy.ber_ptr = ber->ber_ptr;
        return (kmfber_skip_tag(&bercopy, len));
}

static int
ber_getnint(BerElement *ber, ber_int_t *num, ber_slen_t len)
{
        int i;
        ber_int_t value;
        unsigned char buffer[sizeof (ber_int_t)];
        /*
         * The tag and length have already been stripped off.  We should
         * be sitting right before len bytes of 2's complement integer,
         * ready to be read straight into an int.  We may have to sign
         * extend after we read it in.
         */

        if (len > sizeof (buffer))
                return (-1);

        /* read into the low-order bytes of netnum */
        if (kmfber_read(ber, (char *)buffer, len) != len)
                return (-1);

        /* This sets the required sign extension */
        if (len != 0) {
                value = 0x80 & buffer[0] ? (-1) : 0;
        } else {
                value = 0;
        }

        for (i = 0; i < len; i++)
                value = (value << 8) | buffer[i];

        *num = value;

        return (len);
}

static ber_tag_t
kmfber_get_int(BerElement *ber, ber_int_t *num)
{
        ber_tag_t       tag;
        ber_len_t       len;

        if ((tag = kmfber_skip_tag(ber, &len)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);

        /*
         * len is being demoted to a long here --  possible conversion error
         */

        if (ber_getnint(ber, num, (int)len) != (ber_slen_t)len)
                return (KMFBER_DEFAULT);
        else
                return (tag);
}

static ber_tag_t
kmfber_get_stringb(BerElement *ber, char *buf, ber_len_t *len)
{
        ber_len_t       datalen;
        ber_tag_t       tag;
#ifdef STR_TRANSLATION
        char            *transbuf;
#endif /* STR_TRANSLATION */

        if ((tag = kmfber_skip_tag(ber, &datalen)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);
        if (datalen > (*len - 1))
                return (KMFBER_DEFAULT);

        /*
         * datalen is being demoted to a long here --  possible conversion error
         */

        if (kmfber_read(ber, buf, datalen) != (ber_slen_t)datalen)
                return (KMFBER_DEFAULT);

        buf[datalen] = '\0';

#ifdef STR_TRANSLATION
        if (datalen > 0 && (ber->ber_options & KMFBER_OPT_TRANSLATE_STRINGS)
            != 0 && ber->ber_decode_translate_proc != NULL) {

                transbuf = buf;
                ++datalen;
                if ((*(ber->ber_decode_translate_proc))(&transbuf, &datalen,
                    0) != 0) {
                        return (KMFBER_DEFAULT);
                }
                if (datalen > *len) {
                        free(transbuf);
                        return (KMFBER_DEFAULT);
                }
                (void) memmove(buf, transbuf, datalen);
                free(transbuf);
                --datalen;
        }
#endif /* STR_TRANSLATION */

        *len = datalen;
        return (tag);
}

static ber_tag_t
kmfber_get_stringa(BerElement *ber, char **buf)
{
        ber_len_t       datalen;
        ber_tag_t       tag;

        if ((tag = kmfber_skip_tag(ber, &datalen)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);

        if ((*buf = (char *)malloc((size_t)datalen + 1)) == NULL)
                return (KMFBER_DEFAULT);

        /*
         * datalen is being demoted to a long here --  possible conversion error
         */
        if (kmfber_read(ber, *buf, datalen) != (ber_slen_t)datalen)
                return (KMFBER_DEFAULT);
        (*buf)[datalen] = '\0';

        return (tag);
}

ber_tag_t
ber_get_oid(BerElement *ber, struct berval *oid)
{
        ber_len_t       len;
        ber_tag_t       tag;

        if ((tag = kmfber_skip_tag(ber, &len)) != 0x06) {
                return (KMFBER_DEFAULT);
        }

        if ((oid->bv_val = (char *)malloc((size_t)len + 1)) == NULL) {
                return (KMFBER_DEFAULT);
        }
        oid->bv_len = len;

        if (kmfber_read(ber, oid->bv_val, oid->bv_len) !=
            (ber_slen_t)oid->bv_len)
                return (KMFBER_DEFAULT);

        return (tag);
}

ber_tag_t
ber_get_bigint(BerElement *ber, struct berval **bv)
{
        ber_len_t       len;
        ber_tag_t       tag;

        if ((*bv = (struct berval *)malloc(sizeof (struct berval)))
            == NULL) {
                return (KMFBER_DEFAULT);
        }
        (*bv)->bv_len = 0;
        (*bv)->bv_val = NULL;

        if ((tag = kmfber_skip_tag(ber, &len)) != BER_INTEGER) {
                return (KMFBER_DEFAULT);
        }

        if (((*bv)->bv_val = (char *)malloc((size_t)len + 1))
            == NULL) {
                return (KMFBER_DEFAULT);
        }

        /*
         * len is being demoted to a long here --  possible conversion error
         */
        if (kmfber_read(ber, (*bv)->bv_val, len) != (ber_slen_t)len)
                return (KMFBER_DEFAULT);

        (*bv)->bv_len = len;

        /* If DER encoding, strip leading 0's if high-order bit is set */
        if (ber->ber_options & KMFBER_OPT_USE_DER) {
                char *p = (*bv)->bv_val;
                while ((*p == 0x00) && ((*bv)->bv_len > 0) && (p[1] & 0x80)) {
                        p++;
                        (*bv)->bv_len--;
                }
                /*
                 * Shift the buffer to the beginning of the allocated space
                 * so it can be properly freed later.
                 */
                if ((p > (*bv)->bv_val) && ((*bv)->bv_len > 0))
                        (void) bcopy(p, (*bv)->bv_val, (*bv)->bv_len);
        }

        return (tag);
}

static ber_tag_t
kmfber_get_stringal(BerElement *ber, struct berval **bv)
{
        ber_len_t       len;
        ber_tag_t       tag;

        if ((*bv = (struct berval *)malloc(sizeof (struct berval)))
            == NULL) {
                return (KMFBER_DEFAULT);
        }

        if ((tag = kmfber_skip_tag(ber, &len)) == KMFBER_DEFAULT) {
                return (KMFBER_DEFAULT);
        }

        if (((*bv)->bv_val = (char *)malloc((size_t)len + 1))
            == NULL) {
                return (KMFBER_DEFAULT);
        }

        /*
         * len is being demoted to a long here --  possible conversion error
         */
        if (kmfber_read(ber, (*bv)->bv_val, len) != (ber_slen_t)len)
                return (KMFBER_DEFAULT);
        ((*bv)->bv_val)[len] = '\0';
        (*bv)->bv_len = len;

        return (tag);
}

static ber_tag_t
kmfber_get_bitstringa(BerElement *ber, char **buf, ber_len_t *blen)
{
        ber_len_t       datalen;
        ber_tag_t       tag;
        unsigned char   unusedbits;

        if ((tag = kmfber_skip_tag(ber, &datalen)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);

        if ((*buf = (char *)malloc((size_t)datalen - 1)) == NULL)
                return (KMFBER_DEFAULT);

        if (kmfber_read(ber, (char *)&unusedbits, 1) != 1)
                return (KMFBER_DEFAULT);

        /* Subtract 1 for the unused bits */
        datalen--;

        /*
         * datalen is being demoted to a long here --  possible conversion error
         */
        if (kmfber_read(ber, *buf, datalen) != (ber_slen_t)datalen)
                return (KMFBER_DEFAULT);

        *blen = datalen * 8 - unusedbits;
        return (tag);
}

static ber_tag_t
kmfber_get_null(BerElement *ber)
{
        ber_len_t       len;
        ber_tag_t tag;

        if ((tag = kmfber_skip_tag(ber, &len)) == KMFBER_DEFAULT)
                return (KMFBER_DEFAULT);

        if (len != 0)
                return (KMFBER_DEFAULT);

        return (tag);
}

static ber_tag_t
kmfber_get_boolean(BerElement *ber, int *boolval)
{
        ber_int_t       longbool;
        int             rc;

        rc = kmfber_get_int(ber, &longbool);
        *boolval = longbool;

        return (rc);
}

ber_tag_t
kmfber_first_element(BerElement *ber, ber_len_t *len, char **last)
{
        /* skip the sequence header, use the len to mark where to stop */
        if (kmfber_skip_tag(ber, len) == KMFBER_DEFAULT) {
                return (KMFBER_ERROR);
        }

        *last = ber->ber_ptr + *len;

        if (*last == ber->ber_ptr) {
                return (KMFBER_END_OF_SEQORSET);
        }

        return (kmfber_peek_tag(ber, len));
}

ber_tag_t
kmfber_next_element(BerElement *ber, ber_len_t *len, char *last)
{
        if (ber->ber_ptr == last) {
                return (KMFBER_END_OF_SEQORSET);
        }

        return (kmfber_peek_tag(ber, len));
}

void
kmfber_bvfree(struct berval *bv)
{
        if (bv != NULL) {
                if (bv->bv_val != NULL) {
                        free(bv->bv_val);
                }
                free((char *)bv);
        }
}

void
kmfber_bvecfree(struct berval **bv)
{
        int     i;

        if (bv != NULL) {
                for (i = 0; bv[i] != NULL; i++) {
                        kmfber_bvfree(bv[i]);
                }
                free((char *)bv);
        }
}

/* VARARGS */
ber_tag_t
kmfber_scanf(BerElement *ber, const char *fmt, ...)
{
        va_list         ap;
        char            *last, *p;
        char            *s, **ss, ***sss;
        struct berval   ***bv, **bvp, *bval;
        int             *i, j;
        ber_slen_t      *l;
        ber_int_t       rc, tag, *b_int;
        ber_tag_t       *t;
        ber_len_t       len;
        size_t          array_size;

        va_start(ap, fmt);

        for (rc = 0, p = (char *)fmt; *p && rc != KMFBER_DEFAULT; p++) {
        switch (*p) {
                case 'a':       /* octet string - allocate storage as needed */
                ss = va_arg(ap, char **);
                rc = kmfber_get_stringa(ber, ss);
                break;

                case 'b':       /* boolean */
                i = va_arg(ap, int *);
                rc = kmfber_get_boolean(ber, i);
                break;

                case 'D':       /* Object ID */
                bval = va_arg(ap, struct berval *);
                rc = ber_get_oid(ber, bval);
                break;
                case 'e':       /* enumerated */
                case 'i':       /* int */
                b_int = va_arg(ap, ber_int_t *);
                rc = kmfber_get_int(ber, b_int);
                break;

                case 'l':       /* length of next item */
                l = va_arg(ap, ber_slen_t *);
                rc = kmfber_peek_tag(ber, (ber_len_t *)l);
                break;

                case 'n':       /* null */
                rc = kmfber_get_null(ber);
                break;

                case 's':       /* octet string - in a buffer */
                s = va_arg(ap, char *);
                l = va_arg(ap, ber_slen_t *);
                rc = kmfber_get_stringb(ber, s, (ber_len_t *)l);
                break;

                case 'o':       /* octet string in a supplied berval */
                bval = va_arg(ap, struct berval *);
                (void) kmfber_peek_tag(ber, &bval->bv_len);
                rc = kmfber_get_stringa(ber, &bval->bv_val);
                break;

                case 'I': /* variable length Integer */
                /* Treat INTEGER same as an OCTET string, but ignore the tag */
                bvp = va_arg(ap, struct berval **);
                rc = ber_get_bigint(ber, bvp);
                break;
                case 'O': /* octet string - allocate & include length */
                bvp = va_arg(ap, struct berval **);
                rc = kmfber_get_stringal(ber, bvp);
                break;

                case 'B':       /* bit string - allocate storage as needed */
                ss = va_arg(ap, char **);
                l = va_arg(ap, ber_slen_t *); /* for length, in bits */
                rc = kmfber_get_bitstringa(ber, ss, (ber_len_t *)l);
                break;

                case 't':       /* tag of next item */
                t = va_arg(ap, ber_tag_t *);
                *t = kmfber_peek_tag(ber, &len);
                rc = (ber_int_t)(*t);
                break;

                case 'T':       /* skip tag of next item */
                t = va_arg(ap, ber_tag_t *);
                *t = kmfber_skip_tag(ber, &len);
                rc = (ber_int_t)(*t);
                break;

                case 'v':       /* sequence of strings */
                sss = va_arg(ap, char ***);
                if (sss == NULL)
                        break;
                *sss = NULL;
                j = 0;
                array_size = 0;
                for (tag = kmfber_first_element(ber, &len, &last);
                    (tag != KMFBER_DEFAULT &&
                    tag != KMFBER_END_OF_SEQORSET &&
                    rc != KMFBER_DEFAULT);
                    tag = kmfber_next_element(ber, &len, last)) {
                        if (*sss == NULL) {
                                /* Make room for at least 15 strings */
                                *sss = (char **)malloc(16 * sizeof (char *));
                                array_size = 16;
                        } else {
                                if ((size_t)(j+2) > array_size) {
                                        /* We'v overflowed our buffer */
                                        *sss = (char **)realloc(*sss,
                                            (array_size * 2) * sizeof (char *));
                                        array_size = array_size * 2;
                                }
                        }
                        rc = kmfber_get_stringa(ber, &((*sss)[j]));
                        j++;
                }
                if (rc != KMFBER_DEFAULT && tag != KMFBER_END_OF_SEQORSET) {
                        rc = KMFBER_DEFAULT;
                }
                if (j > 0)
                        (*sss)[j] = NULL;
                break;

                case 'V':       /* sequence of strings + lengths */
                bv = va_arg(ap, struct berval ***);
                *bv = NULL;
                j = 0;
                for (tag = kmfber_first_element(ber, &len, &last);
                    (tag != KMFBER_DEFAULT &&
                    tag != KMFBER_END_OF_SEQORSET &&
                    rc != KMFBER_DEFAULT);
                    tag = kmfber_next_element(ber, &len, last)) {
                        if (*bv == NULL) {
                                *bv = (struct berval **)malloc(
                                    2 * sizeof (struct berval *));
                        } else {
                                *bv = (struct berval **)realloc(*bv,
                                    (j + 2) * sizeof (struct berval *));
                        }
                        rc = kmfber_get_stringal(ber, &((*bv)[j]));
                        j++;
                }
                if (rc != KMFBER_DEFAULT &&
                    tag != KMFBER_END_OF_SEQORSET) {
                        rc = KMFBER_DEFAULT;
                }
                if (j > 0)
                        (*bv)[j] = NULL;
                break;

                case 'x':       /* skip the next element - whatever it is */
                if ((rc = kmfber_skip_tag(ber, &len)) == KMFBER_DEFAULT)
                        break;
                ber->ber_ptr += len;
                break;

                case '{':       /* begin sequence */
                case '[':       /* begin set */
                if (*(p + 1) != 'v' && *(p + 1) != 'V')
                        rc = kmfber_skip_tag(ber, &len);
                break;

                case '}':       /* end sequence */
                case ']':       /* end set */
                break;

                default:
                rc = KMFBER_DEFAULT;
                break;
                }
        }


        va_end(ap);
        if (rc == KMFBER_DEFAULT) {
        va_start(ap, fmt);
        for (p--; fmt < p && *fmt; fmt++) {
                switch (*fmt) {
                case 'a':       /* octet string - allocate storage as needed */
                        ss = va_arg(ap, char **);
                        if (ss != NULL && *ss != NULL) {
                                free(*ss);
                                *ss = NULL;
                        }
                        break;

                case 'b':       /* boolean */
                        i = va_arg(ap, int *);
                        break;

                case 'e':       /* enumerated */
                case 'i':       /* int */
                        l = va_arg(ap, ber_slen_t *);
                        break;

                case 'l':       /* length of next item */
                        l = va_arg(ap, ber_slen_t *);
                        break;

                case 'n':       /* null */
                        break;

                case 's':       /* octet string - in a buffer */
                        s = va_arg(ap, char *);
                        l = va_arg(ap, ber_slen_t *);
                        break;

                case 'o':       /* octet string in a supplied berval */
                        bval = va_arg(ap, struct berval *);
                        if (bval->bv_val) free(bval->bv_val);
                        (void) memset(bval, 0, sizeof (struct berval));
                        break;

                case 'O':       /* octet string - allocate & include length */
                        bvp = va_arg(ap, struct berval **);
                        kmfber_bvfree(*bvp);
                        bvp = NULL;
                        break;

                case 'B':       /* bit string - allocate storage as needed */
                        ss = va_arg(ap, char **);
                        l = va_arg(ap, ber_slen_t *); /* for length, in bits */
                        if (ss != NULL && *ss != NULL) {
                                free(*ss);
                                *ss = NULL;
                        }
                        break;

                case 't':       /* tag of next item */
                        t = va_arg(ap, ber_tag_t *);
                        break;
                case 'T':       /* skip tag of next item */
                        t = va_arg(ap, ber_tag_t *);
                        break;

                case 'v':       /* sequence of strings */
                        sss = va_arg(ap, char ***);
                        if (sss != NULL && *sss != NULL) {
                                ber_svecfree(*sss);
                                *sss = NULL;
                        }
                        break;

                case 'V':       /* sequence of strings + lengths */
                        bv = va_arg(ap, struct berval ***);
                        kmfber_bvecfree(*bv);
                        *bv = NULL;
                        break;

                case 'x':       /* skip the next element - whatever it is */
                        break;

                case '{':       /* begin sequence */
                case '[':       /* begin set */
                        break;

                case '}':       /* end sequence */
                case ']':       /* end set */
                        break;

                default:
                        break;
                }
        } /* for */
        va_end(ap);
        } /* if */

        return (rc);
}

struct berval *
kmfber_bvdup(const struct berval *bv)
{
        struct berval   *new;

        if ((new = (struct berval *)malloc(sizeof (struct berval)))
            == NULL) {
                return (NULL);
        }
        if (bv->bv_val == NULL) {
                new->bv_val = NULL;
                new->bv_len = 0;
        } else {
                if ((new->bv_val = (char *)malloc(bv->bv_len + 1))
                    == NULL) {
                        return (NULL);
                }
                (void) memmove(new->bv_val, bv->bv_val, (size_t)bv->bv_len);
                new->bv_val[bv->bv_len] = '\0';
                new->bv_len = bv->bv_len;
        }

        return (new);
}