diff options
author | Martin Hedenfal <martinh@cvs.openbsd.org> | 2010-05-31 17:36:32 +0000 |
---|---|---|
committer | Martin Hedenfal <martinh@cvs.openbsd.org> | 2010-05-31 17:36:32 +0000 |
commit | 28c2c79b07ed71b5906389cdda8588b4b5ddb055 (patch) | |
tree | 8eb48a984a6919ca495b6f04acc0ff17b126923f /usr.sbin/ldapd/ber.c | |
parent | 4351d86e27905803bc2e0db9b38ad6075b8ceee1 (diff) |
Initial import of ldapd, a small LDAP daemon. Work in progress.
ok deraadt@ jacekm@ gilles@ back@ henning@ blambert@
Diffstat (limited to 'usr.sbin/ldapd/ber.c')
-rw-r--r-- | usr.sbin/ldapd/ber.c | 1247 |
1 files changed, 1247 insertions, 0 deletions
diff --git a/usr.sbin/ldapd/ber.c b/usr.sbin/ldapd/ber.c new file mode 100644 index 00000000000..602bbe7074d --- /dev/null +++ b/usr.sbin/ldapd/ber.c @@ -0,0 +1,1247 @@ +/* $OpenBSD: ber.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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. + */ + +#include <sys/types.h> +#include <sys/param.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <err.h> /* XXX for debug output */ +#include <stdio.h> /* XXX for debug output */ +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <stdint.h> + +#include "ber.h" + +#define BER_TYPE_CONSTRUCTED 0x20 /* otherwise primitive */ +#define BER_TYPE_SINGLE_MAX 30 +#define BER_TAG_MASK 0x1f +#define BER_TAG_MORE 0x80 /* more subsequent octets */ +#define BER_TAG_TYPE_MASK 0x7f +#define BER_CLASS_SHIFT 6 + +static int ber_dump_element(struct ber *ber, struct ber_element *root); +static void ber_dump_header(struct ber *ber, struct ber_element *root); +static void ber_putc(struct ber *ber, u_char c); +static void ber_write(struct ber *ber, void *buf, size_t len); +static ssize_t get_id(struct ber *b, unsigned long *tag, int *class, + int *cstruct); +static ssize_t get_len(struct ber *b, ssize_t *len); +static ssize_t ber_read_element(struct ber *ber, struct ber_element *elm); +static ssize_t ber_readbuf(struct ber *b, void *buf, size_t nbytes); +static ssize_t ber_getc(struct ber *b, u_char *c); +static ssize_t ber_read(struct ber *ber, void *buf, size_t len); + +#ifdef DEBUG +#define DPRINTF(...) printf(__VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#endif + +struct ber_element * +ber_get_element(unsigned long encoding) +{ + struct ber_element *elm; + + if ((elm = calloc(1, sizeof(*elm))) == NULL) + return NULL; + + elm->be_encoding = encoding; + ber_set_header(elm, BER_CLASS_UNIVERSAL, BER_TYPE_DEFAULT); + + return elm; +} + +void +ber_set_header(struct ber_element *elm, int class, unsigned long type) +{ + elm->be_class = class & BER_CLASS_MASK; + if (type == BER_TYPE_DEFAULT) + type = elm->be_encoding; + elm->be_type = type; +} + +void +ber_link_elements(struct ber_element *prev, struct ber_element *elm) +{ + if (prev != NULL) { + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub == NULL) + prev->be_sub = elm; + else + prev->be_next = elm; + } +} + +struct ber_element * +ber_unlink_elements(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub != NULL) { + elm = prev->be_sub; + prev->be_sub = NULL; + } else { + elm = prev->be_next; + prev->be_next = NULL; + } + + return (elm); +} + +void +ber_replace_elements(struct ber_element *prev, struct ber_element *new) +{ + struct ber_element *ber, *next; + + ber = ber_unlink_elements(prev); + next = ber_unlink_elements(ber); + ber_link_elements(new, next); + ber_link_elements(prev, new); + + /* cleanup old element */ + ber_free_elements(ber); +} + +struct ber_element * +ber_add_sequence(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SEQUENCE)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_set(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SET)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_enumerated(struct ber_element *prev, long long val) +{ + struct ber_element *elm; + u_int i, len = 0; + u_char cur, last = 0; + + if ((elm = ber_get_element(BER_TYPE_ENUMERATED)) == NULL) + return NULL; + + elm->be_numeric = val; + + for (i = 0; i < sizeof(long long); i++) { + cur = val & 0xff; + if (cur != 0 && cur != 0xff) + len = i; + if ((cur == 0 && last & 0x80) || + (cur == 0xff && (last & 0x80) == 0)) + len = i; + val >>= 8; + last = cur; + } + elm->be_len = len + 1; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_integer(struct ber_element *prev, long long val) +{ + struct ber_element *elm; + u_int i, len = 0; + u_char cur, last = 0; + + if ((elm = ber_get_element(BER_TYPE_INTEGER)) == NULL) + return NULL; + + elm->be_numeric = val; + + for (i = 0; i < sizeof(long long); i++) { + cur = val & 0xff; + if (cur != 0 && cur != 0xff) + len = i; + if ((cur == 0 && last & 0x80) || + (cur == 0xff && (last & 0x80) == 0)) + len = i; + val >>= 8; + last = cur; + } + elm->be_len = len + 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_integer(struct ber_element *elm, long long *n) +{ + if (elm->be_encoding != BER_TYPE_INTEGER) + return -1; + + *n = elm->be_numeric; + return 0; +} + +int +ber_get_enumerated(struct ber_element *elm, long long *n) +{ + if (elm->be_encoding != BER_TYPE_ENUMERATED) + return -1; + + *n = elm->be_numeric; + return 0; +} + + +struct ber_element * +ber_add_boolean(struct ber_element *prev, int bool) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_BOOLEAN)) == NULL) + return NULL; + + elm->be_numeric = bool ? 0xff : 0; + elm->be_len = 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_boolean(struct ber_element *elm, int *b) +{ + if (elm->be_encoding != BER_TYPE_BOOLEAN) + return -1; + + *b = !(elm->be_numeric == 0); + return 0; +} + +struct ber_element * +ber_add_string(struct ber_element *prev, const char *string) +{ + return ber_add_nstring(prev, string, strlen(string)); +} + +struct ber_element * +ber_add_nstring(struct ber_element *prev, const char *string0, size_t len) +{ + struct ber_element *elm; + char *string; + + if ((string = calloc(1, len + 1)) == NULL) + return NULL; + if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) { + free(string); + return NULL; + } + + bcopy(string0, string, len); + elm->be_val = string; + elm->be_len = len; + elm->be_free = 1; /* free string on cleanup */ + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_string(struct ber_element *elm, char **s) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *s = elm->be_val; + return 0; +} + +int +ber_get_nstring(struct ber_element *elm, void **p, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *p = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_bitstring(struct ber_element *prev, const void *v0, size_t len) +{ + struct ber_element *elm; + void *v; + + if ((v = calloc(1, len)) == NULL) + return NULL; + if ((elm = ber_get_element(BER_TYPE_BITSTRING)) == NULL) { + free(v); + return NULL; + } + + bcopy(v0, v, len); + elm->be_val = v; + elm->be_len = len; + elm->be_free = 1; /* free string on cleanup */ + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_bitstring(struct ber_element *elm, void **v, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_BITSTRING) + return -1; + + *v = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_null(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_NULL)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_null(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_NULL) + return -1; + + return 0; +} + +struct ber_element * +ber_add_eoc(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_EOC)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_eoc(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_EOC) + return -1; + + return 0; +} + +size_t +ber_oid2ber(struct ber_oid *o, u_int8_t *buf, size_t len) +{ + u_int32_t v; + u_int i, j = 0, k; + + if (o->bo_n < BER_MIN_OID_LEN || o->bo_n > BER_MAX_OID_LEN || + o->bo_id[0] > 2 || o->bo_id[1] > 40) + return (0); + + v = (o->bo_id[0] * 40) + o->bo_id[1]; + for (i = 2, j = 0; i <= o->bo_n; v = o->bo_id[i], i++) { + for (k = 28; k >= 7; k -= 7) { + if (v >= (u_int)(1 << k)) { + if (len) + buf[j] = v >> k | BER_TAG_MORE; + j++; + } + } + if (len) + buf[j] = v & BER_TAG_TYPE_MASK; + j++; + } + + return (j); +} + +int +ber_string2oid(const char *oidstr, struct ber_oid *o) +{ + char *sp, *p, str[BUFSIZ]; + const char *errstr; + + if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str)) + return (-1); + bzero(o, sizeof(*o)); + + /* Parse OID strings in the common forms n.n.n, n_n_n_n, or n-n-n */ + for (p = sp = str; p != NULL; sp = p) { + if ((p = strpbrk(p, "._-")) != NULL) + *p++ = '\0'; + o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr); + if (errstr || o->bo_n > BER_MAX_OID_LEN) + return (-1); + } + + return (0); +} + +struct ber_element * +ber_add_oid(struct ber_element *prev, struct ber_oid *o) +{ + struct ber_element *elm; + u_int8_t *buf; + size_t len; + + if ((elm = ber_get_element(BER_TYPE_OBJECT)) == NULL) + return (NULL); + + if ((len = ber_oid2ber(o, NULL, 0)) == 0) + goto fail; + + if ((buf = calloc(1, len)) == NULL) + goto fail; + + elm->be_val = buf; + elm->be_len = len; + elm->be_free = 1; + + if (ber_oid2ber(o, buf, len) != len) + goto fail; + + ber_link_elements(prev, elm); + + return (elm); + + fail: + ber_free_elements(elm); + return (NULL); +} + +struct ber_element * +ber_add_noid(struct ber_element *prev, struct ber_oid *o, int n) +{ + struct ber_oid no; + + if (n > BER_MAX_OID_LEN) + return (NULL); + no.bo_n = n; + bcopy(&o->bo_id, &no.bo_id, sizeof(no.bo_id)); + + return (ber_add_oid(prev, &no)); +} + +struct ber_element * +ber_add_oidstring(struct ber_element *prev, const char *oidstr) +{ + struct ber_oid o; + + if (ber_string2oid(oidstr, &o) == -1) + return (NULL); + + return (ber_add_oid(prev, &o)); +} + +int +ber_get_oid(struct ber_element *elm, struct ber_oid *o) +{ + u_int8_t *buf; + size_t len, i = 0, j = 0; + + if (elm->be_encoding != BER_TYPE_OBJECT) + return (-1); + + buf = elm->be_val; + len = elm->be_len; + + if (!buf[i]) + return (-1); + + bzero(o, sizeof(*o)); + o->bo_id[j++] = buf[i] / 40; + o->bo_id[j++] = buf[i++] % 40; + for (; i < len && j < BER_MAX_OID_LEN; i++) { + o->bo_id[j] = (o->bo_id[j] << 7) + (buf[i] & ~0x80); + if (buf[i] & 0x80) + continue; + j++; + } + o->bo_n = j; + + return (0); +} + +struct ber_element * +ber_printf_elements(struct ber_element *ber, char *fmt, ...) +{ + va_list ap; + int d, class; + size_t len; + unsigned long type; + long long i; + char *s; + void *p; + struct ber_oid *o; + struct ber_element *sub = ber, *e; + + va_start(ap, fmt); + while (*fmt) { + switch (*fmt++) { + case 'B': + p = va_arg(ap, void *); + len = va_arg(ap, size_t); + ber = ber_add_bitstring(ber, p, len); + break; + case 'b': + d = va_arg(ap, int); + ber = ber_add_boolean(ber, d); + break; + case 'd': + d = va_arg(ap, int); + ber = ber_add_integer(ber, d); + break; + case 'e': + e = va_arg(ap, struct ber_element *); + ber_link_elements(ber, e); + break; + case 'E': + i = va_arg(ap, long long); + ber = ber_add_enumerated(ber, i); + break; + case 'i': + i = va_arg(ap, long long); + ber = ber_add_integer(ber, i); + break; + case 'O': + o = va_arg(ap, struct ber_oid *); + ber = ber_add_oid(ber, o); + break; + case 'o': + s = va_arg(ap, char *); + ber = ber_add_oidstring(ber, s); + break; + case 's': + s = va_arg(ap, char *); + ber = ber_add_string(ber, s); + break; + case 't': + class = va_arg(ap, int); + type = va_arg(ap, unsigned long); + ber_set_header(ber, class, type); + break; + case 'x': + s = va_arg(ap, char *); + len = va_arg(ap, size_t); + ber = ber_add_nstring(ber, s, len); + break; + case '0': + ber = ber_add_null(ber); + break; + case '{': + ber = sub = ber_add_sequence(ber); + break; + case '(': + ber = sub = ber_add_set(ber); + break; + case '}': + case ')': + ber = sub; + break; + case '.': + ber = ber_add_eoc(ber); + break; + default: + break; + } + } + va_end(ap); + + return (ber); +} + +int +ber_scanf_elements(struct ber_element *ber, char *fmt, ...) +{ +#define _MAX_SEQ 128 + va_list ap; + int *d, level = -1; + unsigned long *t; + long long *i; + void **ptr; + size_t *len, ret = 0, n = strlen(fmt); + char **s; + struct ber_oid *o; + struct ber_element *parent[_MAX_SEQ], **e; + + bzero(parent, sizeof(struct ber_element *) * _MAX_SEQ); + + va_start(ap, fmt); + while (*fmt != '\0' && ber != NULL) { + switch (*fmt++) { + case 'B': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_bitstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case 'b': + d = va_arg(ap, int *); + if (ber_get_boolean(ber, d) == -1) + goto fail; + ret++; + break; + case 'e': + e = va_arg(ap, struct ber_element **); + *e = ber; + ret++; + continue; + case 'E': + i = va_arg(ap, long long *); + if (ber_get_enumerated(ber, i) == -1) + goto fail; + ret++; + break; + case 'i': + i = va_arg(ap, long long *); + if (ber_get_integer(ber, i) == -1) + goto fail; + ret++; + break; + case 'o': + o = va_arg(ap, struct ber_oid *); + if (ber_get_oid(ber, o) == -1) + goto fail; + ret++; + break; + case 'S': + ret++; + break; + case 's': + s = va_arg(ap, char **); + if (ber_get_string(ber, s) == -1) + goto fail; + ret++; + break; + case 't': + d = va_arg(ap, int *); + t = va_arg(ap, unsigned long *); + *d = ber->be_class; + *t = ber->be_type; + ret++; + continue; + case 'x': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_nstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case '0': + if (ber->be_encoding != BER_TYPE_NULL) + goto fail; + ret++; + break; + case '.': + if (ber->be_encoding != BER_TYPE_EOC) + goto fail; + ret++; + break; + case '{': + case '(': + if (ber->be_encoding != BER_TYPE_SEQUENCE && + ber->be_encoding != BER_TYPE_SET) + goto fail; + if (ber->be_sub == NULL || level >= _MAX_SEQ-1) + goto fail; + parent[++level] = ber; + ber = ber->be_sub; + ret++; + continue; + case '}': + case ')': + if (parent[level] == NULL) + goto fail; + ber = parent[level--]; + ret++; + continue; + default: + goto fail; + } + + ber = ber->be_next; + } + va_end(ap); + return (ret == n ? 0 : -1); + + fail: + va_end(ap); + return (-1); + +} + +/* + * write ber elements to the socket + * + * params: + * ber holds the socket + * root fully populated element tree + * + * returns number of bytes written, or -1 on error and sets errno + */ +int +ber_write_elements(struct ber *ber, struct ber_element *root) +{ + size_t len; + + /* calculate length because only the definite form is required */ + len = ber_calc_len(root); + DPRINTF("write ber element of %zd bytes length\n", len); + + if (ber->br_wbuf != NULL && ber->br_wbuf + len > ber->br_wend) { + free(ber->br_wbuf); + ber->br_wbuf = NULL; + } + if (ber->br_wbuf == NULL) { + if ((ber->br_wbuf = malloc(len)) == NULL) + return -1; + ber->br_wend = ber->br_wbuf + len; + } + + /* reset write pointer */ + ber->br_wptr = ber->br_wbuf; + + if (ber_dump_element(ber, root) == -1) + return -1; + + /* XXX this should be moved to a different function */ + if (ber->fd != -1) + return write(ber->fd, ber->br_wbuf, len); + + return (len); +} + +/* + * read ber elements from the socket + * + * params: + * ber holds the socket and lot more + * root if NULL, build up an element tree from what we receive on + * the wire. If not null, use the specified encoding for the + * elements received. + * + * returns: + * !=NULL, elements read and store in the ber_element tree + * NULL, type mismatch or read error + */ +struct ber_element * +ber_read_elements(struct ber *ber, struct ber_element *elm) +{ + struct ber_element *root = elm; + + if (root == NULL) { + if ((root = ber_get_element(0)) == NULL) + return NULL; + } + + DPRINTF("read ber elements, root %p\n", root); + + if (ber_read_element(ber, root) == -1) { + /* Cleanup if root was allocated by us */ + if (elm == NULL) + ber_free_elements(root); + return NULL; + } + + return root; +} + +void +ber_free_elements(struct ber_element *root) +{ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + ber_free_elements(root->be_sub); + if (root->be_next) + ber_free_elements(root->be_next); + if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING || + root->be_encoding == BER_TYPE_BITSTRING || + root->be_encoding == BER_TYPE_OBJECT)) + free(root->be_val); + free(root); +} + +/* + * internal functions + */ + +size_t +ber_calc_len(struct ber_element *root) +{ + unsigned long t; + size_t s; + size_t size = 2; /* minimum 1 byte head and 1 byte size */ + + /* calculate the real length of a sequence or set */ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + root->be_len = ber_calc_len(root->be_sub); + + /* fix header length for extended types */ + if (root->be_type > BER_TYPE_SINGLE_MAX) + for (t = root->be_type; t > 0; t >>= 7) + size++; + if (root->be_len >= BER_TAG_MORE) + for (s = root->be_len; s > 0; s >>= 8) + size++; + + /* calculate the length of the following elements */ + if (root->be_next) + size += ber_calc_len(root->be_next); + + /* This is an empty element, do not use a minimal size */ + if (root->be_type == BER_TYPE_EOC && root->be_len == 0) + return (0); + + return (root->be_len + size); +} + +static int +ber_dump_element(struct ber *ber, struct ber_element *root) +{ + unsigned long long l; + int i; + uint8_t u; + + ber_dump_header(ber, root); + + switch (root->be_encoding) { + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + l = (unsigned long long)root->be_numeric; + for (i = root->be_len; i > 0; i--) { + u = (l >> ((i - 1) * 8)) & 0xff; + ber_putc(ber, u); + } + break; + case BER_TYPE_BITSTRING: + return -1; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + ber_write(ber, root->be_val, root->be_len); + break; + case BER_TYPE_NULL: /* no payload */ + case BER_TYPE_EOC: + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (root->be_sub && ber_dump_element(ber, root->be_sub) == -1) + return -1; + break; + } + + if (root->be_next == NULL) + return 0; + return ber_dump_element(ber, root->be_next); +} + +static void +ber_dump_header(struct ber *ber, struct ber_element *root) +{ + u_char id = 0, t, buf[8]; + unsigned long type; + size_t size; + + /* class universal, type encoding depending on type value */ + /* length encoding */ + if (root->be_type <= BER_TYPE_SINGLE_MAX) { + id = root->be_type | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + } else { + id = BER_TAG_MASK | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + + for (t = 0, type = root->be_type; type > 0; type >>= 7) + buf[t++] = type & ~BER_TAG_MORE; + + while (t-- > 0) { + if (t > 0) + buf[t] |= BER_TAG_MORE; + ber_putc(ber, buf[t]); + } + } + + if (root->be_len < BER_TAG_MORE) { + /* short form */ + ber_putc(ber, root->be_len); + } else { + for (t = 0, size = root->be_len; size > 0; size >>= 8) + buf[t++] = size & 0xff; + + ber_putc(ber, t | BER_TAG_MORE); + + while (t > 0) + ber_putc(ber, buf[--t]); + } +} + +static void +ber_putc(struct ber *ber, u_char c) +{ + if (ber->br_wptr + 1 <= ber->br_wend) + *ber->br_wptr = c; + ber->br_wptr++; +} + +static void +ber_write(struct ber *ber, void *buf, size_t len) +{ + if (ber->br_wptr + len <= ber->br_wend) + bcopy(buf, ber->br_wptr, len); + ber->br_wptr += len; +} + +/* + * extract a BER encoded tag. There are two types, a short and long form. + */ +static ssize_t +get_id(struct ber *b, unsigned long *tag, int *class, int *cstruct) +{ + u_char u; + size_t i = 0; + unsigned long t = 0; + + if (ber_getc(b, &u) == -1) + return -1; + + *class = (u >> BER_CLASS_SHIFT) & BER_CLASS_MASK; + *cstruct = (u & BER_TYPE_CONSTRUCTED) == BER_TYPE_CONSTRUCTED; + + if ((u & BER_TAG_MASK) != BER_TAG_MASK) { + *tag = u & BER_TAG_MASK; + return 1; + } + + do { + if (ber_getc(b, &u) == -1) + return -1; + t = (t << 7) | (u & ~BER_TAG_MORE); + i++; + } while (u & BER_TAG_MORE); + + if (i > sizeof(unsigned long)) { + errno = ERANGE; + return -1; + } + + *tag = t; + return i + 1; +} + +/* + * extract length of a ber object -- if length is unknown an error is returned. + */ +static ssize_t +get_len(struct ber *b, ssize_t *len) +{ + u_char u, n; + ssize_t s, r; + + if (ber_getc(b, &u) == -1) + return -1; + if ((u & BER_TAG_MORE) == 0) { + /* short form */ + *len = u; + return 1; + } + + n = u & ~BER_TAG_MORE; + if (sizeof(ssize_t) < n) { + errno = ERANGE; + return -1; + } + r = n + 1; + + for (s = 0; n > 0; n--) { + if (ber_getc(b, &u) == -1) + return -1; + s = (s << 8) | u; + } + + if (s < 0) { + /* overflow */ + errno = ERANGE; + return -1; + } + + if (s == 0) { + /* invalid encoding */ + errno = EINVAL; + return -1; + } + + *len = s; + return r; +} + +static ssize_t +ber_read_element(struct ber *ber, struct ber_element *elm) +{ + long long val = 0; + struct ber_element *next; + unsigned long type; + int i, class, cstruct; + ssize_t len, r, totlen = 0; + u_char c; + + if ((r = get_id(ber, &type, &class, &cstruct)) == -1) + return -1; + DPRINTF("ber read got class %d type %lu, %s\n", + class, type, cstruct ? "constructive" : "primitive"); + totlen += r; + if ((r = get_len(ber, &len)) == -1) + return -1; + DPRINTF("ber read element size %zd\n", len); + totlen += r + len; + + elm->be_type = type; + elm->be_len = len; + elm->be_class = class; + + if (elm->be_encoding == 0) { + /* try to figure out the encoding via class, type and cstruct */ + if (cstruct) + elm->be_encoding = BER_TYPE_SEQUENCE; + else if (class == BER_CLASS_UNIVERSAL) + elm->be_encoding = type; + else if (ber->br_application != NULL) { + /* + * Ask the application to map the encoding to a + * universal type. For example, a SMI IpAddress + * type is defined as 4 byte OCTET STRING. + */ + elm->be_encoding = (*ber->br_application)(elm); + } else + /* last resort option */ + elm->be_encoding = BER_TYPE_NULL; + } + + switch (elm->be_encoding) { + case BER_TYPE_EOC: /* End-Of-Content */ + break; + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + if (len > (ssize_t)sizeof(long long)) + return -1; + for (i = 0; i < len; i++) { + if (ber_getc(ber, &c) != 1) + return -1; + val <<= 8; + val |= c; + } + + /* sign extend if MSB is set */ + if (val >> ((i - 1) * 8) & 0x80) + val |= ULLONG_MAX << (i * 8); + elm->be_numeric = val; + break; + case BER_TYPE_BITSTRING: + elm->be_val = malloc(len); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + break; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + elm->be_val = malloc(len + 1); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + ((u_char *)elm->be_val)[len] = '\0'; + break; + case BER_TYPE_NULL: /* no payload */ + if (len != 0) + return -1; + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (elm->be_sub == NULL) { + if ((elm->be_sub = ber_get_element(0)) == NULL) + return -1; + } + next = elm->be_sub; + while (len > 0) { + r = ber_read_element(ber, next); + if (r == -1) + return -1; + len -= r; + if (len > 0 && next->be_next == NULL) { + if ((next->be_next = ber_get_element(0)) == + NULL) + return -1; + } + next = next->be_next; + } + break; + } + return totlen; +} + +static ssize_t +ber_readbuf(struct ber *b, void *buf, size_t nbytes) +{ + size_t sz; + size_t len; + + if (b->br_rbuf == NULL) + return -1; + + sz = b->br_rend - b->br_rptr; + len = MIN(nbytes, sz); + if (len == 0) { + errno = ECANCELED; + return (-1); /* end of buffer and parser wants more data */ + } + + bcopy(b->br_rptr, buf, len); + b->br_rptr += len; + + return (len); +} + +void +ber_set_readbuf(struct ber *b, void *buf, size_t len) +{ + b->br_rbuf = b->br_rptr = buf; + b->br_rend = (u_int8_t *)buf + len; +} + +ssize_t +ber_get_writebuf(struct ber *b, void **buf) +{ + if (b->br_wbuf == NULL) + return -1; + *buf = b->br_wbuf; + return (b->br_wend - b->br_wbuf); +} + +void +ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *)) +{ + b->br_application = cb; +} + +void +ber_free(struct ber *b) +{ + if (b->br_wbuf != NULL) + free (b->br_wbuf); +} + +static ssize_t +ber_getc(struct ber *b, u_char *c) +{ + ssize_t r; + /* + * XXX calling read here is wrong in many ways. The most obvious one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + if (b->fd == -1) + r = ber_readbuf(b, c, 1); + else + r = read(b->fd, c, 1); + return r; +} + +static ssize_t +ber_read(struct ber *ber, void *buf, size_t len) +{ + u_char *b = buf; + ssize_t r, remain = len; + + /* + * XXX calling read here is wrong in many ways. The most obvious one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + + while (remain > 0) { + if (ber->fd == -1) + r = ber_readbuf(ber, b, remain); + else + r = read(ber->fd, b, remain); + if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (r == 0) + return (b - (u_char *)buf); + b += r; + remain -= r; + } + return (b - (u_char *)buf); +} |