diff options
author | harti <harti@FreeBSD.org> | 2003-11-10 08:53:38 +0000 |
---|---|---|
committer | harti <harti@FreeBSD.org> | 2003-11-10 08:53:38 +0000 |
commit | ea9d8683bc24e904e026c05abd5768f41c3b551e (patch) | |
tree | 150e45ef74a56ce93475bd8e0436d6da856d4a18 /contrib/bsnmp/lib | |
download | FreeBSD-src-ea9d8683bc24e904e026c05abd5768f41c3b551e.zip FreeBSD-src-ea9d8683bc24e904e026c05abd5768f41c3b551e.tar.gz |
Virgin import of bsnmp 1.4
Diffstat (limited to 'contrib/bsnmp/lib')
-rw-r--r-- | contrib/bsnmp/lib/asn1.3 | 484 | ||||
-rw-r--r-- | contrib/bsnmp/lib/asn1.c | 994 | ||||
-rw-r--r-- | contrib/bsnmp/lib/asn1.h | 186 | ||||
-rw-r--r-- | contrib/bsnmp/lib/bsnmpagent.3 | 417 | ||||
-rw-r--r-- | contrib/bsnmp/lib/bsnmpclient.3 | 590 | ||||
-rw-r--r-- | contrib/bsnmp/lib/bsnmplib.3 | 306 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmp.c | 1027 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmp.h | 176 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpagent.c | 1031 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpagent.h | 159 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpclient.c | 1658 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpclient.h | 182 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmppriv.h | 47 |
13 files changed, 7257 insertions, 0 deletions
diff --git a/contrib/bsnmp/lib/asn1.3 b/contrib/bsnmp/lib/asn1.3 new file mode 100644 index 0000000..3751e50 --- /dev/null +++ b/contrib/bsnmp/lib/asn1.3 @@ -0,0 +1,484 @@ +.\" +.\" Copyright (c) 2001-2003 +.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus). +.\" All rights reserved. +.\" +.\" Author: Harti Brandt <harti@freebsd.org> +.\" +.\" Redistribution of this software and documentation 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 or documentation 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. +.\" 3. Neither the name of the Institute nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS +.\" AND ITS 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 +.\" FRAUNHOFER FOKUS OR ITS 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.3,v 1.2 2002/08/16 10:02:53 hbb Exp $ +.\" +.Dd August 15, 2002 +.Dt asn1 3 +.Os +.Sh NAME +.Nm asn_get_header , +.Nm asn_put_header , +.Nm asn_put_temp_header , +.Nm asn_commit_header , +.Nm asn_get_integer_raw , +.Nm asn_get_integer , +.Nm asn_put_integer , +.Nm asn_get_octetstring_raw , +.Nm asn_get_octetstring , +.Nm asn_put_octetstring , +.Nm asn_get_null_raw , +.Nm asn_get_null , +.Nm asn_put_null , +.Nm asn_put_exception , +.Nm asn_get_objid_raw , +.Nm asn_get_objid , +.Nm asn_put_objid , +.Nm asn_get_sequence , +.Nm asn_get_ipaddress_raw , +.Nm asn_get_ipaddress , +.Nm asn_put_ipaddress , +.Nm asn_get_uint32_raw , +.Nm asn_put_uint32 , +.Nm asn_get_counter64_raw , +.Nm asn_put_counter64 , +.Nm asn_get_timeticks , +.Nm asn_put_timeticks , +.Nm asn_skip , +.Nm asn_slice_oid , +.Nm asn_append_oid , +.Nm asn_compare_oid , +.Nm asn_is_suboid , +.Nm asn_oid2str_r , +.Nm asn_oid2str +.Nd "ASN.1 library for SNMP" +.Sh LIBRARY +Begemot SNMP library +.Pq libbsnmp, -lbsnmp +.Sh SYNOPSIS +.In bsnmp/asn1.h +.Ft enum asn_err +.Fn asn_get_header "struct asn_buf *buf" "u_char *type" "asn_len_t *lenp" +.Ft enum asn_err +.Fn asn_put_header "struct asn_buf *buf" "u_char type" "asn_len_t len" +.Ft enum asn_err +.Fn asn_put_temp_header "struct asn_buf *buf" "u_char type" "u_char **ptr" +.Ft enum asn_err +.Fn asn_commit_header "struct asn_buf *buf" "u_char *ptr" +.Ft enum asn_err +.Fn asn_get_integer_raw "struct asn_buf *buf" "asn_len_t len" "int32_t *res" +.Ft enum asn_err +.Fn asn_get_integer "struct asn_buf *buf" "int32_t *res" +.Ft enum asn_err +.Fn asn_put_integer "struct asn_buf *buf" "int32_t arg" +.Ft enum asn_err +.Fn asn_get_octetstring_raw "struct asn_buf *buf" "asn_len_t len" "u_char *out" "u_int *outsize" +.Ft enum asn_err +.Fn asn_get_octetstring "struct asn_buf *buf" "u_char *out" "u_int *outsize" +.Ft enum asn_err +.Fn asn_put_octetstring "struct asn_buf *buf" "const u_char *str" "u_int strsize" +.Ft enum asn_err +.Fn asn_get_null_raw "struct asn_buf *buf" "asn_len_t len" +.Ft enum asn_err +.Fn asn_get_null "struct asn_buf *buf" +.Ft enum asn_err +.Fn asn_put_null "struct asn_buf *buf" +.Ft enum asn_err +.Fn asn_put_exception "struct asn_buf *buf" "u_int type" +.Ft enum asn_err +.Fn asn_get_objid_raw "struct asn_buf *buf" "asn_len_t len" "struct asn_oid *oid" +.Ft enum asn_err +.Fn asn_get_objid "struct asn_buf *buf" "struct asn_oid *oid" +.Ft enum asn_err +.Fn asn_put_objid "struct asn_buf *buf" "const struct asn_oid *oid" +.Ft enum asn_err +.Fn asn_get_sequence "struct asn_buf *buf" "asn_len_t *lenp" +.Ft enum asn_err +.Fn asn_get_ipaddress_raw "struct asn_buf *buf" "asn_len_t len" "u_char *ipa" +.Ft enum asn_err +.Fn asn_get_ipaddress "struct asn_buf *buf" "u_char *ipa" +.Ft enum asn_err +.Fn asn_put_ipaddress "struct asn_buf *buf" "const u_char *ipa" +.Ft enum asn_err +.Fn asn_get_uint32_raw "struct asn_buf *buf" "asn_len_t len" "u_int32_t *res" +.Ft enum asn_err +.Fn asn_put_uint32 "struct asn_buf *buf" "u_char type" "u_int32_t val" +.Ft enum asn_err +.Fn asn_get_counter64_raw "struct asn_buf *buf" "asn_len_t len" "u_int64_t *res" +.Ft enum asn_err +.Fn asn_put_counter64 "struct asn_buf *buf" "u_int64_t val" +.Ft enum asn_err +.Fn asn_get_timeticks "struct asn_buf *buf" "u_int32_t *valp" +.Ft enum asn_err +.Fn asn_put_timeticks "struct asn_buf *buf" "u_int32_t val" +.Ft enum asn_err +.Fn asn_skip "struct asn_buf *buf" "asn_len_t len" +.Ft void +.Fn asn_slice_oid "struct asn_oid *dest" "const struct asn_oid *src" "u_int from" "u_int to" +.Ft void +.Fn asn_append_oid "struct asn_oid *to" "const struct asn_oid *from" +.Ft int +.Fn asn_compare_oid "const struct asn_oid *oid1" "const struct asn_oid *oid2" +.Ft int +.Fn asn_is_suboid "const struct asn_oid *oid1" "const struct asn_oid *oid2" +.Ft char * +.Fn asn_oid2str_r "const struct asn_oid *oid" "char *buf" +.Ft char * +.Fn asn_oid2str "const struct asn_oid *oid" +.Sh DESCRIPTION +The ASN.1 library contains routines to handle ASN.1 encoding for SNMP. +It supports only the restricted form of ASN.1 as required by SNMP. There +are two basic structures used throughout the library: +.Bd -literal -offset indent +/* these restrictions are in the SMI */ +#define ASN_MAXID 0xffffffff +#define ASN_MAXOIDLEN 128 + +/* type of subidentifiers */ +typedef u_int32_t asn_subid_t; + +struct asn_oid { + u_int len; + asn_subid_t subs[ASN_MAXOIDLEN]; +}; +.Ed +.Pp +This structure represents an OID with the restrictions defined in the SNMP +SMI. +.Fa len +holds the current length of the OID and +.Fa subs +holds the elements of the OID. +.Bd -literal -offset indent +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 +.Ed +.Pp +This structure is used to encode and decode ASN.1. It describes the output +buffer for encoding routines and the input buffer for decoding routines. +For encoding +.Fa asn_len +holds the number of remaining free octets in the buffer. The first free byte +is pointed to by +.Fa asn_ptr . +For decoding +.Fa asn_len +holds the number of remaining bytes to decode. The next byte to decode is pointed +to by +.Fa asn_cptr . +.Pp +Most of the functions return an error code +.Fa "enum asn_error" : +.Bd -literal -offset indent +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) +.Ed +.Pp +If +.Fn ASN_ERR_STOPPED +returns true, the error was fatal and processing has stopped at the point +of error. +.Pp +The function +.Fn asn_get_header +reads the next header from the input octet stream. It returns the tag +in the variable pointed to by +.Fa type +(note that only single byte tags are supported) and the decoded length field +in the value pointed to by +.Fa lenp +(this is restricted to a unsigned 32-bit value). All errors in this function +are fatal and stop processing. +.Pp +The function +.Fn asn_put_header +writes an ASN.1 header. +.Fa type +is the tag to write and is restricted to one byte tags (i.e. tags +lesser or equal than 0x30). +.Fa len +is the length of the value and is restricted to 16-bit. +.Pp +The functions +.Fn asn_put_temp_header +and +.Fn asn_commit_header +are used to write a header when the length of the value is not known in +advance, for example, for sequences. +.Fn asn_put_temp_header +writes a header with the given tag +.Fa type +and space for the maximum supported length field and sets the pointer pointed +to by +.Fa ptr +to the begin of this length field. This pointer must then be fed into +.Fn asn_commit_header +directly after writing the value to the buffer. The function will compute the +length, insert it into the right place and shift the value if the resulting +length field is shorter than the estimated one. +.Pp +The function +.Fn asn_get_integer_raw +is used to decode a signed integer value (32-bit). It assumes, that the +header of the integer has been decoded already. +.Fa len +is the length obtained from the ASN.1 header and the integer will be returned +in the value pointed to by +.Fa res . +.Pp +The function +.Fn asn_get_integer +decodes a complete 32-bit signed integer including the header. If the +tag is wrong +.Li ASN_ERR_TAG +is returned. +The function +.Fn asn_put_integer +encodes a 32-bit signed integer. +.Pp +The function +.Fn asn_get_octetstring_raw +decodes the value field of an ASN.1 octet string. The length obtained from the +header must be fed into the +.Fa len +argument and +.Fa out +must point to a buffer to receive the octet string. On entry to the function +.Fa outsize +must point to the size of the buffer. On exit +.Fa outsize +will point to the number of octets decoded (if no error occurs this will be +equal to +.Fa len ). +The function +.Fn asn_get_octetstring +decodes an octetstring including the header. +.Fa out +must point to a buffer to receive the string, +.Fa outsize +must point to the size of the buffer. On exit of the function +.Fa outsize +will point to the number of octets decoded. +The function +.Fn asn_put_octetstring +encodes an octetstring (including the header). +.Fa str +points to the string to encode and +.Fa strsize +is the length of the string (the string may contain embedded +.Li NUL Ns s). +.Pp +The function +.Fn asn_get_null_raw +decodes a null value. +.Fa len +is the length obtained from the header and must be 0. +The function +.Fn asn_get_null +decodes a null including the header and the function +.Fn asn_put_null +encodes a null. +.Pp +The function +.Fn asn_put_exception +is used to encode an SNMPv2 exception. The exception type is +.Fa type . +.Pp +The function +.Fn asn_get_objid_raw +is used to decode an OID value. +.Fa len +must be the value length obtained from the header and +.Fa oid +will receive the decoded OID. +The function +.Fn asn_get_objid +decodes a complete OID (including the header) and the function +.Fn asn_put_objid +encodes a complete OID. +.Pp +The function +.Fn asn_get_sequence +decodes a sequence header. +The length of the sequence value will be stored in the value pointed to by +.Fa lenp . +.Pp +The function +.Fn asn_get_ipaddress_raw +decodes an IP address value. +.Fa len +is the length from the header and must be 4. +.Fa ipa +will receive the decoded IP address and must point to a buffer of at least +four bytes. +The function +.Fn asn_get_ipaddress +decodes a complete IP address (including the header) and +.Fn asn_put_ipaddress +encodes an IP address. +.Pp +The function +.Fn asn_get_uint32_raw +decodes an unsigned 32-bit integer value. +.Fa len +is the length from the header and +.Fa res +will get the decoded value. +The function +.Fn asn_put_uint32 +encodes an unsigned 32-bit integer value and inserts the tag given in +.Fa type +into the header. +.Pp +The function +.Fn asn_get_counter64_raw +decodes an unsigned 64-bit integer value. +.Fa len +must be the value length from the header. The resulting value is +stored into the variable pointed to by +.Fa res . +The function +.Fn asn_put_counter64 +encodes a complete unsigned 64-bit value. +.Pp +The function +.Fn asn_get_timeticks +decodes an ASN.1 object of type +.Li TIMETICKS +and the function +.Fn asn_put_timeticks +encodes such an object. +.Pp +The function +.Fn asn_skip +can be used to skip +.Fa len +bytes in the input buffer. +.Pp +The function +.Fn asn_slice_oid +splits a part out from an OID. It takes all the subids from the OID +pointed to by +.Fa src +starting with the subid at position +.Fa from +(the first subid beeing subid 0) up to, but not including, subid +.Fa to +and generates a new OID in +.Fa dest . +If +.Fa to +is less or equal to +.Fa from +the resulting OID will have a length of zero. +.Pp +The function +.Fn asn_append_oid +appends the OID +.Fa from +to the OID +.Fa to +given that the resulting OID is not too long. If the maximum length is exceeded +the result is undefined. +.Pp +The function +.Fn asn_compare_oid +compares two oids and returns the values +.Li -1 , +.Li 0 or +.Li +1 +when +.Fa oid1 +is lesser than, equal, or larger than +.Fa oid2 +resp. +.Pp +The function +.Fn asn_is_suboid +returns 1 if +.Fa oid1 +is equal to the leading part of +.Fa oid2 . +It returns 0 otherwise. +.Pp +The function +.Fn asn_oid2str_r +makes a printable string from +.Fa oid . +The buffer pointed to by +.Fa str +must be large enough to hold the result. The constant +.Li ASN_OIDSTRLEN +is defined to be the length of the maximum string generated by this function +(including the trailing NUL). +The function +.Fn asn_oid2str +makes a printable string from +.Fa oid +into a private buffer that is overwritten by each call. +.Sh DIAGNOSTICS +When an error occures in any of the function the function pointed to +by the global pointer +.Bd -literal -offset indent +extern void (*asn_error)(const struct asn_buf *, const char *, ...); +.Ed +.Pp +is called with the current buffer (this may be +.Li NULL ) +and a +.Xr printf 3 +style format string. +There is a default error handler in the library that prints a message +starting with +.Sq ASN.1: +followed by the error message and an optional dump of the buffer. +.Sh SEE ALSO +.Xr snmpd 1 , +.Xr gensnmptree 1 , +.Xr bsnmplib 3 +.Xr bsnmpclient 3 , +.Xr bsnmpagent 3 +.Sh STANDARDS +This implementation conforms to the applicable IETF RFCs and ITU-T +recommendations. +.Sh AUTHORS +.An Hartmut Brandt Aq brandt@fokus.gmd.de diff --git a/contrib/bsnmp/lib/asn1.c b/contrib/bsnmp/lib/asn1.c new file mode 100644 index 0000000..533b9dd --- /dev/null +++ b/contrib/bsnmp/lib/asn1.c @@ -0,0 +1,994 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.24 2003/01/28 13:44:34 hbb Exp $ + * + * ASN.1 for SNMP. + */ +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <inttypes.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) +{ + u_int64_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; + 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]; + u_int64_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 = (u_int64_t)-(ival + 1); + neg = 1; + } else + val = (u_int64_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, u_int64_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; + } + + 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, u_int64_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, u_int32_t *vp) +{ + u_int64_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 = (u_int32_t)v; + } + return (err); +} + +enum asn_err +asn_put_uint32(struct asn_buf *b, u_char type, u_int32_t val) +{ + u_int64_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, u_int64_t *vp) +{ + return (asn_get_real_unsigned(b, len, vp)); +} + +enum asn_err +asn_put_counter64(struct asn_buf *b, u_int64_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, u_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_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, u_int32_t val) +{ + u_int64_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/contrib/bsnmp/lib/asn1.h b/contrib/bsnmp/lib/asn1.h new file mode 100644 index 0000000..83a31e5 --- /dev/null +++ b/contrib/bsnmp/lib/asn1.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.16 2002/02/11 10:19:57 hbb Exp $ + * + * ASN.1 for SNMP + */ +#ifndef asn1_h_ +#define asn1_h_ + +#include <sys/types.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 u_int32_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 u_int16_t would give conversion warnings on gcc */ +typedef u_int32_t asn_len_t; /* could be also u_int16_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, u_int32_t *); +enum asn_err asn_put_uint32(struct asn_buf *, u_char, u_int32_t); + +enum asn_err asn_get_counter64_raw(struct asn_buf *, asn_len_t, u_int64_t *); +enum asn_err asn_put_counter64(struct asn_buf *, u_int64_t); + +enum asn_err asn_get_timeticks(struct asn_buf *, u_int32_t *); +enum asn_err asn_put_timeticks(struct asn_buf *, u_int32_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/contrib/bsnmp/lib/bsnmpagent.3 b/contrib/bsnmp/lib/bsnmpagent.3 new file mode 100644 index 0000000..a9a8104 --- /dev/null +++ b/contrib/bsnmp/lib/bsnmpagent.3 @@ -0,0 +1,417 @@ +.\" +.\" Copyright (c) 2001-2003 +.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus). +.\" All rights reserved. +.\" +.\" Author: Harti Brandt <harti@freebsd.org> +.\" +.\" Redistribution of this software and documentation 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 or documentation 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. +.\" 3. Neither the name of the Institute nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS +.\" AND ITS 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 +.\" FRAUNHOFER FOKUS OR ITS 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/bsnmpagent.3,v 1.1 2002/08/16 12:22:58 hbb Exp $ +.\" +.Dd August 16, 2002 +.Dt bsnmpagent 3 +.Os +.Sh NAME +.Nm snmp_depop_t , +.Nm snmp_set_finish_t , +.Nm snmp_op_t , +.Nm tree , +.Nm tree_size , +.Nm snmp_trace , +.Nm snmp_debug , +.Nm snmp_get , +.Nm snmp_getnext , +.Nm snmp_getbulk , +.Nm snmp_set , +.Nm snmp_make_errresp , +.Nm snmp_dep_lookup , +.Nm snmp_set_atfinish , +.Nm snmp_init_context , +.Nm snmp_dep_commit , +.Nm snmp_dep_rollback +.Nd "SNMP agent library" +.Sh LIBRARY +Begemot SNMP library +.Pq libbsnmp, -lbsnmp +.Sh SYNOPSIS +.In asn1.h +.In snmp.h +.In snmpagent.h +.Ft typedef int +.Fn (*snmp_depop_t) "struct snmp_context *ctx" "struct snmp_dependency *dep" "enum snmp_depop op" +.Ft typedef void +.Fn (*snmp_set_finish_t) "struct snmp_context *ctx" "int fail" "void *uarg" +.Ft typedef int +.Fn (*snmp_op_t) "struct snmp_context *ctx" "struct snmp_value *val" "u_int len" "u_int idx" "enum snmp_op op" +.Vt extern struct snmp_node *tree ; +.Vt extern u_int tree_size ; +.Vt extern u_int snmp_trace ; +.Vt extern void (*snmp_debug)(const char *fmt, ...) ; +.Ft enum snmp_ret +.Fn snmp_get "struct snmp_pdu *pdu" "struct asn_buf *resp_b" "struct snmp_pdu *resp" "void *data" +.Ft enum snmp_ret +.Fn snmp_getnext "struct snmp_pdu *pdu" "struct asn_buf *resp_b" "struct snmp_pdu *resp" "void *data" +.Ft enum snmp_ret +.Fn snmp_getbulk "struct snmp_pdu *pdu" "struct asn_buf *resp_b" "struct snmp_pdu *resp" "void *data" +.Ft enum snmp_ret +.Fn snmp_set "struct snmp_pdu *pdu" "struct asn_buf *resp_b" "struct snmp_pdu *resp" "void *data" +.Ft enum snmp_ret +.Fn snmp_make_errresp "const struct snmp_pdu *pdu" "struct asn_buf *req_b" "struct asn_buf *resp_b" +.Ft struct snmp_dependency * +.Fn snmp_dep_lookup "struct snmp_context *ctx" "const struct asn_oid *base" "const struct asn_oid *idx" "size_t alloc" "snmp_depop_t func" +.Ft int +.Fn snmp_set_atfinish "struct snmp_context *ctx" "snmp_set_finish_t func" "void *uarg" +.Ft struct snmp_context * +.Fn snmp_init_context "void" +.Ft int +.Fn snmp_dep_commit "struct snmp_context *ctx" +.Ft int +.Fn snmp_dep_rollback "struct snmp_context *ctx" +.Sh DESCRIPTION +The SNMP library contains routines to easily build SNMP agent applications +that use SNMP versions 1 or 2. Note, however, that it may be even easier to +build an +.Xr snmpd 1 +loadable module, that handles the new MIB (see +.Xr snmpmod 3 ). +.Pp +Most of the agent routines operate on a global array that the describes the +complete MIB served by the agent. This array is held in the two variables: +.Bd -literal -offset indent +extern struct snmp_node *tree; +extern u_int tree_size; +.Ed +.Pp +The elements of the array are of type +.Vt struct snmp_node : +.Bd -literal -offset indent +typedef int (*snmp_op_t)(struct snmp_context *, struct snmp_value *, + u_int, u_int, enum snmp_op); + +struct snmp_node { + struct asn_oid oid; + const char *name; /* name of the leaf */ + enum snmp_node_type type; /* type of this node */ + enum snmp_syntax syntax; + snmp_op_t op; + u_int flags; + u_int32_t index; /* index data */ + void *data; /* application data */ +}; +.Ed +.Pp +The fields of this structure are described below. +.Bl -tag -width "syntax" +.It Va oid +Base OID of the scalar or table column. +.It Va name +Name of this variable. +.It Va type +Type of this variable. One of: +.Bd -literal -offset indent +enum snmp_node_type { + SNMP_NODE_LEAF = 1, + SNMP_NODE_COLUMN +}; +.Ed +.It Va syntax +The SNMP syntax of this variable. +.It Va op +The user supplied handler for this variable. The handler is called with +the following arguments: +.Bl -tag -width "ctx" +.It Fa ctx +A pointer to the context (see below). +.Li NULL . +.It Fa val +The value to be set or retrieved. For GETNEXT and GETBULK operations the oid in +this value is the current OID. The function (called in this case only for +table rows) must find the lexically next existing OID within the same column and +set the oid and value subfields accordingly. If the table column is exhausted the +function must return +.Li SNMP_ERR_NOSUCHNAME . +For all other operations the oid in +.Fa val +is the oid to fetch or set. +.It Fa len +The length of the base oid without index. +.It Fa idx +For table columns this is the index expression from the node (see below). +.It Fa op +This is the operation to execute, one of: +.Bd -literal -offset indent +enum snmp_op { + SNMP_OP_GET = 1, + SNMP_OP_GETNEXT, + SNMP_OP_SET, + SNMP_OP_COMMIT, + SNMP_OP_ROLLBACK, +}; +.Ed +.El +.Pp +The user handler must return an appropiate SNMP v2 error code. If the original +PDU was a version 1 PDU, the error code is mapped automatically. +.It Va flags +Currently only the flag +.Li SNMP_NODE_CANSET is defined and set for nodes, that can be written or +created. +.It Va index +This word describes the index for table columns. Each part of the index +takes 4 bits starting at bit 4. Bits 0 to 3 hold the number of index parts. +This arrangment allows for tables with up to seven indexes. Each bit group +contains the syntax for the index part. There are a number of macros to +help in parsing this field: +.Bd -literal -offset indent +#define SNMP_INDEXES_MAX 7 +#define SNMP_INDEX_SHIFT 4 +#define SNMP_INDEX_MASK 0xf +#define SNMP_INDEX_COUNT(V) ((V) & SNMP_INDEX_MASK) +#define SNMP_INDEX(V,I) \e + (((V) >> (((I) + 1) * SNMP_INDEX_SHIFT)) & \e + SNMP_INDEX_MASK) +.Ed +.It Va data +This field may contain arbitrary data and is not used by the library. +.El +.Pp +The easiest way to construct the node table is +.Xr gensnmptree 1 . +Note, that one must be careful when changing the tree while executing a SET +operation. Consult the sources for +.Xr snmpd 1 . +.Pp +The global variable +.Va snmp_trace +together with the function pointed to by +.Va snmp_debug +help in debugging the library and the agent. +.Va snmp_trace is a bit mask with the following bits: +.Bd -literal -offset indent +enum { + SNMP_TRACE_GET, + SNMP_TRACE_GETNEXT, + SNMP_TRACE_SET, + SNMP_TRACE_DEPEND, + SNMP_TRACE_FIND, +}; +.Ed +.Pp +Setting a bit to true causes the library to call +.Fn snmp_debug +in strategic places with a debug string. The library contains a default +implementation for the debug function that prints a message to standard error. +.Pp +Many of the functions use a so called context: +.Bd -literal -offset indent +struct snmp_context { + u_int var_index; + struct snmp_scratch *scratch; + struct snmp_dependency *dep; + void *data; /* user data */ +}; + +struct snmp_scratch { + void *ptr1; + void *ptr2; + u_int32_t int1; + u_int32_t int2; +}; +.Ed +.Pp +The fields are used as follows: +.Bl -tag -width ".It Va var_index" +.It Va va_index +For the node operation callback this is the +index of the variable binding that should be returned if an error occures. +Set by the library. In all other functions this is undefined. +.It Va scratch +For the node operation callback this is a pointer to a per variable binding +scratch area that can be used to implement the commit and rollback. Set +by the library. +.It Va dep +In the dependency callback function (see below) this is a pointer to the +current dependency. Set by the library. +.It Va data +This is the +.Fa data +argument from the call to the library and is not used by the library. +.El +.Pp +The next three functions execute different kinds of GET requests. +The function +.Fn snmp_get +executes an SNMP GET operation, the function +.Fn snmp_getnext +executes an SNMP GETNEXT operation and the function +.Fn snmp_getbulk +executes an SNMP GETBULK operation. +For all three functions the response PDU is constructed and encoded +on the fly. If everything is ok, the response PDU is returned in +.Fa resp +and +.Fa resp_b . +The caller must call +.Fn snmp_pdu_free +to free the response PDU in this case. One of the following values may be +returned: +.Bl -tag -width ".It Li SNMP_RET_ERR" +.It Li SNMP_RET_OK +Operation successful, response PDU may be sent. +.It Li SNMP_RET_IGN +Operation failed, no response PDU constructed. Request is ignored. +.It Li SNMP_RET_ERR +Error in operation. The error code and index have been set in +.Fa pdu . +No response PDU has been constructed. +The caller may construct an error response PDU via +.Fn snmp_make_errresp . +.El +.Pp +The function +.Fn snmp_set +executes an SNMP SET operation. The arguments are the same as for the previous +three functions. The operation of this functions is, however, much more complex. +.Pp +The SET operation occures in several stages: +.Bl -enum -offset indent +.It +For each binding search the corresponding nodes, check that the +variable is writeable and the syntax is ok. The writeable check can be done +only for scalars. For columns it must be done in the node's operation callback +function. +.It +For each binding call the node's operation callback with function SNMP_OP_SET. +The callback may create dependencies or finalizers (see below). For simple +scalars the scratch area may be enough to handle commit and rollback, for +interdependend table columns dependencies may be necessary. +.It +If the previous step fails at any point, the node's operation callback +functions are called for all bindings for which SNMP_OP_SET was executed +with SNMP_OP_ROLLBACK, in the opposite order. This allows all variables to +undo the effect of the SET operation. After this all the dependencies +are freed +and the finalizers are executed with a fail flag of 1. Then the function +returns to the caller with an appropriate error indication. +.It +If the SET step was successful for all bindings, the dependency callbacks +are executed in the order in which the dependencies were created with an +operation of SNMP_DEPOP_COMMIT. If any of the dependencies fails, all the +committed dependencies are called again in the opposite order +with SNMP_DEPOP_ROLLBACK. Than for all bindings from the last to the first +the node's operation callback is called with SNMP_OP_ROLLBACK to undo +the effect of SNMP_OP_SET. At the end the dependencies are freed +and the finalizers are called with a fail flag +of 1 and the function returns to the caller with an appropriate error indication. +.It +If the dependency commits were successful, for each binding the node's +operation callback is called with SNMP_OP_COMMIT. Any error returned from +the callbacks is ignored (an error message is generated via +.Fn snmp_error ). +.It +Now the dependencies are freed and the finalizers are called +with a fail flag of 0. Then the function returns +.Li SNMP_ERR_OK . +.El +.Pp +There are to mechanisms to help in complex SET operations: dependencies and +finalizers. A dependency is used if several bindings depend on each other. +A typical example is the creation of a conceptual row, which requires +the setting of several columns to succeed. A dependency is identified by +two OIDs. In the table case, the first oid is typically the table's base OID +and the second one the index. Both of these can easily be generated from the +variables OID with +.Fn asn_slice_oid . +The function +.Fn snmp_dep_lookup +tries to find a dependency based on these two OIDs and, if it cannot find one +creates a new one. This means for the table example, that the function +returns the same dependency for each of the columns of the same table row. +This allows during the SNMP_OP_SET processing to collect all information +about the row into the dependency. The arguments to +.Fn snmp_dep_lookup +are: the two OIDs to identify the dependency (they are copied into newly +created dependencies), the size of the structure to allocate and +the dependency callback. +.Pp +When all SNMP_OP_SET operations have succeeded the dependencies are executed. +At this stage the dependency callback has all information about the given +table row that was available in this SET PDU and can operate accordingly. +.Pp +If a SNMP_OP_SET operation fails, the dependency callbacks are never +called. The nodes SNMP_OP_ROLLBACK operations have to ensure, that +any dynamically allocated data is freed. +.Pp +Finalizers are a +.Sq last change +to do processing. +They are called after everything has been done, just before returning to the +user. They get a flag, that tells them, whether the return to the user is a good +one or not. The typical use is to finally remove deleted table elements. +Finalizers are created with +.Fn snmp_set_atfinish +which takes the callback function and a user data pointer as argument. +.Pp +The function +.Fn snmp_make_errresp +makes an error response if an operation has failed. It takes the original +request PDU (it will look only on the error code and index fields), the +buffer containing the original PDU and a buffer for the error PDU. It copies +the bindings field from the original PDUs buffer directly to the response +PDU and thus does not depend on the decodability of this field. It may return +the same values as the operation functions. +.Pp +The next three functions allow some parts of the SET operation to be executed. +This is only used in +.Xr snmpd 1 +to implement the configuration as a single transaction. +The function +.Fn snmp_init_context +creates and initializes a context. +The function +.Fn snmp_dep_commit +executes SNMP_DEPOP_COMMIT for all dependencies in the context stopping at +the first error. +The function +.Fn snmp_dep_rollback +executes SNMP_DEPOP_ROLLBACK starting at the previous of the current +dependency in the context. +.Sh DIAGNOSTICS +If an error occures in any of the function an error indication as described +above is returned. Additionally the functions may call snmp_error on unexected +errors. +.Sh SEE ALSO +.Xr snmpd 1 , +.Xr gensnmptree 1 , +.Xr bsnmplib 3 +.Xr bsnmpclient 3 , +.Xr snmpmod 3 +.Sh STANDARDS +This implementation conforms to the applicable IETF RFCs and ITU-T +recommendations. +.Sh AUTHORS +.An Hartmut Brandt Aq brandt@fokus.gmd.de diff --git a/contrib/bsnmp/lib/bsnmpclient.3 b/contrib/bsnmp/lib/bsnmpclient.3 new file mode 100644 index 0000000..f4a7eac --- /dev/null +++ b/contrib/bsnmp/lib/bsnmpclient.3 @@ -0,0 +1,590 @@ +.\" +.\" Copyright (c) 2001-2003 +.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus). +.\" All rights reserved. +.\" +.\" Author: Harti Brandt <harti@freebsd.org> +.\" +.\" Redistribution of this software and documentation 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 or documentation 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. +.\" 3. Neither the name of the Institute nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS +.\" AND ITS 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 +.\" FRAUNHOFER FOKUS OR ITS 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/bsnmpclient.3,v 1.3 2002/12/11 15:54:07 hbb Exp $ +.\" +.Dd August 15, 2002 +.Dt bsnmpclient 3 +.Os +.Sh NAME +.Nm snmp_client , +.Nm snmp_send_cb_f , +.Nm snmp_timeout_cb_f , +.Nm snmp_timeout_start_f , +.Nm snmp_timeout_stop_f , +.Nm snmp_open , +.Nm snmp_close , +.Nm snmp_pdu_create , +.Nm snmp_add_binding , +.Nm snmp_pdu_check , +.Nm snmp_pdu_send , +.Nm snmp_oid_append , +.Nm snmp_receive , +.Nm snmp_table_cb_f , +.Nm snmp_table_fetch , +.Nm snmp_table_fetch_async , +.Nm snmp_dialog +.Nd "SNMP client library" +.Sh LIBRARY +Begemot SNMP library +.Pq libbsnmp, -lbsnmp +.Sh SYNOPSIS +.In asn1.h +.In snmp.h +.In snmpclient.h +.Ft typedef void +.Fn (*snmp_send_cb_f) "struct snmp_pdu *req" "struct snmp_pdu *resp" "void *uarg" +.Ft typedef void +.Fn (*snmp_timeout_cb_f) "void *uarg" +.Ft typedef void * +.Fn (*snmp_timeout_start_f) "struct timeval *timeout" "snmp_timeout_cb_f callback" "void *uarg" +.Ft typedef void +.Fn (*snmp_timeout_stop_f) "void *timeout_id" +.Vt extern struct snmp_client snmp_client ; +.Ft void +.Fn snmp_client_init "struct snmp_client *client" +.Ft int +.Fn snmp_client_set_host "struct snmp_client *client" "const char *host" +.Ft int +.Fn snmp_client_set_port "struct snmp_client *client" "const char *port" +.Ft int +.Fn snmp_open "const char *host" "const char *port" "const char *read_community" "const char *write_community" +.Ft void +.Fn snmp_close "void" +.Ft void +.Fn snmp_pdu_create "struct snmp_pdu *pdu" "u_int op" +.Ft int +.Fn snmp_add_binding "struct snmp_pdu *pdu" "..." +.Ft int +.Fn snmp_pdu_check "const struct snmp_pdu *req" "const struct snmp_pdu *resp" +.Ft int32_t +.Fn snmp_pdu_send "struct snmp_pdu *pdu" "snmp_send_cb_f func" "void *uarg" +.Ft int +.Fn snmp_oid_append "struct asn_oid *oid" "const char *fmt" "..." +.Ft int +.Fn snmp_receive "int blocking" +.Ft typedef void +.Fn (*snmp_table_cb_f) "void *list" "void *arg" "int res" +.Ft int +.Fn snmp_table_fetch "const struct snmp_table *descr" "void *list" +.Ft int +.Fn snmp_table_fetch_async "const struct snmp_table *descr" "void *list" "snmp_table_cb_f callback" "void *uarg" +.Ft int +.Fn snmp_dialog "struct snmp_pdu *req" "struct snmp_pdu *resp" +.Sh DESCRIPTION +The SNMP library contains routines to easily build SNMP client applications +that use SNMP versions 1 or 2. Most of the routines use a +.Vt struct snmp_client : +.Bd -literal -offset indent +struct snmp_client { + enum snmp_version version; + int local; /* use local socket */ + + /* 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; + + /* private */ + char local_path[sizeof(SNMP_LOCAL_PATH)]; +}; +.Ed +.Pp +The fields of this structure are described below. +.Bl -tag -width "timeout_start" +.It Va version +This is the version of SNMP to use. See +.Xr bsnmplib 3 +for applicable values. The default version is +.Li SNMP_V2c . +.It Va local +If this is set to true, the library opens a +.Ux +domain socket rather than +an UDP socket. It uses the +.Va chost +field as the path to the server's socket. +.It Va cport +The SNMP agent's UDP port number. This may be a symbolic port number (from +.Pa /etc/services +or a numeric port number. If this field is +.Li NULL +(the default) the standard SNMP port is used. This field should not be changed +directly but rather by calling +.Fn snmp_client_set_port . +.It Va chost +The SNMP agent's host name, IP address or +.Ux +domain socket path name. +If this is +.Li NULL +(the default) +.Li localhost +is assumed. This field should not be changed directly but rather through +calling +.Fn snmp_client_set_host . +.It Va read_community +This is the community name to be used for all requests except SET requests. +The default is +.Sq public . +.It Va write_community +The community name to be used for SET requests. The default is +.Sq private . +.It Va timeout +The maximum time to wait for responses to requests. If the time elapses, the +request is resent up to +.Va retries +times. The default is 3 seconds. +.It Va retries +Number of times a request PDU is to be resent. If set to 0, the request is +sent only once. The default is 3 retransmissions. +.It Va dump_pdus +If set to a non-zero value all received and sent PDUs are dumped via +.Xr snmp_pdu_dump 3 . +The default is not to dump PDUs. +.It Va txbuflen +The encoding buffer size to be allocated for transmitted PDUs. The default is +10000 octets. +.It Va rxbuflen +The decoding buffer size to be allocated for received PDUs. This is the size +of the maximum PDU that can be received. The default is 10000 octets. +.It Va fd +After calling +.Fn snmp_open +this is the file socket file descriptor used for sending and receiving PDUs. +.It Va next_reqid +The request id of the next PDU to send. Used internal by the library. +.It Va max_reqid +The maximum request id to use for outging PDUs. The default is +.Li INT32_MAX . +.It Va min_reqid +The minimum request id to use for outgoing PDUs. Request ids are allocated +linerily starting at +.Va min_reqid +up to +.Va max_reqid . +.It Va error +If an error happens, this field is set to a printable string describing the +error. +.It Va timeout_start +This field must point to a function setting up a one shot timeout. After the +timeout has elapsed, the given callback function must be called with the +user argument. The +.Fn timeout_start +function must return a +.Vt void * +identifying the timeout. +.It Va timeout_stop +This field must be set to a function that stops a running timeout. The function +will be called with the return value of the corresponding +.Fn timeout_start +function. +.It Va local_path +If in local socket mode, the name of the clients socket. Not needed by the +application. +.El +.Pp +In the current implementation there is a global variable +.Bd -unfilled -offset indent +.Vt extern struct snmp_client snmp_client ; +.Ed +.Pp +that is used by all the library functions. The first call into the library must +be a call to +.Fn snmp_client_init +to initialize this global variable to the default values. +After this call and before calling +.Fn snmp_open +the fields of the variable may be modified by the user. +The modification of the +.Va chost +and +.Va cport +fields should be done only via the functions +.Fn snmp_client_set_host +and +.Fn snmp_client_set_port . +.Pp +The function +.Fn snmp_open +creates a UDP or +.Ux +domain socket and connects it to the agent's IP address and port. +If any of the arguments of the call is not +.Li NULL +the corresponding field in the global +.Va snmp_client +is set from the argument. Otherwise the values that are already in that variable +are used. +The function +.Fn snmp_close +closes the socket, stops all timeouts and frees all dynamically allocated +resources. +.Pp +The next three functions are used to create request PDUs. The function +.Fn snmp_pdu_create +initializes a PDU of type +.Va op . +It does not allocate space for the PDU itself. This is the responsibility of +the caller. +.Fn snmp_add_binding +adds bindings to the PDU and returns the (zero based) index of the first new +binding. The arguments are pairs of pointer to the OIDs and syntax constants, +terminated by a NULL. The call +.Bd -literal -offset indent +snmp_add_binding(&pdu, + &oid1, SNMP_SYNTAX_INTEGER, + &oid2, SNMP_SYNTAX_OCTETSTRING, + NULL); +.Ed +.Pp +adds two new bindings to the PDU and returns the index of the first one. +It is the responsibility of the caller to set the value part of the binding +if neccesary. The functions returns -1 if the maximum number of bindings +is exhausted. +The function +.Fn snmp_oid_append +can be used to construct variable OIDs for requests. It takes a pointer +to an +.Vt struct asn_oid +that is to be constructed, a format string, and a number of arguments +the type of which depends on the format string. The format string is interpreted +character by character in the following way: +.Bl -tag -width ".It Li ( Va N Ns Li )" +.It Li i +This format expects an argument of type +.Vt asn_subid_t +and appends this as a single integer to the OID. +.It Li a +This format expects an argument of type +.Vt struct in_addr +and appends to four parts of the IP address to the OID. +.It Li s +This format expects an argument of type +.Vt const char * +and appends the length of the string (as computed by +.Xr strlen 3 ) +and each of the characters in the string to the OID. +.It Li ( Va N Ns Li ) +This format expects no argument. +.Va N +must be a decimal number and is stored into an internal variable +.Va size . +.It Li b +This format expects an argument of type +.Vt const char * +and appends +.Va size +characters from the string to the OID. The string may contain +.Li NUL +characters. +.It Li c +This format expects two arguments: one of type +.Vt size_t +and one of type +.Vt const u_char * . +The first argument gives the number of bytes to append to the OID from the string +pointed to by the second argument. +.El +.Pp +The function +.Fn snmp_pdu_check +may be used to check a response PDU. A number of checks are performed +(error code, equal number of bindings, syntaxes and values for SET PDUs). +The function returns +1 if everything is ok, 0 if a NOSUCHNAME or similar +error was detected, -1 if the response PDU had fatal errors +and -2 if +.Fa resp +is +.Li NULL +(a timeout occured). +.Pp +The function +.Fn snmp_pdu_send +encodes and sends the given PDU. It records the PDU together with the callback +and user pointers in an internal list and arranges for retransmission if no +response is received. When a response is received or the retransmission count +is exceeded the callback +.Fa func +is called with the orignal request PDU, the response PDU and the user argument +.Fa uarg . +If the retransmit count is exceeded, +.Fa func +is called with the original request PDU, the reponse pointer set to +.Li NULL +and the user argument +.Fa uarg . +The caller should not free the request PDU until the callback function is +called. The callback function must free the request PDU and the response +PDU (if not +.Li NULL ). +.Pp +The function +.Fn snmp_receive +tries to receive a PDU. If the argument is zero, the function polls to see +whether a packet is available, if the argument is non-zero, the function blocks +until the next packet is received. The packet is delivered via the usual callback +mechanism (non-response packets are silently dropped). +The function returns 0, if a packet was received and successfully dispatched, +-1 if an error occured or no packet was available (in polling mode). +.Pp +The next two functions are used to retrieve tables from SNMP agents. The use +the following input structure, that describes the table: +.Bd -literal -offset indent +struct snmp_table { + struct asn_oid table; + struct asn_oid last_change; + u_int max_iter; + size_t entry_size; + u_int index_size; + u_int64_t req_mask; + + struct snmp_table_entry { + asn_subid_t subid; + enum snmp_syntax syntax; + off_t offset; + } entries[]; +}; +.Ed +.Pp +The fields of this structure have the following meaning: +.Bl -tag -width "last_change" +.It Va table +This is the base OID of the table. +.It Va last_change +Some tables have a scalar variable of type TIMETICKS attached to them, +that holds the time when the table was last changed. This OID should be +the OID of this variable (without the \&.0 index). When the table is retrieved +with multiple GET requests, and the variable changes between two request, +the table fetch is restarted. +.It Va max_iter +Maximum number of tries to fetch the table. +.It Va entry_size +The table fetching routines return a list of structure one for each table +row. This variable is the size of one structure and used to +.Xr malloc 3 +the structure. +.It Va index_size +This is the number of index columns in the table. +.It Va req_mask +This is a bit mask with a 1 for each table column that is required. +Bit 0 corresponds to the first element (index 0) in the array +.Va entries , +bit 1 to the second (index 1) and so on. SNMP tables may be sparse. For sparse +columns the bit should not be set. If the bit for a given column is set and +the column value cannot be retrieved for a given row, the table fetch is +restarted assuming that the table is currently beeing modified by the agent. +The bits for the index columns are ignored. +.It Va entries +This is a variable sized array of column descriptors. This array is terminated +by an element with syntax +.Li SNMP_SYNTAX_NULL . +The first +.Va index_size +elements describe all the index columns of the table, the rest are normal +columns. If for a the column at +.Ql entries[N] +the expression +.Ql req_mask & (1 << N) +yields true, the column is considered a required column. +The fields of this the array elements have the following meaning: +.Bl -tag -width "syntax" +.It Va subid +This is the OID subid of the column. This is ignored for index entries. Index +entries are decoded according to the +.Va syntax +field. +.It Va syntax +This is the syntax of the column or index. A syntax of +.Li SNMP_SYNTAX_NULL +terminates the array. +.It Va offset +This is the starting offset of the value of the column in the return structures. +This field can be set with the ISO-C +.Fn offsetof +macro. +.El +.El +.Pp +Both table fetching functions return TAILQ (see +.Xr queue 3 ) +of structures--one for each table row. These structures must start with a +.Fn TAILQ_ENTRY +and a +.Vt u_int64_t +and are allocated via +.Xr malloc 3 . +The +.Fa list +argument of the table functions must point to a +.Fn TAILQ_HEAD . +The +.Vt u_int64_t +fields, usually called +.Va found +is used to indicate which of the columns have been found for the given +row. It is encoded like the +.Fa req_mask +field. +.Pp +The function +.Fn snmp_table_fetch +synchronuosly fetches the given table. If everything is ok 0 is returned. +Otherwise the function returns -1 and sets an appropriate error string. +The function +.Fn snmp_table_fetch_async +fetches the tables asynchronuosly. If either the entire table is fetch, or +an error occures the callback function +.Fa callback +is called with the callers arguments +.Fa list +and +.Fa uarg +and a parameter that is either 0 if the table was fetched, or +-1 if there was an error. The function itself returns -1 if it could not +initialize fetching of the table. +.Pp +The following table description is used to fetch the ATM interface table: +.Bd -literal -offset indent +/* + * ATM interface table + */ +struct atmif { + TAILQ_ENTRY(atmif) link; + u_int64_t found; + int32_t index; + u_char *ifname; + size_t ifnamelen; + u_int32_t node_id; + u_int32_t pcr; + int32_t media; + u_int32_t vpi_bits; + u_int32_t vci_bits; + u_int32_t max_vpcs; + u_int32_t max_vccs; + u_char *esi; + size_t esilen; + int32_t carrier; +}; +TAILQ_HEAD(atmif_list, atmif); + +/* list of all ATM interfaces */ +struct atmif_list atmif_list; + +static const struct snmp_table atmif_table = { + OIDX_begemotAtmIfTable, + OIDX_begemotAtmIfTableLastChange, 2, + sizeof(struct atmif), + 1, 0x7ffULL, + { + { 0, SNMP_SYNTAX_INTEGER, + offsetof(struct atmif, index) }, + { 1, SNMP_SYNTAX_OCTETSTRING, + offsetof(struct atmif, ifname) }, + { 2, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, node_id) }, + { 3, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, pcr) }, + { 4, SNMP_SYNTAX_INTEGER, + offsetof(struct atmif, media) }, + { 5, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, vpi_bits) }, + { 6, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, vci_bits) }, + { 7, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, max_vpcs) }, + { 8, SNMP_SYNTAX_GAUGE, + offsetof(struct atmif, max_vccs) }, + { 9, SNMP_SYNTAX_OCTETSTRING, + offsetof(struct atmif, esi) }, + { 10, SNMP_SYNTAX_INTEGER, + offsetof(struct atmif, carrier) }, + { 0, SNMP_SYNTAX_NULL, 0 } + } +}; + +\&... + if (snmp_table_fetch(&atmif_table, &atmif_list) != 0) + errx(1, "AtmIf table: %s", snmp_client.error); +\&... +.Ed +.Pp +The function +.Fn snmp_dialog +is used to execute a synchonuous dialog with the agent. The request PDU +.Fa req +is sent and the function blocks until the response PDU is received. Note, +that asynchonuous receives are handled (i.e. callback functions of other send +calls or table fetches may be called while in the function). The response +PDU is returned in +.Fa resp . +If no reponse could be received after all timeouts and retries, the function +returns -1. If a response was received 0 is returned. +.Sh DIAGNOSTICS +If an error occures in any of the function an error indication as described +above is returned. Additionally the function sets a printable error string +in the +.Va error +filed of +.Va snmp_client . +.Sh SEE ALSO +.Xr snmpd 1 , +.Xr gensnmptree 1 , +.Xr bsnmplib 3 +.Xr bsnmpagent 3 +.Sh STANDARDS +This implementation conforms to the applicable IETF RFCs and ITU-T +recommendations. +.Sh AUTHORS +.An Hartmut Brandt Aq brandt@fokus.gmd.de +.An Kendy Kutzner Aq kutzner@fokus.gmd.de diff --git a/contrib/bsnmp/lib/bsnmplib.3 b/contrib/bsnmp/lib/bsnmplib.3 new file mode 100644 index 0000000..13f9651 --- /dev/null +++ b/contrib/bsnmp/lib/bsnmplib.3 @@ -0,0 +1,306 @@ +.\" +.\" Copyright (c) 2001-2003 +.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus). +.\" All rights reserved. +.\" +.\" Author: Harti Brandt <harti@freebsd.org> +.\" +.\" Redistribution of this software and documentation 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 or documentation 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. +.\" 3. Neither the name of the Institute nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS +.\" AND ITS 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 +.\" FRAUNHOFER FOKUS OR ITS 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/bsnmplib.3,v 1.2 2002/08/16 10:02:53 hbb Exp $ +.\" +.Dd August 15, 2002 +.Dt bsnmplib 3 +.Os +.Sh NAME +.Nm snmp_value_free , +.Nm snmp_value_parse , +.Nm snmp_value_copy , +.Nm snmp_pdu_free , +.Nm snmp_code snmp_pdu_decode , +.Nm snmp_code snmp_pdu_encode , +.Nm snmp_pdu_dump , +.Nm TRUTH_MK , +.Nm TRUTH_GET , +.Nm TRUTH_OK +.Nd "SNMP decoding and encoding library" +.Sh LIBRARY +Begemot SNMP library +.Pq libbsnmp, -lbsnmp +.Sh SYNOPSIS +.In bsnmp/asn1.h +.In bsnmp/snmp.h +.Ft void +.Fn snmp_value_free "struct snmp_value *value" +.Ft int +.Fn snmp_value_parse "const char *buf" "enum snmp_syntax" "union snmp_values *value" +.Ft int +.Fn snmp_value_copy "struct snmp_value *to" "const struct snmp_value *from" +.Ft void +.Fn snmp_pdu_free "struct snmp_pdu *value" +.Ft enum snmp_code +.Fn snmp_pdu_decode "struct asn_buf *buf" "struct snmp_pdu *pdu" "int32_t *ip" +.Ft enum snmp_code +.Fn snmp_pdu_encode "struct snmp_pdu *pdu" "struct asn_buf *buf" +.Ft void +.Fn snmp_pdu_dump "const struct snmp_pdu *pdu" +.Ft int +.Fn TRUTH_MK "F" +.Ft int +.Fn TRUTH_GET "T" +.Ft int +.Fn TRUTH_OK "T" +.Sh DESCRIPTION +The SNMP library contains routines to handle SNMP version 1 and 2 PDUs. +There are two basic structures used throughout the library: +.Bd -literal -offset indent +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]; + u_int32_t uint32; /* also gauge32, counter32, + unsigned32, timeticks */ + u_int64_t counter64; + } v; +}; +.Ed +.Pp +This structure represents one variable binding from an SNMP PDU. The +field +.Fa var +is the ASN.1 of the variable that is bound. +.Fa syntax +contains either the syntax code of the value or an exception code for SNMPv2 +and may be one of: +.Bd -literal -offset indent +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, + /* exceptions */ + SNMP_SYNTAX_NOSUCHOBJECT, + SNMP_SYNTAX_NOSUCHINSTANCE, + SNMP_SYNTAX_ENDOFMIBVIEW, +}; +.Ed +The field +.Fa v +holds the actual value depending on +.Fa syntax . +Note, that if +.Fa syntax +is +.Li SNMP_SYNTAX_OCTETSTRING +and +.Fa v.octetstring.len +is not zero, +.Fa v.octetstring.octets +points to a string allocated by +.Xr malloc 3 . +.Pp +.Bd -literal -offset indent +#define SNMP_COMMUNITY_MAXLEN 128 +#define SNMP_MAX_BINDINGS 100 + +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; + u_int32_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; +}; +.Ed +This structure contains a decoded SNMP PDU. +.Fa version +is one of +.Bd -literal -offset indent +enum snmp_version { + SNMP_Verr = 0, + SNMP_V1 = 1, + SNMP_V2c, +}; +.Ed +and +.Fa type +is the type of the PDU. +.Pp +The function +.Fn snmp_value_free +is used to free all the dynamic allocated contents of an SNMP value. It does +not free the structure pointed to by +.Fa value +itself. +.Pp +The function +.Fn snmp_value_parse +parses the ASCII representation of an SNMP value into its binary form. +This function is mainly used by the configuration file reader of +.Xr snmpd 1 . +.Pp +The function +.Fn snmp_value_copy +makes a deep copy of the value pointed to by +.Fa from +to the structure pointed to by +.Fa to . +It assumes that +.Fa to +is uninitialized and will overwrite its previous contents. It does not itself +allocate the structure pointed to by +.Fa to . +.Pp +The function +.Fn snmp_pdu_free +frees all the dynamically allocated components of the PDU. It does not itself +free the structure pointed to by +.Fa pdu . +.Pp +The function +.Fn snmp_pdu_decode +decodes the PDU pointed to by +.Fa buf +and stores the result into +.Fa pdu . +If an error occurs in a variable binding the (1 based) index of this binding +is stored in the variable pointed to by +.Fa ip . +.Pp +The function +.Fn snmp_pdu_encode +encodes the PDU +.Fa pdu +into the an octetstring in buffer +.Fa buf . +.Pp +The function +.Fn snmp_pdu_dump +dumps the PDU in a human readable form by calling +.Fn snmp_printf . +.Pp +The function +.Fn TRUTH_MK +takes a C truth value (zero or non-zero) and makes an SNMP truth value (2 or 1). +The function +.Fn TRUTH_GET +takes an SNMP truth value and makes a C truth value (0 or 1). +The function +.Fn TRUTH_OK +checks, whether its argument is a legal SNMP truth value. +.Sh DIAGNOSTICS +When an error occures in any of the function the function pointed to +by the global pointer +.Bd -literal -offset indent +extern void (*snmp_error)(const char *, ...); +.Ed +.Pp +with a +.Xr printf 3 +style format string. +There is a default error handler in the library that prints a message +starting with +.Sq SNMP: +followed by the error message to standard error. +.Pp +The function pointed to by +.Bd -literal -offset indent +extern void (*snmp_printf)(const char *, ...); +.Ed +.Pp +is called by the +.Fn snmp_pdu_dump +function. +The default handler is +.Xr printf 3 . +.Sh ERRORS +.Fn snmp_pdu_decode +will return one of the following return codes: +.Bl -tag -width Er +.It Bq Er SNMP_CODE_OK +Success. +.It Bq Er SNMP_CODE_FAILED +The ASN.1 coding was wrong. +.It Bq Er SNMP_CODE_BADLEN +A variable binding value had a wrong length field. +.It Bq Er SNMP_CODE_OORANGE +A variable binding value was out of the allowed range. +.It Bq Er SNMP_CODE_BADVERS +The PDU is of an unsupported version. +.It Bq Er SNMP_CODE_BADENQ +There was an ASN.1 value with an unsupported tag. +.El +.Pp +.Fn snmp_pdu_encode +will return one of the following return codes: +.Bl -tag -width Er +.It Bq Er SNMP_CODE_OK +Success. +.It Bq Er SNMP_CODE_FAILED +Encoding failed. +.El +.Sh SEE ALSO +.Xr snmpd 1 , +.Xr gensnmptree 1 , +.Xr bsnmplib 3 +.Xr bsnmpclient 3 , +.Xr bsnmpagent 3 +.Sh STANDARDS +This implementation conforms to the applicable IETF RFCs and ITU-T +recommendations. +.Sh AUTHORS +.An Hartmut Brandt Aq brandt@fokus.gmd.de diff --git a/contrib/bsnmp/lib/snmp.c b/contrib/bsnmp/lib/snmp.c new file mode 100644 index 0000000..786bf8c --- /dev/null +++ b/contrib/bsnmp/lib/snmp.c @@ -0,0 +1,1027 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.34 2003/01/28 13:44:34 hbb Exp $ + * + * SNMP + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.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); +} + +/* + * 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: + { + u_int64_t sub; + + sub = strtoull(str, &end, 0); + if (*end != '\0' || sub > 0xffffffff) + return (-1); + v->uint32 = (u_int32_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/contrib/bsnmp/lib/snmp.h b/contrib/bsnmp/lib/snmp.h new file mode 100644 index 0000000..30b1e2c --- /dev/null +++ b/contrib/bsnmp/lib/snmp.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.27 2002/03/08 14:24:58 hbb 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]; + u_int32_t uint32; /* also gauge32, counter32, + unsigned32, timeticks */ + u_int64_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; + u_int32_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); + +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/contrib/bsnmp/lib/snmpagent.c b/contrib/bsnmp/lib/snmpagent.c new file mode 100644 index 0000000..19ac661 --- /dev/null +++ b/contrib/bsnmp/lib/snmpagent.c @@ -0,0 +1,1031 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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/snmpagent.c,v 1.14 2003/01/30 11:23:00 hbb Exp $ + * + * SNMP Agent functions + */ +#include <sys/types.h> +#include <sys/queue.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> + +#include "asn1.h" +#include "snmp.h" +#include "snmppriv.h" +#include "snmpagent.h" + +static void snmp_debug_func(const char *fmt, ...); + +void (*snmp_debug)(const char *fmt, ...) = snmp_debug_func; + +struct snmp_node *tree; +u_int tree_size; + +/* + * Structure to hold dependencies during SET processing + * The last two members of this structure must be the + * dependency visible by the user and the user data. + */ +struct depend { + TAILQ_ENTRY(depend) link; + size_t len; /* size of data part */ + snmp_depop_t func; + struct snmp_dependency dep; + u_char data[]; +}; +TAILQ_HEAD(depend_list, depend); + +/* + * Structure to hold atfinish functions during SET processing. + */ +struct finish { + STAILQ_ENTRY(finish) link; + snmp_set_finish_t func; + void *arg; +}; +STAILQ_HEAD(finish_list, finish); + +/* + * Set context + */ +struct context { + struct snmp_context ctx; + struct depend_list dlist; + struct finish_list flist; + const struct snmp_node *node[SNMP_MAX_BINDINGS]; + struct snmp_scratch scratch[SNMP_MAX_BINDINGS]; + struct depend *depend; +}; + +#define TR(W) (snmp_trace & SNMP_TRACE_##W) +u_int snmp_trace = 0; + +static char oidbuf[ASN_OIDSTRLEN]; + +/* + * Allocate a context + */ +struct snmp_context * +snmp_init_context(void) +{ + struct context *context; + + if ((context = malloc(sizeof(*context))) == NULL) + return (NULL); + + memset(context, 0, sizeof(*context)); + TAILQ_INIT(&context->dlist); + STAILQ_INIT(&context->flist); + + return (&context->ctx); +} + +/* + * Find a variable for SET/GET and the first GETBULK pass. + * Return the node pointer. If the search fails, set the errp to + * the correct SNMPv2 GET exception code. + */ +static struct snmp_node * +find_node(const struct snmp_value *value, enum snmp_syntax *errp) +{ + struct snmp_node *tp; + + if (TR(FIND)) + snmp_debug("find: searching %s", + asn_oid2str_r(&value->var, oidbuf)); + + /* + * If we have an exact match (the entry in the table is a + * sub-oid from the variable) we have found what we are for. + * If the table oid is higher than the variable, there is no match. + */ + for (tp = tree; tp < tree + tree_size; tp++) { + if (asn_is_suboid(&tp->oid, &value->var)) + goto found; + if (asn_compare_oid(&tp->oid, &value->var) >= 0) + break; + } + + if (TR(FIND)) + snmp_debug("find: no match"); + *errp = SNMP_SYNTAX_NOSUCHOBJECT; + return (NULL); + + found: + /* leafs must have a 0 instance identifier */ + if (tp->type == SNMP_NODE_LEAF && + (value->var.len != tp->oid.len + 1 || + value->var.subs[tp->oid.len] != 0)) { + if (TR(FIND)) + snmp_debug("find: bad leaf index"); + *errp = SNMP_SYNTAX_NOSUCHINSTANCE; + return (NULL); + } + if (TR(FIND)) + snmp_debug("find: found %s", + asn_oid2str_r(&value->var, oidbuf)); + return (tp); +} + +static struct snmp_node * +find_subnode(const struct snmp_value *value) +{ + struct snmp_node *tp; + + for (tp = tree; tp < tree + tree_size; tp++) { + if (asn_is_suboid(&value->var, &tp->oid)) + return (tp); + } + return (NULL); +} + +/* + * Execute a GET operation. The tree is rooted at the global 'root'. + * Build the response PDU on the fly. If the return code is SNMP_RET_ERR + * the pdu error status and index will be set. + */ +enum snmp_ret +snmp_get(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *data) +{ + int ret; + u_int i; + struct snmp_node *tp; + enum snmp_syntax except; + struct context context; + enum asn_err err; + + memset(&context, 0, sizeof(context)); + context.ctx.data = data; + + memset(resp, 0, sizeof(*resp)); + strcpy(resp->community, pdu->community); + resp->version = pdu->version; + resp->type = SNMP_PDU_RESPONSE; + resp->request_id = pdu->request_id; + resp->version = pdu->version; + + if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) + /* cannot even encode header - very bad */ + return (SNMP_RET_IGN); + + for (i = 0; i < pdu->nbindings; i++) { + resp->bindings[i].var = pdu->bindings[i].var; + if ((tp = find_node(&pdu->bindings[i], &except)) == NULL) { + if (pdu->version == SNMP_V1) { + if (TR(GET)) + snmp_debug("get: nosuchname"); + pdu->error_status = SNMP_ERR_NOSUCHNAME; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + if (TR(GET)) + snmp_debug("get: exception %u", except); + resp->bindings[i].syntax = except; + + } else { + /* call the action to fetch the value. */ + resp->bindings[i].syntax = tp->syntax; + ret = (*tp->op)(&context.ctx, &resp->bindings[i], + tp->oid.len, tp->index, SNMP_OP_GET); + if (TR(GET)) + snmp_debug("get: action returns %d", ret); + + if (ret == SNMP_ERR_NOSUCHNAME) { + if (pdu->version == SNMP_V1) { + pdu->error_status = SNMP_ERR_NOSUCHNAME; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + if (TR(GET)) + snmp_debug("get: exception noSuchInstance"); + resp->bindings[i].syntax = SNMP_SYNTAX_NOSUCHINSTANCE; + + } else if (ret != SNMP_ERR_NOERROR) { + pdu->error_status = SNMP_ERR_GENERR; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + } + resp->nbindings++; + + err = snmp_binding_encode(resp_b, &resp->bindings[i]); + + if (err == ASN_ERR_EOBUF) { + pdu->error_status = SNMP_ERR_TOOBIG; + pdu->error_index = 0; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + if (err != ASN_ERR_OK) { + if (TR(GET)) + snmp_debug("get: binding encoding: %u", err); + pdu->error_status = SNMP_ERR_GENERR; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + } + + return (snmp_fix_encoding(resp_b, resp)); +} + +static struct snmp_node * +next_node(const struct snmp_value *value, int *pnext) +{ + struct snmp_node *tp; + + if (TR(FIND)) + snmp_debug("next: searching %s", + asn_oid2str_r(&value->var, oidbuf)); + + *pnext = 0; + for (tp = tree; tp < tree + tree_size; tp++) { + if (asn_is_suboid(&tp->oid, &value->var)) { + /* the tree OID is a sub-oid of the requested OID. */ + if (tp->type == SNMP_NODE_LEAF) { + if (tp->oid.len == value->var.len) { + /* request for scalar type */ + if (TR(FIND)) + snmp_debug("next: found scalar %s", + asn_oid2str_r(&tp->oid, oidbuf)); + return (tp); + } + /* try next */ + } else { + if (TR(FIND)) + snmp_debug("next: found column %s", + asn_oid2str_r(&tp->oid, oidbuf)); + return (tp); + } + } else if (asn_is_suboid(&value->var, &tp->oid) || + asn_compare_oid(&tp->oid, &value->var) >= 0) { + if (TR(FIND)) + snmp_debug("next: found %s", + asn_oid2str_r(&tp->oid, oidbuf)); + *pnext = 1; + return (tp); + } + } + + if (TR(FIND)) + snmp_debug("next: failed"); + + return (NULL); +} + +static enum snmp_ret +do_getnext(struct context *context, const struct snmp_value *inb, + struct snmp_value *outb, struct snmp_pdu *pdu) +{ + const struct snmp_node *tp; + int ret, next; + + if ((tp = next_node(inb, &next)) == NULL) + goto eofMib; + + /* retain old variable if we are doing a GETNEXT on an exact + * matched leaf only */ + if (tp->type == SNMP_NODE_LEAF || next) + outb->var = tp->oid; + else + outb->var = inb->var; + + for (;;) { + outb->syntax = tp->syntax; + if (tp->type == SNMP_NODE_LEAF) { + /* make a GET operation */ + outb->var.subs[outb->var.len++] = 0; + ret = (*tp->op)(&context->ctx, outb, tp->oid.len, + tp->index, SNMP_OP_GET); + } else { + /* make a GETNEXT */ + ret = (*tp->op)(&context->ctx, outb, tp->oid.len, + tp->index, SNMP_OP_GETNEXT); + } + if (ret != SNMP_ERR_NOSUCHNAME) { + /* got something */ + if (ret != SNMP_ERR_NOERROR && TR(GETNEXT)) + snmp_debug("getnext: %s returns %u", + asn_oid2str(&outb->var), ret); + break; + } + + /* object has no data - try next */ + if (++tp == tree + tree_size) + break; + outb->var = tp->oid; + } + + if (ret == SNMP_ERR_NOSUCHNAME) { + eofMib: + outb->var = inb->var; + if (pdu->version == SNMP_V1) { + pdu->error_status = SNMP_ERR_NOSUCHNAME; + return (SNMP_RET_ERR); + } + outb->syntax = SNMP_SYNTAX_ENDOFMIBVIEW; + + } else if (ret != SNMP_ERR_NOERROR) { + pdu->error_status = SNMP_ERR_GENERR; + return (SNMP_RET_ERR); + } + return (SNMP_RET_OK); +} + + +/* + * Execute a GETNEXT operation. The tree is rooted at the global 'root'. + * Build the response PDU on the fly. The return is: + */ +enum snmp_ret +snmp_getnext(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *data) +{ + struct context context; + u_int i; + enum asn_err err; + enum snmp_ret result; + + memset(&context, 0, sizeof(context)); + context.ctx.data = data; + + memset(resp, 0, sizeof(*resp)); + strcpy(resp->community, pdu->community); + resp->type = SNMP_PDU_RESPONSE; + resp->request_id = pdu->request_id; + resp->version = pdu->version; + + if (snmp_pdu_encode_header(resp_b, resp)) + return (SNMP_RET_IGN); + + for (i = 0; i < pdu->nbindings; i++) { + result = do_getnext(&context, &pdu->bindings[i], + &resp->bindings[i], pdu); + + if (result != SNMP_RET_OK) { + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (result); + } + + resp->nbindings++; + + err = snmp_binding_encode(resp_b, &resp->bindings[i]); + + if (err == ASN_ERR_EOBUF) { + pdu->error_status = SNMP_ERR_TOOBIG; + pdu->error_index = 0; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + if (err != ASN_ERR_OK) { + if (TR(GET)) + snmp_debug("getnext: binding encoding: %u", err); + pdu->error_status = SNMP_ERR_GENERR; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + } + return (snmp_fix_encoding(resp_b, resp)); +} + +enum snmp_ret +snmp_getbulk(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *data) +{ + struct context context; + u_int i; + int cnt; + u_int non_rep; + int eomib; + enum snmp_ret result; + enum asn_err err; + + memset(&context, 0, sizeof(context)); + context.ctx.data = data; + + memset(resp, 0, sizeof(*resp)); + strcpy(resp->community, pdu->community); + resp->version = pdu->version; + resp->type = SNMP_PDU_RESPONSE; + resp->request_id = pdu->request_id; + resp->version = pdu->version; + + if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) + /* cannot even encode header - very bad */ + return (SNMP_RET_IGN); + + if ((non_rep = pdu->error_status) > pdu->nbindings) + non_rep = pdu->nbindings; + + /* non-repeaters */ + for (i = 0; i < non_rep; i++) { + result = do_getnext(&context, &pdu->bindings[i], + &resp->bindings[resp->nbindings], pdu); + + if (result != SNMP_RET_OK) { + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (result); + } + + err = snmp_binding_encode(resp_b, + &resp->bindings[resp->nbindings++]); + + if (err == ASN_ERR_EOBUF) + goto done; + + if (err != ASN_ERR_OK) { + if (TR(GET)) + snmp_debug("getnext: binding encoding: %u", err); + pdu->error_status = SNMP_ERR_GENERR; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + } + + if (non_rep == pdu->nbindings) + goto done; + + /* repeates */ + for (cnt = 0; cnt < pdu->error_index; cnt++) { + eomib = 1; + for (i = non_rep; i < pdu->nbindings; i++) { + if (cnt == 0) + result = do_getnext(&context, &pdu->bindings[i], + &resp->bindings[resp->nbindings], pdu); + else + result = do_getnext(&context, + &resp->bindings[resp->nbindings - + (pdu->nbindings - non_rep)], + &resp->bindings[resp->nbindings], pdu); + + if (result != SNMP_RET_OK) { + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (result); + } + if (resp->bindings[resp->nbindings].syntax != + SNMP_SYNTAX_ENDOFMIBVIEW) + eomib = 0; + + err = snmp_binding_encode(resp_b, + &resp->bindings[resp->nbindings++]); + + if (err == ASN_ERR_EOBUF) + goto done; + + if (err != ASN_ERR_OK) { + if (TR(GET)) + snmp_debug("getnext: binding encoding: %u", err); + pdu->error_status = SNMP_ERR_GENERR; + pdu->error_index = i + 1; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + } + if (eomib) + break; + } + + done: + return (snmp_fix_encoding(resp_b, resp)); +} + +/* + * Rollback a SET operation. Failed index is 'i'. + */ +static void +rollback(struct context *context, struct snmp_pdu *pdu, u_int i) +{ + struct snmp_value *b; + const struct snmp_node *np; + int ret; + + while (i-- > 0) { + b = &pdu->bindings[i]; + np = context->node[i]; + + context->ctx.scratch = &context->scratch[i]; + + ret = (*np->op)(&context->ctx, b, np->oid.len, np->index, + SNMP_OP_ROLLBACK); + + if (ret != SNMP_ERR_NOERROR) { + snmp_error("set: rollback failed (%d) on variable %s " + "index %u", ret, asn_oid2str(&b->var), i); + if (pdu->version != SNMP_V1) { + pdu->error_status = SNMP_ERR_UNDO_FAILED; + pdu->error_index = 0; + } + } + } +} + +/* + * Commit dependencies. + */ +int +snmp_dep_commit(struct snmp_context *ctx) +{ + struct context *context = (struct context *)ctx; + int ret; + + TAILQ_FOREACH(context->depend, &context->dlist, link) { + ctx->dep = &context->depend->dep; + + if (TR(SET)) + snmp_debug("set: dependency commit %s", + asn_oid2str(&ctx->dep->obj)); + + ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_COMMIT); + + if (ret != SNMP_ERR_NOERROR) { + if (TR(SET)) + snmp_debug("set: dependency failed %d", ret); + return (ret); + } + } + return (SNMP_ERR_NOERROR); +} + +/* + * Rollback dependencies + */ +int +snmp_dep_rollback(struct snmp_context *ctx) +{ + struct context *context = (struct context *)ctx; + int ret, ret1; + char objbuf[ASN_OIDSTRLEN]; + char idxbuf[ASN_OIDSTRLEN]; + + ret1 = SNMP_ERR_NOERROR; + while ((context->depend = + TAILQ_PREV(context->depend, depend_list, link)) != NULL) { + ctx->dep = &context->depend->dep; + + if (TR(SET)) + snmp_debug("set: dependency rollback %s", + asn_oid2str(&ctx->dep->obj)); + + ret = context->depend->func(ctx, ctx->dep, SNMP_DEPOP_ROLLBACK); + + if (ret != SNMP_ERR_NOERROR) { + snmp_debug("set: dep rollback returns %u: %s %s", ret, + asn_oid2str_r(&ctx->dep->obj, objbuf), + asn_oid2str_r(&ctx->dep->idx, idxbuf)); + if (ret1 == SNMP_ERR_NOERROR) + ret1 = ret; + } + } + return (ret1); +} + +/* + * Do a SET operation. + */ +enum snmp_ret +snmp_set(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *data) +{ + int ret; + u_int i; + enum snmp_ret code; + enum asn_err asnerr; + struct context context; + const struct snmp_node *np; + struct finish *f; + struct depend *d; + struct snmp_value *b; + enum snmp_syntax except; + + memset(&context, 0, sizeof(context)); + TAILQ_INIT(&context.dlist); + STAILQ_INIT(&context.flist); + context.ctx.data = data; + + memset(resp, 0, sizeof(*resp)); + strcpy(resp->community, pdu->community); + resp->type = SNMP_PDU_RESPONSE; + resp->request_id = pdu->request_id; + resp->version = pdu->version; + + if (snmp_pdu_encode_header(resp_b, resp)) + return (SNMP_RET_IGN); + + /* + * 1. Find all nodes, check that they are writeable and + * that the syntax is ok, copy over the binding to the response. + */ + for (i = 0; i < pdu->nbindings; i++) { + b = &pdu->bindings[i]; + + if ((np = context.node[i] = find_node(b, &except)) == NULL) { + /* not found altogether or LEAF with wrong index */ + if (TR(SET)) + snmp_debug("set: node not found %s", + asn_oid2str_r(&b->var, oidbuf)); + if (pdu->version == SNMP_V1) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NOSUCHNAME; + } else if ((np = find_subnode(b)) != NULL) { + /* 2. intermediate object */ + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NOT_WRITEABLE; + } else if (except == SNMP_SYNTAX_NOSUCHOBJECT) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NO_ACCESS; + } else { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NO_CREATION; + } + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + /* + * 2. write/createable? + * Can check this for leafs only, because in v2 we have + * to differentiate between NOT_WRITEABLE and NO_CREATION + * and only the action routine for COLUMNS knows, whether + * a column exists. + */ + if (np->type == SNMP_NODE_LEAF && + !(np->flags & SNMP_NODE_CANSET)) { + if (pdu->version == SNMP_V1) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NOSUCHNAME; + } else { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_NOT_WRITEABLE; + } + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + /* + * 3. Ensure the right syntax + */ + if (np->syntax != b->syntax) { + if (pdu->version == SNMP_V1) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_BADVALUE; /* v2: wrongType */ + } else { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_WRONG_TYPE; + } + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + /* + * 4. Copy binding + */ + if (snmp_value_copy(&resp->bindings[i], b)) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_GENERR; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + asnerr = snmp_binding_encode(resp_b, &resp->bindings[i]); + if (asnerr == ASN_ERR_EOBUF) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_TOOBIG; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } else if (asnerr != ASN_ERR_OK) { + pdu->error_index = i + 1; + pdu->error_status = SNMP_ERR_GENERR; + snmp_pdu_free(resp); + return (SNMP_RET_ERR); + } + resp->nbindings++; + } + + code = SNMP_RET_OK; + + /* + * 2. Call the SET method for each node. If a SET fails, rollback + * everything. Map error codes depending on the version. + */ + for (i = 0; i < pdu->nbindings; i++) { + b = &pdu->bindings[i]; + np = context.node[i]; + + context.ctx.var_index = i + 1; + context.ctx.scratch = &context.scratch[i]; + + ret = (*np->op)(&context.ctx, b, np->oid.len, np->index, + SNMP_OP_SET); + + if (TR(SET)) + snmp_debug("set: action %s returns %d", np->name, ret); + + if (pdu->version == SNMP_V1) { + switch (ret) { + case SNMP_ERR_NO_ACCESS: + ret = SNMP_ERR_NOSUCHNAME; + break; + case SNMP_ERR_WRONG_TYPE: + /* should no happen */ + ret = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_WRONG_LENGTH: + ret = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_WRONG_ENCODING: + /* should not happen */ + ret = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_WRONG_VALUE: + ret = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_NO_CREATION: + ret = SNMP_ERR_NOSUCHNAME; + break; + case SNMP_ERR_INCONS_VALUE: + ret = SNMP_ERR_BADVALUE; + break; + case SNMP_ERR_RES_UNAVAIL: + ret = SNMP_ERR_GENERR; + break; + case SNMP_ERR_COMMIT_FAILED: + ret = SNMP_ERR_GENERR; + break; + case SNMP_ERR_UNDO_FAILED: + ret = SNMP_ERR_GENERR; + break; + case SNMP_ERR_AUTH_ERR: + /* should not happen */ + ret = SNMP_ERR_GENERR; + break; + case SNMP_ERR_NOT_WRITEABLE: + ret = SNMP_ERR_NOSUCHNAME; + break; + case SNMP_ERR_INCONS_NAME: + ret = SNMP_ERR_BADVALUE; + break; + } + } + if (ret != SNMP_ERR_NOERROR) { + pdu->error_index = i + 1; + pdu->error_status = ret; + + rollback(&context, pdu, i); + snmp_pdu_free(resp); + + code = SNMP_RET_ERR; + + goto errout; + } + } + + /* + * 3. Call dependencies + */ + if (TR(SET)) + snmp_debug("set: set operations ok"); + + if ((ret = snmp_dep_commit(&context.ctx)) != SNMP_ERR_NOERROR) { + pdu->error_status = ret; + pdu->error_index = context.ctx.var_index; + + if ((ret = snmp_dep_rollback(&context.ctx)) != SNMP_ERR_NOERROR) { + if (pdu->version != SNMP_V1) { + pdu->error_status = SNMP_ERR_UNDO_FAILED; + pdu->error_index = 0; + } + } + rollback(&context, pdu, i); + snmp_pdu_free(resp); + + code = SNMP_RET_ERR; + + goto errout; + } + + /* + * 4. Commit and copy values from the original packet to the response. + * This is not the commit operation from RFC 1905 but rather an + * 'FREE RESOURCES' operation. It shouldn't fail. + */ + if (TR(SET)) + snmp_debug("set: commiting"); + + for (i = 0; i < pdu->nbindings; i++) { + b = &resp->bindings[i]; + np = context.node[i]; + + context.ctx.var_index = i + 1; + context.ctx.scratch = &context.scratch[i]; + + ret = (*np->op)(&context.ctx, b, np->oid.len, np->index, + SNMP_OP_COMMIT); + + if (ret != SNMP_ERR_NOERROR) + snmp_error("set: commit failed (%d) on" + " variable %s index %u", ret, + asn_oid2str_r(&b->var, oidbuf), i); + } + + if (snmp_fix_encoding(resp_b, resp) != SNMP_CODE_OK) { + snmp_error("set: fix_encoding failed"); + snmp_pdu_free(resp); + code = SNMP_RET_IGN; + } + + /* + * Done + */ + errout: + while ((d = TAILQ_FIRST(&context.dlist)) != NULL) { + TAILQ_REMOVE(&context.dlist, d, link); + free(d); + } + + /* + * call finish function + */ + while ((f = STAILQ_FIRST(&context.flist)) != NULL) { + STAILQ_REMOVE_HEAD(&context.flist, link); + (*f->func)(&context.ctx, code != SNMP_RET_OK, f->arg); + free(f); + } + + if (TR(SET)) + snmp_debug("set: returning %d", code); + + return (code); +} +/* + * Lookup a dependency. If it doesn't exist, create one + */ +struct snmp_dependency * +snmp_dep_lookup(struct snmp_context *ctx, const struct asn_oid *obj, + const struct asn_oid *idx, size_t len, snmp_depop_t func) +{ + struct context *context; + struct depend *d; + + context = (struct context *)(void *) + ((char *)ctx - offsetof(struct context, ctx)); + if (TR(DEPEND)) { + snmp_debug("depend: looking for %s", asn_oid2str(obj)); + if (idx) + snmp_debug("depend: index is %s", asn_oid2str(idx)); + } + TAILQ_FOREACH(d, &context->dlist, link) + if (asn_compare_oid(obj, &d->dep.obj) == 0 && + ((idx == NULL && d->dep.idx.len == 0) || + (idx != NULL && asn_compare_oid(idx, &d->dep.idx) == 0))) { + if(TR(DEPEND)) + snmp_debug("depend: found"); + return (&d->dep); + } + + if(TR(DEPEND)) + snmp_debug("depend: creating"); + + if ((d = malloc(offsetof(struct depend, dep) + len)) == NULL) + return (NULL); + memset(&d->dep, 0, len); + + d->dep.obj = *obj; + if (idx == NULL) + d->dep.idx.len = 0; + else + d->dep.idx = *idx; + d->len = len; + d->func = func; + + TAILQ_INSERT_TAIL(&context->dlist, d, link); + + return (&d->dep); +} + +/* + * Register a finish function. + */ +int +snmp_set_atfinish(struct snmp_context *ctx, snmp_set_finish_t func, void *arg) +{ + struct context *context; + struct finish *f; + + context = (struct context *)(void *) + ((char *)ctx - offsetof(struct context, ctx)); + if ((f = malloc(sizeof(struct finish))) == NULL) + return (-1); + f->func = func; + f->arg = arg; + STAILQ_INSERT_TAIL(&context->flist, f, link); + + return (0); +} + +/* + * Make an error response from a PDU. We do this without decoding the + * variable bindings. This means we can sent the junk back to a caller + * that has sent us junk in the first place. + */ +enum snmp_ret +snmp_make_errresp(const struct snmp_pdu *pdu, struct asn_buf *pdu_b, + struct asn_buf *resp_b) +{ + asn_len_t len; + struct snmp_pdu resp; + enum asn_err err; + enum snmp_code code; + + memset(&resp, 0, sizeof(resp)); + + /* Message sequence */ + if (asn_get_sequence(pdu_b, &len) != ASN_ERR_OK) + return (SNMP_RET_IGN); + if (pdu_b->asn_len < len) + return (SNMP_RET_IGN); + + err = snmp_parse_message_hdr(pdu_b, &resp, &len); + if (ASN_ERR_STOPPED(err)) + return (SNMP_RET_IGN); + if (pdu_b->asn_len < len) + return (SNMP_RET_IGN); + pdu_b->asn_len = len; + + err = snmp_parse_pdus_hdr(pdu_b, &resp, &len); + if (ASN_ERR_STOPPED(err)) + return (SNMP_RET_IGN); + if (pdu_b->asn_len < len) + return (SNMP_RET_IGN); + pdu_b->asn_len = len; + + /* now we have the bindings left - construct new message */ + resp.error_status = pdu->error_status; + resp.error_index = pdu->error_index; + resp.type = SNMP_PDU_RESPONSE; + + code = snmp_pdu_encode_header(resp_b, &resp); + if (code != SNMP_CODE_OK) + return (SNMP_RET_IGN); + + if (pdu_b->asn_len > resp_b->asn_len) + /* too short */ + return (SNMP_RET_IGN); + (void)memcpy(resp_b->asn_ptr, pdu_b->asn_cptr, pdu_b->asn_len); + resp_b->asn_len -= pdu_b->asn_len; + resp_b->asn_ptr += pdu_b->asn_len; + + code = snmp_fix_encoding(resp_b, &resp); + if (code != SNMP_CODE_OK) + return (SNMP_RET_IGN); + + return (SNMP_RET_OK); +} + +static void +snmp_debug_func(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} diff --git a/contrib/bsnmp/lib/snmpagent.h b/contrib/bsnmp/lib/snmpagent.h new file mode 100644 index 0000000..a854138 --- /dev/null +++ b/contrib/bsnmp/lib/snmpagent.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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/snmpagent.h,v 1.9 2002/03/08 14:24:58 hbb Exp $ + * + * Header file for SNMP functions. This requires snmp.h to be included. + */ +#ifndef snmp_agent_h_ +#define snmp_agent_h_ + +struct snmp_dependency; + +/* Semi-Opaque object for SET operations */ +struct snmp_context { + u_int var_index; + struct snmp_scratch *scratch; + struct snmp_dependency *dep; + void *data; /* user data */ +}; + +struct snmp_scratch { + void *ptr1; + void *ptr2; + u_int32_t int1; + u_int32_t int2; +}; + +enum snmp_depop { + SNMP_DEPOP_COMMIT, + SNMP_DEPOP_ROLLBACK +}; + +typedef int (*snmp_depop_t)(struct snmp_context *, struct snmp_dependency *, + enum snmp_depop); + +struct snmp_dependency { + struct asn_oid obj; + struct asn_oid idx; +}; + +/* + * Functions to be called at the end of a SET operation. + */ +typedef void (*snmp_set_finish_t)(struct snmp_context *, int fail, void *); + +/* + * The TREE + */ +enum snmp_node_type { + SNMP_NODE_LEAF = 1, + SNMP_NODE_COLUMN +}; + +enum snmp_op { + SNMP_OP_GET = 1, + SNMP_OP_GETNEXT, + SNMP_OP_SET, + SNMP_OP_COMMIT, + SNMP_OP_ROLLBACK, +}; + +enum snmp_ret { + /* OK, generate a response */ + SNMP_RET_OK = 0, + /* Error, ignore packet (no response) */ + SNMP_RET_IGN = 1, + /* Error, generate response from original packet */ + SNMP_RET_ERR = 2 +}; + +typedef int (*snmp_op_t)(struct snmp_context *, struct snmp_value *, + u_int, u_int, enum snmp_op); + +struct snmp_node { + struct asn_oid oid; + const char *name; /* name of the leaf */ + enum snmp_node_type type; /* type of this node */ + enum snmp_syntax syntax; + snmp_op_t op; + u_int flags; + u_int32_t index; /* index data */ + void *data; /* application data */ +}; +extern struct snmp_node *tree; +extern u_int tree_size; + +#define SNMP_NODE_CANSET 0x0001 /* SET allowed */ + +#define SNMP_INDEXES_MAX 7 +#define SNMP_INDEX_SHIFT 4 +#define SNMP_INDEX_MASK 0xf +#define SNMP_INDEX_COUNT(V) ((V) & SNMP_INDEX_MASK) +#define SNMP_INDEX(V,I) \ + (((V) >> (((I) + 1) * SNMP_INDEX_SHIFT)) & SNMP_INDEX_MASK) + +enum { + SNMP_TRACE_GET = 0x00000001, + SNMP_TRACE_GETNEXT = 0x00000002, + SNMP_TRACE_SET = 0x00000004, + SNMP_TRACE_DEPEND = 0x00000008, + SNMP_TRACE_FIND = 0x00000010, +}; +/* trace flag for the following functions */ +extern u_int snmp_trace; + +/* called to write the trace */ +extern void (*snmp_debug)(const char *fmt, ...); + +enum snmp_ret snmp_get(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *); +enum snmp_ret snmp_getnext(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *); +enum snmp_ret snmp_getbulk(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *); +enum snmp_ret snmp_set(struct snmp_pdu *pdu, struct asn_buf *resp_b, + struct snmp_pdu *resp, void *); + +enum snmp_ret snmp_make_errresp(const struct snmp_pdu *, struct asn_buf *, + struct asn_buf *); + +struct snmp_dependency *snmp_dep_lookup(struct snmp_context *, + const struct asn_oid *, const struct asn_oid *, size_t, snmp_depop_t); + +int snmp_set_atfinish(struct snmp_context *, snmp_set_finish_t func, void *arg); + +struct snmp_context *snmp_init_context(void); +int snmp_dep_commit(struct snmp_context *); +int snmp_dep_rollback(struct snmp_context *); + +#endif diff --git a/contrib/bsnmp/lib/snmpclient.c b/contrib/bsnmp/lib/snmpclient.c new file mode 100644 index 0000000..e1206b0 --- /dev/null +++ b/contrib/bsnmp/lib/snmpclient.c @@ -0,0 +1,1658 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * Kendy Kutzner + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.24 2003/01/28 13:44:34 hbb Exp $ + * + * Support functions for SNMP clients. + */ +#include <sys/types.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 <inttypes.h> +#include <err.h> +#include <limits.h> + +#include "asn1.h" +#include "snmp.h" +#include "snmpclient.h" +#include "snmppriv.h" + +/* 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; + +/* + * Prototype table entry. All C-structure produced by the table function must + * start with these two fields. This relies on the fact, that all TAILQ_ENTRY + * are compatible with each other in the sense implied by ANSI-C. + */ +struct entry { + TAILQ_ENTRY(entry) link; + u_int64_t found; +}; +TAILQ_HEAD(table, entry); + +/* + * working list entry. This list is used to hold the Index part of the + * table row's. The entry list and the work list parallel each other. + */ +struct work { + TAILQ_ENTRY(work) link; + struct asn_oid index; +}; +TAILQ_HEAD(worklist, work); + +/* + * Table working data + */ +struct tabwork { + const struct snmp_table *descr; + struct table *table; + struct worklist worklist; + u_int32_t last_change; + int first; + u_int iter; + snmp_table_cb_f callback; + void *arg; + struct snmp_pdu pdu; +}; + +/* + * Set the error string + */ +static void +seterr(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(snmp_client.error, sizeof(snmp_client.error), fmt, ap); + va_end(ap); +} + +/* + * Free the entire table and work list. If table is NULL only the worklist + * is freed. + */ +static void +table_free(struct tabwork *work, int all) +{ + struct work *w; + struct entry *e; + const struct snmp_table_entry *d; + u_int i; + + while ((w = TAILQ_FIRST(&work->worklist)) != NULL) { + TAILQ_REMOVE(&work->worklist, w, link); + free(w); + } + + if (all == 0) + return; + + while ((e = TAILQ_FIRST(work->table)) != NULL) { + for (i = 0; work->descr->entries[i].syntax != SNMP_SYNTAX_NULL; + i++) { + d = &work->descr->entries[i]; + if (d->syntax == SNMP_SYNTAX_OCTETSTRING && + (e->found & ((u_int64_t)1 << i))) + free(*(void **)(void *) + ((u_char *)e + d->offset)); + } + TAILQ_REMOVE(work->table, e, link); + free(e); + } +} + +/* + * Find the correct table entry for the given variable. If non exists, + * create one. + */ +static struct entry * +table_find(struct tabwork *work, const struct asn_oid *var) +{ + struct entry *e, *e1; + struct work *w, *w1; + u_int i, p, j; + size_t len; + u_char *ptr; + struct asn_oid oid; + + /* get index */ + asn_slice_oid(&oid, var, work->descr->table.len + 2, var->len); + + e = TAILQ_FIRST(work->table); + w = TAILQ_FIRST(&work->worklist); + while (e != NULL) { + if (asn_compare_oid(&w->index, &oid) == 0) + return (e); + e = TAILQ_NEXT(e, link); + w = TAILQ_NEXT(w, link); + } + + /* Not found create new one */ + if ((e = malloc(work->descr->entry_size)) == NULL) { + seterr("no memory for table entry"); + return (NULL); + } + if ((w = malloc(sizeof(*w))) == NULL) { + seterr("no memory for table entry"); + free(e); + return (NULL); + } + w->index = oid; + memset(e, 0, work->descr->entry_size); + + /* decode index */ + p = work->descr->table.len + 2; + for (i = 0; i < work->descr->index_size; i++) { + switch (work->descr->entries[i].syntax) { + + case SNMP_SYNTAX_INTEGER: + if (var->len < p + 1) { + seterr("bad index: need integer"); + goto err; + } + if (var->subs[p] > INT32_MAX) { + seterr("bad index: integer too large"); + goto err; + } + *(int32_t *)(void *)((u_char *)e + + work->descr->entries[i].offset) = var->subs[p++]; + break; + + case SNMP_SYNTAX_OCTETSTRING: + if (var->len < p + 1) { + seterr("bad index: need string length"); + goto err; + } + len = var->subs[p++]; + if (var->len < p + len) { + seterr("bad index: string too short"); + goto err; + } + if ((ptr = malloc(len + 1)) == NULL) { + seterr("no memory for index string"); + goto err; + } + for (j = 0; j < len; j++) { + if (var->subs[p] > UCHAR_MAX) { + seterr("bad index: char too large"); + free(ptr); + goto err; + } + ptr[j] = var->subs[p++]; + } + ptr[j] = '\0'; + *(u_char **)(void *)((u_char *)e + + work->descr->entries[i].offset) = ptr; + *(size_t *)(void *)((u_char *)e + + work->descr->entries[i].offset + sizeof(u_char *)) + = len; + break; + + case SNMP_SYNTAX_OID: + if (var->len < p + 1) { + seterr("bad index: need oid length"); + goto err; + } + oid.len = var->subs[p++]; + if (var->len < p + oid.len) { + seterr("bad index: oid too short"); + goto err; + } + for (j = 0; j < oid.len; j++) + oid.subs[j] = var->subs[p++]; + *(struct asn_oid *)(void *)((u_char *)e + + work->descr->entries[i].offset) = oid; + break; + + case SNMP_SYNTAX_IPADDRESS: + if (var->len < p + 4) { + seterr("bad index: need ip-address"); + goto err; + } + for (j = 0; j < 4; j++) { + if (var->subs[p] > 0xff) { + seterr("bad index: ipaddress too large"); + goto err; + } + ((u_char *)e + + work->descr->entries[i].offset)[j] = + var->subs[p++]; + } + break; + + case SNMP_SYNTAX_GAUGE: + if (var->len < p + 1) { + seterr("bad index: need unsigned"); + goto err; + } + if (var->subs[p] > UINT32_MAX) { + seterr("bad index: unsigned too large"); + goto err; + } + *(u_int32_t *)(void *)((u_char *)e + + work->descr->entries[i].offset) = var->subs[p++]; + break; + + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_TIMETICKS: + case SNMP_SYNTAX_COUNTER64: + case SNMP_SYNTAX_NULL: + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + abort(); + } + e->found |= (u_int64_t)1 << i; + } + + /* link into the correct place */ + e1 = TAILQ_FIRST(work->table); + w1 = TAILQ_FIRST(&work->worklist); + while (e1 != NULL) { + if (asn_compare_oid(&w1->index, &w->index) > 0) + break; + e1 = TAILQ_NEXT(e1, link); + w1 = TAILQ_NEXT(w1, link); + } + if (e1 == NULL) { + TAILQ_INSERT_TAIL(work->table, e, link); + TAILQ_INSERT_TAIL(&work->worklist, w, link); + } else { + TAILQ_INSERT_BEFORE(e1, e, link); + TAILQ_INSERT_BEFORE(w1, w, link); + } + + return (e); + + err: + /* + * Error happend. Free all octet string index parts and the entry + * itself. + */ + for (i = 0; i < work->descr->index_size; i++) { + if (work->descr->entries[i].syntax == SNMP_SYNTAX_OCTETSTRING && + (e->found & ((u_int64_t)1 << i))) + free(*(void **)(void *)((u_char *)e + + work->descr->entries[i].offset)); + } + free(e); + free(w); + return (NULL); +} + +/* + * Assign the value + */ +static int +table_value(const struct snmp_table *descr, struct entry *e, + const struct snmp_value *b) +{ + u_int i; + u_char *ptr; + + for (i = descr->index_size; + descr->entries[i].syntax != SNMP_SYNTAX_NULL; i++) + if (descr->entries[i].subid == + b->var.subs[descr->table.len + 1]) + break; + if (descr->entries[i].syntax == SNMP_SYNTAX_NULL) + return (0); + + /* check syntax */ + if (b->syntax != descr->entries[i].syntax) { + seterr("bad syntax (%u instead of %u)", b->syntax, + descr->entries[i].syntax); + return (-1); + } + + switch (b->syntax) { + + case SNMP_SYNTAX_INTEGER: + *(int32_t *)(void *)((u_char *)e + descr->entries[i].offset) = + b->v.integer; + break; + + case SNMP_SYNTAX_OCTETSTRING: + if ((ptr = malloc(b->v.octetstring.len + 1)) == NULL) { + seterr("no memory for string"); + return (-1); + } + memcpy(ptr, b->v.octetstring.octets, b->v.octetstring.len); + ptr[b->v.octetstring.len] = '\0'; + *(u_char **)(void *)((u_char *)e + descr->entries[i].offset) = + ptr; + *(size_t *)(void *)((u_char *)e + descr->entries[i].offset + + sizeof(u_char *)) = b->v.octetstring.len; + break; + + case SNMP_SYNTAX_OID: + *(struct asn_oid *)(void *)((u_char *)e + descr->entries[i].offset) = + b->v.oid; + break; + + case SNMP_SYNTAX_IPADDRESS: + memcpy((u_char *)e + descr->entries[i].offset, + b->v.ipaddress, 4); + break; + + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + *(u_int32_t *)(void *)((u_char *)e + descr->entries[i].offset) = + b->v.uint32; + break; + + case SNMP_SYNTAX_COUNTER64: + *(u_int64_t *)(void *)((u_char *)e + descr->entries[i].offset) = + b->v.counter64; + break; + + case SNMP_SYNTAX_NULL: + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + abort(); + } + e->found |= (u_int64_t)1 << i; + + return (0); +} + +/* + * Initialize the first PDU to send + */ +static void +table_init_pdu(const struct snmp_table *descr, struct snmp_pdu *pdu) +{ + if (snmp_client.version == SNMP_V1) + snmp_pdu_create(pdu, SNMP_PDU_GETNEXT); + else { + snmp_pdu_create(pdu, SNMP_PDU_GETBULK); + pdu->error_index = 10; + } + if (descr->last_change.len != 0) { + pdu->bindings[pdu->nbindings].syntax = SNMP_SYNTAX_NULL; + pdu->bindings[pdu->nbindings].var = descr->last_change; + pdu->nbindings++; + if (pdu->version != SNMP_V1) + pdu->error_status++; + } + pdu->bindings[pdu->nbindings].var = descr->table; + pdu->bindings[pdu->nbindings].syntax = SNMP_SYNTAX_NULL; + pdu->nbindings++; +} + +/* + * Return code: + * 0 - End Of Table + * -1 - Error + * -2 - Last change changed - again + * +1 - ok, continue + */ +static int +table_check_response(struct tabwork *work, const struct snmp_pdu *resp) +{ + const struct snmp_value *b; + struct entry *e; + + if (resp->error_status != SNMP_ERR_NOERROR) { + if (snmp_client.version == SNMP_V1 && + resp->error_status == SNMP_ERR_NOSUCHNAME && + resp->error_index == + (work->descr->last_change.len == 0) ? 1 : 2) + /* EOT */ + return (0); + /* Error */ + seterr("error fetching table: status=%d index=%d", + resp->error_status, resp->error_index); + return (-1); + } + + for (b = resp->bindings; b < resp->bindings + resp->nbindings; b++) { + if (work->descr->last_change.len != 0 && b == resp->bindings) { + if (!asn_is_suboid(&work->descr->last_change, &b->var) || + b->var.len != work->descr->last_change.len + 1 || + b->var.subs[work->descr->last_change.len] != 0) { + seterr("last_change: bad response"); + return (-1); + } + if (b->syntax != SNMP_SYNTAX_TIMETICKS) { + seterr("last_change: bad syntax %u", b->syntax); + return (-1); + } + if (work->first) { + work->last_change = b->v.uint32; + work->first = 0; + + } else if (work->last_change != b->v.uint32) { + if (++work->iter >= work->descr->max_iter) { + seterr("max iteration count exceeded"); + return (-1); + } + table_free(work, 1); + return (-2); + } + + continue; + } + if (!asn_is_suboid(&work->descr->table, &b->var) || + b->syntax == SNMP_SYNTAX_ENDOFMIBVIEW) + return (0); + + if ((e = table_find(work, &b->var)) == NULL) + return (-1); + if (table_value(work->descr, e, b)) + return (-1); + } + return (+1); +} + +/* + * Check table consistency + */ +static int +table_check_cons(struct tabwork *work) +{ + struct entry *e; + + TAILQ_FOREACH(e, work->table, link) + if ((e->found & work->descr->req_mask) != + work->descr->req_mask) { + if (work->descr->last_change.len == 0) { + if (++work->iter >= work->descr->max_iter) { + seterr("max iteration count exceeded"); + return (-1); + } + return (-2); + } + seterr("inconsistency detected %llx %llx", + e->found, work->descr->req_mask); + return (-1); + } + return (0); +} + +/* + * Fetch a table. Returns 0 if ok, -1 on errors. + * This is the synchronuous variant. + */ +int +snmp_table_fetch(const struct snmp_table *descr, void *list) +{ + struct snmp_pdu resp; + struct tabwork work; + int ret; + + work.descr = descr; + work.table = (struct table *)list; + work.iter = 0; + TAILQ_INIT(work.table); + TAILQ_INIT(&work.worklist); + work.callback = NULL; + work.arg = NULL; + + again: + /* + * We come to this label when the code detects that the table + * has changed while fetching it. + */ + work.first = 1; + work.last_change = 0; + table_init_pdu(descr, &work.pdu); + + for (;;) { + if (snmp_dialog(&work.pdu, &resp)) { + seterr("SNMP error: no response"); + table_free(&work, 1); + return (-1); + } + if ((ret = table_check_response(&work, &resp)) == 0) { + snmp_pdu_free(&resp); + break; + } + if (ret == -1) { + snmp_pdu_free(&resp); + table_free(&work, 1); + return (-1); + } + if (ret == -2) { + snmp_pdu_free(&resp); + goto again; + } + + work.pdu.bindings[work.pdu.nbindings - 1].var = + resp.bindings[resp.nbindings - 1].var; + + snmp_pdu_free(&resp); + } + + if ((ret = table_check_cons(&work)) == -1) { + table_free(&work, 1); + return (-1); + } + if (ret == -2) { + table_free(&work, 1); + goto again; + } + /* + * Free index list + */ + table_free(&work, 0); + return (0); +} + +/* + * Callback for table + */ +static void +table_cb(struct snmp_pdu *req __unused, struct snmp_pdu *resp, void *arg) +{ + struct tabwork *work = arg; + int ret; + + if (resp == NULL) { + /* timeout */ + seterr("no response to fetch table request"); + table_free(work, 1); + work->callback(work->table, work->arg, -1); + free(work); + return; + } + + if ((ret = table_check_response(work, resp)) == 0) { + /* EOT */ + snmp_pdu_free(resp); + + if ((ret = table_check_cons(work)) == -1) { + /* error happend */ + table_free(work, 1); + work->callback(work->table, work->arg, -1); + free(work); + return; + } + if (ret == -2) { + /* restart */ + again: + table_free(work, 1); + work->first = 1; + work->last_change = 0; + table_init_pdu(work->descr, &work->pdu); + if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) { + work->callback(work->table, work->arg, -1); + free(work); + return; + } + return; + } + /* + * Free index list + */ + table_free(work, 0); + work->callback(work->table, work->arg, 0); + free(work); + return; + } + + if (ret == -1) { + /* error */ + snmp_pdu_free(resp); + table_free(work, 1); + work->callback(work->table, work->arg, -1); + free(work); + return; + } + + if (ret == -2) { + /* again */ + snmp_pdu_free(resp); + goto again; + } + + /* next part */ + + work->pdu.bindings[work->pdu.nbindings - 1].var = + resp->bindings[resp->nbindings - 1].var; + + snmp_pdu_free(resp); + + if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) { + table_free(work, 1); + work->callback(work->table, work->arg, -1); + free(work); + return; + } +} + +int +snmp_table_fetch_async(const struct snmp_table *descr, void *list, + snmp_table_cb_f func, void *arg) +{ + struct tabwork *work; + + if ((work = malloc(sizeof(*work))) == NULL) { + seterr("%s", strerror(errno)); + return (-1); + } + + work->descr = descr; + work->table = (struct table *)list; + work->iter = 0; + TAILQ_INIT(work->table); + TAILQ_INIT(&work->worklist); + + work->callback = func; + work->arg = arg; + + /* + * Start by sending the first PDU + */ + work->first = 1; + work->last_change = 0; + table_init_pdu(descr, &work->pdu); + + if (snmp_pdu_send(&work->pdu, table_cb, work) == -1) + return (-1); + return (0); +} + +/* + * Append an index to an oid + */ +int +snmp_oid_append(struct asn_oid *oid, const char *fmt, ...) +{ + va_list va; + int size; + char *nextptr; + const u_char *str; + size_t len; + struct in_addr ina; + int ret; + + va_start(va, fmt); + + size = 0; + + ret = 0; + while (*fmt != '\0') { + switch (*fmt++) { + case 'i': + /* just an integer more */ + if (oid->len + 1 > ASN_MAXOIDLEN) { + warnx("%s: OID too long for integer", __func__); + ret = -1; + break; + } + oid->subs[oid->len++] = va_arg(va, asn_subid_t); + break; + + case 'a': + /* append an IP address */ + if (oid->len + 4 > ASN_MAXOIDLEN) { + warnx("%s: OID too long for ip-addr", __func__); + ret = -1; + break; + } + ina = va_arg(va, struct in_addr); + ina.s_addr = ntohl(ina.s_addr); + oid->subs[oid->len++] = (ina.s_addr >> 24) & 0xff; + oid->subs[oid->len++] = (ina.s_addr >> 16) & 0xff; + oid->subs[oid->len++] = (ina.s_addr >> 8) & 0xff; + oid->subs[oid->len++] = (ina.s_addr >> 0) & 0xff; + break; + + case 's': + /* append a null-terminated string, + * length is computed */ + str = (const u_char *)va_arg(va, const char *); + len = strlen((const char *)str); + if (oid->len + len + 1 > ASN_MAXOIDLEN) { + warnx("%s: OID too long for string", __func__); + ret = -1; + break; + } + oid->subs[oid->len++] = len; + while (len--) + oid->subs[oid->len++] = *str++; + break; + + case '(': + /* the integer value between ( and ) is stored + * in size */ + size = strtol(fmt, &nextptr, 10); + if (*nextptr != ')') + abort(); + fmt = ++nextptr; + break; + + case 'b': + /* append `size` characters */ + str = (const u_char *)va_arg(va, const char *); + if (oid->len + size > ASN_MAXOIDLEN) { + warnx("%s: OID too long for string", __func__); + ret = -1; + break; + } + while (size--) + oid->subs[oid->len++] = *str++; + break; + + case 'c': + /* get size and the octets from the arguments */ + size = va_arg(va, size_t); + str = va_arg(va, const u_char *); + if (oid->len + size + 1 > ASN_MAXOIDLEN) { + warnx("%s: OID too long for string", __func__); + ret = -1; + break; + } + oid->subs[oid->len++] = size; + while (size--) + oid->subs[oid->len++] = *str++; + break; + + default: + abort(); + } + } + va_end(va); + return (ret); +} + +/* + * Initialize a client structure + */ +void +snmp_client_init(struct snmp_client *c) +{ + memset(c, 0, sizeof(*c)); + + c->version = SNMP_V2c; + c->local = 0; + 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("%s", strerror(errno)); + return (-1); + } + strcpy(snmp_client.chost, DEFAULT_HOST); + } + if (host != NULL) { + if ((ptr = malloc(1 + strlen(host))) == NULL) { + seterr("%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("%s", strerror(errno)); + return (-1); + } + strcpy(snmp_client.cport, DEFAULT_PORT); + } + if (port != NULL) { + if ((ptr = malloc(1 + strlen(port))) == NULL) { + seterr("%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("%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("%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("%s", strerror(errno)); + freeaddrinfo(res0); + return (-1); + } + } else + break; + } + freeaddrinfo(res0); + return (0); +} + +static void +remove_local(void) +{ + (void)remove(snmp_client.local_path); +} + +/* + * Open local socket + */ +static int +open_client_local(const char *path) +{ + struct sockaddr_un sa; + char *ptr; + + if (snmp_client.chost == NULL) { + if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_LOCAL))) + == NULL) { + seterr("%s", strerror(errno)); + return (-1); + } + strcpy(snmp_client.chost, DEFAULT_LOCAL); + } + if (path != NULL) { + if ((ptr = malloc(1 + strlen(path))) == NULL) { + seterr("%s", strerror(errno)); + return (-1); + } + free(snmp_client.chost); + snmp_client.chost = ptr; + strcpy(snmp_client.chost, path); + } + + if ((snmp_client.fd = socket(PF_LOCAL, SOCK_DGRAM, 0)) == -1) { + seterr("%s", strerror(errno)); + return (-1); + } + + snprintf(snmp_client.local_path, sizeof(snmp_client.local_path), + "%s", SNMP_LOCAL_PATH); + + if (mktemp(snmp_client.local_path) == NULL) { + seterr("%s", strerror(errno)); + (void)close(snmp_client.fd); + snmp_client.fd = -1; + return (-1); + } + + sa.sun_family = AF_LOCAL; + sa.sun_len = sizeof(sa); + strcpy(sa.sun_path, snmp_client.local_path); + + if (bind(snmp_client.fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + seterr("%s", strerror(errno)); + (void)close(snmp_client.fd); + snmp_client.fd = -1; + (void)remove(snmp_client.local_path); + return (-1); + } + atexit(remove_local); + + sa.sun_family = AF_LOCAL; + sa.sun_len = offsetof(struct sockaddr_un, sun_path) + + strlen(snmp_client.chost); + strncpy(sa.sun_path, snmp_client.chost, sizeof(sa.sun_path) - 1); + sa.sun_path[sizeof(sa.sun_path) - 1] = '\0'; + + if (connect(snmp_client.fd, (struct sockaddr *)&sa, sa.sun_len) == -1) { + seterr("%s", strerror(errno)); + (void)close(snmp_client.fd); + snmp_client.fd = -1; + (void)remove(snmp_client.local_path); + return (-1); + } + 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("%s", strerror(errno)); + return (-1); + } + + /* copy community strings */ + if (readcomm != NULL) + strlcpy(snmp_client.read_community, readcomm, + sizeof(snmp_client.read_community)); + if (writecomm != NULL) + strlcpy(snmp_client.write_community, writecomm, + sizeof(snmp_client.write_community)); + + if (!snmp_client.local) { + if (open_client_udp(host, port)) + return (-1); + } else { + if (open_client_local(host)) + 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("%s", strerror(errno)); + (void)close(snmp_client.fd); + snmp_client.fd = -1; + if (snmp_client.local) + (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) + (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) + strlcpy(pdu->community, snmp_client.write_community, + sizeof(pdu->community)); + else + strlcpy(pdu->community, snmp_client.read_community, + sizeof(pdu->community)); + + 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("%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("%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("%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("%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; + socklen_t optlen; + + if ((buf = malloc(snmp_client.rxbuflen)) == NULL) { + seterr("%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("setsockopt: %s", strerror(errno)); + free(buf); + return (-1); + } + optlen = sizeof(*tv); + if (getsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, + tv, &optlen) == -1) { + seterr("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("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("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("recv: %s", strerror(saved_errno)); + return (-1); + } + if (ret == 0) + abort(); + + abuf.asn_ptr = buf; + abuf.asn_len = ret; + + if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) { + seterr("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("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; + + for (i = 0; i <= snmp_client.retries; i++) { + (void)gettimeofday(&end, NULL); + timeradd(&end, &snmp_client.timeout, &end); + if ((reqid = snmp_send_packet(req)) == -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); + } + } + } + seterr("retry count exceeded"); + return (-1); +} + +int +snmp_client_set_host(struct snmp_client *cl, const char *h) +{ + char *np; + + if (h == NULL) { + if (cl->chost != NULL) + free(cl->chost); + cl->chost = NULL; + } else { + if ((np = malloc(strlen(h) + 1)) == NULL) + return (-1); + strcpy(np, h); + if (cl->chost != NULL) + free(cl->chost); + cl->chost = np; + } + return (0); +} + +int +snmp_client_set_port(struct snmp_client *cl, const char *p) +{ + char *np; + + if (p == NULL) { + if (cl->cport != NULL) + free(cl->cport); + cl->cport = NULL; + } else { + if ((np = malloc(strlen(p) + 1)) == NULL) + return (-1); + strcpy(np, p); + if (cl->cport != NULL) + free(cl->cport); + cl->cport = np; + } + return (0); +} diff --git a/contrib/bsnmp/lib/snmpclient.h b/contrib/bsnmp/lib/snmpclient.h new file mode 100644 index 0000000..700d020 --- /dev/null +++ b/contrib/bsnmp/lib/snmpclient.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * Kendy Kutzner + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.15 2002/12/11 15:54:07 hbb 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" + + +/* 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 local; /* use local socket */ + + /* 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 */ + u_int64_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); + +#endif /* _BSNMP_SNMPCLIENT_H */ diff --git a/contrib/bsnmp/lib/snmppriv.h b/contrib/bsnmp/lib/snmppriv.h new file mode 100644 index 0000000..82189f0 --- /dev/null +++ b/contrib/bsnmp/lib/snmppriv.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation 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 or documentation 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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.7 2002/12/11 15:54:07 hbb Exp $ + * + * Private functions. + */ +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" |