diff options
Diffstat (limited to 'daemon')
| -rw-r--r-- | daemon/Makefile.am | 11 | ||||
| -rw-r--r-- | daemon/bsnmp/Makefile.am | 4 | ||||
| -rw-r--r-- | daemon/bsnmp/asn1.c | 1000 | ||||
| -rw-r--r-- | daemon/bsnmp/asn1.h | 183 | ||||
| -rw-r--r-- | daemon/bsnmp/snmp.c | 1081 | ||||
| -rw-r--r-- | daemon/bsnmp/snmp.h | 174 | ||||
| -rw-r--r-- | daemon/bsnmp/snmppriv.h | 45 | ||||
| -rw-r--r-- | daemon/common/hash.c | 380 | ||||
| -rw-r--r-- | daemon/common/hash.h | 151 | ||||
| -rw-r--r-- | daemon/common/server-mainloop.c | 424 | ||||
| -rw-r--r-- | daemon/common/server-mainloop.h | 27 | ||||
| -rw-r--r-- | daemon/common/sock-any.c | 385 | ||||
| -rw-r--r-- | daemon/common/sock-any.h | 90 | ||||
| -rw-r--r-- | daemon/common/stringx.c | 155 | ||||
| -rw-r--r-- | daemon/common/stringx.h | 53 | ||||
| -rw-r--r-- | daemon/config.c | 575 | ||||
| -rw-r--r-- | daemon/rrd-update.c | 134 | ||||
| -rw-r--r-- | daemon/rrdbotd.c | 239 | ||||
| -rw-r--r-- | daemon/rrdbotd.h | 182 | ||||
| -rw-r--r-- | daemon/snmp-engine.c | 636 | ||||
| -rw-r--r-- | daemon/snmp-help.c | 111 | ||||
| -rw-r--r-- | daemon/snmpclient.c | 967 | ||||
| -rw-r--r-- | daemon/snmpclient.h | 187 | ||||
| -rw-r--r-- | daemon/usuals.h | 77 | 
24 files changed, 7271 insertions, 0 deletions
| 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 <harti@freebsd.org> + * + * 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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#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. + * + * <bytes> + */ +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> <len> <data ...> + * + * 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> <len> <subid...> + */ +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> <len> <data...> + */ +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 <len> ... + */ +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 <len> ... + */ +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 <len> ... + */ +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 <harti@freebsd.org> + * + * 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 <sys/types.h> +#include <stdint.h> + +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 <harti@freebsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <stdint.h> +#include <string.h> +#include <ctype.h> +#include <netdb.h> +#include <errno.h> + +#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> <len> <data...> */ + +	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 <harti@freebsd.org> + * + * 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 <sys/types.h> + +#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 <harti@freebsd.org> + * + * 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 <sys/cdefs.h> + +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 <nielsen@memberwebs.com> + */ + +/* 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 <sys/types.h> +#include <stdlib.h> +#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 +     * <http://citeseer.nj.nec.com/salz92internetnews.html>. +     * +     * 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 <rse@engelschall.com> +     */ +    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 <nielsen@memberwebs.com> + */ + +/* 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 <errno.h> +#include <sys/time.h> + +#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 <stdint.h> + +/* 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 <nielsen@memberwebs.com> + * + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <stdio.h> + +#include "sock-any.h" + +#include <arpa/inet.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#ifndef __SOCK_ANY_H__ +#define __SOCK_ANY_H__ + +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> + +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 <nielsen@memberwebs.com> + * + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <syslog.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#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 <nielsen@memberwebs.com> + * + */ + +#include "usuals.h" +#include <errno.h> +#include <unistd.h> +#include <syslog.h> +#include <dirent.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#define _GNU_SOURCE + +#include "usuals.h" +#include <errno.h> +#include <syslog.h> +#include <unistd.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#include "usuals.h" +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <syslog.h> + +/* TODO: Abstract these headers away nicely */ +#include <bsnmp/asn1.h> +#include <bsnmp/snmp.h> + +#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 <values.h> + +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 <nielsen@memberwebs.com> + * + */ + +#ifndef __RRDBOTD_H__ +#define __RRDBOTD_H__ + +#include <values.h> +#include <stdint.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#include "usuals.h" +#include <errno.h> +#include <syslog.h> + +#include <bsnmp/asn1.h> +#include <bsnmp/snmp.h> + +#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 <nielsen@memberwebs.com> + * + */ + +#include "usuals.h" +#include <errno.h> +#include <syslog.h> + +#include <bsnmp/asn1.h> +#include <bsnmp/snmp.h> + +#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 <harti@freebsd.org> + *         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 <sys/types.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdint.h> +#include <limits.h> +#ifdef HAVE_ERR_H +#include <err.h> +#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 <harti@freebsd.org> + *	   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 <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <stddef.h> + + +#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 <nielsen@memberwebs.com> + * + */ + +#ifndef __USUALS_H__ +#define __USUALS_H__ + +#include <sys/types.h> + +#include "config.h" + +#include <sys/types.h> +#include <sys/param.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#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__ */ | 
