From 94dc650256935aa0c5cfff7675e535c54bb8c9b7 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 27 Jan 2006 00:45:54 +0000 Subject: Move rrdbot code into right project. --- daemon/Makefile.am | 11 + daemon/bsnmp/Makefile.am | 4 + daemon/bsnmp/asn1.c | 1000 ++++++++++++++++++++++++++++++++++++ daemon/bsnmp/asn1.h | 183 +++++++ daemon/bsnmp/snmp.c | 1081 +++++++++++++++++++++++++++++++++++++++ daemon/bsnmp/snmp.h | 174 +++++++ daemon/bsnmp/snmppriv.h | 45 ++ daemon/common/hash.c | 380 ++++++++++++++ daemon/common/hash.h | 151 ++++++ daemon/common/server-mainloop.c | 424 +++++++++++++++ daemon/common/server-mainloop.h | 27 + daemon/common/sock-any.c | 385 ++++++++++++++ daemon/common/sock-any.h | 90 ++++ daemon/common/stringx.c | 155 ++++++ daemon/common/stringx.h | 53 ++ daemon/config.c | 575 +++++++++++++++++++++ daemon/rrd-update.c | 134 +++++ daemon/rrdbotd.c | 239 +++++++++ daemon/rrdbotd.h | 182 +++++++ daemon/snmp-engine.c | 636 +++++++++++++++++++++++ daemon/snmp-help.c | 111 ++++ daemon/snmpclient.c | 967 ++++++++++++++++++++++++++++++++++ daemon/snmpclient.h | 187 +++++++ daemon/usuals.h | 77 +++ 24 files changed, 7271 insertions(+) create mode 100644 daemon/Makefile.am create mode 100644 daemon/bsnmp/Makefile.am create mode 100644 daemon/bsnmp/asn1.c create mode 100644 daemon/bsnmp/asn1.h create mode 100644 daemon/bsnmp/snmp.c create mode 100644 daemon/bsnmp/snmp.h create mode 100644 daemon/bsnmp/snmppriv.h create mode 100644 daemon/common/hash.c create mode 100644 daemon/common/hash.h create mode 100644 daemon/common/server-mainloop.c create mode 100644 daemon/common/server-mainloop.h create mode 100644 daemon/common/sock-any.c create mode 100644 daemon/common/sock-any.h create mode 100644 daemon/common/stringx.c create mode 100644 daemon/common/stringx.h create mode 100644 daemon/config.c create mode 100644 daemon/rrd-update.c create mode 100644 daemon/rrdbotd.c create mode 100644 daemon/rrdbotd.h create mode 100644 daemon/snmp-engine.c create mode 100644 daemon/snmp-help.c create mode 100644 daemon/snmpclient.c create mode 100644 daemon/snmpclient.h create mode 100644 daemon/usuals.h diff --git a/daemon/Makefile.am b/daemon/Makefile.am new file mode 100644 index 0000000..fa8144a --- /dev/null +++ b/daemon/Makefile.am @@ -0,0 +1,11 @@ +SUBDIRS = bsnmp + +sbin_PROGRAMS = rrdbotd + +rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c \ + snmp-help.c snmp-engine.c rrd-update.c \ + common/server-mainloop.c common/server-mainloop.h \ + common/sock-any.h common/sock-any.c \ + usuals.h common/stringx.h common/stringx.c common/hash.h common/hash.c +rrdbotd_CFLAGS = -I${top_srcdir}/src/common/ -I${top_srcdir} -Ibsnmp +rrdbotd_LDADD = $(top_builddir)/src/bsnmp/libbsnmp-custom.a diff --git a/daemon/bsnmp/Makefile.am b/daemon/bsnmp/Makefile.am new file mode 100644 index 0000000..7a7a739 --- /dev/null +++ b/daemon/bsnmp/Makefile.am @@ -0,0 +1,4 @@ + +noinst_LIBRARIES = libbsnmp-custom.a +libbsnmp_custom_a_SOURCES = asn1.c asn1.h snmp.c snmp.h snmppriv.h + diff --git a/daemon/bsnmp/asn1.c b/daemon/bsnmp/asn1.c new file mode 100644 index 0000000..8a206ee --- /dev/null +++ b/daemon/bsnmp/asn1.c @@ -0,0 +1,1000 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/asn1.c,v 1.28 2004/08/06 08:46:49 brandt Exp $ + * + * ASN.1 for SNMP. + */ +#include +#include +#include +#include +#include +#include +#include +#include "asn1.h" + +static void asn_error_func(const struct asn_buf *, const char *, ...); + +void (*asn_error)(const struct asn_buf *, const char *, ...) = asn_error_func; + +/* + * Read the next header. This reads the tag (note, that only single + * byte tags are supported for now) and the length field. The length field + * is restricted to a 32-bit value. + * All errors of this function stop the decoding. + */ +enum asn_err +asn_get_header(struct asn_buf *b, u_char *type, asn_len_t *len) +{ + u_int length; + + if (b->asn_len == 0) { + asn_error(b, "no identifier for header"); + return (ASN_ERR_EOBUF); + } + *type = *b->asn_cptr; + if ((*type & ASN_TYPE_MASK) > 0x30) { + asn_error(b, "types > 0x30 not supported (%u)", + *type & ASN_TYPE_MASK); + return (ASN_ERR_FAILED); + } + b->asn_cptr++; + b->asn_len--; + if (b->asn_len == 0) { + asn_error(b, "no length field"); + return (ASN_ERR_EOBUF); + } + if (*b->asn_cptr & 0x80) { + length = *b->asn_cptr++ & 0x7f; + b->asn_len--; + if (length == 0) { + asn_error(b, "indefinite length not supported"); + return (ASN_ERR_FAILED); + } + if (length > ASN_MAXLENLEN) { + asn_error(b, "long length too long (%u)", length); + return (ASN_ERR_FAILED); + } + if (length > b->asn_len) { + asn_error(b, "long length truncated"); + return (ASN_ERR_EOBUF); + } + *len = 0; + while (length--) { + *len = (*len << 8) | *b->asn_cptr++; + b->asn_len--; + } + } else { + *len = *b->asn_cptr++; + b->asn_len--; + } + return (ASN_ERR_OK); +} + +/* + * Write a length field (restricted to values < 2^32-1) and return the + * number of bytes this field takes. If ptr is NULL, the length is computed + * but nothing is written. If the length would be too large return 0. + */ +static u_int +asn_put_len(u_char *ptr, asn_len_t len) +{ + u_int lenlen, lenlen1; + asn_len_t tmp; + + if (len > ASN_MAXLEN) { + asn_error(NULL, "encoding length too long: (%u)", len); + return (0); + } + + if (len <= 127) { + if (ptr) + *ptr++ = (u_char)len; + return (1); + } else { + lenlen = 0; + /* compute number of bytes for value (is at least 1) */ + for (tmp = len; tmp != 0; tmp >>= 8) + lenlen++; + if (ptr != NULL) { + *ptr++ = (u_char)lenlen | 0x80; + lenlen1 = lenlen; + while (lenlen1-- > 0) { + ptr[lenlen1] = len & 0xff; + len >>= 8; + } + } + return (lenlen + 1); + } +} + +/* + * Write a header (tag and length fields). + * Tags are restricted to one byte tags (value <= 0x30) and the + * lenght field to 16-bit. All errors stop the encoding. + */ +enum asn_err +asn_put_header(struct asn_buf *b, u_char type, asn_len_t len) +{ + u_int lenlen; + + /* tag field */ + if ((type & ASN_TYPE_MASK) > 0x30) { + asn_error(NULL, "types > 0x30 not supported (%u)", + type & ASN_TYPE_MASK); + return (ASN_ERR_FAILED); + } + if (b->asn_len == 0) + return (ASN_ERR_EOBUF); + + *b->asn_ptr++ = type; + b->asn_len--; + + /* length field */ + if ((lenlen = asn_put_len(NULL, len)) == 0) + return (ASN_ERR_FAILED); + if (b->asn_len < lenlen) + return (ASN_ERR_EOBUF); + + (void)asn_put_len(b->asn_ptr, len); + b->asn_ptr += lenlen; + b->asn_len -= lenlen; + return (ASN_ERR_OK); +} + + +/* + * This constructs a temporary sequence header with space for the maximum + * length field (three byte). Set the pointer that ptr points to to the + * start of the encoded header. This is used for a later call to + * asn_commit_header which will fix-up the length field and move the + * value if needed. All errors should stop the encoding. + */ +#define TEMP_LEN (1 + ASN_MAXLENLEN + 1) +enum asn_err +asn_put_temp_header(struct asn_buf *b, u_char type, u_char **ptr) +{ + int ret; + + if (b->asn_len < TEMP_LEN) + return (ASN_ERR_EOBUF); + *ptr = b->asn_ptr; + if ((ret = asn_put_header(b, type, ASN_MAXLEN)) == ASN_ERR_OK) + assert(b->asn_ptr == *ptr + TEMP_LEN); + return (ret); +} +enum asn_err +asn_commit_header(struct asn_buf *b, u_char *ptr) +{ + asn_len_t len; + u_int lenlen, shift; + + /* compute length of encoded value without header */ + len = b->asn_ptr - (ptr + TEMP_LEN); + + /* insert length. may not fail. */ + lenlen = asn_put_len(ptr + 1, len); + if (lenlen > TEMP_LEN - 1) + return (ASN_ERR_FAILED); + + if (lenlen < TEMP_LEN - 1) { + /* shift value down */ + shift = (TEMP_LEN - 1) - lenlen; + memmove(ptr + 1 + lenlen, ptr + TEMP_LEN, len); + b->asn_ptr -= shift; + b->asn_len += shift; + } + return (ASN_ERR_OK); +} +#undef TEMP_LEN + +/* + * BER integer. This may be used to get a signed 64 bit integer at maximum. + * The maximum length should be checked by the caller. This cannot overflow + * if the caller ensures that len is at maximum 8. + * + * + */ +static enum asn_err +asn_get_real_integer(struct asn_buf *b, asn_len_t len, int64_t *vp) +{ + uint64_t val; + int neg = 0; + enum asn_err err; + + if (b->asn_len < len) { + asn_error(b, "truncated integer"); + return (ASN_ERR_EOBUF); + } + if (len == 0) { + asn_error(b, "zero-length integer"); + *vp = 0; + return (ASN_ERR_BADLEN); + } + err = ASN_ERR_OK; + if (len > 8) + err = ASN_ERR_RANGE; + else if (len > 1 && + ((*b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) || + (*b->asn_cptr == 0xff && (b->asn_cptr[1] & 0x80) == 0x80))) { + asn_error(b, "non-minimal integer"); + err = ASN_ERR_BADLEN; + } + + if (*b->asn_cptr & 0x80) + neg = 1; + val = 0; + while (len--) { + val <<= 8; + val |= neg ? (u_char)~*b->asn_cptr : *b->asn_cptr; + b->asn_len--; + b->asn_cptr++; + } + if (neg) { + *vp = -(int64_t)val - 1; + } else + *vp = (int64_t)val; + return (err); +} + +/* + * Write a signed integer with the given type. The caller has to ensure + * that the actual value is ok for this type. + */ +static enum asn_err +asn_put_real_integer(struct asn_buf *b, u_char type, int64_t ival) +{ + int i, neg = 0; +# define OCTETS 8 + u_char buf[OCTETS]; + uint64_t val; + enum asn_err ret; + + if (ival < 0) { + /* this may fail if |INT64_MIN| > |INT64_MAX| and + * the value is between * INT64_MIN <= ival < -(INT64_MAX+1) */ + val = (uint64_t)-(ival + 1); + neg = 1; + } else + val = (uint64_t)ival; + + /* split the value into octets */ + for (i = OCTETS - 1; i >= 0; i--) { + buf[i] = val & 0xff; + if (neg) + buf[i] = ~buf[i]; + val >>= 8; + } + /* no leading 9 zeroes or ones */ + for (i = 0; i < OCTETS - 1; i++) + if (!((buf[i] == 0xff && (buf[i + 1] & 0x80) != 0) || + (buf[i] == 0x00 && (buf[i + 1] & 0x80) == 0))) + break; + if ((ret = asn_put_header(b, type, OCTETS - i))) + return (ret); + if (OCTETS - (u_int)i > b->asn_len) + return (ASN_ERR_EOBUF); + + while (i < OCTETS) { + *b->asn_ptr++ = buf[i++]; + b->asn_len--; + } + return (ASN_ERR_OK); +# undef OCTETS +} + + +/* + * The same for unsigned 64-bitters. Here we have the problem, that overflow + * can happen, because the value maybe 9 bytes long. In this case the + * first byte must be 0. + */ +static enum asn_err +asn_get_real_unsigned(struct asn_buf *b, asn_len_t len, uint64_t *vp) +{ + enum asn_err err; + + if (b->asn_len < len) { + asn_error(b, "truncated integer"); + return (ASN_ERR_EOBUF); + } + if (len == 0) { + asn_error(b, "zero-length integer"); + *vp = 0; + return (ASN_ERR_BADLEN); + } + err = ASN_ERR_OK; + *vp = 0; + if ((*b->asn_cptr & 0x80) || (len == 9 && *b->asn_cptr != 0)) { + /* negative integer or too larger */ + *vp = 0xffffffffffffffffULL; + err = ASN_ERR_RANGE; + } else if (len > 1 && + *b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) { + asn_error(b, "non-minimal unsigned"); + err = ASN_ERR_BADLEN; + } + + while (len--) { + *vp = (*vp << 8) | *b->asn_cptr++; + b->asn_len--; + } + return (err); +} + + +/* + * Values with the msb on need 9 octets. + */ +static int +asn_put_real_unsigned(struct asn_buf *b, u_char type, uint64_t val) +{ + int i; +# define OCTETS 9 + u_char buf[OCTETS]; + enum asn_err ret; + + /* split the value into octets */ + for (i = OCTETS - 1; i >= 0; i--) { + buf[i] = val & 0xff; + val >>= 8; + } + /* no leading 9 zeroes */ + for (i = 0; i < OCTETS - 1; i++) + if (!(buf[i] == 0x00 && (buf[i + 1] & 0x80) == 0)) + break; + if ((ret = asn_put_header(b, type, OCTETS - i))) + return (ret); + if (OCTETS - (u_int)i > b->asn_len) + return (ASN_ERR_EOBUF); + + while (i < OCTETS) { + *b->asn_ptr++ = buf[i++]; + b->asn_len--; + } +#undef OCTETS + return (ASN_ERR_OK); +} + +/* + * The ASN.1 INTEGER type is restricted to 32-bit signed by the SMI. + */ +enum asn_err +asn_get_integer_raw(struct asn_buf *b, asn_len_t len, int32_t *vp) +{ + int64_t val; + enum asn_err ret; + + if ((ret = asn_get_real_integer(b, len, &val)) == ASN_ERR_OK) { + if (len > 4) + ret = ASN_ERR_BADLEN; + else if (val > INT32_MAX || val < INT32_MIN) + /* may not happen */ + ret = ASN_ERR_RANGE; + *vp = (int32_t)val; + } + return (ret); +} + +enum asn_err +asn_get_integer(struct asn_buf *b, int32_t *vp) +{ + asn_len_t len; + u_char type; + enum asn_err err; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != ASN_TYPE_INTEGER) { + asn_error(b, "bad type for integer (%u)", type); + return (ASN_ERR_TAG); + } + + return (asn_get_integer_raw(b, len, vp)); +} + +enum asn_err +asn_put_integer(struct asn_buf *b, int32_t val) +{ + return (asn_put_real_integer(b, ASN_TYPE_INTEGER, val)); +} + +/* + * OCTETSTRING + * + * <0x04> + * + * Get an octetstring. noctets must point to the buffer size and on + * return will contain the size of the octetstring, regardless of the + * buffer size. + */ +enum asn_err +asn_get_octetstring_raw(struct asn_buf *b, asn_len_t len, u_char *octets, + u_int *noctets) +{ + enum asn_err err = ASN_ERR_OK; + + if (*noctets < len) { + asn_error(b, "octetstring truncated"); + err = ASN_ERR_RANGE; + } + if (b->asn_len < len) { + asn_error(b, "truncatet octetstring"); + return (ASN_ERR_EOBUF); + } + if (*noctets < len) + memcpy(octets, b->asn_cptr, *noctets); + else + memcpy(octets, b->asn_cptr, len); + *noctets = len; + b->asn_cptr += len; + b->asn_len -= len; + return (err); +} + +enum asn_err +asn_get_octetstring(struct asn_buf *b, u_char *octets, u_int *noctets) +{ + enum asn_err err; + u_char type; + asn_len_t len; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != ASN_TYPE_OCTETSTRING) { + asn_error(b, "bad type for octetstring (%u)", type); + return (ASN_ERR_TAG); + } + return (asn_get_octetstring_raw(b, len, octets, noctets)); +} + +enum asn_err +asn_put_octetstring(struct asn_buf *b, const u_char *octets, u_int noctets) +{ + enum asn_err ret; + + if ((ret = asn_put_header(b, ASN_TYPE_OCTETSTRING, noctets)) != ASN_ERR_OK) + return (ret); + if (b->asn_len < noctets) + return (ASN_ERR_EOBUF); + + memcpy(b->asn_ptr, octets, noctets); + b->asn_ptr += noctets; + b->asn_len -= noctets; + return (ASN_ERR_OK); +} + +/* + * NULL + * + * <0x05> <0x00> + */ +enum asn_err +asn_get_null_raw(struct asn_buf *b, asn_len_t len) +{ + if (len != 0) { + if (b->asn_len < len) { + asn_error(b, "truncated NULL"); + return (ASN_ERR_EOBUF); + } + asn_error(b, "bad length for NULL (%u)", len); + b->asn_len -= len; + b->asn_ptr += len; + return (ASN_ERR_BADLEN); + } + return (ASN_ERR_OK); +} + +enum asn_err +asn_get_null(struct asn_buf *b) +{ + u_char type; + asn_len_t len; + enum asn_err err; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != ASN_TYPE_NULL) { + asn_error(b, "bad type for NULL (%u)", type); + return (ASN_ERR_TAG); + } + return (asn_get_null_raw(b, len)); +} + +enum asn_err +asn_put_null(struct asn_buf *b) +{ + return (asn_put_header(b, ASN_TYPE_NULL, 0)); +} + +enum asn_err +asn_put_exception(struct asn_buf *b, u_int except) +{ + return (asn_put_header(b, ASN_CLASS_CONTEXT | except, 0)); +} + +/* + * OBJID + * + * <0x06> + */ +enum asn_err +asn_get_objid_raw(struct asn_buf *b, asn_len_t len, struct asn_oid *oid) +{ + asn_subid_t subid; + enum asn_err err; + + if (b->asn_len < len) { + asn_error(b, "truncated OBJID"); + return (ASN_ERR_EOBUF); + } + oid->len = 0; + if (len == 0) { + asn_error(b, "short OBJID"); + oid->subs[oid->len++] = 0; + oid->subs[oid->len++] = 0; + return (ASN_ERR_BADLEN); + } + err = ASN_ERR_OK; + while (len != 0) { + if (oid->len == ASN_MAXOIDLEN) { + asn_error(b, "OID too long (%u)", oid->len); + b->asn_cptr += len; + b->asn_len -= len; + return (ASN_ERR_BADLEN); + } + subid = 0; + do { + if (len == 0) { + asn_error(b, "unterminated subid"); + return (ASN_ERR_EOBUF); + } + if (subid > (ASN_MAXID >> 7)) { + asn_error(b, "OBID subid too larger"); + err = ASN_ERR_RANGE; + } + subid = (subid << 7) | (*b->asn_cptr & 0x7f); + len--; + b->asn_len--; + } while (*b->asn_cptr++ & 0x80); + if (oid->len == 0) { + if (subid < 80) { + oid->subs[oid->len++] = subid / 40; + oid->subs[oid->len++] = subid % 40; + } else { + oid->subs[oid->len++] = 2; + oid->subs[oid->len++] = subid - 80; + } + } else { + oid->subs[oid->len++] = subid; + } + } + return (err); + +} + +enum asn_err +asn_get_objid(struct asn_buf *b, struct asn_oid *oid) +{ + u_char type; + asn_len_t len; + enum asn_err err; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != ASN_TYPE_OBJID) { + asn_error(b, "bad type for OBJID (%u)", type); + return (ASN_ERR_TAG); + } + return (asn_get_objid_raw(b, len, oid)); +} + +enum asn_err +asn_put_objid(struct asn_buf *b, const struct asn_oid *oid) +{ + asn_subid_t first, sub; + enum asn_err err, err1; + u_int i, oidlen; + asn_len_t len; + + err = ASN_ERR_OK; + if (oid->len == 0) { + /* illegal */ + asn_error(NULL, "short oid"); + err = ASN_ERR_RANGE; + first = 0; + oidlen = 2; + } else if (oid->len == 1) { + /* illegal */ + asn_error(b, "short oid"); + if (oid->subs[0] > 2) + asn_error(NULL, "oid[0] too large (%u)", oid->subs[0]); + err = ASN_ERR_RANGE; + first = oid->subs[0] * 40; + oidlen = 2; + } else { + if (oid->len > ASN_MAXOIDLEN) { + asn_error(NULL, "oid too long %u", oid->len); + err = ASN_ERR_RANGE; + } + if (oid->subs[0] > 2 || + (oid->subs[0] < 2 && oid->subs[0] >= 40)) { + asn_error(NULL, "oid out of range (%u,%u)", + oid->subs[0], oid->subs[1]); + err = ASN_ERR_RANGE; + } + first = 40 * oid->subs[0] + oid->subs[1]; + oidlen = oid->len; + } + len = 0; + for (i = 1; i < oidlen; i++) { + sub = (i == 1) ? first : oid->subs[i]; + if (sub > ASN_MAXID) { + asn_error(NULL, "oid subid too large"); + err = ASN_ERR_RANGE; + } + len += (sub <= 0x7f) ? 1 + : (sub <= 0x3fff) ? 2 + : (sub <= 0x1fffff) ? 3 + : (sub <= 0xfffffff) ? 4 + : 5; + } + if ((err1 = asn_put_header(b, ASN_TYPE_OBJID, len)) != ASN_ERR_OK) + return (err1); + if (b->asn_len < len) + return (ASN_ERR_EOBUF); + + for (i = 1; i < oidlen; i++) { + sub = (i == 1) ? first : oid->subs[i]; + if (sub <= 0x7f) { + *b->asn_ptr++ = sub; + b->asn_len--; + } else if (sub <= 0x3fff) { + *b->asn_ptr++ = (sub >> 7) | 0x80; + *b->asn_ptr++ = sub & 0x7f; + b->asn_len -= 2; + } else if (sub <= 0x1fffff) { + *b->asn_ptr++ = (sub >> 14) | 0x80; + *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; + *b->asn_ptr++ = sub & 0x7f; + b->asn_len -= 3; + } else if (sub <= 0xfffffff) { + *b->asn_ptr++ = (sub >> 21) | 0x80; + *b->asn_ptr++ = ((sub >> 14) & 0x7f) | 0x80; + *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; + *b->asn_ptr++ = sub & 0x7f; + b->asn_len -= 4; + } else { + *b->asn_ptr++ = (sub >> 28) | 0x80; + *b->asn_ptr++ = ((sub >> 21) & 0x7f) | 0x80; + *b->asn_ptr++ = ((sub >> 14) & 0x7f) | 0x80; + *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; + *b->asn_ptr++ = sub & 0x7f; + b->asn_len -= 5; + } + } + return (err); +} +/* + * SEQUENCE header + * + * <0x10|0x20> + */ +enum asn_err +asn_get_sequence(struct asn_buf *b, asn_len_t *len) +{ + u_char type; + enum asn_err err; + + if ((err = asn_get_header(b, &type, len)) != ASN_ERR_OK) + return (err); + if (type != (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED)) { + asn_error(b, "bad sequence type %u", type); + return (ASN_ERR_TAG); + } + if (*len > b->asn_len) { + asn_error(b, "truncated sequence"); + return (ASN_ERR_EOBUF); + } + return (ASN_ERR_OK); +} + +/* + * Application types + * + * 0x40 4 MSB 2MSB 2LSB LSB + */ +enum asn_err +asn_get_ipaddress_raw(struct asn_buf *b, asn_len_t len, u_char *addr) +{ + u_int i; + + if (b->asn_len < len) { + asn_error(b, "truncated ip-address"); + return (ASN_ERR_EOBUF); + } + if (len < 4) { + asn_error(b, "short length for ip-Address %u", len); + for (i = 0; i < len; i++) + *addr++ = *b->asn_cptr++; + while (i++ < len) + *addr++ = 0; + b->asn_len -= len; + return (ASN_ERR_BADLEN); + } + for (i = 0; i < 4; i++) + *addr++ = *b->asn_cptr++; + b->asn_cptr += len - 4; + b->asn_len -= len; + return (ASN_ERR_OK); +} + +enum asn_err +asn_get_ipaddress(struct asn_buf *b, u_char *addr) +{ + u_char type; + asn_len_t len; + enum asn_err err; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != (ASN_CLASS_APPLICATION|ASN_APP_IPADDRESS)) { + asn_error(b, "bad type for ip-address %u", type); + return (ASN_ERR_TAG); + } + return (asn_get_ipaddress_raw(b, len, addr)); +} + +enum asn_err +asn_put_ipaddress(struct asn_buf *b, const u_char *addr) +{ + enum asn_err err; + + if ((err = asn_put_header(b, ASN_CLASS_APPLICATION|ASN_APP_IPADDRESS, + 4)) != ASN_ERR_OK) + return (err); + if (b->asn_len < 4) + return (ASN_ERR_EOBUF); + + memcpy(b->asn_ptr, addr, 4); + b->asn_ptr += 4; + b->asn_len -= 4; + return (ASN_ERR_OK); +} + + +/* + * UNSIGNED32 + * + * 0x42|0x41 ... + */ +enum asn_err +asn_get_uint32_raw(struct asn_buf *b, asn_len_t len, uint32_t *vp) +{ + uint64_t v; + enum asn_err err; + + if ((err = asn_get_real_unsigned(b, len, &v)) == ASN_ERR_OK) { + if (len > 5) { + asn_error(b, "uint32 too long %u", len); + err = ASN_ERR_BADLEN; + } else if (v > UINT32_MAX) { + asn_error(b, "uint32 too large %llu", v); + err = ASN_ERR_RANGE; + } + *vp = (uint32_t)v; + } + return (err); +} + +enum asn_err +asn_put_uint32(struct asn_buf *b, u_char type, uint32_t val) +{ + uint64_t v = val; + + return (asn_put_real_unsigned(b, ASN_CLASS_APPLICATION|type, v)); +} + +/* + * COUNTER64 + * 0x46 ... + */ +enum asn_err +asn_get_counter64_raw(struct asn_buf *b, asn_len_t len, uint64_t *vp) +{ + return (asn_get_real_unsigned(b, len, vp)); +} + +enum asn_err +asn_put_counter64(struct asn_buf *b, uint64_t val) +{ + return (asn_put_real_unsigned(b, + ASN_CLASS_APPLICATION | ASN_APP_COUNTER64, val)); +} + +/* + * TimeTicks + * 0x43 ... + */ +enum asn_err +asn_get_timeticks(struct asn_buf *b, uint32_t *vp) +{ + asn_len_t len; + u_char type; + enum asn_err err; + + if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) + return (err); + if (type != (ASN_CLASS_APPLICATION|ASN_APP_TIMETICKS)) { + asn_error(b, "bad type for timeticks %u", type); + return (ASN_ERR_TAG); + } + return (asn_get_uint32_raw(b, len, vp)); +} + +enum asn_err +asn_put_timeticks(struct asn_buf *b, uint32_t val) +{ + uint64_t v = val; + + return (asn_put_real_unsigned(b, + ASN_CLASS_APPLICATION | ASN_APP_TIMETICKS, v)); +} + +/* + * Construct a new OID by taking a range of sub ids of the original oid. + */ +void +asn_slice_oid(struct asn_oid *dest, const struct asn_oid *src, + u_int from, u_int to) +{ + if (from >= to) { + dest->len = 0; + return; + } + dest->len = to - from; + memcpy(dest->subs, &src->subs[from], dest->len * sizeof(dest->subs[0])); +} + +/* + * Append from to to + */ +void +asn_append_oid(struct asn_oid *to, const struct asn_oid *from) +{ + memcpy(&to->subs[to->len], &from->subs[0], + from->len * sizeof(from->subs[0])); + to->len += from->len; +} + +/* + * Skip a value + */ +enum asn_err +asn_skip(struct asn_buf *b, asn_len_t len) +{ + if (b->asn_len < len) + return (ASN_ERR_EOBUF); + b->asn_cptr += len; + b->asn_len -= len; + return (ASN_ERR_OK); +} + +/* + * Compare two OIDs. + * + * o1 < o2 : -1 + * o1 > o2 : +1 + * o1 = o2 : 0 + */ +int +asn_compare_oid(const struct asn_oid *o1, const struct asn_oid *o2) +{ + u_long i; + + for (i = 0; i < o1->len && i < o2->len; i++) { + if (o1->subs[i] < o2->subs[i]) + return (-1); + if (o1->subs[i] > o2->subs[i]) + return (+1); + } + if (o1->len < o2->len) + return (-1); + if (o1->len > o2->len) + return (+1); + return (0); +} + +/* + * Check whether an OID is a sub-string of another OID. + */ +int +asn_is_suboid(const struct asn_oid *o1, const struct asn_oid *o2) +{ + u_long i; + + for (i = 0; i < o1->len; i++) + if (i >= o2->len || o1->subs[i] != o2->subs[i]) + return (0); + return (1); +} + +/* + * Put a string representation of an oid into a user buffer. This buffer + * is assumed to be at least ASN_OIDSTRLEN characters long. + * + * sprintf is assumed not to fail here. + */ +char * +asn_oid2str_r(const struct asn_oid *oid, char *buf) +{ + u_int len, i; + char *ptr; + + if ((len = oid->len) > ASN_MAXOIDLEN) + len = ASN_MAXOIDLEN; + buf[0] = '\0'; + for (i = 0, ptr = buf; i < len; i++) { + if (i > 0) + *ptr++ = '.'; + ptr += sprintf(ptr, "%u", oid->subs[i]); + } + return (buf); +} + +/* + * Make a string from an OID in a private buffer. + */ +char * +asn_oid2str(const struct asn_oid *oid) +{ + static char str[ASN_OIDSTRLEN]; + + return (asn_oid2str_r(oid, str)); +} + + +static void +asn_error_func(const struct asn_buf *b, const char *err, ...) +{ + va_list ap; + u_long i; + + fprintf(stderr, "ASN.1: "); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + + if (b != NULL) { + fprintf(stderr, " at"); + for (i = 0; b->asn_len > i; i++) + fprintf(stderr, " %02x", b->asn_cptr[i]); + } + fprintf(stderr, "\n"); +} diff --git a/daemon/bsnmp/asn1.h b/daemon/bsnmp/asn1.h new file mode 100644 index 0000000..23b7814 --- /dev/null +++ b/daemon/bsnmp/asn1.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/asn1.h,v 1.18 2004/08/06 08:46:50 brandt Exp $ + * + * ASN.1 for SNMP + */ +#ifndef asn1_h_ +#define asn1_h_ + +#include +#include + +struct asn_buf { + union { + u_char *ptr; + const u_char *cptr; + } asn_u; + size_t asn_len; +}; +#define asn_cptr asn_u.cptr +#define asn_ptr asn_u.ptr + +/* these restrictions are in the SMI */ +#define ASN_MAXID 0xffffffff +#define ASN_MAXOIDLEN 128 + +/* the string needed for this (with trailing zero) */ +#define ASN_OIDSTRLEN (ASN_MAXOIDLEN * (10 + 1) - 1 + 1) + +/* type of subidentifiers */ +typedef uint32_t asn_subid_t; + +struct asn_oid { + u_int len; + asn_subid_t subs[ASN_MAXOIDLEN]; +}; + +enum asn_err { + /* conversion was ok */ + ASN_ERR_OK = 0, + /* conversion failed and stopped */ + ASN_ERR_FAILED = 1 | 0x1000, + /* length field bad, value skipped */ + ASN_ERR_BADLEN = 2, + /* out of buffer, stopped */ + ASN_ERR_EOBUF = 3 | 0x1000, + /* length ok, but value is out of range */ + ASN_ERR_RANGE = 4, + /* not the expected tag, stopped */ + ASN_ERR_TAG = 5 | 0x1000, +}; +#define ASN_ERR_STOPPED(E) (((E) & 0x1000) != 0) + +/* type for the length field of encoded values. The length is restricted + * to 65535, but using uint16_t would give conversion warnings on gcc */ +typedef uint32_t asn_len_t; /* could be also uint16_t */ + +/* maximal length of a long length field without the length of the length */ +#define ASN_MAXLEN 65535 +#define ASN_MAXLENLEN 2 /* number of bytes in a length */ + +/* maximum size of an octet string as per SMIv2 */ +#define ASN_MAXOCTETSTRING 65535 + +extern void (*asn_error)(const struct asn_buf *, const char *, ...); + +enum asn_err asn_get_header(struct asn_buf *, u_char *, asn_len_t *); +enum asn_err asn_put_header(struct asn_buf *, u_char, asn_len_t); + +enum asn_err asn_put_temp_header(struct asn_buf *, u_char, u_char **); +enum asn_err asn_commit_header(struct asn_buf *, u_char *); + +enum asn_err asn_get_integer_raw(struct asn_buf *, asn_len_t, int32_t *); +enum asn_err asn_get_integer(struct asn_buf *, int32_t *); +enum asn_err asn_put_integer(struct asn_buf *, int32_t); + +enum asn_err asn_get_octetstring_raw(struct asn_buf *, asn_len_t, u_char *, u_int *); +enum asn_err asn_get_octetstring(struct asn_buf *, u_char *, u_int *); +enum asn_err asn_put_octetstring(struct asn_buf *, const u_char *, u_int); + +enum asn_err asn_get_null_raw(struct asn_buf *b, asn_len_t); +enum asn_err asn_get_null(struct asn_buf *); +enum asn_err asn_put_null(struct asn_buf *); + +enum asn_err asn_put_exception(struct asn_buf *, u_int); + +enum asn_err asn_get_objid_raw(struct asn_buf *, asn_len_t, struct asn_oid *); +enum asn_err asn_get_objid(struct asn_buf *, struct asn_oid *); +enum asn_err asn_put_objid(struct asn_buf *, const struct asn_oid *); + +enum asn_err asn_get_sequence(struct asn_buf *, asn_len_t *); + +enum asn_err asn_get_ipaddress_raw(struct asn_buf *, asn_len_t, u_char *); +enum asn_err asn_get_ipaddress(struct asn_buf *, u_char *); +enum asn_err asn_put_ipaddress(struct asn_buf *, const u_char *); + +enum asn_err asn_get_uint32_raw(struct asn_buf *, asn_len_t, uint32_t *); +enum asn_err asn_put_uint32(struct asn_buf *, u_char, uint32_t); + +enum asn_err asn_get_counter64_raw(struct asn_buf *, asn_len_t, uint64_t *); +enum asn_err asn_put_counter64(struct asn_buf *, uint64_t); + +enum asn_err asn_get_timeticks(struct asn_buf *, uint32_t *); +enum asn_err asn_put_timeticks(struct asn_buf *, uint32_t); + +enum asn_err asn_skip(struct asn_buf *, asn_len_t); + +/* + * Utility functions for OIDs + */ +/* get a sub-OID from the middle of another OID */ +void asn_slice_oid(struct asn_oid *, const struct asn_oid *, u_int, u_int); + +/* append an OID to another one */ +void asn_append_oid(struct asn_oid *, const struct asn_oid *); + +/* compare two OIDs */ +int asn_compare_oid(const struct asn_oid *, const struct asn_oid *); + +/* check whether the first is a suboid of the second one */ +int asn_is_suboid(const struct asn_oid *, const struct asn_oid *); + +/* format an OID into a user buffer of size ASN_OIDSTRLEN */ +char *asn_oid2str_r(const struct asn_oid *, char *); + +/* format an OID into a private static buffer */ +char *asn_oid2str(const struct asn_oid *); + +enum { + ASN_TYPE_BOOLEAN = 0x01, + ASN_TYPE_INTEGER = 0x02, + ASN_TYPE_BITSTRING = 0x03, + ASN_TYPE_OCTETSTRING = 0x04, + ASN_TYPE_NULL = 0x05, + ASN_TYPE_OBJID = 0x06, + ASN_TYPE_SEQUENCE = 0x10, + + ASN_TYPE_CONSTRUCTED = 0x20, + ASN_CLASS_UNIVERSAL = 0x00, + ASN_CLASS_APPLICATION = 0x40, + ASN_CLASS_CONTEXT = 0x80, + ASN_CLASS_PRIVATE = 0xc0, + ASN_TYPE_MASK = 0x1f, + + ASN_APP_IPADDRESS = 0x00, + ASN_APP_COUNTER = 0x01, + ASN_APP_GAUGE = 0x02, + ASN_APP_TIMETICKS = 0x03, + ASN_APP_OPAQUE = 0x04, /* not implemented */ + ASN_APP_COUNTER64 = 0x06, + + ASN_EXCEPT_NOSUCHOBJECT = 0x00, + ASN_EXCEPT_NOSUCHINSTANCE = 0x01, + ASN_EXCEPT_ENDOFMIBVIEW = 0x02, +}; + +#endif diff --git a/daemon/bsnmp/snmp.c b/daemon/bsnmp/snmp.c new file mode 100644 index 0000000..f758262 --- /dev/null +++ b/daemon/bsnmp/snmp.c @@ -0,0 +1,1081 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/snmp.c,v 1.38 2004/08/06 08:46:53 brandt Exp $ + * + * SNMP + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asn1.h" +#include "snmp.h" +#include "snmppriv.h" + +static void snmp_error_func(const char *, ...); +static void snmp_printf_func(const char *, ...); + +void (*snmp_error)(const char *, ...) = snmp_error_func; +void (*snmp_printf)(const char *, ...) = snmp_printf_func; + +/* + * Get the next variable binding from the list. + * ASN errors on the sequence or the OID are always fatal. + */ +static enum asn_err +get_var_binding(struct asn_buf *b, struct snmp_value *binding) +{ + u_char type; + asn_len_t len, trailer; + enum asn_err err; + + if (asn_get_sequence(b, &len) != ASN_ERR_OK) { + snmp_error("cannot parse varbind header"); + return (ASN_ERR_FAILED); + } + + /* temporary truncate the length so that the parser does not + * eat up bytes behind the sequence in the case the encoding is + * wrong of inner elements. */ + trailer = b->asn_len - len; + b->asn_len = len; + + if (asn_get_objid(b, &binding->var) != ASN_ERR_OK) { + snmp_error("cannot parse binding objid"); + return (ASN_ERR_FAILED); + } + if (asn_get_header(b, &type, &len) != ASN_ERR_OK) { + snmp_error("cannot parse binding value header"); + return (ASN_ERR_FAILED); + } + + switch (type) { + + case ASN_TYPE_NULL: + binding->syntax = SNMP_SYNTAX_NULL; + err = asn_get_null_raw(b, len); + break; + + case ASN_TYPE_INTEGER: + binding->syntax = SNMP_SYNTAX_INTEGER; + err = asn_get_integer_raw(b, len, &binding->v.integer); + break; + + case ASN_TYPE_OCTETSTRING: + binding->syntax = SNMP_SYNTAX_OCTETSTRING; + binding->v.octetstring.octets = malloc(len); + if (binding->v.octetstring.octets == NULL) { + snmp_error("%s", strerror(errno)); + return (ASN_ERR_FAILED); + } + binding->v.octetstring.len = len; + err = asn_get_octetstring_raw(b, len, + binding->v.octetstring.octets, + &binding->v.octetstring.len); + if (ASN_ERR_STOPPED(err)) { + free(binding->v.octetstring.octets); + binding->v.octetstring.octets = NULL; + } + break; + + case ASN_TYPE_OBJID: + binding->syntax = SNMP_SYNTAX_OID; + err = asn_get_objid_raw(b, len, &binding->v.oid); + break; + + case ASN_CLASS_APPLICATION|ASN_APP_IPADDRESS: + binding->syntax = SNMP_SYNTAX_IPADDRESS; + err = asn_get_ipaddress_raw(b, len, binding->v.ipaddress); + break; + + case ASN_CLASS_APPLICATION|ASN_APP_TIMETICKS: + binding->syntax = SNMP_SYNTAX_TIMETICKS; + err = asn_get_uint32_raw(b, len, &binding->v.uint32); + break; + + case ASN_CLASS_APPLICATION|ASN_APP_COUNTER: + binding->syntax = SNMP_SYNTAX_COUNTER; + err = asn_get_uint32_raw(b, len, &binding->v.uint32); + break; + + case ASN_CLASS_APPLICATION|ASN_APP_GAUGE: + binding->syntax = SNMP_SYNTAX_GAUGE; + err = asn_get_uint32_raw(b, len, &binding->v.uint32); + break; + + case ASN_CLASS_APPLICATION|ASN_APP_COUNTER64: + binding->syntax = SNMP_SYNTAX_COUNTER64; + err = asn_get_counter64_raw(b, len, &binding->v.counter64); + break; + + case ASN_CLASS_CONTEXT | ASN_EXCEPT_NOSUCHOBJECT: + binding->syntax = SNMP_SYNTAX_NOSUCHOBJECT; + err = asn_get_null_raw(b, len); + break; + + case ASN_CLASS_CONTEXT | ASN_EXCEPT_NOSUCHINSTANCE: + binding->syntax = SNMP_SYNTAX_NOSUCHINSTANCE; + err = asn_get_null_raw(b, len); + break; + + case ASN_CLASS_CONTEXT | ASN_EXCEPT_ENDOFMIBVIEW: + binding->syntax = SNMP_SYNTAX_ENDOFMIBVIEW; + err = asn_get_null_raw(b, len); + break; + + default: + if ((err = asn_skip(b, len)) == ASN_ERR_OK) + err = ASN_ERR_TAG; + snmp_error("bad binding value type 0x%x", type); + break; + } + + if (ASN_ERR_STOPPED(err)) { + snmp_error("cannot parse binding value"); + return (err); + } + + if (b->asn_len != 0) + snmp_error("ignoring junk at end of binding"); + + b->asn_len = trailer; + + return (err); +} + +/* + * Parse the different PDUs contents. Any ASN error in the outer components + * are fatal. Only errors in variable values may be tolerated. If all + * components can be parsed it returns either ASN_ERR_OK or the first + * error that was found. + */ +enum asn_err +snmp_parse_pdus_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp) +{ + if (pdu->type == SNMP_PDU_TRAP) { + if (asn_get_objid(b, &pdu->enterprise) != ASN_ERR_OK) { + snmp_error("cannot parse trap enterprise"); + return (ASN_ERR_FAILED); + } + if (asn_get_ipaddress(b, pdu->agent_addr) != ASN_ERR_OK) { + snmp_error("cannot parse trap agent address"); + return (ASN_ERR_FAILED); + } + if (asn_get_integer(b, &pdu->generic_trap) != ASN_ERR_OK) { + snmp_error("cannot parse 'generic-trap'"); + return (ASN_ERR_FAILED); + } + if (asn_get_integer(b, &pdu->specific_trap) != ASN_ERR_OK) { + snmp_error("cannot parse 'specific-trap'"); + return (ASN_ERR_FAILED); + } + if (asn_get_timeticks(b, &pdu->time_stamp) != ASN_ERR_OK) { + snmp_error("cannot parse trap 'time-stamp'"); + return (ASN_ERR_FAILED); + } + } else { + if (asn_get_integer(b, &pdu->request_id) != ASN_ERR_OK) { + snmp_error("cannot parse 'request-id'"); + return (ASN_ERR_FAILED); + } + if (asn_get_integer(b, &pdu->error_status) != ASN_ERR_OK) { + snmp_error("cannot parse 'error_status'"); + return (ASN_ERR_FAILED); + } + if (asn_get_integer(b, &pdu->error_index) != ASN_ERR_OK) { + snmp_error("cannot parse 'error_index'"); + return (ASN_ERR_FAILED); + } + } + + if (asn_get_sequence(b, lenp) != ASN_ERR_OK) { + snmp_error("cannot get varlist header"); + return (ASN_ERR_FAILED); + } + + return (ASN_ERR_OK); +} + +static enum asn_err +parse_pdus(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +{ + asn_len_t len, trailer; + struct snmp_value *v; + enum asn_err err, err1; + + err = snmp_parse_pdus_hdr(b, pdu, &len); + if (ASN_ERR_STOPPED(err)) + return (err); + + trailer = b->asn_len - len; + + v = pdu->bindings; + err = ASN_ERR_OK; + while (b->asn_len != 0) { + if (pdu->nbindings == SNMP_MAX_BINDINGS) { + snmp_error("too many bindings (> %u) in PDU", + SNMP_MAX_BINDINGS); + return (ASN_ERR_FAILED); + } + err1 = get_var_binding(b, v); + if (ASN_ERR_STOPPED(err1)) + return (ASN_ERR_FAILED); + if (err1 != ASN_ERR_OK && err == ASN_ERR_OK) { + err = err1; + *ip = pdu->nbindings + 1; + } + pdu->nbindings++; + v++; + } + + b->asn_len = trailer; + + return (err); +} + +/* + * Parse the outer SEQUENCE value. ASN_ERR_TAG means 'bad version'. + */ +enum asn_err +snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp) +{ + int32_t version; + u_char type; + u_int comm_len; + + if (asn_get_integer(b, &version) != ASN_ERR_OK) { + snmp_error("cannot decode version"); + return (ASN_ERR_FAILED); + } + + if (version == 0) { + pdu->version = SNMP_V1; + } else if (version == 1) { + pdu->version = SNMP_V2c; + } else { + pdu->version = SNMP_Verr; + snmp_error("unsupported SNMP version"); + return (ASN_ERR_TAG); + } + + comm_len = SNMP_COMMUNITY_MAXLEN; + if (asn_get_octetstring(b, (u_char *)pdu->community, + &comm_len) != ASN_ERR_OK) { + snmp_error("cannot decode community"); + return (ASN_ERR_FAILED); + } + pdu->community[comm_len] = '\0'; + + if (asn_get_header(b, &type, lenp) != ASN_ERR_OK) { + snmp_error("cannot get pdu header"); + return (ASN_ERR_FAILED); + } + if ((type & ~ASN_TYPE_MASK) != + (ASN_TYPE_CONSTRUCTED | ASN_CLASS_CONTEXT)) { + snmp_error("bad pdu header tag"); + return (ASN_ERR_FAILED); + } + pdu->type = type & ASN_TYPE_MASK; + + switch (pdu->type) { + + case SNMP_PDU_GET: + case SNMP_PDU_GETNEXT: + case SNMP_PDU_RESPONSE: + case SNMP_PDU_SET: + break; + + case SNMP_PDU_TRAP: + if (pdu->version != SNMP_V1) { + snmp_error("bad pdu type %u", pdu->type); + return (ASN_ERR_FAILED); + } + break; + + case SNMP_PDU_GETBULK: + case SNMP_PDU_INFORM: + case SNMP_PDU_TRAP2: + case SNMP_PDU_REPORT: + if (pdu->version == SNMP_V1) { + snmp_error("bad pdu type %u", pdu->type); + return (ASN_ERR_FAILED); + } + break; + + default: + snmp_error("bad pdu type %u", pdu->type); + return (ASN_ERR_FAILED); + } + + + if (*lenp > b->asn_len) { + snmp_error("pdu length too long"); + return (ASN_ERR_FAILED); + } + + return (ASN_ERR_OK); +} + +static enum asn_err +parse_message(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +{ + enum asn_err err; + asn_len_t len, trailer; + + err = snmp_parse_message_hdr(b, pdu, &len); + if (ASN_ERR_STOPPED(err)) + return (err); + + trailer = b->asn_len - len; + b->asn_len = len; + + err = parse_pdus(b, pdu, ip); + if (ASN_ERR_STOPPED(err)) + return (ASN_ERR_FAILED); + + if (b->asn_len != 0) + snmp_error("ignoring trailing junk after pdu"); + + b->asn_len = trailer; + + return (err); +} + +/* + * Decode the PDU except for the variable bindings itself. + * If decoding fails because of a bad binding, but the rest can be + * decoded, ip points to the index of the failed variable (errors + * OORANGE, BADLEN or BADVERS). + */ +enum snmp_code +snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +{ + asn_len_t len; + + memset(pdu, 0, sizeof(*pdu)); + + if (asn_get_sequence(b, &len) != ASN_ERR_OK) { + snmp_error("cannot decode pdu header"); + return (SNMP_CODE_FAILED); + } + if (b->asn_len < len) { + snmp_error("outer sequence value too short"); + return (SNMP_CODE_FAILED); + } + if (b->asn_len != len) { + snmp_error("ignoring trailing junk in message"); + b->asn_len = len; + } + + switch (parse_message(b, pdu, ip)) { + + case ASN_ERR_OK: + return (SNMP_CODE_OK); + + case ASN_ERR_FAILED: + case ASN_ERR_EOBUF: + snmp_pdu_free(pdu); + return (SNMP_CODE_FAILED); + + case ASN_ERR_BADLEN: + return (SNMP_CODE_BADLEN); + + case ASN_ERR_RANGE: + return (SNMP_CODE_OORANGE); + + case ASN_ERR_TAG: + if (pdu->version == SNMP_Verr) + return (SNMP_CODE_BADVERS); + else + return (SNMP_CODE_BADENC); + } + + return (SNMP_CODE_OK); +} + +/* + * Check whether what we have is the complete PDU by snooping at the + * enclosing structure header. This returns: + * -1 if there are ASN.1 errors + * 0 if we need more data + * > 0 the length of this PDU + */ +int +snmp_pdu_snoop(const struct asn_buf *b0) +{ + u_int length; + asn_len_t len; + struct asn_buf b = *b0; + + /* <0x10|0x20> */ + + if (b.asn_len == 0) + return (0); + if (b.asn_cptr[0] != (ASN_TYPE_SEQUENCE | ASN_TYPE_CONSTRUCTED)) { + asn_error(&b, "bad sequence type %u", b.asn_cptr[0]); + return (-1); + } + b.asn_len--; + b.asn_cptr++; + + if (b.asn_len == 0) + return (0); + + if (*b.asn_cptr & 0x80) { + /* long length */ + length = *b.asn_cptr++ & 0x7f; + b.asn_len--; + if (length == 0) { + asn_error(&b, "indefinite length not supported"); + return (-1); + } + if (length > ASN_MAXLENLEN) { + asn_error(&b, "long length too long (%u)", length); + return (-1); + } + if (length > b.asn_len) + return (0); + len = 0; + while (length--) { + len = (len << 8) | *b.asn_cptr++; + b.asn_len--; + } + } else { + len = *b.asn_cptr++; + b.asn_len--; + } + + if (len > b.asn_len) + return (0); + + return (len + b.asn_cptr - b0->asn_cptr); +} + +/* + * Encode the SNMP PDU without the variable bindings field. + * We do this the rather uneffective way by + * moving things around and assuming that the length field will never + * use more than 2 bytes. + * We need a number of pointers to apply the fixes afterwards. + */ +enum snmp_code +snmp_pdu_encode_header(struct asn_buf *b, struct snmp_pdu *pdu) +{ + enum asn_err err; + + if (asn_put_temp_header(b, (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED), + &pdu->outer_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (pdu->version == SNMP_V1) + err = asn_put_integer(b, 0); + else if (pdu->version == SNMP_V2c) + err = asn_put_integer(b, 1); + else + return (SNMP_CODE_BADVERS); + if (err != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_octetstring(b, (u_char *)pdu->community, + strlen(pdu->community)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_temp_header(b, (ASN_TYPE_CONSTRUCTED | ASN_CLASS_CONTEXT | + pdu->type), &pdu->pdu_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (pdu->type == SNMP_PDU_TRAP) { + if (pdu->version != SNMP_V1 || + asn_put_objid(b, &pdu->enterprise) != ASN_ERR_OK || + asn_put_ipaddress(b, pdu->agent_addr) != ASN_ERR_OK || + asn_put_integer(b, pdu->generic_trap) != ASN_ERR_OK || + asn_put_integer(b, pdu->specific_trap) != ASN_ERR_OK || + asn_put_timeticks(b, pdu->time_stamp) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } else { + if (pdu->version == SNMP_V1 && (pdu->type == SNMP_PDU_GETBULK || + pdu->type == SNMP_PDU_INFORM || + pdu->type == SNMP_PDU_TRAP2 || + pdu->type == SNMP_PDU_REPORT)) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(b, pdu->request_id) != ASN_ERR_OK || + asn_put_integer(b, pdu->error_status) != ASN_ERR_OK || + asn_put_integer(b, pdu->error_index) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } + + if (asn_put_temp_header(b, (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED), + &pdu->vars_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_fix_encoding(struct asn_buf *b, const struct snmp_pdu *pdu) +{ + if (asn_commit_header(b, pdu->vars_ptr) != ASN_ERR_OK || + asn_commit_header(b, pdu->pdu_ptr) != ASN_ERR_OK || + asn_commit_header(b, pdu->outer_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + return (SNMP_CODE_OK); +} + +/* + * Encode a binding. Caller must ensure, that the syntax is ok for that version. + * Be sure not to cobber b, when something fails. + */ +enum asn_err +snmp_binding_encode(struct asn_buf *b, const struct snmp_value *binding) +{ + u_char *ptr; + enum asn_err err; + struct asn_buf save = *b; + + if ((err = asn_put_temp_header(b, (ASN_TYPE_SEQUENCE | + ASN_TYPE_CONSTRUCTED), &ptr)) != ASN_ERR_OK) { + *b = save; + return (err); + } + + if ((err = asn_put_objid(b, &binding->var)) != ASN_ERR_OK) { + *b = save; + return (err); + } + + switch (binding->syntax) { + + case SNMP_SYNTAX_NULL: + err = asn_put_null(b); + break; + + case SNMP_SYNTAX_INTEGER: + err = asn_put_integer(b, binding->v.integer); + break; + + case SNMP_SYNTAX_OCTETSTRING: + err = asn_put_octetstring(b, binding->v.octetstring.octets, + binding->v.octetstring.len); + break; + + case SNMP_SYNTAX_OID: + err = asn_put_objid(b, &binding->v.oid); + break; + + case SNMP_SYNTAX_IPADDRESS: + err = asn_put_ipaddress(b, binding->v.ipaddress); + break; + + case SNMP_SYNTAX_TIMETICKS: + err = asn_put_uint32(b, ASN_APP_TIMETICKS, binding->v.uint32); + break; + + case SNMP_SYNTAX_COUNTER: + err = asn_put_uint32(b, ASN_APP_COUNTER, binding->v.uint32); + break; + + case SNMP_SYNTAX_GAUGE: + err = asn_put_uint32(b, ASN_APP_GAUGE, binding->v.uint32); + break; + + case SNMP_SYNTAX_COUNTER64: + err = asn_put_counter64(b, binding->v.counter64); + break; + + case SNMP_SYNTAX_NOSUCHOBJECT: + err = asn_put_exception(b, ASN_EXCEPT_NOSUCHOBJECT); + break; + + case SNMP_SYNTAX_NOSUCHINSTANCE: + err = asn_put_exception(b, ASN_EXCEPT_NOSUCHINSTANCE); + break; + + case SNMP_SYNTAX_ENDOFMIBVIEW: + err = asn_put_exception(b, ASN_EXCEPT_ENDOFMIBVIEW); + break; + } + + if (err != ASN_ERR_OK) { + *b = save; + return (err); + } + + err = asn_commit_header(b, ptr); + if (err != ASN_ERR_OK) { + *b = save; + return (err); + } + + return (ASN_ERR_OK); +} + +/* + * Encode an PDU. + */ +enum snmp_code +snmp_pdu_encode(struct snmp_pdu *pdu, struct asn_buf *resp_b) +{ + u_int idx; + enum snmp_code err; + + if ((err = snmp_pdu_encode_header(resp_b, pdu)) != SNMP_CODE_OK) + return (err); + for (idx = 0; idx < pdu->nbindings; idx++) + if ((err = snmp_binding_encode(resp_b, &pdu->bindings[idx])) + != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + return (snmp_fix_encoding(resp_b, pdu)); +} + +static void +dump_binding(const struct snmp_value *b) +{ + u_int i; + char buf[ASN_OIDSTRLEN]; + + snmp_printf("%s=", asn_oid2str_r(&b->var, buf)); + switch (b->syntax) { + + case SNMP_SYNTAX_NULL: + snmp_printf("NULL"); + break; + + case SNMP_SYNTAX_INTEGER: + snmp_printf("INTEGER %d", b->v.integer); + break; + + case SNMP_SYNTAX_OCTETSTRING: + snmp_printf("OCTET STRING %lu:", b->v.octetstring.len); + for (i = 0; i < b->v.octetstring.len; i++) + snmp_printf(" %02x", b->v.octetstring.octets[i]); + break; + + case SNMP_SYNTAX_OID: + snmp_printf("OID %s", asn_oid2str_r(&b->v.oid, buf)); + break; + + case SNMP_SYNTAX_IPADDRESS: + snmp_printf("IPADDRESS %u.%u.%u.%u", b->v.ipaddress[0], + b->v.ipaddress[1], b->v.ipaddress[2], b->v.ipaddress[3]); + break; + + case SNMP_SYNTAX_COUNTER: + snmp_printf("COUNTER %u", b->v.uint32); + break; + + case SNMP_SYNTAX_GAUGE: + snmp_printf("GAUGE %u", b->v.uint32); + break; + + case SNMP_SYNTAX_TIMETICKS: + snmp_printf("TIMETICKS %u", b->v.uint32); + break; + + case SNMP_SYNTAX_COUNTER64: + snmp_printf("COUNTER64 %lld", b->v.counter64); + break; + + case SNMP_SYNTAX_NOSUCHOBJECT: + snmp_printf("NoSuchObject"); + break; + + case SNMP_SYNTAX_NOSUCHINSTANCE: + snmp_printf("NoSuchInstance"); + break; + + case SNMP_SYNTAX_ENDOFMIBVIEW: + snmp_printf("EndOfMibView"); + break; + + default: + snmp_printf("UNKNOWN SYNTAX %u", b->syntax); + break; + } +} + +static __inline void +dump_bindings(const struct snmp_pdu *pdu) +{ + u_int i; + + for (i = 0; i < pdu->nbindings; i++) { + snmp_printf(" [%u]: ", i); + dump_binding(&pdu->bindings[i]); + snmp_printf("\n"); + } +} + +static __inline void +dump_notrap(const struct snmp_pdu *pdu) +{ + snmp_printf(" request_id=%d", pdu->request_id); + snmp_printf(" error_status=%d", pdu->error_status); + snmp_printf(" error_index=%d\n", pdu->error_index); + dump_bindings(pdu); +} + +void +snmp_pdu_dump(const struct snmp_pdu *pdu) +{ + char buf[ASN_OIDSTRLEN]; + const char *vers; + static const char *types[] = { + [SNMP_PDU_GET] = "GET", + [SNMP_PDU_GETNEXT] = "GETNEXT", + [SNMP_PDU_RESPONSE] = "RESPONSE", + [SNMP_PDU_SET] = "SET", + [SNMP_PDU_TRAP] = "TRAPv1", + [SNMP_PDU_GETBULK] = "GETBULK", + [SNMP_PDU_INFORM] = "INFORM", + [SNMP_PDU_TRAP2] = "TRAPv2", + [SNMP_PDU_REPORT] = "REPORT", + }; + + if (pdu->version == SNMP_V1) + vers = "SNMPv1"; + else if (pdu->version == SNMP_V2c) + vers = "SNMPv2c"; + else + vers = "v?"; + + switch (pdu->type) { + case SNMP_PDU_TRAP: + snmp_printf("%s %s '%s'", types[pdu->type], vers, pdu->community); + snmp_printf(" enterprise=%s", asn_oid2str_r(&pdu->enterprise, buf)); + snmp_printf(" agent_addr=%u.%u.%u.%u", pdu->agent_addr[0], + pdu->agent_addr[1], pdu->agent_addr[2], pdu->agent_addr[3]); + snmp_printf(" generic_trap=%d", pdu->generic_trap); + snmp_printf(" specific_trap=%d", pdu->specific_trap); + snmp_printf(" time-stamp=%u\n", pdu->time_stamp); + dump_bindings(pdu); + break; + + case SNMP_PDU_GET: + case SNMP_PDU_GETNEXT: + case SNMP_PDU_RESPONSE: + case SNMP_PDU_SET: + case SNMP_PDU_GETBULK: + case SNMP_PDU_INFORM: + case SNMP_PDU_TRAP2: + case SNMP_PDU_REPORT: + snmp_printf("%s %s '%s'", types[pdu->type], vers, pdu->community); + dump_notrap(pdu); + break; + + default: + snmp_printf("bad pdu type %u\n", pdu->type); + break; + } +} + +void +snmp_value_free(struct snmp_value *value) +{ + if (value->syntax == SNMP_SYNTAX_OCTETSTRING) + free(value->v.octetstring.octets); + value->syntax = SNMP_SYNTAX_NULL; +} + +int +snmp_value_copy(struct snmp_value *to, const struct snmp_value *from) +{ + to->var = from->var; + to->syntax = from->syntax; + + if (from->syntax == SNMP_SYNTAX_OCTETSTRING) { + if ((to->v.octetstring.len = from->v.octetstring.len) == 0) + to->v.octetstring.octets = NULL; + else { + to->v.octetstring.octets = malloc(to->v.octetstring.len); + if (to->v.octetstring.octets == NULL) + return (-1); + (void)memcpy(to->v.octetstring.octets, + from->v.octetstring.octets, to->v.octetstring.len); + } + } else + to->v = from->v; + return (0); +} + +void +snmp_pdu_free(struct snmp_pdu *pdu) +{ + u_int i; + + for (i = 0; i < pdu->nbindings; i++) + snmp_value_free(&pdu->bindings[i]); +} + +/* + * Parse an ASCII SNMP value into the binary form + */ +int +snmp_value_parse(const char *str, enum snmp_syntax syntax, union snmp_values *v) +{ + char *end; + + switch (syntax) { + + case SNMP_SYNTAX_NULL: + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + if (*str != '\0') + return (-1); + return (0); + + case SNMP_SYNTAX_INTEGER: + v->integer = strtoll(str, &end, 0); + if (*end != '\0') + return (-1); + return (0); + + case SNMP_SYNTAX_OCTETSTRING: + { + u_long len; /* actual length of string */ + u_long alloc; /* allocate length of string */ + u_char *octs; /* actual octets */ + u_long oct; /* actual octet */ + u_char *nocts; /* to avoid memory leak */ + u_char c; /* actual character */ + +# define STUFFC(C) \ + if (alloc == len) { \ + alloc += 100; \ + if ((nocts = realloc(octs, alloc)) == NULL) { \ + free(octs); \ + return (-1); \ + } \ + octs = nocts; \ + } \ + octs[len++] = (C); + + len = alloc = 0; + octs = NULL; + + if (*str == '"') { + str++; + while((c = *str++) != '\0') { + if (c == '"') { + if (*str != '\0') { + free(octs); + return (-1); + } + break; + } + if (c == '\\') { + switch (c = *str++) { + + case '\\': + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + c = 0; + if (!isxdigit(*str)) + break; + if (isdigit(*str)) + c = *str++ - '0'; + else if (isupper(*str)) + c = *str++ - 'A' + 10; + else + c = *str++ - 'a' + 10; + if (!isxdigit(*str)) + break; + if (isdigit(*str)) + c += *str++ - '0'; + else if (isupper(*str)) + c += *str++ - 'A' + 10; + else + c += *str++ - 'a' + 10; + break; + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': + c = *str++ - '0'; + if (*str < '0' || *str > '7') + break; + c = *str++ - '0'; + if (*str < '0' || *str > '7') + break; + c = *str++ - '0'; + break; + default: + break; + } + } + STUFFC(c); + } + } else { + while (*str != '\0') { + oct = strtoul(str, &end, 16); + str = end; + if (oct > 0xff) { + free(octs); + return (-1); + } + STUFFC(oct); + if (*str == ':') + str++; + else if(*str != '\0') { + free(octs); + return (-1); + } + } + } + v->octetstring.octets = octs; + v->octetstring.len = len; + return (0); +# undef STUFFC + } + + case SNMP_SYNTAX_OID: + { + u_long subid; + + v->oid.len = 0; + + for (;;) { + if (v->oid.len == ASN_MAXOIDLEN) + return (-1); + subid = strtoul(str, &end, 10); + str = end; + if (subid > ASN_MAXID) + return (-1); + v->oid.subs[v->oid.len++] = (asn_subid_t)subid; + if (*str == '\0') + break; + if (*str != '.') + return (-1); + str++; + } + return (0); + } + + case SNMP_SYNTAX_IPADDRESS: + { + struct hostent *he; + u_long ip[4]; + int n; + + if (sscanf(str, "%lu.%lu.%lu.%lu%n", &ip[0], &ip[1], &ip[2], + &ip[3], &n) == 4 && (size_t)n == strlen(str) && + ip[0] <= 0xff && ip[1] <= 0xff && + ip[2] <= 0xff && ip[3] <= 0xff) { + v->ipaddress[0] = (u_char)ip[0]; + v->ipaddress[1] = (u_char)ip[1]; + v->ipaddress[2] = (u_char)ip[2]; + v->ipaddress[3] = (u_char)ip[3]; + return (0); + } + + if ((he = gethostbyname(str)) == NULL) + return (-1); + if (he->h_addrtype != AF_INET) + return (-1); + + v->ipaddress[0] = he->h_addr[0]; + v->ipaddress[1] = he->h_addr[1]; + v->ipaddress[2] = he->h_addr[2]; + v->ipaddress[3] = he->h_addr[3]; + return (0); + } + + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + { + uint64_t sub; + + sub = strtoull(str, &end, 0); + if (*end != '\0' || sub > 0xffffffff) + return (-1); + v->uint32 = (uint32_t)sub; + return (0); + } + + case SNMP_SYNTAX_COUNTER64: + v->counter64 = strtoull(str, &end, 0); + if (*end != '\0') + return (-1); + return (0); + } + abort(); +} + +static void +snmp_error_func(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "SNMP: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void +snmp_printf_func(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} diff --git a/daemon/bsnmp/snmp.h b/daemon/bsnmp/snmp.h new file mode 100644 index 0000000..1ae0775 --- /dev/null +++ b/daemon/bsnmp/snmp.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/snmp.h,v 1.30 2004/08/06 08:46:54 brandt Exp $ + * + * Header file for SNMP functions. + */ +#ifndef snmp_h_ +#define snmp_h_ + +#include + +#define SNMP_COMMUNITY_MAXLEN 128 +#define SNMP_MAX_BINDINGS 100 + +enum snmp_syntax { + SNMP_SYNTAX_NULL = 0, + SNMP_SYNTAX_INTEGER, /* == INTEGER32 */ + SNMP_SYNTAX_OCTETSTRING, + SNMP_SYNTAX_OID, + SNMP_SYNTAX_IPADDRESS, + SNMP_SYNTAX_COUNTER, + SNMP_SYNTAX_GAUGE, /* == UNSIGNED32 */ + SNMP_SYNTAX_TIMETICKS, + + /* v2 additions */ + SNMP_SYNTAX_COUNTER64, + SNMP_SYNTAX_NOSUCHOBJECT, /* exception */ + SNMP_SYNTAX_NOSUCHINSTANCE, /* exception */ + SNMP_SYNTAX_ENDOFMIBVIEW, /* exception */ +}; + +struct snmp_value { + struct asn_oid var; + enum snmp_syntax syntax; + union snmp_values { + int32_t integer; /* also integer32 */ + struct { + u_int len; + u_char *octets; + } octetstring; + struct asn_oid oid; + u_char ipaddress[4]; + uint32_t uint32; /* also gauge32, counter32, + unsigned32, timeticks */ + uint64_t counter64; + } v; +}; + +enum snmp_version { + SNMP_Verr = 0, + SNMP_V1 = 1, + SNMP_V2c, +}; + +struct snmp_pdu { + char community[SNMP_COMMUNITY_MAXLEN + 1]; + enum snmp_version version; + u_int type; + + /* trap only */ + struct asn_oid enterprise; + u_char agent_addr[4]; + int32_t generic_trap; + int32_t specific_trap; + uint32_t time_stamp; + + /* others */ + int32_t request_id; + int32_t error_status; + int32_t error_index; + + /* fixes for encoding */ + u_char *outer_ptr; + u_char *pdu_ptr; + u_char *vars_ptr; + + struct snmp_value bindings[SNMP_MAX_BINDINGS]; + u_int nbindings; +}; +#define snmp_v1_pdu snmp_pdu + +#define SNMP_PDU_GET 0 +#define SNMP_PDU_GETNEXT 1 +#define SNMP_PDU_RESPONSE 2 +#define SNMP_PDU_SET 3 +#define SNMP_PDU_TRAP 4 /* v1 */ +#define SNMP_PDU_GETBULK 5 /* v2 */ +#define SNMP_PDU_INFORM 6 /* v2 */ +#define SNMP_PDU_TRAP2 7 /* v2 */ +#define SNMP_PDU_REPORT 8 /* v2 */ + +#define SNMP_ERR_NOERROR 0 +#define SNMP_ERR_TOOBIG 1 +#define SNMP_ERR_NOSUCHNAME 2 /* v1 */ +#define SNMP_ERR_BADVALUE 3 /* v1 */ +#define SNMP_ERR_READONLY 4 /* v1 */ +#define SNMP_ERR_GENERR 5 +#define SNMP_ERR_NO_ACCESS 6 /* v2 */ +#define SNMP_ERR_WRONG_TYPE 7 /* v2 */ +#define SNMP_ERR_WRONG_LENGTH 8 /* v2 */ +#define SNMP_ERR_WRONG_ENCODING 9 /* v2 */ +#define SNMP_ERR_WRONG_VALUE 10 /* v2 */ +#define SNMP_ERR_NO_CREATION 11 /* v2 */ +#define SNMP_ERR_INCONS_VALUE 12 /* v2 */ +#define SNMP_ERR_RES_UNAVAIL 13 /* v2 */ +#define SNMP_ERR_COMMIT_FAILED 14 /* v2 */ +#define SNMP_ERR_UNDO_FAILED 15 /* v2 */ +#define SNMP_ERR_AUTH_ERR 16 /* v2 */ +#define SNMP_ERR_NOT_WRITEABLE 17 /* v2 */ +#define SNMP_ERR_INCONS_NAME 18 /* v2 */ + +#define SNMP_TRAP_COLDSTART 0 +#define SNMP_TRAP_WARMSTART 1 +#define SNMP_TRAP_LINKDOWN 2 +#define SNMP_TRAP_LINKUP 3 +#define SNMP_TRAP_AUTHENTICATION_FAILURE 4 +#define SNMP_TRAP_EGP_NEIGHBOR_LOSS 5 +#define SNMP_TRAP_ENTERPRISE 6 + +enum snmp_code { + SNMP_CODE_OK = 0, + SNMP_CODE_FAILED, + SNMP_CODE_BADVERS, + SNMP_CODE_BADLEN, + SNMP_CODE_BADENC, + SNMP_CODE_OORANGE, +}; + +void snmp_value_free(struct snmp_value *); +int snmp_value_parse(const char *, enum snmp_syntax, union snmp_values *); +int snmp_value_copy(struct snmp_value *, const struct snmp_value *); + +void snmp_pdu_free(struct snmp_pdu *); +enum snmp_code snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *); +enum snmp_code snmp_pdu_encode(struct snmp_pdu *pdu, struct asn_buf *resp_b); + +int snmp_pdu_snoop(const struct asn_buf *); + +void snmp_pdu_dump(const struct snmp_pdu *pdu); + +extern void (*snmp_error)(const char *, ...); +extern void (*snmp_printf)(const char *, ...); + +#define TRUTH_MK(F) ((F) ? 1 : 2) +#define TRUTH_GET(T) (((T) == 1) ? 1 : 0) +#define TRUTH_OK(T) ((T) == 1 || (T) == 2) + +#endif diff --git a/daemon/bsnmp/snmppriv.h b/daemon/bsnmp/snmppriv.h new file mode 100644 index 0000000..87ef60e --- /dev/null +++ b/daemon/bsnmp/snmppriv.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/snmppriv.h,v 1.9 2004/08/06 08:46:58 brandt Exp $ + * + * Private functions. + */ +#include + +enum asn_err snmp_binding_encode(struct asn_buf *, const struct snmp_value *); +enum snmp_code snmp_pdu_encode_header(struct asn_buf *, struct snmp_pdu *); +enum snmp_code snmp_fix_encoding(struct asn_buf *, const struct snmp_pdu *); +enum asn_err snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, + asn_len_t *lenp); +enum asn_err snmp_parse_pdus_hdr(struct asn_buf *b, struct snmp_pdu *pdu, + asn_len_t *lenp); + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT "snmp" +#define DEFAULT_LOCAL "/var/run/snmp.sock" diff --git a/daemon/common/hash.c b/daemon/common/hash.c new file mode 100644 index 0000000..789e85c --- /dev/null +++ b/daemon/common/hash.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + * Modifications for general use by + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "hash.h" + +#define KEY_DATA(he) ((he)->key) + +/* + * The internal form of a hash table. + * + * The table is an array indexed by the hash of the key; collisions + * are resolved by hanging a linked list of hash entries off each + * element of the array. Although this is a really simple design it + * isn't too bad given that pools have a low allocation overhead. + */ + +typedef struct hsh_entry_t hsh_entry_t; + +struct hsh_entry_t +{ + hsh_entry_t* next; + unsigned int hash; + const void* key; + size_t klen; + const void* val; +}; + +/* + * Data structure for iterating through a hash table. + * + * We keep a pointer to the next hash entry here to allow the current + * hash entry to be freed or otherwise mangled between calls to + * hsh_next(). + */ +struct hsh_index_t +{ + hsh_t* ht; + hsh_entry_t* ths; + hsh_entry_t* next; + unsigned int index; +}; + +/* + * The size of the array is always a power of two. We use the maximum + * index rather than the size so that we can use bitwise-AND for + * modular arithmetic. + * The count of hash entries may be greater depending on the chosen + * collision rate. + */ +struct hsh_t +{ + hsh_entry_t** array; + hsh_index_t iterator; /* For hsh_first(...) */ + unsigned int count; + unsigned int max; +}; + + +#define INITIAL_MAX 15 /* tunable == 2^n - 1 */ +#define int_malloc malloc +#define int_calloc calloc +#define int_free free + +/* + * Hash creation functions. + */ + +static hsh_entry_t** alloc_array(hsh_t* ht, unsigned int max) +{ + return (hsh_entry_t**)int_calloc(sizeof(*(ht->array)), (max + 1)); +} + +hsh_t* hsh_create() +{ + hsh_t* ht = int_malloc(sizeof(hsh_t)); + if(ht) + { + ht->count = 0; + ht->max = INITIAL_MAX; + ht->array = alloc_array(ht, ht->max); + if(!ht->array) + { + int_free(ht); + return NULL; + } + } + return ht; +} + +void hsh_free(hsh_t* ht) +{ + hsh_index_t* hi; + + for(hi = hsh_first(ht); hi; hi = hsh_next(hi)) + int_free(hi->ths); + + if(ht->array) + int_free(ht->array); + + int_free(ht); +} + +/* + * Hash iteration functions. + */ + +hsh_index_t* hsh_next(hsh_index_t* hi) +{ + hi->ths = hi->next; + while(!hi->ths) + { + if(hi->index > hi->ht->max) + return NULL; + + hi->ths = hi->ht->array[hi->index++]; + } + hi->next = hi->ths->next; + return hi; +} + +hsh_index_t* hsh_first(hsh_t* ht) +{ + hsh_index_t* hi = &ht->iterator; + + hi->ht = ht; + hi->index = 0; + hi->ths = NULL; + hi->next = NULL; + return hsh_next(hi); +} + +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen) +{ + if(key) + *key = KEY_DATA(hi->ths); + if(klen) + *klen = hi->ths->klen; + return (void*)hi->ths->val; +} + + +/* + * Expanding a hash table + */ + +static int expand_array(hsh_t* ht) +{ + hsh_index_t* hi; + hsh_entry_t** new_array; + unsigned int new_max; + + new_max = ht->max * 2 + 1; + new_array = alloc_array(ht, new_max); + + if(!new_array) + return 0; + + for(hi = hsh_first(ht); hi; hi = hsh_next(hi)) + { + unsigned int i = hi->ths->hash & new_max; + hi->ths->next = new_array[i]; + new_array[i] = hi->ths; + } + + if(ht->array) + free(ht->array); + + ht->array = new_array; + ht->max = new_max; + return 1; +} + +/* + * This is where we keep the details of the hash function and control + * the maximum collision rate. + * + * If val is non-NULL it creates and initializes a new hash entry if + * there isn't already one there; it returns an updatable pointer so + * that hash entries can be removed. + */ + +static hsh_entry_t** find_entry(hsh_t* ht, const void* key, size_t klen, const void* val) +{ + hsh_entry_t** hep; + hsh_entry_t* he; + const unsigned char* p; + unsigned int hash; + size_t i; + + /* + * This is the popular `times 33' hash algorithm which is used by + * perl and also appears in Berkeley DB. This is one of the best + * known hash functions for strings because it is both computed + * very fast and distributes very well. + * + * The originator may be Dan Bernstein but the code in Berkeley DB + * cites Chris Torek as the source. The best citation I have found + * is "Chris Torek, Hash function for text in C, Usenet message + * <27038@mimsy.umd.edu> in comp.lang.c , October, 1990." in Rich + * Salz's USENIX 1992 paper about INN which can be found at + * . + * + * The magic of number 33, i.e. why it works better than many other + * constants, prime or not, has never been adequately explained by + * anyone. So I try an explanation: if one experimentally tests all + * multipliers between 1 and 256 (as I did while writing a low-level + * data structure library some time ago) one detects that even + * numbers are not useable at all. The remaining 128 odd numbers + * (except for the number 1) work more or less all equally well. + * They all distribute in an acceptable way and this way fill a hash + * table with an average percent of approx. 86%. + * + * If one compares the chi^2 values of the variants (see + * Bob Jenkins ``Hashing Frequently Asked Questions'' at + * http://burtleburtle.net/bob/hash/hashfaq.html for a description + * of chi^2), the number 33 not even has the best value. But the + * number 33 and a few other equally good numbers like 17, 31, 63, + * 127 and 129 have nevertheless a great advantage to the remaining + * numbers in the large set of possible multipliers: their multiply + * operation can be replaced by a faster operation based on just one + * shift plus either a single addition or subtraction operation. And + * because a hash function has to both distribute good _and_ has to + * be very fast to compute, those few numbers should be preferred. + * + * -- Ralf S. Engelschall + */ + hash = 0; + + if(klen == HSH_KEY_STRING) + { + for(p = key; *p; p++) + hash = hash * 33 + *p; + + klen = p - (const unsigned char *)key; + } + else + { + for(p = key, i = klen; i; i--, p++) + hash = hash * 33 + *p; + } + + /* scan linked list */ + for(hep = &ht->array[hash & ht->max], he = *hep; + he; hep = &he->next, he = *hep) + { + if(he->hash == hash && + he->klen == klen && + memcmp(KEY_DATA(he), key, klen) == 0) + break; + } + + if(he || !val) + return hep; + + /* add a new entry for non-NULL val */ + he = int_malloc(sizeof(*he)); + + if(he) + { + /* Key points to external data */ + he->key = key; + he->klen = klen; + + he->next = NULL; + he->hash = hash; + he->val = val; + + *hep = he; + ht->count++; + } + + return hep; +} + +void* hsh_get(hsh_t* ht, const void *key, size_t klen) +{ + hsh_entry_t** he = find_entry(ht, key, klen, NULL); + + if(he && *he) + return (void*)((*he)->val); + else + return NULL; +} + +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val) +{ + hsh_entry_t** hep = find_entry(ht, key, klen, val); + + if(hep && *hep) + { + /* replace entry */ + (*hep)->val = val; + + /* check that the collision rate isn't too high */ + if(ht->count > ht->max) + { + if(!expand_array(ht)) + return 0; + } + + return 1; + } + + return 0; +} + +void* hsh_rem(hsh_t* ht, const void* key, size_t klen) +{ + hsh_entry_t** hep = find_entry(ht, key, klen, NULL); + void* val = NULL; + + if(hep && *hep) + { + hsh_entry_t* old = *hep; + *hep = (*hep)->next; + --ht->count; + val = (void*)old->val; + free(old); + } + + return val; +} + +unsigned int hsh_count(hsh_t* ht) +{ + return ht->count; +} + diff --git a/daemon/common/hash.h b/daemon/common/hash.h new file mode 100644 index 0000000..df34a7a --- /dev/null +++ b/daemon/common/hash.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* + * Originally from apache 2.0 + * Modifications for general use by + */ + +/* Copyright 2000-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __HSH_H__ +#define __HSH_H__ + +/* + * OPTIONAL FEATURES + * + * Features to define. You need to build both this file and + * the corresponding hash.c file with whatever options you set here. + * These affect the method signatures, so see the sections below + * for the actual options + */ + +/* + * ARGUMENT DOCUMENTATION + * + * ht: The hashtable + * key: Pointer to the key value + * klen: The length of the key + * val: Pointer to the value + * hi: A hashtable iterator + * stamp: A unix timestamp + */ + + +/* ---------------------------------------------------------------------------------- + * TYPES + */ + +/* Abstract type for hash tables. */ +typedef struct hsh_t hsh_t; + +/* Abstract type for scanning hash tables. */ +typedef struct hsh_index_t hsh_index_t; + +/* ----------------------------------------------------------------------------- + * MAIN + */ + +/* + * hsh_create : Create a hash table + * - returns an allocated hashtable + */ +hsh_t* hsh_create(); + +/* + * hsh_free : Free a hash table + */ +void hsh_free(hsh_t* ht); + +/* + * hsh_count: Number of values in hash table + * - returns the number of entries in hash table + */ +unsigned int hsh_count(hsh_t* ht); + +/* + * hsh_get: Retrieves a value from the hash table + * - returns the value of the entry + */ +void* hsh_get(hsh_t* ht, const void* key, size_t klen); + +/* + * hsh_set: Set a value in the hash table + * - returns 1 if the entry was added properly + */ +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val); + +/* + * hsh_rem: Remove a value from the hash table + * - returns the value of the removed entry + */ +void* hsh_rem(hsh_t* ht, const void* key, size_t klen); + +/* + * hsh_first: Start enumerating through the hash table + * - returns a hash iterator + */ +hsh_index_t* hsh_first(hsh_t* ht); + +/* + * hsh_next: Enumerate through hash table + * - returns the hash iterator or null when no more entries + */ +hsh_index_t* hsh_next(hsh_index_t* hi); + +/* + * hsh_this: While enumerating get current value + * - returns the value that the iterator currently points to + */ +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen); + +/* + * This can be passed as 'klen' in any of the above functions to indicate + * a string-valued key, and have hash compute the length automatically. + */ +#define HSH_KEY_STRING (-1) + +#endif /* __HSH_H__ */ diff --git a/daemon/common/server-mainloop.c b/daemon/common/server-mainloop.c new file mode 100644 index 0000000..07f02b2 --- /dev/null +++ b/daemon/common/server-mainloop.c @@ -0,0 +1,424 @@ + +#include "usuals.h" +#include +#include + +#include "server-mainloop.h" + +typedef struct _socket_callback +{ + int fd; + server_socket_callback callback; + void* arg; + + struct _socket_callback* next; +} +socket_callback; + +typedef struct _timer_callback +{ + struct timeval at; + struct timeval interval; + server_timer_callback callback; + void* arg; + + struct _timer_callback* next; +} +timer_callback; + +typedef struct _server_context +{ + int stopped; + fd_set read_fds; + fd_set write_fds; + int max_fd; + socket_callback* callbacks; + timer_callback* timers; +} +server_context; + +/* Global context */ +static server_context ctx; + +static void +timeval_add(struct timeval* t1, struct timeval* t2) +{ + ASSERT(t1->tv_usec < 1000000); + ASSERT(t2->tv_usec < 1000000); + + t1->tv_sec += t2->tv_sec; + t1->tv_usec += t2->tv_usec; + if(t1->tv_usec >= 1000000) + { + t1->tv_usec -= 1000000; + t1->tv_sec += 1; + } +} + +static void +timeval_subtract(struct timeval* t1, struct timeval* t2) +{ + ASSERT(t1->tv_usec < 1000000); + ASSERT(t2->tv_usec < 1000000); + + t1->tv_sec -= t2->tv_sec; + if(t1->tv_usec < t2->tv_usec) + { + t1->tv_usec += 1000000; + t1->tv_sec -= 1; + } + t1->tv_usec -= t2->tv_usec; +} + +static int +timeval_compare(struct timeval* t1, struct timeval* t2) +{ + ASSERT(t1->tv_usec < 1000000); + ASSERT(t2->tv_usec < 1000000); + + if(t1->tv_sec > t2->tv_sec) + return 1; + else if(t1->tv_sec < t2->tv_sec) + return -1; + else + { + if(t1->tv_usec > t2->tv_usec) + return 1; + else if(t1->tv_usec < t2->tv_usec) + return -1; + else + return 0; + } +} + +#define timeval_empty(tv) \ + ((tv)->tv_sec == 0 && (tv)->tv_usec == 0) + +#define timeval_to_ms(tv) \ + ((((uint64_t)(tv).tv_sec) * 1000L) + (((uint64_t)(tv).tv_usec) / 1000L)) + +static int +timeval_dump(struct timeval* tv) +{ + fprintf(stderr, "{ %d:%d }", tv->tv_sec, tv->tv_usec / 1000); +} + +static int +add_timer(int ms, int oneshot, server_timer_callback callback, void* arg) +{ + struct timeval interval; + timer_callback* cb; + int i; + + ASSERT(ms > 0); + ASSERT(callback != NULL); + + interval.tv_sec = ms / 1000; + interval.tv_usec = (ms % 1000) * 1000; /* into micro seconds */ + + cb = (timer_callback*)calloc(1, sizeof(*cb)); + if(!cb) + { + errno = ENOMEM; + return -1; + } + + if(gettimeofday(&(cb->at), NULL) == -1) + { + free(cb); + return -1; + } + + timeval_add(&(cb->at), &interval); + + if (oneshot) + memset(&(cb->interval), 0, sizeof(cb->interval)); + else + memcpy(&(cb->interval), &interval, sizeof(cb->interval)); + + cb->callback = callback; + cb->arg = arg; + + cb->next = ctx.timers; + ctx.timers = cb; + + return 0; +} + +static timer_callback* +remove_timer(timer_callback* timcb) +{ + timer_callback* cb; + timer_callback* next; + int i; + + if(!ctx.timers) + return; + + /* First in list */; + if(ctx.timers == timcb) + { + cb = ctx.timers; + ctx.timers = ctx.timers->next; + free(cb); + return ctx.timers; + } + + /* One ahead processing of rest */ + for(cb = ctx.timers; cb->next; cb = cb->next) + { + if(cb->next == timcb) + { + next = cb->next->next; + free(cb->next); + cb->next = next; + return cb->next; + } + } +} + +void +server_init() +{ + memset(&ctx, 0, sizeof (ctx)); + FD_ZERO(&ctx.read_fds); + FD_ZERO(&ctx.write_fds); + + ctx.max_fd = -1; + ctx.stopped = 1; + ctx.callbacks = NULL; + ctx.timers = NULL; +} + +void +server_uninit() +{ + timer_callback* timcb; + timer_callback* timn; + socket_callback* sockcb; + socket_callback* sockn; + + for(timcb = ctx.timers; timcb; timcb = timn) + { + timn = timcb->next; + free(timcb); + } + + ctx.timers = NULL; + + for(sockcb = ctx.callbacks; sockcb; sockcb = sockn) + { + sockn = sockcb->next; + free(sockcb); + } + + ctx.timers = NULL; +} + +uint64_t +server_get_time() +{ + struct timeval tv; + if(gettimeofday(&tv, NULL) == -1) + return 0L; + return timeval_to_ms(tv); +} + +int +server_run() +{ + struct timeval* timeout; + struct timeval tv, current; + timer_callback* timcb; + socket_callback* sockcb; + fd_set rfds, wfds; + int r, i; + + /* No watches have been set */ + ASSERT(ctx.max_fd > -1); + + ctx.stopped = 0; + + while(!ctx.stopped) + { + /* Watch for the various fds */ + memcpy(&rfds, &ctx.read_fds, sizeof(rfds)); + memcpy(&wfds, &ctx.write_fds, sizeof(wfds)); + + /* Prepare for timers */ + timeout = NULL; + if(gettimeofday(¤t, NULL) == -1) + return -1; + + /* Cycle through timers */ + for(timcb = ctx.timers; timcb; ) + { + ASSERT(timcb->callback); + + /* Call any timers that have already passed */ + if(timeval_compare(¤t, &timcb->at) >= 0) + { + /* Convert to milliseconds, and make the call */ + r = (timcb->callback)(timeval_to_ms(current), timcb->arg); + + /* Reset timer if so desired */ + if (r == 1 && !timeval_empty(&timcb->interval)) + { + timeval_add(&timcb->at, &timcb->interval); + + /* If the time has already passed, just use current time */ + if(timeval_compare(&(timcb->at), ¤t) <= 0) + memcpy(&(timcb->at), ¤t, sizeof(timcb->at)); + } + + /* Otherwise remove it. Either one shot, or returned 0 */ + else + { + timcb = remove_timer(timcb); + continue; + } + } + + /* Get soonest timer */ + if (!timeout || timeval_compare(timeout, &timcb->at) < 0) + timeout = &timcb->at; + + timcb = timcb->next; + } + + /* Convert to an offset */ + if(timeout) + { + memcpy(&tv, timeout, sizeof(tv)); + timeout = &tv; + timeval_subtract(timeout, ¤t); + } + + /* fprintf(stderr, "selecting with timeout: "); + timeval_dump(timeout); + fprintf(stderr, "\n"); */ + + r = select(ctx.max_fd, &rfds, &wfds, NULL, timeout); + if (r < 0) + { + /* Interrupted so try again, and possibly exit */ + if (errno == EINTR) + continue; + + /* Programmer errors */ + ASSERT (errno != EBADF); + ASSERT (errno != EINVAL); + return r; + } + + /* Timeout, just jump to timeout processing */ + if(r == 0) + continue; + + for(sockcb = ctx.callbacks; sockcb; sockcb = sockcb->next) + { + ASSERT(sockcb->fd != -1); + + /* Call any that are set */ + if (FD_ISSET(sockcb->fd, &rfds)) + (sockcb->callback)(sockcb->fd, SERVER_READ, sockcb->arg); + if (FD_ISSET(sockcb->fd, &wfds)) + (sockcb->callback)(sockcb->fd, SERVER_WRITE, sockcb->arg); + } + } + + return 0; +} + +void +server_stop() +{ + ctx.stopped = 1; +} + +int +server_stopped() +{ + return ctx.stopped; +} + +int +server_watch(int fd, int type, server_socket_callback callback, void* arg) +{ + socket_callback* cb; + int i; + ASSERT(type != 0); + ASSERT(fd != -1); + ASSERT(callback != NULL); + + cb = (socket_callback*)calloc(sizeof(*cb), 1); + if(!cb) + { + errno = ENOMEM; + return -1; + } + + cb->fd = fd; + cb->callback = callback; + cb->arg = arg; + + cb->next = ctx.callbacks; + ctx.callbacks = cb; + + if (type & SERVER_READ) + FD_SET(fd, &ctx.read_fds); + if (type & SERVER_WRITE) + FD_SET(fd, &ctx.write_fds); + + if(fd >= ctx.max_fd) + ctx.max_fd = fd + 1; + + return 0; +} + +void +server_unwatch(int fd) +{ + socket_callback* cb; + int i; + + ASSERT(fd != -1); + + FD_CLR(fd, &ctx.read_fds); + FD_CLR(fd, &ctx.write_fds); + + if(!ctx.callbacks) + return; + + /* First in list */; + if(ctx.callbacks->fd == fd) + { + cb = ctx.callbacks; + ctx.callbacks = cb->next; + free(cb); + return; + } + + /* One ahead processing of rest */ + for(cb = ctx.callbacks; cb->next; cb = cb->next) + { + if(cb->next->fd == fd) + { + cb->next = cb->next->next; + free(cb->next); + return; + } + } +} + +int +server_timer(int ms, server_timer_callback callback, void* arg) +{ + return add_timer(ms, 0, callback, arg); +} + +int +server_oneshot(int ms, server_timer_callback callback, void* arg) +{ + return add_timer(ms, 1, callback, arg); +} diff --git a/daemon/common/server-mainloop.h b/daemon/common/server-mainloop.h new file mode 100644 index 0000000..ceff28d --- /dev/null +++ b/daemon/common/server-mainloop.h @@ -0,0 +1,27 @@ + +#ifndef __SERVER_MAINLOOP_H__ +#define __SERVER_MAINLOOP_H__ + +#include + +/* TODO: Prefix functions with svr */ + +#define SERVER_READ 0x01 +#define SERVER_WRITE 0x02 + +typedef void (*server_socket_callback)(int fd, int type, void* arg); +/* TODO: We should declare our own time type: 'mstime' */ +typedef int (*server_timer_callback)(uint64_t when, void* arg); + +void server_init(); +void server_uninit(); +int server_run(); +void server_stop(); +int server_stopped(); +int server_watch(int fd, int type, server_socket_callback callback, void* arg); +void server_unwatch(int fd); +int server_timer(int length, server_timer_callback callback, void* arg); +int server_oneshot(int length, server_timer_callback callback, void* arg); +uint64_t server_get_time(); + +#endif /* __SERVER_MAINLOOP_H__ */ diff --git a/daemon/common/sock-any.c b/daemon/common/sock-any.c new file mode 100644 index 0000000..1938d5d --- /dev/null +++ b/daemon/common/sock-any.c @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sock-any.h" + +#include + +#define LOCALHOST_ADDR 0x7F000001 + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) +{ + size_t l; + char buf[256]; + char* t; + char* t2; + int defport = (opts & 0xFFFF); + + memset(any, 0, sizeof(*any)); + + /* Just a port? */ + do + { + #define PORT_CHARS "0123456789" + #define PORT_MIN 1 + #define PORT_MAX 5 + + int port = 0; + + l = strspn(addr, PORT_CHARS); + if(l < PORT_MIN || l > PORT_MAX || addr[l] != 0) + break; + + port = strtol(addr, &t2, 10); + if(*t2 || port <= 0 || port >= 65536) + break; + + any->s.in.sin_port = htons(port); + + /* Fill in the type based on defaults */ +#ifdef HAVE_INET6 + if(opts & SANY_OPT_DEFINET6) + any->s.in.sin_family = AF_INET6; + else +#endif + any->s.in.sin_family = AF_INET; + + /* Fill in the address based on defaults */ + if(opts & SANY_OPT_DEFLOCAL) + { +#ifdef HAVE_INET6 + if(opts & SANY_OPT_DEFINET6) + memcpy(&(any->s.in.sin6_addr), &in6addr_loopback, sizeof(struct in6_addr)); + else +#endif + any->s.in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + /* + * Note the 'any' option is the default since we zero out + * the entire structure above. + */ + + any->namelen = sizeof(any->s.in); + return AF_INET; + } + while(0); + + /* Look and see if we can parse an ipv4 address */ + do + { + #define IPV4_PORT_CHARS + #define IPV4_CHARS "0123456789." + #define IPV4_MIN 3 + #define IPV4_MAX 21 + + int port = 0; + t = NULL; + + l = strlen(addr); + if(l < IPV4_MIN || l > IPV4_MAX) + break; + + strcpy(buf, addr); + + /* Find the last set that contains just numbers */ + l = strspn(buf, IPV4_CHARS); + if(l < IPV4_MIN) + break; + + /* Either end of string or port */ + if(buf[l] != 0 && buf[l] != ':') + break; + + /* Get the port out */ + if(buf[l] != 0) + { + t = buf + l + 1; + buf[l] = 0; + } + + if(t) + { + port = strtol(t, &t2, 10); + if(*t2 || port <= 0 || port >= 65536) + break; + } + + any->s.in.sin_family = AF_INET; + any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port)); + + if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0) + break; + + any->namelen = sizeof(any->s.in); + return AF_INET; + } + while(0); + +#ifdef HAVE_INET6 + do + { + #define IPV6_CHARS "0123456789:" + #define IPV6_MIN 3 + #define IPV6_MAX 51 + + int port = -1; + t = NULL; + + l = strlen(addr); + if(l < IPV6_MIN || l > IPV6_MAX) + break; + + /* If it starts with a '[' then we can get port */ + if(buf[0] == '[') + { + port = 0; + addr++; + } + + strcpy(buf, addr); + + /* Find the last set that contains just numbers */ + l = strspn(buf, IPV6_CHARS); + if(l < IPV6_MIN) + break; + + /* Either end of string or port */ + if(buf[l] != 0) + { + /* If had bracket, then needs to end with a bracket */ + if(port != 0 || buf[l] != ']') + break; + + /* Get the port out */ + t = buf + l + 1; + + if(*t = ':') + t++; + } + + if(t) + { + port = strtol(t, &t, 10); + if(*t || port <= 0 || port >= 65536) + break; + } + + any->s.in6.sin6_family = AF_INET6; + any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port); + + if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0) + break; + + any->namelen = sizeof(any->s.in6); + return AF_INET6; + } + while(0); +#endif + + /* A unix socket path */ + do + { + /* No colon and must have a path component */ + if(strchr(addr, ':') || !strchr(addr, '/')) + break; + + l = strlen(addr); + if(l >= sizeof(any->s.un.sun_path)) + break; + + any->s.un.sun_family = AF_UNIX; + strcpy(any->s.un.sun_path, addr); + + any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l); + return AF_UNIX; + } + while(0); + + /* A DNS name and a port? */ + do + { + struct addrinfo* res; + int port = 0; + t = NULL; + + l = strlen(addr); + if(l >= 255 || !isalpha(addr[0])) + break; + + /* Some basic illegal character checks */ + if(strcspn(addr, " /\\") != l) + break; + + strcpy(buf, addr); + + /* Find the last set that contains just numbers */ + t = strchr(buf, ':'); + if(t) + { + *t = 0; + t++; + } + + if(t) + { + port = strtol(t, &t2, 10); + if(*t2 || port <= 0 || port >= 65536) + break; + } + + /* Try and resolve the domain name */ + if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res) + break; + + memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr)); + any->namelen = res->ai_addrlen; + freeaddrinfo(res); + + port = htons((unsigned short)(port <= 0 ? defport : port)); + + switch(any->s.a.sa_family) + { + case PF_INET: + any->s.in.sin_port = port; + break; +#ifdef HAVE_INET6 + case PF_INET6: + any->s.in6.sin6_port = port; + break; +#endif + }; + + return any->s.a.sa_family; + } + while(0); + + return -1; +} + +int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts) +{ + int len = 0; + int port = 0; + + switch(any->s.a.sa_family) + { + case AF_UNIX: + len = strlen(any->s.un.sun_path); + if(addrlen < len + 1) + { + errno = ENOSPC; + return -1; + } + + strcpy(addr, any->s.un.sun_path); + break; + + case AF_INET: + if(inet_ntop(any->s.a.sa_family, &(any->s.in.sin_addr), addr, addrlen) == NULL) + return -1; + port = ntohs(any->s.in.sin_port); + break; + +#ifdef HAVE_INET6 + case AF_INET6: + if(inet_ntop(any->s.a.sa_family, &(any->s.in6.sin6_addr), addr, addrlen) == NULL) + return -1; + port = ntohs(any->s.in6.sin6_port); + break; +#endif + + default: + errno = EAFNOSUPPORT; + return -1; + } + + if(!(opts & SANY_OPT_NOPORT) && port != 0) + { + strncat(addr, ":", addrlen); + addr[addrlen - 1] = 0; + + len = strlen(addr); + addr += len; + addrlen -= len; + + snprintf(addr, addrlen, "%d", port); + } + + return 0; +} + +int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts) +{ + if(a1->s.a.sa_family != a2->s.a.sa_family) + return -1; + + switch(a1->s.a.sa_family) + { + case AF_UNIX: + return strcmp(a1->s.un.sun_path, a2->s.un.sun_path); + + case AF_INET: + if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0) + return -1; + if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port) + return -1; + return 0; +#ifdef HAVE_INET6 + case AF_INET6: + if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0) + return -1; + if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port) + return -1; + return 0; +#endif + default: + errno = EAFNOSUPPORT; + return -1; + } +} diff --git a/daemon/common/sock-any.h b/daemon/common/sock-any.h new file mode 100644 index 0000000..31cb13b --- /dev/null +++ b/daemon/common/sock-any.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#ifndef __SOCK_ANY_H__ +#define __SOCK_ANY_H__ + +#include +#include +#include + +struct sockaddr_any +{ + union _sockaddr_any + { + /* The header */ + struct sockaddr a; + + /* The different types */ + struct sockaddr_un un; + struct sockaddr_in in; +#ifdef HAVE_INET6 + struct sockaddr_in6 in6; +#endif + } s; + size_t namelen; +}; + +#define SANY_ADDR(any) ((any).s.a) +#define SANY_LEN(any) ((any).namelen) +#define SANY_TYPE(any) ((any).s.a.sa_family) + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); + +/* The default port to fill in when no IP/IPv6 port specified */ +#define SANY_OPT_DEFPORT(p) (int)((p) & 0xFFFF) + +/* When only port specified default to IPANY */ +#define SANY_OPT_DEFANY 0x00000000 + +/* When only port specified default to LOCALHOST */ +#define SANY_OPT_DEFLOCAL 0x00100000 + +/* When only port specified default to IPv6 */ +#ifdef HAVE_INET6 +#define SANY_OPT_DEFINET6 0x00200000 +#endif + +int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts); + +/* Don't print or compare the port */ +#define SANY_OPT_NOPORT 0x01000000 + +int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts); + +#endif /* __SOCK_ANY_H__ */ diff --git a/daemon/common/stringx.c b/daemon/common/stringx.c new file mode 100644 index 0000000..37a3df9 --- /dev/null +++ b/daemon/common/stringx.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include + +#include +#include +#include +#include +#include + +#include "usuals.h" +#include "stringx.h" + +void +remove_cr(char* data) +{ + char* p; + for(p = data; *data; data++, p++) + { + while(*data == '\r') + data++; + *p = *data; + } + + /* Renull terminate */ + *p = 0; +} + +char* +trim_start(const char* data) +{ + while(*data && isspace(*data)) + ++data; + return (char*)data; +} + +void +trim_end(char* data) +{ + char* t = data + strlen(data); + while(t > data && isspace(*(t - 1))) + { + t--; + *t = 0; + } +} + +char* +trim_space(char* data) +{ + data = (char*)trim_start(data); + trim_end(data); + return data; +} + +/* String to bool helper function */ +int strtob(const char* str) +{ + if(strcasecmp(str, "0") == 0 || + strcasecmp(str, "no") == 0 || + strcasecmp(str, "false") == 0 || + strcasecmp(str, "f") == 0 || + strcasecmp(str, "off") == 0) + return 0; + + if(strcasecmp(str, "1") == 0 || + strcasecmp(str, "yes") == 0 || + strcasecmp(str, "true") == 0 || + strcasecmp(str, "t") == 0 || + strcasecmp(str, "on") == 0) + return 1; + + return -1; +} + +size_t +strlcpy(char *dst, const char *src, size_t len) +{ + size_t ret = strlen(dst); + + while (len > 1) { + *dst++ = *src++; + len--; + } + if (len > 0) + *dst = '\0'; + return (ret); +} + +size_t strlcat(char* dst, const char* src, size_t siz) +{ + char* d = dst; + const char* s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while(n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if(n == 0) + return dlen + strlen(s); + while(*s != '\0') + { + if(n != 1) + { + *d++ = *s; + n--; + } + + s++; + } + + *d = '\0'; + + return dlen + (s - src); /* count does not include NUL */ +} diff --git a/daemon/common/stringx.h b/daemon/common/stringx.h new file mode 100644 index 0000000..042cf2d --- /dev/null +++ b/daemon/common/stringx.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#ifndef __STRINGX_H__ +#define __STRINGX_H__ + +void remove_cr(char* data); + +char* trim_start(const char* data); +void trim_end(char* data); +char* trim_space(char* data); + +int strtob(const char* str); + +size_t +strlcpy(char *dst, const char *src, size_t len); + +#endif /* __STRINGX_H__ */ diff --git a/daemon/config.c b/daemon/config.c new file mode 100644 index 0000000..ca64514 --- /dev/null +++ b/daemon/config.c @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +/* TODO: Put file names in all the parsing error messages */ + +/* + * These routines parse the configuration files and setup the in memory + * data structures. They're mostly run before becoming a daemon, and just + * exit on error. + */ + +typedef struct _config_ctx +{ + const char* confname; + char* configmem; + uint interval; + uint timeout; + rb_item* items; +} +config_ctx; + +/* ----------------------------------------------------------------------------- + * STRINGS + */ + +#define CONFIG_POLL "poll" +#define CONFIG_INTERVAL "interval" +#define CONFIG_FIELD "field." +#define CONFIG_SNMP "snmp" + +#define FIELD_VALID "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-0123456789." + +/* ----------------------------------------------------------------------------- + * CONFIG LOADING + */ + +static rb_item* +sort_items_by_host(rb_item *item) +{ + rb_item *sort = NULL; + rb_item *cur; + rb_item *it; + + while(item) + { + cur = item; + item = item->next; + cur->next = NULL; + + /* First item */ + if(!sort) + { + sort = cur; + continue; + } + + /* Before first item */ + else if(cur->host <= sort->host) + { + cur->next = sort; + sort = cur; + continue; + } + + for(it = sort; it->next; it = it->next) + { + if(cur->host <= sort->next->host) + break; + } + + ASSERT(it); + cur->next = it->next; + it->next = cur; + } + + return sort; +} + +static void +config_done(config_ctx* ctx) +{ + char key[MAXPATHLEN]; + rb_item* it; + rb_poller* poll; + char *t; + + /* No polling specified */ + if(!ctx->items) + return; + + /* Make sure we found an interval */ + if(ctx->interval == 0) + errx(2, "no interval specified in config file: %s", ctx->confname); + + if(ctx->timeout == 0) + ctx->timeout = g_state.timeout; + + /* And a nice key for lookups */ + snprintf(key, sizeof(key), "%d-%d:%s/%s.rrd", ctx->timeout, + ctx->interval, g_state.rrddir, ctx->confname); + key[sizeof(key) - 1] = 0; + + /* See if we have one of these pollers already */ + poll = (rb_poller*)hsh_get(g_state.poll_by_key, key, -1); + if(!poll) + { + poll = (rb_poller*)calloc(1, sizeof(*poll)); + + if(!poll || !hsh_set(g_state.poll_by_key, key, -1, poll)) + errx(1, "out of memory"); + + strcpy(poll->key, key); + t = strchr(poll->key, ':'); + ASSERT(t); + poll->rrdname = t + 1; + + poll->interval = ctx->interval * 1000; + poll->timeout = ctx->timeout * 1000; + + /* Add it to the main lists */ + poll->next = g_state.polls; + g_state.polls = poll; + } + + /* Get the last item and add to the list */ + for(it = ctx->items; it->next; it = it->next) + it->poller = poll; + + ASSERT(it); + it->poller = poll; + + /* Add the items to this poller */ + it->next = poll->items; + poll->items = sort_items_by_host(ctx->items); + + /* + * This remains allocated for the life of the program as + * All the configuration strings are in this memory. + * This allows all the users of these strings not to worry + * about reallocating or freeing them + */ + rb_atexit(free, ctx->configmem); + ctx->configmem = NULL; + + /* Clear current config and get ready for next */ + ctx->items = NULL; + ctx->interval = 0; + + +} + +static void +parse_uri(char *uri, char** scheme, char** host, + char** user, char** path, config_ctx* ctx) +{ + /* Copy only for error messages as we mess with original */ + char* copy = strdup(uri); + char* t; + + *host = NULL; + *path = NULL; + *user = NULL; + + *scheme = strsep(&uri, ":"); + if(uri == NULL) + errx(2, "invalid poll uri (scheme invalid): %s", copy); + + if((uri[0] != '/' && uri[1] != '/')) + errx(2, "invalid poll uri (scheme invalid): %s", copy); + + uri += 2; + *host = strsep(&uri, "/"); + if(*host[0]) + { + /* Parse the user name out from the host */ + t = strchr(*host, '@'); + if(t) + { + *t = 0; + *user = *host; + *host = t + 1; + } + } + + if(!*host[0]) + errx(2, "invalid poll uri (no hostname found): %s", copy); + + if(!uri || !uri[0] || !uri[1]) + errx(2, "invalid poll uri (no pathname found): %s", copy); + + *path = uri; + + /* This copy only for error messages */ + free(copy); +} + +static rb_item* +parse_item(const char* field, char* uri, config_ctx *ctx) +{ + rb_item *ritem; + rb_host *rhost; + + char* host; + char* user; + char* scheme; + char* path; + + /* Parse the SNMP URI */ + parse_uri(uri, &scheme, &host, &user, &path, ctx); + ASSERT(scheme && host && path); + + /* TODO: SNMP version support */ + + /* Currently we only support SNMP pollers */ + if(strcmp(scheme, CONFIG_SNMP) != 0) + errx(2, "invalid poll scheme: %s", scheme); + + /* TODO: THis code assumes all hosts have the same community + the lookups below won't work wehn host/community is different */ + + /* See if we can find an associated host */ + rhost = (rb_host*)hsh_get(g_state.host_by_name, host, -1); + if(!rhost) + { + /* Make a new one if necessary */ + rhost = (rb_host*)calloc(1, sizeof(*rhost)); + + if(!rhost || !hsh_set(g_state.host_by_name, host, -1, rhost)) + errx(1, "out of memory"); + + /* TODO: Version support */ + rhost->version = 1; + rhost->name = host; + rhost->community = user ? user : "public"; + + /* TODO: Eventually resolving should be in a separate thread, + and done regularly */ + if(sock_any_pton(host, &(rhost->address), + SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) + { + rb_message(LOG_WARNING, "couldn't resolve host address (ignoring): %s", host); + free(rhost); + return; + } + + /* And add it to the list */ + rhost->next = g_state.hosts; + g_state.hosts = rhost; + } + + /* Make a new item */ + ritem = calloc(1, sizeof(*ritem)); + if(!ritem) + errx(1, "out of memory"); + + ritem->rrdfield = field; + ritem->host = rhost; + ritem->poller = NULL; /* Set later in config_done */ + ritem->req = NULL; + ritem->value = RB_UNKNOWN; + + /* And parse the OID */ + if(rb_parse_mib(path, &(ritem->snmpfield)) == -1) + errx(2, "invalid OID: %s", path + 1); + + /* And add it to the list */ + ritem->next = ctx->items; + ctx->items = ritem; +} + +static void +config_value(const char* header, const char* name, char* value, + config_ctx* ctx) +{ + if(strcmp(header, "poll") != 0) + return; + + if(strcmp(name, CONFIG_INTERVAL) == 0) + { + char* t; + int i; + + if(ctx->interval > 0) + errx(2, CONFIG_INTERVAL " specified twice: %s", value); + + i = strtol(value, &t, 10); + if(i < 1) + err(2, CONFIG_INTERVAL " must be a positive number: %s", value); + + ctx->interval = (uint32_t)i; + } + + /* If it starts with "field." */ + if(strncmp(name, CONFIG_FIELD, KL(CONFIG_FIELD)) == 0) + { + rb_poller* poll; + const char* field; + const char* t; + + /* Check the name */ + field = name + KL(CONFIG_FIELD); + t = field + strspn(field, FIELD_VALID); + if(*t) + err(2, "the '%s' field name must only contain characters, digits, underscore and dash", field); + + /* Parse out the field */ + parse_item(field, value, ctx); + } +} + +/* ----------------------------------------------------------------------------- + * CONFIG FILES + */ + +static char* +read_config_file(const char* configfile) +{ + char* config = NULL; + FILE* f = NULL; + long len; + + ASSERT(configfile); + + f = fopen(configfile, "r"); + if(f == NULL) + err(1, "couldn't open config file: %s", configfile); + + /* Figure out size */ + if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) + err(1, "couldn't seek config file: %s", configfile); + + if((config = (char*)malloc(len + 2)) == NULL) + errx(1, "out of memory"); + + /* And read in one block */ + if(fread(config, 1, len, f) != len) + err(1, "couldn't read config file: %s", configfile); + + fclose(f); + + /* Null terminate the data */ + config[len] = '\n'; + config[len + 1] = 0; + + /* Remove nasty dos line endings */ + remove_cr(config); + + rb_messagex(LOG_DEBUG, "read config file: %s", configfile); + return config; +} + +static void +parse_config_file(const char* configfile, config_ctx *ctx) +{ + char* name = NULL; + char* value = NULL; + char* config; + char* next; + char* header; + char* p; + char* t; + int pos; + + config = read_config_file(configfile); + ctx->configmem = config; + next = config; + + /* Go through lines and process them */ + while((t = strchr(next, '\n')) != NULL) + { + *t = 0; + p = next; /* Do this before cleaning below */ + next = t + 1; + + t = trim_start(p); + + /* Continuation line (had spaces at start) */ + if(p < t && *t) + { + if(!value) + errx(2, "invalid continuation in config: %s", p); + + /* Calculate the end of the current value */ + t = value + strlen(value); + ASSERT(t < p); + + /* Continuations are separated by spaces */ + *t = ' '; + t++; + + continue; + } + + // No continuation hand off value if necessary + if(name && value) + { + rb_messagex(LOG_DEBUG, "parsed configuration value: [%s] %s = %s", header, name, value); + config_value(header, name, value, ctx); + } + + name = NULL; + value = NULL; + + /* Empty lines / comments at start / comments without continuation */ + if(!*t || *p == '#') + continue; + + /* A header */ + if(*p == '[') + { + t = p + strcspn(p, "]"); + if(!*t || t == p + 1) + errx(2, "invalid config header: %s", p); + + *t = 0; + header = trim_space(p + 1); + continue; + } + + /* Look for the break between name = value on the same line */ + t = p + strcspn(p, ":="); + if(!*t) + errx(2, "invalid config line: %s", p); + + /* Null terminate and split value part */ + *t = 0; + t++; + + name = trim_space(p); + value = trim_space(t); + } + + if(name && value) + { + rb_messagex(LOG_DEBUG, "parsed configuration value: [%s] %s = %s", header, name, value); + config_value(header, name, value, ctx); + } + + config_done(ctx); + + /* If nobody claimed this memory then we don't need to keep it around */ + if(ctx->configmem) + free(ctx->configmem); + ctx->configmem = NULL; +} + +void +rb_config_parse() +{ + char configfile[MAXPATHLEN]; + struct dirent* dire; + config_ctx ctx; + DIR* dir; + + /* Setup the hash tables properly */ + g_state.poll_by_key = hsh_create(); + g_state.host_by_name = hsh_create(); + + dir = opendir(g_state.confdir); + if(!dir) + err("couldn't read config directory: %s", g_state.confdir); + + while((dire = readdir(dir)) != NULL) + { + if(dire->d_type != DT_REG && dire->d_type != DT_LNK) + continue; + + /* Build a happy path name */ + snprintf(configfile, MAXPATHLEN, "%s/%s", g_state.confdir, dire->d_name); + configfile[MAXPATHLEN - 1] = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.confname = dire->d_name; + + parse_config_file(configfile, &ctx); + } + + closedir(dir); +} + +/* ----------------------------------------------------------------------------- + * FREEING MEMORY + */ + +static void +free_items(rb_item* item) +{ + rb_item* next; + for(; item; item = next) + { + next = item->next; + free(item); + } +} + +static void +free_hosts(rb_host* host) +{ + rb_host* next; + for(; host; host = next) + { + next = host->next; + free(host); + } +} + +static void +free_pollers(rb_poller* poll) +{ + rb_poller* next; + for(; poll; poll = next) + { + free_items(poll->items); + + next = poll->next; + free(poll); + } + +} + +void +rb_config_free() +{ + hsh_free(g_state.poll_by_key); + hsh_free(g_state.host_by_name); + + free_hosts(g_state.hosts); + + /* Note that rb_item's are owned by pollers */ + free_pollers(g_state.polls); +} diff --git a/daemon/rrd-update.c b/daemon/rrd-update.c new file mode 100644 index 0000000..b4be997 --- /dev/null +++ b/daemon/rrd-update.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#define _GNU_SOURCE + +#include "usuals.h" +#include +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +#define MAX_NUMLEN 40 + +void rb_rrd_update(rb_poller *poll) +{ + char buf[MAX_NUMLEN]; + const char* argv[5]; + char* template; + char* items; + int r, tlen, ilen; + rb_item *it; + + if(!poll->items) + return; + + tlen = 0; + ilen = 3; + + for(it = poll->items; it; it = it->next) + { + tlen += strlen(it->rrdfield) + 1; + ilen += 40; + } + + template = (char*)calloc(tlen, sizeof(char)); + items = (char*)calloc(ilen, sizeof(char)); + + if(!template || !items) + { + if(items) + free(items); + if(template) + free(template); + rb_messagex(LOG_CRIT, "out of memory"); + return; + } + + /* Put in the right time */ + snprintf(items, ilen, "%lld:", (poll->last_polled / 1000L)); + + /* Build the rest of the arguments */ + for(it = poll->items; it; it = it->next) + { + if(it != poll->items) + { + strlcat(template, ":", tlen); + strlcat(items, ":", ilen); + } + + strlcat(template, it->rrdfield, tlen); + + if(it->value == RB_UNKNOWN) + strlcat(items, "U", ilen); + else + { + snprintf(buf, MAX_NUMLEN, "%.4lf", it->value); + buf[MAX_NUMLEN - 1] = 0; + strlcat(items, buf, ilen); + } + } + + /* Always have to clear before calling rrdtool. klunky :( */ + optind = 0; + opterr = 0; + + /* TODO: We need support for @ to specify when these values occurred */ + + argv[0] = "rrdupdate"; + argv[1] = poll->rrdname; + argv[2] = "-t"; + argv[3] = template; + argv[4] = items; + + rb_messagex(LOG_DEBUG, "updating RRD file: %s", poll->rrdname); + rb_messagex(LOG_DEBUG, "> template: %s", template); + rb_messagex(LOG_DEBUG, "> values: %s", items); + + rrd_clear_error(); + r = rrd_update(5, argv); + + if(r != 0) + rb_messagex(LOG_ERR, "couldn't update rrd file: %s: %s", + poll->rrdname, rrd_get_error()); + + free(template); + free(items); +} diff --git a/daemon/rrdbotd.c b/daemon/rrdbotd.c new file mode 100644 index 0000000..95ab092 --- /dev/null +++ b/daemon/rrdbotd.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include +#include +#include + +/* TODO: Abstract these headers away nicely */ +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +/* TODO: Temporary */ +#include "snmpclient.h" + +/* ----------------------------------------------------------------------------- + * GLOBALS + */ + +/* The one main state object */ +rb_state g_state; + +/* TODO: These should be set from the command line */ +static int daemonized = 0; +static int debug_level = 7; + +/* ----------------------------------------------------------------------------- + * CLEANUP + */ + +typedef struct _exit_stack +{ + voidfunc func; + void* data; + + /* We have a list of these beauties */ + struct _exit_stack* next; +} +exit_stack; + +/* Our exit stack */ +static exit_stack* atexits = NULL; +static int atexit_registered = 0; + +static void +atexit_do_stack(void) +{ + exit_stack* next; + for(; atexits; atexits = next) + { + next = atexits->next; + (atexits->func)(atexits->data); + free(atexits); + } +} + +void +rb_atexit(voidfunc func, void* data) +{ + exit_stack* ae; + + ASSERT(func); + + ae = (exit_stack*)calloc(1, sizeof(exit_stack)); + if(ae) + { + ae->func = func; + ae->data = data; + ae->next = atexits; + atexits = ae; + + if(!atexit_registered) + atexit(atexit_do_stack); + } +} + +/* ----------------------------------------------------------------------------- + * LOGGING + */ + +static void +vmessage (int level, int err, const char* msg, va_list ap) +{ + #define MAX_MSGLEN 1024 + char buf[MAX_MSGLEN]; + int e = errno; + + if(daemonized) { + if (level >= LOG_DEBUG) + return; + } else { + if(debug_level < level) + return; + } + + ASSERT (msg); + snprintf(buf, MAX_MSGLEN, "%s%s", msg, err ? ": " : ""); + + if(err) + strncat(buf, strerror(e), MAX_MSGLEN); + + /* As a precaution */ + buf[MAX_MSGLEN - 1] = 0; + + /* Either to syslog or stderr */ + if (daemonized) + vsyslog (level, buf, ap); + else + vwarnx (buf, ap); +} + +void +rb_messagex (int level, const char* msg, ...) +{ + va_list ap; + va_start(ap, msg); + vmessage(level, 0, msg, ap); + va_end(ap); +} + +void +rb_message (int level, const char* msg, ...) +{ + va_list ap; + va_start(ap, msg); + vmessage(level, 1, msg, ap); + va_end(ap); +} + +/* ----------------------------------------------------------------------------- + * STARTUP + */ + +static void +usage() +{ + fprintf(stderr, "usage: rrdcollectd\n"); + fprintf(stderr, " rrdcollectd -v\n"); + exit(2); +} + +#include + +int +main(int argc, char* argv[]) +{ + int daemonize; + char ch; + + /* Initialize the state stuff */ + memset(&g_state, 0, sizeof(g_state)); + + /* TODO: These should come from configure, and from arguments */ + g_state.rrddir = "/data/projects/rrdui/work"; + g_state.confdir = "/data/projects/rrdui/conf"; + g_state.retries = 3; + g_state.timeout = 5; + + /* Parse the arguments nicely */ + while((ch = getopt(argc, argv, "v")) != -1) + { + switch(ch) + { + + /* Print version number */ + case 'v': + printf("rrdcollectd (version %s)\n", VERSION); + exit(0); + break; + + /* Usage information */ + case '?': + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + /* The mainloop server */ + server_init(); + + /* Parse config and setup SNMP system */ + rb_config_parse(); + rb_snmp_engine_init(); + + /* Now let it go */ + if(server_run() == -1) + err(1, "critical failure running SNMP engine"); + + /* Cleanups */ + rb_snmp_engine_uninit(); + rb_config_free(); + server_uninit(); + + return 0; +} + diff --git a/daemon/rrdbotd.h b/daemon/rrdbotd.h new file mode 100644 index 0000000..74f8fd5 --- /dev/null +++ b/daemon/rrdbotd.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#ifndef __RRDBOTD_H__ +#define __RRDBOTD_H__ + +#include +#include + +#include "asn1.h" +#include "snmp.h" +#include "sock-any.h" +#include "hash.h" + +/* ----------------------------------------------------------------------------- + * DATA + */ + +typedef uint64_t mstime; +#define RB_UNKNOWN -DBL_MAX + +struct _rb_item; +struct _rb_poller; +struct _rb_host; +struct _rb_request; + +/* + * Note that all the members are either in the config memory + * or inline. This helps us keep memory management simple. + */ + +typedef struct _rb_item +{ + struct _rb_request* req; + + /* Specific to this item */ + const char* rrdfield; + struct snmp_value snmpfield; + + /* The last value / current request */ + double value; + + /* Pointers to related */ + const struct _rb_poller* poller; + const struct _rb_host* host; + + /* Next in list of items */ + struct _rb_item* next; +} +rb_item; + +typedef struct _rb_host +{ + const char* name; + const char* community; + int version; + + /* Host resolving and book keeping */ + struct sockaddr_any address; + mstime interval; + mstime last_resolved; + + /* Next in list of hosts */ + struct _rb_host* next; +} +rb_host; + +typedef struct _rb_poller +{ + /* The hash key is interval-timeout:rrdname */ + char key[MAXPATHLEN]; + + /* This points into the memory above */ + const char* rrdname; + + mstime interval; + mstime timeout; + + /* The things to poll. rb_poller owns this list */ + rb_item* items; + + /* Book keeping */ + mstime last_polled; + + /* Next in list of pollers */ + struct _rb_poller* next; +} +rb_poller; + +typedef struct _rb_state +{ + /* Settings from command line */ + const char* confdir; + const char* rrddir; + uint retries; + uint timeout; + + /* All the pollers/hosts */ + rb_poller* polls; + rb_host* hosts; + + /* Quick lookups for responses */ + hsh_t* poll_by_key; + hsh_t* host_by_name; +} +rb_state; + +/* One global rb_state with all the main settings */ +extern rb_state g_state; + +/* ----------------------------------------------------------------------------- + * UTILITIES (rrdbotd.c) + */ + +void rb_messagex (int level, const char* msg, ...); +void rb_message (int level, const char* msg, ...); + +typedef void (*voidfunc)(void*); +void rb_atexit (voidfunc func, void* data); + +/* ----------------------------------------------------------------------------- + * CONFIG (config.c) + */ + +void rb_config_parse(); +void rb_config_free(); + +/* ----------------------------------------------------------------------------- + * SNMP HELPERS (snmp-help.c) + */ + +int rb_snmp_parse_mib(const char* oid, struct snmp_value* value); + +/* ----------------------------------------------------------------------------- + * SNMP ENGINE (snmp-engine.c) + */ + +void rb_snmp_engine_init(); +void rb_snmp_engine_uninit(); + +/* ----------------------------------------------------------------------------- + * RRD UPDATE CODE (rrd-update.c) + */ + +void rb_rrd_update(rb_poller *poll); + +#endif /* __RRDBOTD_H__ */ diff --git a/daemon/snmp-engine.c b/daemon/snmp-engine.c new file mode 100644 index 0000000..e1bff30 --- /dev/null +++ b/daemon/snmp-engine.c @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include + +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" +#include "server-mainloop.h" + +/* The socket to use */ +int snmp_socket = -1; + +/* The last request: start at a strange number */ +uint32_t snmp_request = 0x0A0A0A0A; + +/* Since we only deal with one packet at a time, global buffer */ +unsigned char snmp_buffer[0x1000]; + +/* ----------------------------------------------------------------------------- + * REQUESTS + */ + +/* rb_request waaaaayyyyy too big */ +typedef struct _rb_request +{ + /* The SNMP request identifier */ + uint32_t id; + + mstime next_retry; /* Time of the next retry */ + mstime interval; /* How long between retries */ + mstime timeout; /* When this request times out */ + uint sent; /* How many times we've sent */ + + /* The poller and host associated with this request */ + rb_poller* poll; + const rb_host* host; + + /* The actual request data */ + struct snmp_pdu pdu; +} +rb_request; + +/* a scrolling window on a loop */ +static rb_request* requests = NULL; +static int reqhigh = -1; +static int reqlow = -1; +static uint nrequests = 0; + +static rb_request* +new_req() +{ + rb_request* req = NULL; + uint num; + int i, first, overlap = 0; + + if(nrequests) + { + /* We allocate in a loop starting after the last allocation. */ + for(i = (reqhigh + 1) % nrequests; i != reqhigh; + i = (i + 1) % nrequests) + { + /* + * We can overlap past reqlow, but in that case no + * updating reqhigh. This can happen after reallocating. + */ + if(i == reqlow) + overlap = 1; + + if(requests[i].id == 0) + { + req = &(requests[i]); + + if(!overlap) + reqhigh = i; + break; + } + } + } + + if(!req) + { + /* + * A note about the scrolling window and extending allocations... + * The only reason this works is because whenever we reallocate + * reqhigh and reqlow are the same. + */ + ASSERT(reqlow == reqhigh); + + /* Reallocate the request block */ + /* TODO: Once we use less memory this can be higher */ + num = nrequests ? nrequests * 2 : 32; + requests = (rb_request*)realloc(requests, sizeof(rb_request) * num); + if(!requests) + { + /* Note we leave old requests allocated */ + errno = ENOMEM; + return NULL; + } + + /* Clear out the new ones */ + memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests)); + + /* We return the next one */ + req = requests + nrequests; + + nrequests = num; + + if(reqhigh == -1) + reqhigh = 0; + if(reqlow == -1) + reqlow = nrequests - 1; + } + + /* A incrementing counter for each request */ + req->id = snmp_request++; + return req; +} + +static rb_request* +find_req(uint32_t id) +{ + int i, first; + + if(!nrequests) + return NULL; + + /* + * Search backwards from the in the scrolling window. This gives + * us as high performance for the high performing pollers and + * less performance for the low ones. + */ + for(i = reqhigh, first = 1; first || i != reqlow; + i = (i ? i : nrequests) - 1) + { + if(id == requests[i].id) + return &(requests[i]); + first = 0; + } + + return NULL; +} + +static void +free_req(rb_request* req) +{ + int i; + + memset(req, 0, sizeof(*req)); + + /* Update the bottom of the scrolling loop */ + for(i = reqlow; i != reqhigh; i = (i + 1) % nrequests) + { + /* If used then done */ + if(requests[i].id) + break; + + /* reqlow is not inclusive */ + reqlow = i; + } +} + +/* ----------------------------------------------------------------------------- + * PACKET HANDLING + */ + +static void +finish_poll(rb_poller* poll, mstime when) +{ +#ifdef _DEBUG + { + rb_item* it; + for(it = poll->items; it; it = it->next) + ASSERT(!it->req); + } +#endif + + /* Update the book-keeping */ + poll->last_polled = when; + + rb_messagex(LOG_DEBUG, "collected poll values. sending them to rrd"); + + /* And send off our collection of values */ + rb_rrd_update(poll); +} + +static void +send_req(rb_request* req, mstime when) +{ + struct asn_buf b; + ssize_t ret; + + b.asn_ptr = snmp_buffer; + b.asn_len = sizeof(snmp_buffer); + + if(snmp_pdu_encode(&(req->pdu), &b)) + rb_message(LOG_CRIT, "couldn't encode snmp buffer"); + else + { + ret = sendto(snmp_socket, snmp_buffer, b.asn_ptr - snmp_buffer, 0, + &SANY_ADDR(req->host->address), SANY_LEN(req->host->address)); + if(ret == -1) + rb_message(LOG_ERR, "couldn't send snmp packet to: %s", req->host->name); + else + rb_messagex(LOG_DEBUG, "sent request '%d' to: %s", req->id, req->host->name); + } + + /* And update our bookkeeping */ + req->sent++; + if(req->sent <= g_state.retries) + req->next_retry = when + req->interval; +} + +static void +timeout_req(rb_request* req, mstime when) +{ + rb_poller* poll = req->poll; + int incomplete = 0; + rb_item* it; + + ASSERT(poll); + + /* + * Marks of this requests items as unknown. Request is + * over, free. See if poller is done + */ + + for(it = poll->items; it; it = it->next) + { + if(it->req == req) + { + rb_messagex(LOG_DEBUG, "value for field '%s' timed out", it->rrdfield); + + it->value = RB_UNKNOWN; + it->req = NULL; + } + + else if(it->req) + incomplete = 1; + } + + free_req(req); + + if(!incomplete) + finish_poll(poll, when); +} + +static void +check_req(rb_request* req, mstime when) +{ + ASSERT(req->id); + + /* See if it's timed out */ + if(when >= req->timeout) + timeout_req(req, when); + + if(!req->next_retry) + return; + + /* Resend if necessary */ + if(when >= req->next_retry) + send_req(req, when); +} + +static void +respond_req(rb_request* req, struct snmp_pdu* pdu, mstime when) +{ + struct snmp_value* value; + rb_poller* poll = req->poll; + rb_item* item; + int incomplete = 0; + int i; + + ASSERT(req->id == pdu->request_id); + + for(i = 0; i < pdu->nbindings; i++) + { + value = &(pdu->bindings[i]); + + for(item = poll->items; item; item = item->next) + { + if(asn_compare_oid(&(value->var), &(item->snmpfield.var)) == 0) + { + const char *msg = NULL; + switch(value->syntax) + { + case SNMP_SYNTAX_NULL: + item->value = RB_UNKNOWN; + break; + case SNMP_SYNTAX_INTEGER: + item->value = value->v.integer; + break; + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + item->value = value->v.uint32; + break; + case SNMP_SYNTAX_COUNTER64: + item->value = value->v.counter64; + break; + case SNMP_SYNTAX_OCTETSTRING: + case SNMP_SYNTAX_OID: + case SNMP_SYNTAX_IPADDRESS: + msg = "snmp server returned non numeric value for field: %s"; + break; + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + msg = "value not available on snmp server for field: %s"; + break; + default: + msg = "snmp server returned invalid or unsupported value for field: %s"; + break; + } + + if(msg) + rb_messagex(LOG_WARNING, msg, item->rrdfield); + else + rb_messagex(LOG_DEBUG, "got value for field '%s': %0.2f", + item->rrdfield, item->value); + + /* Mark this value as done */ + item->req = NULL; + break; + } + } + } + + /* We're done with this request */ + free_req(req); + + /* Now see if the entire request is done */ + for(item = poll->items; item; item = item->next) + { + if(item->req) + return; + } + + + /* And if so then hand off */ + finish_poll(poll, when); +} + +static int +poller_timer(mstime when, void* arg) +{ + rb_poller* poll = (rb_poller*)arg; + const rb_host* last_host = NULL; + rb_request* req = NULL; + rb_item* it; + + /* + * If the previous poll has not completed, then we count it + * as a timeout. + */ + for(it = poll->items; it; it = it->next) + { + if(it->req) + { + ASSERT(it->req->poll == poll); + timeout_req(it->req, when); + } + + /* timeout_req above should have cleared this */ + ASSERT(!it->req); + } + + + for(it = poll->items; it; it = it->next) + { + /* + * We assume that the polled items are sorted by host. Done + * in config.c. This allows us to fire off the least amount + * of requests. Generate new requests when: + * + * - first or new host + * - too many items in the same request + */ + if(!req || it->host != last_host || + req->pdu.nbindings >= SNMP_MAX_BINDINGS) + { + /* Send off last request ... */ + if(req) + send_req(req, when); + + /* ... and make a new one */ + req = new_req(); + if(!req) + { + rb_message(LOG_CRIT, "couldn't allocate a new snmp request"); + return 1; + } + + req->poll = poll; + req->host = it->host; + + rb_messagex(LOG_DEBUG, "preparing request '%d' for: %s@%s", + req->id, req->host->community, req->host->name); + + /* Setup the packet */ + strlcpy(req->pdu.community, req->host->community, sizeof(req->pdu.community)); + req->pdu.request_id = req->id; + req->pdu.version = req->host->version; + req->pdu.type = SNMP_PDU_GET; + req->pdu.error_status = 0; + req->pdu.error_index = 0; + req->pdu.nbindings = 0; + + /* Send interval is 200 ms when poll interval is below 2 seconds */ + req->interval = (poll->interval <= 2000) ? 200L : 600L; + + /* Timeout is for the last packet sent, not first */ + req->timeout = when + (req->interval * ((mstime)g_state.retries)) + poll->timeout; + req->sent = 0; + + last_host = it->host; + } + + /* Add an item to this request */ + req->pdu.bindings[req->pdu.nbindings].var = it->snmpfield.var; + req->pdu.bindings[req->pdu.nbindings].syntax = it->snmpfield.syntax; + req->pdu.nbindings++; + + /* Mark item as active by this request */ + it->req = req; + it->value = RB_UNKNOWN; + } + + if(req) + send_req(req, when); + + return 1; +} + + +static void +receive_resp(int fd, int type, void* arg) +{ + char hostname[MAXPATHLEN]; + struct sockaddr_any from; + struct snmp_pdu pdu; + struct asn_buf b; + rb_request* req; + int len, ret; + int32_t ip; + + ASSERT(snmp_socket == fd); + + /* Read in the packet */ + + SANY_LEN(from) = sizeof(from); + len = recvfrom(snmp_socket, snmp_buffer, sizeof(snmp_buffer), 0, + &SANY_ADDR(from), &SANY_LEN(from)); + if(len < 0) + { + if(errno != EAGAIN && errno != EWOULDBLOCK) + rb_message(LOG_ERR, "error receiving snmp packet from network"); + return; + } + + + if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1) + strcpy(hostname, "[UNKNOWN]"); + + rb_messagex(LOG_DEBUG, "received packet from: %s", hostname); + + /* Now parse the packet */ + + b.asn_ptr = snmp_buffer; + b.asn_len = len; + + ret = snmp_pdu_decode(&b, &pdu, &ip); + if(ret != SNMP_CODE_OK) + { + rb_message(LOG_WARNING, "invalid snmp packet received from: %s", hostname); + return; + } + + /* Lookup and check the request properly */ + + req = find_req(pdu.request_id); + if(!req) + return; + + if(pdu.error_status != SNMP_ERR_NOERROR) + { + /* TODO: Textual errors */ + rb_message(LOG_ERR, "snmp error '%d' from host: %s", pdu.error_status, hostname); + return; + } + + if(pdu.version != req->pdu.version) + rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname); + + rb_messagex(LOG_DEBUG, "response to request '%d' from: %s", req->id, hostname); + respond_req(req, &pdu, server_get_time()); +} + +static int +resend_timer(mstime when, void* arg) +{ + int i; + + /* Search forwards through the scrolling window */ + for(i = (reqlow + 1) % nrequests; i <= reqhigh; + i = (i + 1) % nrequests) + { + if(requests[i].id) + check_req(&(requests[i]), when); + } + + return 1; +} + +static int +prep_timer(mstime when, void* arg) +{ + /* + * We don't prepare all timers at exactly the same time + * but we sort of randomly start the various timers. We're + * going to be hitting these over and over again, so there's + * lots of benefits to spreading them out randomly over a + * few seconds. + */ + + rb_poller* poll; + int next; + + /* All done? */ + if(!arg) + return 0; + + poll = (rb_poller*)arg; + if(server_timer(poll->interval, poller_timer, poll) == -1) + rb_message(LOG_CRIT, "couldn't setup poller timer"); + + /* Setup the next poller anywhere between 0 and 750 ms */ + next = rand() % 750; + server_oneshot(next, prep_timer, poll->next); + return 0; +} + + +void +rb_snmp_engine_init() +{ + struct sockaddr_in addr; + rb_request* req; + + ASSERT(snmp_socket == -1); + snmp_socket = socket(PF_INET, SOCK_DGRAM, 0); + if(snmp_socket < 0) + err(1, "couldn't open snmp socket"); + + /* Get a random IPv4 UDP socket for client use */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + + if(bind(snmp_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) + err(1, "couldn't listen on port"); + + if (server_watch(snmp_socket, SERVER_READ, receive_resp, NULL) == -1) + err(1, "couldn't listen on socket"); + + /* Allocate some requests to make sure we have memory */ + req = new_req(); + if(!req) + err(1, "out of memory"); + free_req(req); + + /* Start the preparation timers for setting up randomly */ + if(server_oneshot(100, prep_timer, g_state.polls) == -1) + err(1, "couldn't setup timer"); + + /* We fire off the resend timer every 1/5 second */ + if(server_timer(200, resend_timer, NULL) == -1) + err(1, "couldn't setup timer"); +} + +void +rb_snmp_engine_uninit() +{ + if(snmp_socket != -1) + { + server_unwatch(snmp_socket); + close(snmp_socket); + snmp_socket = -1; + } + + if(requests) + { + free(requests); + nrequests = 0; + reqhigh = 0; + reqlow = 0; + } +} diff --git a/daemon/snmp-help.c b/daemon/snmp-help.c new file mode 100644 index 0000000..cf13d40 --- /dev/null +++ b/daemon/snmp-help.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include + +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +static int +parse_numeric_mib(const char* mib, struct asn_oid* oid) +{ + int ret = 0; + unsigned int sub; + char* next; + char* t; + char* copy; + char* src; + + memset(oid, 0, sizeof(*oid)); + + copy = src = strdup(mib); + if(!src) + return -1; + + while(src && *src) + { + next = strchr(src, '.'); + if(next) + *next = 0; + + sub = strtoul(src, &t, 10); + + /* Too many parts */ + if(oid->len > ASN_MAXOIDLEN) + ret = -1; + + /* An invalid number */ + if(*t) + ret = -1; + + /* Make sure this is a valid part */ + if((oid->len == 0 && sub < 1) || sub < 0 || sub >= ASN_MAXID) + ret -1; + + if(ret < 0) + break; + + oid->subs[oid->len] = sub; + oid->len++; + + src = next ? next + 1 : NULL; + } + + free(copy); + return ret; +} + +int +rb_parse_mib(const char* mib, struct snmp_value* value) +{ + /* + * TODO: Eventually we'll have code here to parse symbolic + * names, and initialize snmp_value to the right type and + * all that jazz. For now we just have numeric OID support. + */ + + value->syntax = SNMP_SYNTAX_NULL; + memset(&(value->v), 0, sizeof(value->v)); + + return parse_numeric_mib(mib, &(value->var)); +} diff --git a/daemon/snmpclient.c b/daemon/snmpclient.c new file mode 100644 index 0000000..5d316ad --- /dev/null +++ b/daemon/snmpclient.c @@ -0,0 +1,967 @@ +/* + * Copyright (c) 2004-2005 + * Hartmut Brandt. + * All rights reserved. + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * Kendy Kutzner + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/snmpclient.c,v 1.31 2005/05/23 11:10:13 brandt_h Exp $ + * + * Support functions for SNMP clients. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ERR_H +#include +#endif + +#include "asn1.h" +#include "snmp.h" +#include "snmpclient.h" +#include "snmppriv.h" + +/* ---------------------------------------------------------------------------- */ + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + + +/* ---------------------------------------------------------------------------- */ + +/* global context */ +struct snmp_client snmp_client; + +/* List of all outstanding requests */ +struct sent_pdu { + int reqid; + struct snmp_pdu *pdu; + struct timeval time; + u_int retrycount; + snmp_send_cb_f callback; + void *arg; + void *timeout_id; + LIST_ENTRY(sent_pdu) entries; +}; +LIST_HEAD(sent_pdu_list, sent_pdu); + +static struct sent_pdu_list sent_pdus; + +/* + * Set the error string + */ +static void +seterr(struct snmp_client *sc, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(sc->error, sizeof(sc->error), fmt, ap); + va_end(ap); +} + +/* + * Initialize a client structure + */ +void +snmp_client_init(struct snmp_client *c) +{ + memset(c, 0, sizeof(*c)); + + c->version = SNMP_V2c; + c->trans = SNMP_TRANS_UDP; + c->chost = NULL; + c->cport = NULL; + + strcpy(c->read_community, "public"); + strcpy(c->write_community, "private"); + + c->timeout.tv_sec = 3; + c->timeout.tv_usec = 0; + c->retries = 3; + c->dump_pdus = 0; + c->txbuflen = c->rxbuflen = 10000; + + c->fd = -1; + + c->max_reqid = INT32_MAX; + c->min_reqid = 0; + c->next_reqid = 0; +} + + +/* + * Open UDP client socket + */ +static int +open_client_udp(const char *host, const char *port) +{ + int error; + char *ptr; + struct addrinfo hints, *res0, *res; + + /* copy host- and portname */ + if (snmp_client.chost == NULL) { + if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_HOST))) + == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + strcpy(snmp_client.chost, DEFAULT_HOST); + } + if (host != NULL) { + if ((ptr = malloc(1 + strlen(host))) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + free(snmp_client.chost); + snmp_client.chost = ptr; + strcpy(snmp_client.chost, host); + } + if (snmp_client.cport == NULL) { + if ((snmp_client.cport = malloc(1 + sizeof(DEFAULT_PORT))) + == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + strcpy(snmp_client.cport, DEFAULT_PORT); + } + if (port != NULL) { + if ((ptr = malloc(1 + strlen(port))) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + free(snmp_client.cport); + snmp_client.cport = ptr; + strcpy(snmp_client.cport, port); + } + + /* open connection */ + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0); + if (error != 0) { + seterr(&snmp_client, "%s: %s", snmp_client.chost, + gai_strerror(error)); + return (-1); + } + res = res0; + for (;;) { + if ((snmp_client.fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) == -1) { + if ((res = res->ai_next) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + freeaddrinfo(res0); + return (-1); + } + } else if (connect(snmp_client.fd, res->ai_addr, + res->ai_addrlen) == -1) { + if ((res = res->ai_next) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + freeaddrinfo(res0); + return (-1); + } + } else + break; + } + freeaddrinfo(res0); + return (0); +} + +/* + * SNMP_OPEN + */ +int +snmp_open(const char *host, const char *port, const char *readcomm, + const char *writecomm) +{ + struct timeval tout; + + /* still open ? */ + if (snmp_client.fd != -1) { + errno = EBUSY; + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + + /* copy community strings */ + if (readcomm != NULL) { + strncpy(snmp_client.read_community, readcomm, + sizeof(snmp_client.read_community)); + snmp_client.read_community[sizeof(snmp_client.read_community) - 1] = 0; + } + if (writecomm != NULL) { + strncpy(snmp_client.write_community, writecomm, + sizeof(snmp_client.write_community)); + snmp_client.write_community[sizeof(snmp_client.write_community) - 1] = 0; + } + + switch (snmp_client.trans) { + + case SNMP_TRANS_UDP: + if (open_client_udp(host, port)) + return (-1); + break; + + default: + seterr(&snmp_client, "bad transport mapping"); + return (-1); + } + tout.tv_sec = 0; + tout.tv_usec = 0; + if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_SNDTIMEO, + &tout, sizeof(struct timeval)) == -1) { + seterr(&snmp_client, "%s", strerror(errno)); + (void)close(snmp_client.fd); + snmp_client.fd = -1; + if (snmp_client.local_path[0] != '\0') + (void)remove(snmp_client.local_path); + return (-1); + } + + /* initialize list */ + LIST_INIT(&sent_pdus); + + return (0); +} + + +/* + * SNMP_CLOSE + * + * closes connection to snmp server + * - function cannot fail + * - clears connection + * - clears list of sent pdus + * + * input: + * void + * return: + * void + */ +void +snmp_close(void) +{ + struct sent_pdu *p1; + + if (snmp_client.fd != -1) { + (void)close(snmp_client.fd); + snmp_client.fd = -1; + if (snmp_client.local_path[0] != '\0') + (void)remove(snmp_client.local_path); + } + while(!LIST_EMPTY(&sent_pdus)){ + p1 = LIST_FIRST(&sent_pdus); + if (p1->timeout_id != NULL) + snmp_client.timeout_stop(p1->timeout_id); + LIST_REMOVE(p1, entries); + free(p1); + } + free(snmp_client.chost); + free(snmp_client.cport); +} + +/* + * initialize a snmp_pdu structure + */ +void +snmp_pdu_create(struct snmp_pdu *pdu, u_int op) +{ + memset(pdu,0,sizeof(struct snmp_pdu)); + if (op == SNMP_PDU_SET) + strncpy(pdu->community, snmp_client.write_community, + sizeof(pdu->community)); + else + strncpy(pdu->community, snmp_client.read_community, + sizeof(pdu->community)); + pdu->community[sizeof(pdu->community) - 1] = 0; + + + pdu->type = op; + pdu->version = snmp_client.version; + pdu->error_status = 0; + pdu->error_index = 0; + pdu->nbindings = 0; +} + +/* add pairs of (struct asn_oid, enum snmp_syntax) to an existing pdu */ +/* added 10/04/02 by kek: check for MAX_BINDINGS */ +int +snmp_add_binding(struct snmp_v1_pdu *pdu, ...) +{ + va_list ap; + const struct asn_oid *oid; + u_int ret; + + va_start(ap, pdu); + + ret = pdu->nbindings; + while ((oid = va_arg(ap, const struct asn_oid *)) != NULL) { + if (pdu->nbindings >= SNMP_MAX_BINDINGS){ + va_end(ap); + return (-1); + } + pdu->bindings[pdu->nbindings].var = *oid; + pdu->bindings[pdu->nbindings].syntax = + va_arg(ap, enum snmp_syntax); + pdu->nbindings++; + } + va_end(ap); + return (ret); +} + + +static int32_t +snmp_next_reqid(struct snmp_client * c) +{ + int32_t i; + + i = c->next_reqid; + if (c->next_reqid >= c->max_reqid) + c->next_reqid = c->min_reqid; + else + c->next_reqid++; + return (i); +} + +/* + * Send request and return request id. + */ +static int32_t +snmp_send_packet(struct snmp_pdu * pdu) +{ + u_char *buf; + struct asn_buf b; + ssize_t ret; + + if ((buf = malloc(snmp_client.txbuflen)) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + + pdu->request_id = snmp_next_reqid(&snmp_client); + + b.asn_ptr = buf; + b.asn_len = snmp_client.txbuflen; + if (snmp_pdu_encode(pdu, &b)) { + seterr(&snmp_client, "%s", strerror(errno)); + free(buf); + return (-1); + } + + if (snmp_client.dump_pdus) + snmp_pdu_dump(pdu); + + if ((ret = send(snmp_client.fd, buf, b.asn_ptr - buf, 0)) == -1) { + seterr(&snmp_client, "%s", strerror(errno)); + free(buf); + return (-1); + } + free(buf); + + return pdu->request_id; +} + +/* + * to be called when a snmp request timed out + */ +static void +snmp_timeout(void * listentry_ptr) +{ + struct sent_pdu *listentry = listentry_ptr; + +#if 0 + warnx("snmp request %i timed out, attempt (%i/%i)", + listentry->reqid, listentry->retrycount, snmp_client.retries); +#endif + + listentry->retrycount++; + if (listentry->retrycount > snmp_client.retries) { + /* there is no answer at all */ + LIST_REMOVE(listentry, entries); + listentry->callback(listentry->pdu, NULL, listentry->arg); + free(listentry); + } else { + /* try again */ + /* new request with new request ID */ + listentry->reqid = snmp_send_packet(listentry->pdu); + listentry->timeout_id = + snmp_client.timeout_start(&snmp_client.timeout, + snmp_timeout, listentry); + } +} + +int32_t +snmp_pdu_send(struct snmp_pdu *pdu, snmp_send_cb_f func, void *arg) +{ + struct sent_pdu *listentry; + int32_t id; + + if ((listentry = malloc(sizeof(struct sent_pdu))) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + + /* here we really send */ + if ((id = snmp_send_packet(pdu)) == -1) { + free(listentry); + return (-1); + } + + /* add entry to list of sent PDUs */ + listentry->pdu = pdu; + if (gettimeofday(&listentry->time, NULL) == -1) + warn("gettimeofday() failed"); + + listentry->reqid = pdu->request_id; + listentry->callback = func; + listentry->arg = arg; + listentry->retrycount=1; + listentry->timeout_id = + snmp_client.timeout_start(&snmp_client.timeout, snmp_timeout, + listentry); + + LIST_INSERT_HEAD(&sent_pdus, listentry, entries); + + return (id); +} + +/* + * Receive an SNMP packet. + * + * tv controls how we wait for a packet: if tv is a NULL pointer, + * the receive blocks forever, if tv points to a structure with all + * members 0 the socket is polled, in all other cases tv specifies the + * maximum time to wait for a packet. + * + * Return: + * -1 on errors + * 0 on timeout + * +1 if packet received + */ +static int +snmp_receive_packet(struct snmp_pdu *pdu, struct timeval *tv) +{ + int dopoll, setpoll; + int flags; + int saved_errno; + u_char *buf; + int ret; + struct asn_buf abuf; + int32_t ip; +#ifdef bsdi + int optlen; +#else + socklen_t optlen; +#endif + + if ((buf = malloc(snmp_client.rxbuflen)) == NULL) { + seterr(&snmp_client, "%s", strerror(errno)); + return (-1); + } + dopoll = setpoll = 0; + flags = 0; + if (tv != NULL) { + /* poll or timeout */ + if (tv->tv_sec != 0 || tv->tv_usec != 0) { + /* wait with timeout */ + if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, + tv, sizeof(*tv)) == -1) { + seterr(&snmp_client, "setsockopt: %s", + strerror(errno)); + free(buf); + return (-1); + } + optlen = sizeof(*tv); + if (getsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, + tv, &optlen) == -1) { + seterr(&snmp_client, "getsockopt: %s", + strerror(errno)); + free(buf); + return (-1); + } + /* at this point tv_sec and tv_usec may appear + * as 0. This happens for timeouts lesser than + * the clock granularity. The kernel rounds these to + * 0 and this would result in a blocking receive. + * Instead of an else we check tv_sec and tv_usec + * again below and if this rounding happens, + * switch to a polling receive. */ + } + if (tv->tv_sec == 0 && tv->tv_usec == 0) { + /* poll */ + dopoll = 1; + if ((flags = fcntl(snmp_client.fd, F_GETFL, 0)) == -1) { + seterr(&snmp_client, "fcntl: %s", + strerror(errno)); + free(buf); + return (-1); + } + if (!(flags & O_NONBLOCK)) { + setpoll = 1; + flags |= O_NONBLOCK; + if (fcntl(snmp_client.fd, F_SETFL, flags) == -1) { + seterr(&snmp_client, "fcntl: %s", + strerror(errno)); + free(buf); + return (-1); + } + } + } + } + ret = recv(snmp_client.fd, buf, snmp_client.rxbuflen, 0); + saved_errno = errno; + if (tv != NULL) { + if (dopoll) { + if (setpoll) { + flags &= ~O_NONBLOCK; + (void)fcntl(snmp_client.fd, F_SETFL, flags); + } + } else { + tv->tv_sec = 0; + tv->tv_usec = 0; + (void)setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, + tv, sizeof(*tv)); + } + } + if (ret == -1) { + free(buf); + if (errno == EAGAIN || errno == EWOULDBLOCK) + return (0); + seterr(&snmp_client, "recv: %s", strerror(saved_errno)); + return (-1); + } + if (ret == 0) { + /* this happens when we have a streaming socket and the + * remote side has closed it */ + free(buf); + seterr(&snmp_client, "recv: socket closed by peer"); + errno = EPIPE; + return (-1); + } + + abuf.asn_ptr = buf; + abuf.asn_len = ret; + + if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) { + seterr(&snmp_client, "snmp_decode_pdu: failed %d", ret); + free(buf); + return (-1); + } + free(buf); + if (snmp_client.dump_pdus) + snmp_pdu_dump(pdu); + + return (+1); +} + +static int +snmp_deliver_packet(struct snmp_pdu * resp) +{ + struct sent_pdu *listentry; + + if (resp->type != SNMP_PDU_RESPONSE) { + warn("ignoring snmp pdu %u", resp->type); + return (-1); + } + + LIST_FOREACH(listentry, &sent_pdus, entries) + if (listentry->reqid == resp->request_id) + break; + if (listentry == NULL) + return (-1); + + LIST_REMOVE(listentry, entries); + listentry->callback(listentry->pdu, resp, listentry->arg); + + snmp_client.timeout_stop(listentry->timeout_id); + + free(listentry); + return (0); +} + +int +snmp_receive(int blocking) +{ + int ret; + + struct timeval tv; + struct snmp_pdu * resp; + + memset(&tv, 0, sizeof(tv)); + + resp = malloc(sizeof(struct snmp_pdu)); + if (resp == NULL) { + seterr(&snmp_client, "no memory for returning PDU"); + return (-1) ; + } + + if ((ret = snmp_receive_packet(resp, blocking ? NULL : &tv)) <= 0) { + free(resp); + return (ret); + } + ret = snmp_deliver_packet(resp); + snmp_pdu_free(resp); + free(resp); + return (ret); +} + + +/* + * Check a GETNEXT response. Here we have three possible outcomes: -1 an + * unexpected error happened. +1 response is ok and is within the table 0 + * response is ok, but is behind the table or error is NOSUCHNAME. The req + * should point to a template PDU which contains the base OIDs and the + * syntaxes. This is really only useful to sweep non-sparse tables. + */ +static int +ok_getnext(const struct snmp_pdu * req, const struct snmp_pdu * resp) +{ + u_int i; + + if (resp->version != req->version) { + warnx("SNMP GETNEXT: response has wrong version"); + return (-1); + } + + if (resp->error_status == SNMP_ERR_NOSUCHNAME) + return (0); + + if (resp->error_status != SNMP_ERR_NOERROR) { + warnx("SNMP GETNEXT: error %d", resp->error_status); + return (-1); + } + if (resp->nbindings != req->nbindings) { + warnx("SNMP GETNEXT: bad number of bindings in response"); + return (-1); + } + for (i = 0; i < req->nbindings; i++) { + if (!asn_is_suboid(&req->bindings[i].var, + &resp->bindings[i].var)) { + if (i != 0) + warnx("SNMP GETNEXT: inconsistent table " + "response"); + return (0); + } + if (resp->version != SNMP_V1 && + resp->bindings[i].syntax == SNMP_SYNTAX_ENDOFMIBVIEW) + return (0); + + if (resp->bindings[i].syntax != req->bindings[i].syntax) { + warnx("SNMP GETNEXT: bad syntax in response"); + return (0); + } + } + return (1); +} + +/* + * Check a GET response. Here we have three possible outcomes: -1 an + * unexpected error happened. +1 response is ok. 0 NOSUCHNAME The req should + * point to a template PDU which contains the OIDs and the syntaxes. This + * is only useful for SNMPv1 or single object GETS. + */ +static int +ok_get(const struct snmp_pdu * req, const struct snmp_pdu * resp) +{ + u_int i; + + if (resp->version != req->version) { + warnx("SNMP GET: response has wrong version"); + return (-1); + } + + if (resp->error_status == SNMP_ERR_NOSUCHNAME) + return (0); + + if (resp->error_status != SNMP_ERR_NOERROR) { + warnx("SNMP GET: error %d", resp->error_status); + return (-1); + } + + if (resp->nbindings != req->nbindings) { + warnx("SNMP GET: bad number of bindings in response"); + return (-1); + } + for (i = 0; i < req->nbindings; i++) { + if (asn_compare_oid(&req->bindings[i].var, + &resp->bindings[i].var) != 0) { + warnx("SNMP GET: bad OID in response"); + return (-1); + } + if (snmp_client.version != SNMP_V1 && + (resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHOBJECT || + resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHINSTANCE)) + return (0); + if (resp->bindings[i].syntax != req->bindings[i].syntax) { + warnx("SNMP GET: bad syntax in response"); + return (-1); + } + } + return (1); +} + +/* + * Check the reponse to a SET PDU. We check: - the error status must be 0 - + * the number of bindings must be equal in response and request - the + * syntaxes must be the same in response and request - the OIDs must be the + * same in response and request + */ +static int +ok_set(const struct snmp_pdu * req, const struct snmp_pdu * resp) +{ + u_int i; + + if (resp->version != req->version) { + warnx("SNMP SET: response has wrong version"); + return (-1); + } + + if (resp->error_status == SNMP_ERR_NOSUCHNAME) { + warnx("SNMP SET: error %d", resp->error_status); + return (0); + } + if (resp->error_status != SNMP_ERR_NOERROR) { + warnx("SNMP SET: error %d", resp->error_status); + return (-1); + } + + if (resp->nbindings != req->nbindings) { + warnx("SNMP SET: bad number of bindings in response"); + return (-1); + } + for (i = 0; i < req->nbindings; i++) { + if (asn_compare_oid(&req->bindings[i].var, + &resp->bindings[i].var) != 0) { + warnx("SNMP SET: wrong OID in response to SET"); + return (-1); + } + if (resp->bindings[i].syntax != req->bindings[i].syntax) { + warnx("SNMP SET: bad syntax in response"); + return (-1); + } + } + return (1); +} + +/* + * Simple checks for response PDUs against request PDUs. Return values: 1=ok, + * 0=nosuchname or similar, -1=failure, -2=no response at all + */ +int +snmp_pdu_check(const struct snmp_pdu *req, + const struct snmp_pdu *resp) +{ + if (resp == NULL) + return (-2); + + switch (req->type) { + + case SNMP_PDU_GET: + return (ok_get(req, resp)); + + case SNMP_PDU_SET: + return (ok_set(req, resp)); + + case SNMP_PDU_GETNEXT: + return (ok_getnext(req, resp)); + + } + errx(1, "%s: bad pdu type %i", __func__, req->type); +} + +int +snmp_dialog(struct snmp_v1_pdu *req, struct snmp_v1_pdu *resp) +{ + u_int i; + int32_t reqid; + int ret; + struct timeval tv = snmp_client.timeout; + struct timeval end; + struct snmp_pdu pdu; + + /* + * Make a copy of the request and replace the syntaxes by NULL + * if this is a GET,GETNEXT or GETBULK. + */ + pdu = *req; + if (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GETNEXT || + pdu.type == SNMP_PDU_GETBULK) { + for (i = 0; i < pdu.nbindings; i++) + pdu.bindings[i].syntax = SNMP_SYNTAX_NULL; + } + + for (i = 0; i <= snmp_client.retries; i++) { + (void)gettimeofday(&end, NULL); + timeradd(&end, &snmp_client.timeout, &end); + if ((reqid = snmp_send_packet(&pdu)) == -1) + return (-1); + for (;;) { + (void)gettimeofday(&tv, NULL); + if (timercmp(&end, &tv, <=)) + break; + timersub(&end, &tv, &tv); + if ((ret = snmp_receive_packet(resp, &tv)) == 0) + /* timeout */ + break; + + if (ret > 0) { + if (reqid == resp->request_id) + return (0); + /* not for us */ + (void)snmp_deliver_packet(resp); + } + if (ret < 0 && errno == EPIPE) + /* stream closed */ + return (-1); + } + } + errno = ETIMEDOUT; + seterr(&snmp_client, "retry count exceeded"); + return (-1); +} + +/* + * parse a server specification + * + * [trans::][community@][server][:port] + */ +int +snmp_parse_server(struct snmp_client *sc, const char *str) +{ + const char *p, *s = str; + + /* look for a double colon */ + for (p = s; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } + if (*p == ':' && p[1] == ':') + break; + } + if (*p != '\0') { + if (p > s) { + if (p - s == 3 && strncmp(s, "udp", 3) == 0) + sc->trans = SNMP_TRANS_UDP; + else if (p - s == 6 && strncmp(s, "stream", 6) == 0) + sc->trans = SNMP_TRANS_LOC_STREAM; + else if (p - s == 5 && strncmp(s, "dgram", 5) == 0) + sc->trans = SNMP_TRANS_LOC_DGRAM; + else { + seterr(sc, "unknown SNMP transport '%.*s'", + (int)(p - s), s); + return (-1); + } + } + s = p + 2; + } + + /* look for a @ */ + for (p = s; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } + if (*p == '@') + break; + } + + if (*p != '\0') { + if (p - s > SNMP_COMMUNITY_MAXLEN) { + seterr(sc, "community string too long"); + return (-1); + } + strncpy(sc->read_community, s, p - s); + sc->read_community[p - s] = '\0'; + strncpy(sc->write_community, s, p - s); + sc->write_community[p - s] = '\0'; + s = p + 1; + } + + /* look for a colon */ + for (p = s; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } + if (*p == ':') + break; + } + + if (*p == ':') { + if (p > s) { + /* host:port */ + free(sc->chost); + if ((sc->chost = malloc(p - s + 1)) == NULL) { + seterr(sc, "%s", strerror(errno)); + return (-1); + } + strncpy(sc->chost, s, p - s); + sc->chost[p - s] = '\0'; + } + /* port */ + free(sc->cport); + if ((sc->cport = malloc(strlen(p + 1) + 1)) == NULL) { + seterr(sc, "%s", strerror(errno)); + return (-1); + } + strcpy(sc->cport, p + 1); + + } else if (p > s) { + /* host */ + free(sc->chost); + if ((sc->chost = malloc(strlen(s) + 1)) == NULL) { + seterr(sc, "%s", strerror(errno)); + return (-1); + } + strcpy(sc->chost, s); + } + return (0); +} diff --git a/daemon/snmpclient.h b/daemon/snmpclient.h new file mode 100644 index 0000000..f47493f --- /dev/null +++ b/daemon/snmpclient.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt + * Kendy Kutzner + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Begemot: bsnmp/lib/snmpclient.h,v 1.19 2005/05/23 11:10:14 brandt_h Exp $ + */ +#ifndef _BSNMP_SNMPCLIENT_H +#define _BSNMP_SNMPCLIENT_H + +#include +#include +#include +#include +#include + + +#define SNMP_STRERROR_LEN 200 + +#define SNMP_LOCAL_PATH "/tmp/snmpXXXXXXXXXXXXXX" + +/* + * transport methods + */ +#define SNMP_TRANS_UDP 0 +#define SNMP_TRANS_LOC_DGRAM 1 +#define SNMP_TRANS_LOC_STREAM 2 + +/* type of callback function for responses + * this callback function is responsible for free() any memory associated with + * any of the PDUs. Therefor it may call snmp_pdu_free() */ +typedef void (*snmp_send_cb_f)(struct snmp_pdu *, struct snmp_pdu *, void *); + +/* type of callback function for timeouts */ +typedef void (*snmp_timeout_cb_f)(void * ); + +/* timeout start function */ +typedef void *(*snmp_timeout_start_f)(struct timeval *timeout, + snmp_timeout_cb_f callback, void *); + +/* timeout stop function */ +typedef void (*snmp_timeout_stop_f)(void *timeout_id); + +/* + * Client context. + */ +struct snmp_client { + enum snmp_version version; + int trans; /* which transport to use */ + + /* these two are read-only for the application */ + char *cport; /* port number as string */ + char *chost; /* host name or IP address as string */ + + char read_community[SNMP_COMMUNITY_MAXLEN + 1]; + char write_community[SNMP_COMMUNITY_MAXLEN + 1]; + + struct timeval timeout; + u_int retries; + + int dump_pdus; + + size_t txbuflen; + size_t rxbuflen; + + int fd; + + int32_t next_reqid; + int32_t max_reqid; + int32_t min_reqid; + + char error[SNMP_STRERROR_LEN]; + + snmp_timeout_start_f timeout_start; + snmp_timeout_stop_f timeout_stop; + + char local_path[sizeof(SNMP_LOCAL_PATH)]; +}; + +/* the global context */ +extern struct snmp_client snmp_client; + +/* initizialies a snmp_client structure */ +void snmp_client_init(struct snmp_client *); + +/* initialize fields */ +int snmp_client_set_host(struct snmp_client *, const char *); +int snmp_client_set_port(struct snmp_client *, const char *); + +/* open connection to snmp server (hostname or portname can be NULL) */ +int snmp_open(const char *_hostname, const char *_portname, + const char *_read_community, const char *_write_community); + +/* close connection */ +void snmp_close(void); + +/* initialize a snmp_pdu structure */ +void snmp_pdu_create(struct snmp_pdu *, u_int _op); + +/* add pairs of (struct asn_oid *, enum snmp_syntax) to an existing pdu */ +int snmp_add_binding(struct snmp_pdu *, ...); + +/* check wheater the answer is valid or not */ +int snmp_pdu_check(const struct snmp_pdu *_req, const struct snmp_pdu *_resp); + +int32_t snmp_pdu_send(struct snmp_pdu *_pdu, snmp_send_cb_f _func, void *_arg); + +/* append an index to an oid */ +int snmp_oid_append(struct asn_oid *_oid, const char *_fmt, ...); + +/* receive a packet */ +int snmp_receive(int _blocking); + +/* + * This structure is used to describe an SNMP table that is to be fetched. + * The C-structure that is produced by the fetch function must start with + * a TAILQ_ENTRY and an u_int64_t. + */ +struct snmp_table { + /* base OID of the table */ + struct asn_oid table; + /* type OID of the LastChange variable for the table if any */ + struct asn_oid last_change; + /* maximum number of iterations if table has changed */ + u_int max_iter; + /* size of the C-structure */ + size_t entry_size; + /* number of index fields */ + u_int index_size; + /* bit mask of required fields */ + uint64_t req_mask; + + /* indexes and columns to fetch. Ended by a NULL syntax entry */ + struct snmp_table_entry { + /* the column sub-oid, ignored for index fields */ + asn_subid_t subid; + /* the syntax of the column or index */ + enum snmp_syntax syntax; + /* offset of the field into the C-structure. For octet strings + * this points to an u_char * followed by a size_t */ + off_t offset; +#if defined(__GNUC__) && __GNUC__ < 3 + } entries[0]; +#else + } entries[]; +#endif +}; + +/* callback type for table fetch */ +typedef void (*snmp_table_cb_f)(void *_list, void *_arg, int _res); + +/* fetch a table. The argument points to a TAILQ_HEAD */ +int snmp_table_fetch(const struct snmp_table *descr, void *); +int snmp_table_fetch_async(const struct snmp_table *, void *, + snmp_table_cb_f, void *); + +/* send a request and wait for the response */ +int snmp_dialog(struct snmp_pdu *_req, struct snmp_pdu *_resp); + +/* parse a server specification */ +int snmp_parse_server(struct snmp_client *, const char *); + +#endif /* _BSNMP_SNMPCLIENT_H */ diff --git a/daemon/usuals.h b/daemon/usuals.h new file mode 100644 index 0000000..f83a117 --- /dev/null +++ b/daemon/usuals.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#ifndef __USUALS_H__ +#define __USUALS_H__ + +#include + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define countof(x) (sizeof(x) / sizeof(x[0])) + +#ifdef _DEBUG + #include "assert.h" + #define ASSERT(x) assert(x) +#else + #define ASSERT(x) +#endif + +#define KL(s) ((sizeof(s) - 1) / sizeof(char)) +#define RETURN(x) { ret = (x); goto finally; } + +#endif /* __USUALS_H__ */ -- cgit v1.2.3