/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* ASN.1 encoding related routines
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "asn1.h"
#include "pdu.h"
#include "debug.h"
/*
* This routine builds a 'SEQUENCE OF' ASN.1 object in the buffer
* using the 'id' and 'length' supplied. This is probably the place
* where using "reverse" asn encoding will help.
*/
uchar_t *
asn_build_sequence(uchar_t *buf, size_t *bufsz_p, uchar_t id, size_t length)
{
/*
* When rebuilding sequence (which we do many times), we'll
* simply pass NULL to bufsz_p to skip the error check.
*/
if ((bufsz_p) && (*bufsz_p < 4))
return (NULL);
buf[0] = id;
buf[1] = (uchar_t)(ASN_LONG_LEN | 0x02); /* following 2 octets */
buf[2] = (uchar_t)((length >> 8) & 0xff);
buf[3] = (uchar_t)(length & 0xff);
if (bufsz_p)
*bufsz_p -= 4;
LOGASNSEQ(buf, 4);
return (buf + 4);
}
/*
* The next two routines, asn_build_header() and asn_build_length(), build
* the header and length for an arbitrary object type into the buffer. The
* length of the object is encoded using as few length octets as possible.
*/
uchar_t *
asn_build_header(uchar_t *buf, size_t *bufsz_p, uchar_t id, size_t length)
{
if (*bufsz_p < 1)
return (NULL);
buf[0] = id;
(*bufsz_p)--;
return (asn_build_length(buf + 1, bufsz_p, length));
}
uchar_t *
asn_build_length(uchar_t *buf, size_t *bufsz_p, size_t length)
{
if (length < 0x80) {
if (*bufsz_p < 1)
return (NULL);
buf[0] = (uchar_t)length;
(*bufsz_p)--;
LOGASNLENGTH(buf, 1);
return (buf + 1);
} else if (length <= 0xFF) {
if (*bufsz_p < 2)
return (NULL);
buf[0] = (uchar_t)(ASN_LONG_LEN | 0x01);
buf[1] = (uchar_t)length;
*bufsz_p -= 2;
LOGASNLENGTH(buf, 2);
return (buf + 2);
} else {
if (*bufsz_p < 3)
return (NULL);
buf[0] = (uchar_t)(ASN_LONG_LEN | 0x02);
buf[1] = (uchar_t)((length >> 8) & 0xff);
buf[2] = (uchar_t)(length & 0xff);
*bufsz_p -= 3;
LOGASNLENGTH(buf, 3);
return (buf + 3);
}
}
/*
* Builds an ASN.1 encoded integer in the buffer using as few octets
* as possible.
*/
uchar_t *
asn_build_int(uchar_t *buf, size_t *bufsz_p, uchar_t id, int val)
{
uint_t uival;
int ival, i;
short sval;
char cval;
size_t valsz;
uchar_t *p, *valp;
/*
* We need to "pack" the integer before sending it, so determine
* the minimum number of bytes in which we can pack the integer
*/
uival = ((uint_t)val >> BUILD_INT_SHIFT) & BUILD_INT_MASK;
ival = val;
sval = (short)val; /* yes, loss of data intended */
cval = (char)val; /* yes, loss of data intended */
if (val == (int)cval)
valsz = 1;
else if (val == (int)sval)
valsz = 2;
else if (uival == BUILD_INT_MASK || uival == 0)
valsz = 3;
else
valsz = 4;
/*
* Prepare the ASN.1 header for the integer
*/
if ((p = asn_build_header(buf, bufsz_p, id, valsz)) == NULL)
return (NULL);
/*
* If we have enough space left, encode the integer
*/
if (*bufsz_p < valsz)
return (NULL);
else {
valp = (uchar_t *)&ival;
for (i = 0; i < valsz; i++)
p[i] = valp[sizeof (int) - valsz + i];
*bufsz_p -= valsz;
LOGASNINT(buf, p + valsz - buf);
return (p + valsz);
}
}
/*
* Builds an ASN.1 encoded octet string in the buffer. The source string
* need not be null-terminated.
*/
uchar_t *
asn_build_string(uchar_t *buf, size_t *bufsz_p, uchar_t id, uchar_t *str,
size_t slen)
{
uchar_t *p;
if ((p = asn_build_header(buf, bufsz_p, id, slen)) == NULL)
return (NULL);
if (*bufsz_p < slen)
return (NULL);
else {
if (str) {
(void) memcpy(p, str, slen);
} else {
(void) memset(p, 0, slen);
}
*bufsz_p -= slen;
LOGASNOCTSTR(buf, p + slen - buf);
return (p + slen);
}
}
/*
* Builds an Object Identifier into the buffer according to the OID
* packing and encoding rules.
*/
uchar_t *
asn_build_objid(uchar_t *buf, size_t *bufsz_p, uchar_t id, void *oidp,
size_t n_subids)
{
oid *objid = oidp;
size_t oid_asnlen;
oid subid, first_subid;
uchar_t subid_len[MAX_SUBIDS_IN_OID];
uchar_t *p;
int i, ndx;
/*
* Eliminate invalid cases
*/
if (n_subids < MIN_SUBIDS_IN_OID || n_subids > MAX_SUBIDS_IN_OID)
return (NULL);
if ((objid[0] > 2) || (objid[0] < 2 && objid[1] >= 40))
return (NULL);
/*
* The BER encoding rule for the ASN.1 Object Identifier states
* that after packing the first two subids into one, each subsequent
* component is considered as the next subid. Each subidentifier is
* then encoded as a non-negative integer using as few 7-bit blocks
* as possible. The blocks are packed in octets with the first bit of
* each octet equal to 1, except for the last octet of each subid.
*/
oid_asnlen = 0;
for (i = 0, ndx = 0; i < n_subids; i++, ndx++) {
if (i == 0) {
/*
* The packing formula for the first two subids
* of an OID is given by Z = (X * 40) + Y
*/
subid = objid[0] * 40 + objid[1];
first_subid = subid;
i++; /* done with both subids 0 and 1 */
} else {
subid = objid[i];
}
if (subid < (oid) 0x80)
subid_len[ndx] = 1;
else if (subid < (oid) 0x4000)
subid_len[ndx] = 2;
else if (subid < (oid) 0x200000)
subid_len[ndx] = 3;
else if (subid < (oid) 0x10000000)
subid_len[ndx] = 4;
else {
subid_len[ndx] = 5;
}
oid_asnlen += subid_len[ndx];
}
if ((p = asn_build_header(buf, bufsz_p, id, oid_asnlen)) == NULL)
return (NULL);
if (*bufsz_p < oid_asnlen)
return (NULL);
/*
* Store the encoded OID
*/
for (i = 0, ndx = 0; i < n_subids; i++, ndx++) {
if (i == 0) {
subid = first_subid;
i++;
} else {
subid = objid[i];
}
switch (subid_len[ndx]) {
case 1:
*p++ = (uchar_t)subid;
break;
case 2:
*p++ = (uchar_t)((subid >> 7) | 0x80);
*p++ = (uchar_t)(subid & 0x7f);
break;
case 3:
*p++ = (uchar_t)((subid >> 14) | 0x80);
*p++ = (uchar_t)(((subid >> 7) & 0x7f) | 0x80);
*p++ = (uchar_t)(subid & 0x7f);
break;
case 4:
*p++ = (uchar_t)((subid >> 21) | 0x80);
*p++ = (uchar_t)(((subid >> 14) & 0x7f) | 0x80);
*p++ = (uchar_t)(((subid >> 7) & 0x7f) | 0x80);
*p++ = (uchar_t)(subid & 0x7f);
break;
case 5:
*p++ = (uchar_t)((subid >> 28) | 0x80);
*p++ = (uchar_t)(((subid >> 21) & 0x7f) | 0x80);
*p++ = (uchar_t)(((subid >> 14) & 0x7f) | 0x80);
*p++ = (uchar_t)(((subid >> 7) & 0x7f) | 0x80);
*p++ = (uchar_t)(subid & 0x7f);
break;
}
}
*bufsz_p -= oid_asnlen;
LOGASNOID(buf, p - buf);
return (p);
}
/*
* Build an ASN_NULL object val into the request packet
*/
uchar_t *
asn_build_null(uchar_t *buf, size_t *bufsz_p, uchar_t id)
{
uchar_t *p;
p = asn_build_header(buf, bufsz_p, id, 0);
LOGASNNULL(buf, p - buf);
return (p);
}
/*
* This routine parses a 'SEQUENCE OF' object header from the input
* buffer stream. If the identifier tag (made up of class, constructed
* type and data type tag) does not match the expected identifier tag,
* returns failure.
*/
uchar_t *
asn_parse_sequence(uchar_t *buf, size_t *bufsz_p, uchar_t exp_id)
{
uchar_t *p;
uchar_t id;
if ((p = asn_parse_header(buf, bufsz_p, &id)) == NULL)
return (NULL);
if (id != exp_id)
return (NULL);
return (p);
}
/*
* Return the type identifier of the ASN object via 'id'
*/
uchar_t *
asn_parse_header(uchar_t *buf, size_t *bufsz_p, uchar_t *id)
{
uchar_t *p;
size_t asnobj_len, hdrlen;
/*
* Objects with extension tag type are not supported
*/
if ((buf[0] & ASN_EXT_TAG) == ASN_EXT_TAG)
return (NULL);
/*
* Parse the length field of the ASN object in the header
*/
if ((p = asn_parse_length(buf + 1, &asnobj_len)) == NULL)
return (NULL);
/*
* Check if the rest of the msg packet is big enough for the
* full length of the object
*/
hdrlen = p - buf;
if (*bufsz_p < (asnobj_len + hdrlen))
return (NULL);
*id = buf[0];
*bufsz_p -= hdrlen;
return (p);
}
/*
* This routine parses the length of the object as specified in its
* header. The 'Indefinite' form of representing length is not supported.
*/
uchar_t *
asn_parse_length(uchar_t *buf, size_t *asnobj_len_p)
{
uchar_t *p;
int n_length_octets;
/*
* First, check for the short-definite form. Length of
* the object is simply the least significant 7-bits of
* the first byte.
*/
if ((buf[0] & ASN_LONG_LEN) == 0) {
*asnobj_len_p = (size_t)buf[0];
return (buf + 1);
}
/*
* Then, eliminate the indefinite form. The ASN_LONG_LEN
* bit of the first byte will be set and the least significant
* 7-bites of that byte will be zeros.
*/
if (buf[0] == (uchar_t)ASN_LONG_LEN)
return (NULL);
/*
* Then, eliminate the long-definite case when the number of
* follow-up octets is more than what the size var can hold.
*/
n_length_octets = buf[0] & ~ASN_LONG_LEN;
if (n_length_octets > sizeof (*asnobj_len_p))
return (NULL);
/*
* Finally gather the length
*/
p = buf + 1;
*asnobj_len_p = 0;
while (n_length_octets--) {
*asnobj_len_p <<= 8;
*asnobj_len_p |= *p++;
}
return (p);
}
/*
* Parses an integer out of the input buffer
*/
uchar_t *
asn_parse_int(uchar_t *buf, size_t *bufsz_p, int *ival)
{
size_t asnobj_len, hdrlen;
uchar_t int_id;
uchar_t *p;
int_id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
if (buf[0] != int_id)
return (NULL);
/*
* Read in the length of the object; Note that integers are
* "packed" when sent from agent to manager and vice-versa,
* so the size of the object could be less than sizeof (int).
*/
if ((p = asn_parse_length(buf + 1, &asnobj_len)) == NULL)
return (NULL);
/*
* Is there sufficient space left in the packet to read the integer ?
*/
hdrlen = p - buf;
if (*bufsz_p < (hdrlen + asnobj_len))
return (NULL);
/*
* Update space left in the buffer after the integer is read
*/
*bufsz_p -= (hdrlen + asnobj_len);
/*
* Read in the integer value
*/
*ival = (*p & ASN_BIT8) ? -1 : 0;
while (asnobj_len--) {
*ival <<= 8;
*ival |= *p++;
}
return (p);
}
/*
* Parses an unsigned integer out of the input buffer
*/
uchar_t *
asn_parse_uint(uchar_t *buf, size_t *bufsz_p, uint_t *uival)
{
size_t asnobj_len, hdrlen;
uchar_t *p;
if ((buf[0] != ASN_COUNTER) && (buf[0] != ASN_TIMETICKS))
return (NULL);
/*
* Read in the length of the object. Integers are sent the same
* way unsigned integers are sent. Except that, if the MSB was 1
* in the unsigned int value, a null-byte is attached to the front.
* Otherwise, packing rules are the same as for integer values.
*/
if ((p = asn_parse_length(buf + 1, &asnobj_len)) == NULL)
return (NULL);
/*
* Is there sufficient space left in the packet to read in the value ?
*/
hdrlen = p - buf;
if (*bufsz_p < (hdrlen + asnobj_len))
return (NULL);
/*
* Update space left in the buffer after the uint is read
*/
*bufsz_p -= (hdrlen + asnobj_len);
/*
* Read in the unsigned integer (this should never get
* initialized to ~0 if it was sent right)
*/
*uival = (*p & ASN_BIT8) ? ~0 : 0;
while (asnobj_len--) {
*uival <<= 8;
*uival |= *p++;
}
return (p);
}
/*
* Parses a string (ASN_OCTET_STR or ASN_BIT_STR) out of the input buffer.
* The memory for the string is allocated inside the routine and must be
* freed by the caller when it is no longer needed. If the string type is
* ASN_OCTET_STR, the returned string is null-terminated, and the returned
* length indicates the strlen value. If the string type is ASN_BIT_STR,
* the returned string is not null-terminated, and the returned length
* indicates the number of bytes.
*/
uchar_t *
asn_parse_string(uchar_t *buf, size_t *bufsz_p, uchar_t **str_p, size_t *slen)
{
uchar_t *p;
uchar_t id1, id2;
size_t asnobj_len, hdrlen;
/*
* Octet and bit strings are supported
*/
id1 = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR;
id2 = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_BIT_STR;
if ((buf[0] != id1) && (buf[0] != id2))
return (NULL);
/*
* Parse out the length of the object and verify source buf sz
*/
if ((p = asn_parse_length(buf + 1, &asnobj_len)) == NULL)
return (NULL);
hdrlen = p - buf;
if (*bufsz_p < (hdrlen + asnobj_len))
return (NULL);
/*
* Allocate for and copy out the string
*/
if ((*str_p = (uchar_t *)calloc(1, asnobj_len + 1)) == NULL)
return (NULL);
(void) memcpy(*str_p, p, asnobj_len);
/*
* Terminate the octet string with a null
*/
if (buf[0] == id1) {
(*str_p)[asnobj_len] = 0;
}
/*
* Update pointers and return
*/
*slen = asnobj_len;
*bufsz_p -= (hdrlen + asnobj_len);
return (p + asnobj_len);
}
/*
* Parses an object identifier out of the input packet buffer. Space for
* the oid object is allocated within this routine and must be freed by the
* caller when no longer needed.
*/
uchar_t *
asn_parse_objid(uchar_t *msg, size_t *varsz_p, void *oidp, size_t *n_subids)
{
oid **objid_p = oidp;
oid *objid;
uchar_t *p;
size_t hdrlen, asnobj_len;
oid subid;
int i, ndx;
uchar_t exp_id;
/*
* Check id
*/
exp_id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID;
if (msg[0] != exp_id)
return (NULL);
/*
* Read object length
*/
if ((p = asn_parse_length(msg + 1, &asnobj_len)) == NULL)
return (NULL);
/*
* Check space in input message
*/
hdrlen = p - msg;
if (*varsz_p < (hdrlen + asnobj_len))
return (NULL);
/*
* Since the OID subidentifiers are packed in 7-bit blocks with
* MSB set to 1 for all but the last octet, the number of subids
* is simply the number of octets with MSB equal to 0, plus 1
* (since the first two subids were packed into one subid and have
* to be expanded back to two).
*/
*n_subids = 1;
for (i = 0; i < asnobj_len; i++) {
if ((p[i] & ASN_BIT8) == 0)
(*n_subids)++;
}
/*
* Now allocate for the oid and parse the OID into it
*/
if ((objid = (oid *) calloc(1, (*n_subids) * sizeof (oid))) == NULL)
return (NULL);
ndx = 1; /* start from 1 to allow for unpacking later */
subid = 0;
for (i = 0; i < asnobj_len; i++) {
subid = subid << 7;
subid |= (p[i] & ~ASN_BIT8);
if ((p[i] & ASN_BIT8) == 0) {
objid[ndx] = subid;
ndx++;
subid = 0;
}
}
/*
* Now unpack the first two subids from the subid at index 1.
*/
if (objid[1] < 40) {
objid[0] = 0;
} else if (objid[1] < 80) {
objid[0] = 1;
objid[1] -= 40;
} else {
objid[0] = 2;
objid[1] -= 80;
}
*objid_p = objid;
*varsz_p -= (hdrlen + asnobj_len);
return (msg + hdrlen + asnobj_len);
}
/*
* Parses the value of an OID object out of the input message buffer.
* Only type tags less than ASN_EXT_TAG (0x1f) are supported.
*/
uchar_t *
asn_parse_objval(uchar_t *msg, size_t *varsz_p, void *varlistp)
{
pdu_varlist_t *vp = varlistp;
uchar_t *p;
size_t n_subids;
size_t hdrlen, asnobj_len;
vp->type = msg[0] & ASN_EXT_TAG;
if (vp->type == ASN_EXT_TAG)
return (NULL);
/*
* Currently we handle ASN_INTEGER, ASN_OCTET_STR, ASN_BIT_STR
* and ASN_TIMETICKS types.
*/
switch (msg[0]) {
case ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER:
vp->val.iptr = (int *)calloc(1, sizeof (int));
if (vp->val.iptr == NULL)
return (NULL);
if ((p = asn_parse_int(msg, varsz_p, vp->val.iptr)) == NULL) {
free(vp->val.iptr);
return (NULL);
}
vp->val_len = sizeof (int);
break;
case ASN_COUNTER:
case ASN_TIMETICKS:
vp->val.uiptr = (uint_t *)calloc(1, sizeof (uint_t));
if (vp->val.uiptr == NULL)
return (NULL);
if ((p = asn_parse_uint(msg, varsz_p, vp->val.uiptr)) == NULL) {
free(vp->val.uiptr);
return (NULL);
}
vp->val_len = sizeof (uint_t);
vp->type = msg[0];
break;
case ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR:
case ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_BIT_STR:
p = asn_parse_string(msg, varsz_p, &vp->val.str, &vp->val_len);
if (p == NULL)
return (NULL);
break;
case ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID:
p = asn_parse_objid(msg, varsz_p, &vp->val.objid, &n_subids);
if (p == NULL)
return (NULL);
vp->val_len = n_subids * sizeof (oid);
break;
case ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_NULL:
case SNMP_NOSUCHOBJECT:
case SNMP_NOSUCHINSTANCE:
case SNMP_ENDOFMIBVIEW:
default:
p = asn_parse_length(msg + 1, &asnobj_len);
if (p == NULL)
return (NULL);
hdrlen = p - msg;
if (*varsz_p < (hdrlen + asnobj_len))
return (NULL);
vp->type = msg[0];
vp->val_len = asnobj_len;
*varsz_p -= (hdrlen + asnobj_len);
p += asnobj_len;
break;
}
return (p);
}