diff --git a/contrib/bsnmp/lib/asn1.c b/contrib/bsnmp/lib/asn1.c index a03dac70c529..f1f9267c0226 100644 --- a/contrib/bsnmp/lib/asn1.c +++ b/contrib/bsnmp/lib/asn1.c @@ -1,1045 +1,1045 @@ /* * Copyright (c) 2001-2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Harti Brandt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/lib/asn1.c,v 1.31 2005/10/06 07:14:58 brandt_h Exp $ * * ASN.1 for SNMP. */ #include #include #include #include #include #ifdef HAVE_STDINT_H #include #elif defined(HAVE_INTTYPES_H) #include #endif #include #include "support.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) > 0x1e) { asn_error(b, "tags > 0x1e not supported (%#x)", *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--; } #ifdef BOGUS_CVE_2019_5610_FIX /* * This is the fix from CVE-2019-5610. * * This is the wrong place. Each of the asn functions should check * that it has enough info for its own work. */ if (*len > b->asn_len) { asn_error(b, "lenen %u exceeding asn_len %u", *len, b->asn_len); return (ASN_ERR_EOBUF); } #endif 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 <= 0x1e) 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) > 0x1e) { asn_error(NULL, "types > 0x1e not supported (%#x)", 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, size_t *moved) { 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; if (moved != NULL) *moved = shift; } return (ASN_ERR_OK); } #undef TEMP_LEN /* * BER integer. This may be used to get a signed 64 bit integer at maximum. * The maximum length should be checked by the caller. This cannot overflow * if the caller ensures that len is at maximum 8. * * */ static enum asn_err asn_get_real_integer(struct asn_buf *b, asn_len_t len, int64_t *vp) { uint64_t val; int neg = 0; enum asn_err err; if (b->asn_len < len) { asn_error(b, "truncated integer"); return (ASN_ERR_EOBUF); } if (len == 0) { asn_error(b, "zero-length integer"); *vp = 0; return (ASN_ERR_BADLEN); } err = ASN_ERR_OK; if (len > 8) { asn_error(b, "integer too long"); err = ASN_ERR_RANGE; } else if (len > 1 && ((*b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) || (*b->asn_cptr == 0xff && (b->asn_cptr[1] & 0x80) == 0x80))) { asn_error(b, "non-minimal integer"); err = ASN_ERR_BADLEN; } if (*b->asn_cptr & 0x80) neg = 1; val = 0; while (len--) { val <<= 8; val |= neg ? (u_char)~*b->asn_cptr : *b->asn_cptr; b->asn_len--; b->asn_cptr++; } if (neg) { *vp = -(int64_t)val - 1; } else *vp = (int64_t)val; return (err); } /* * Write a signed integer with the given type. The caller has to ensure * that the actual value is ok for this type. */ static enum asn_err asn_put_real_integer(struct asn_buf *b, u_char type, int64_t ival) { int i, neg = 0; # define OCTETS 8 u_char buf[OCTETS]; uint64_t val; enum asn_err ret; if (ival < 0) { /* this may fail if |INT64_MIN| > |INT64_MAX| and * the value is between * INT64_MIN <= ival < -(INT64_MAX+1) */ val = (uint64_t)-(ival + 1); neg = 1; } else val = (uint64_t)ival; /* split the value into octets */ for (i = OCTETS - 1; i >= 0; i--) { buf[i] = val & 0xff; if (neg) buf[i] = ~buf[i]; val >>= 8; } /* no leading 9 zeroes or ones */ for (i = 0; i < OCTETS - 1; i++) if (!((buf[i] == 0xff && (buf[i + 1] & 0x80) != 0) || (buf[i] == 0x00 && (buf[i + 1] & 0x80) == 0))) break; if ((ret = asn_put_header(b, type, OCTETS - i))) return (ret); if (OCTETS - (u_int)i > b->asn_len) return (ASN_ERR_EOBUF); while (i < OCTETS) { *b->asn_ptr++ = buf[i++]; b->asn_len--; } return (ASN_ERR_OK); # undef OCTETS } /* * The same for unsigned 64-bitters. Here we have the problem, that overflow * can happen, because the value maybe 9 bytes long. In this case the * first byte must be 0. */ static enum asn_err asn_get_real_unsigned(struct asn_buf *b, asn_len_t len, uint64_t *vp) { *vp = 0; if (b->asn_len < len) { asn_error(b, "truncated integer"); return (ASN_ERR_EOBUF); } if (len == 0) { /* X.690: 8.3.1 */ asn_error(b, "zero-length integer"); return (ASN_ERR_BADLEN); } if (len > 1 && *b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) { /* X.690: 8.3.2 */ asn_error(b, "non-minimal unsigned"); b->asn_cptr += len; b->asn_len -= len; return (ASN_ERR_BADLEN); } enum asn_err err = ASN_ERR_OK; if ((*b->asn_cptr & 0x80) || len > 9 || (len == 9 && *b->asn_cptr != 0)) { /* negative integer or too larger */ *vp = 0xffffffffffffffffULL; asn_error(b, "unsigned too large or negative"); b->asn_cptr += len; b->asn_len -= len; return (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, uint64_t val) { int i; # define OCTETS 9 u_char buf[OCTETS]; enum asn_err ret; /* split the value into octets */ for (i = OCTETS - 1; i >= 0; i--) { buf[i] = val & 0xff; val >>= 8; } /* no leading 9 zeroes */ for (i = 0; i < OCTETS - 1; i++) if (!(buf[i] == 0x00 && (buf[i + 1] & 0x80) == 0)) break; if ((ret = asn_put_header(b, type, OCTETS - i))) return (ret); if (OCTETS - (u_int)i > b->asn_len) return (ASN_ERR_EOBUF); while (i < OCTETS) { *b->asn_ptr++ = buf[i++]; b->asn_len--; } #undef OCTETS return (ASN_ERR_OK); } /* * The ASN.1 INTEGER type is restricted to 32-bit signed by the SMI. */ enum asn_err asn_get_integer_raw(struct asn_buf *b, asn_len_t len, int32_t *vp) { int64_t val; enum asn_err ret; if ((ret = asn_get_real_integer(b, len, &val)) == ASN_ERR_OK) { if (len > 4) { asn_error(b, "integer too long"); ret = ASN_ERR_BADLEN; } else if (val > INT32_MAX || val < INT32_MIN) { /* may not happen */ asn_error(b, "integer out of range"); ret = ASN_ERR_RANGE; } *vp = (int32_t)val; } return (ret); } enum asn_err asn_get_integer(struct asn_buf *b, int32_t *vp) { asn_len_t len; u_char type; enum asn_err err; if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) return (err); if (type != ASN_TYPE_INTEGER) { asn_error(b, "bad type for integer (%u)", type); return (ASN_ERR_TAG); } return (asn_get_integer_raw(b, len, vp)); } enum asn_err asn_put_integer(struct asn_buf *b, int32_t val) { return (asn_put_real_integer(b, ASN_TYPE_INTEGER, val)); } /* * OCTETSTRING * * <0x04> * * Get an octetstring. noctets must point to the buffer size and on * return will contain the size of the octetstring, regardless of the * buffer size. */ enum asn_err asn_get_octetstring_raw(struct asn_buf *b, asn_len_t len, u_char *octets, u_int *noctets) { enum asn_err err = ASN_ERR_OK; if (*noctets < len) { asn_error(b, "octetstring truncated"); err = ASN_ERR_RANGE; } if (b->asn_len < len) { asn_error(b, "truncatet octetstring"); return (ASN_ERR_EOBUF); } if (*noctets < len) memcpy(octets, b->asn_cptr, *noctets); else memcpy(octets, b->asn_cptr, len); *noctets = len; b->asn_cptr += len; b->asn_len -= len; return (err); } enum asn_err asn_get_octetstring(struct asn_buf *b, u_char *octets, u_int *noctets) { enum asn_err err; u_char type; asn_len_t len; if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) return (err); if (type != ASN_TYPE_OCTETSTRING) { asn_error(b, "bad type for octetstring (%u)", type); return (ASN_ERR_TAG); } return (asn_get_octetstring_raw(b, len, octets, noctets)); } enum asn_err asn_put_octetstring(struct asn_buf *b, const u_char *octets, u_int noctets) { enum asn_err ret; if ((ret = asn_put_header(b, ASN_TYPE_OCTETSTRING, noctets)) != ASN_ERR_OK) return (ret); if (b->asn_len < noctets) return (ASN_ERR_EOBUF); memcpy(b->asn_ptr, octets, noctets); b->asn_ptr += noctets; b->asn_len -= noctets; return (ASN_ERR_OK); } /* * NULL * * <0x05> <0x00> */ enum asn_err asn_get_null_raw(struct asn_buf *b, asn_len_t len) { if (len != 0) { if (b->asn_len < len) { asn_error(b, "truncated NULL"); return (ASN_ERR_EOBUF); } asn_error(b, "bad length for NULL (%u)", len); b->asn_len -= len; b->asn_ptr += len; return (ASN_ERR_BADLEN); } return (ASN_ERR_OK); } enum asn_err asn_get_null(struct asn_buf *b) { u_char type; asn_len_t len; enum asn_err err; if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) return (err); if (type != ASN_TYPE_NULL) { asn_error(b, "bad type for NULL (%u)", type); return (ASN_ERR_TAG); } return (asn_get_null_raw(b, len)); } enum asn_err asn_put_null(struct asn_buf *b) { return (asn_put_header(b, ASN_TYPE_NULL, 0)); } enum asn_err asn_put_exception(struct asn_buf *b, u_int except) { return (asn_put_header(b, ASN_CLASS_CONTEXT | except, 0)); } /* * OBJID * * <0x06> */ enum asn_err asn_get_objid_raw(struct asn_buf *b, asn_len_t len, struct asn_oid *oid) { asn_subid_t subid; enum asn_err err; if (b->asn_len < len) { asn_error(b, "truncated OBJID"); return (ASN_ERR_EOBUF); } oid->len = 0; if (len == 0) { asn_error(b, "short OBJID"); oid->subs[oid->len++] = 0; oid->subs[oid->len++] = 0; return (ASN_ERR_BADLEN); } err = ASN_ERR_OK; while (len != 0) { if (oid->len == ASN_MAXOIDLEN) { asn_error(b, "OID too long (%u)", oid->len); b->asn_cptr += len; b->asn_len -= len; return (ASN_ERR_BADLEN); } subid = 0; do { if (len == 0) { asn_error(b, "unterminated subid"); return (ASN_ERR_EOBUF); } if (subid > (ASN_MAXID >> 7)) { asn_error(b, "OID 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(NULL, "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[1] >= 40) || (oid->subs[0] == 2 && oid->subs[1] > ASN_MAXID - 2 * 40)) { asn_error(NULL, "oid out of range (%u,%u)", oid->subs[0], oid->subs[1]); err = ASN_ERR_RANGE; } first = 40 * oid->subs[0] + oid->subs[1]; oidlen = oid->len; } len = 0; for (i = 1; i < oidlen; i++) { sub = (i == 1) ? first : oid->subs[i]; if (sub > ASN_MAXID) { asn_error(NULL, "oid subid too large"); err = ASN_ERR_RANGE; } len += (sub <= 0x7f) ? 1 : (sub <= 0x3fff) ? 2 : (sub <= 0x1fffff) ? 3 : (sub <= 0xfffffff) ? 4 : 5; } if ((err1 = asn_put_header(b, ASN_TYPE_OBJID, len)) != ASN_ERR_OK) return (err1); if (b->asn_len < len) return (ASN_ERR_EOBUF); for (i = 1; i < oidlen; i++) { sub = (i == 1) ? first : oid->subs[i]; if (sub <= 0x7f) { *b->asn_ptr++ = sub; b->asn_len--; } else if (sub <= 0x3fff) { *b->asn_ptr++ = (sub >> 7) | 0x80; *b->asn_ptr++ = sub & 0x7f; b->asn_len -= 2; } else if (sub <= 0x1fffff) { *b->asn_ptr++ = (sub >> 14) | 0x80; *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; *b->asn_ptr++ = sub & 0x7f; b->asn_len -= 3; } else if (sub <= 0xfffffff) { *b->asn_ptr++ = (sub >> 21) | 0x80; *b->asn_ptr++ = ((sub >> 14) & 0x7f) | 0x80; *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; *b->asn_ptr++ = sub & 0x7f; b->asn_len -= 4; } else { *b->asn_ptr++ = (sub >> 28) | 0x80; *b->asn_ptr++ = ((sub >> 21) & 0x7f) | 0x80; *b->asn_ptr++ = ((sub >> 14) & 0x7f) | 0x80; *b->asn_ptr++ = ((sub >> 7) & 0x7f) | 0x80; *b->asn_ptr++ = sub & 0x7f; b->asn_len -= 5; } } return (err); } /* * SEQUENCE header * * <0x10|0x20> */ enum asn_err asn_get_sequence(struct asn_buf *b, asn_len_t *len) { u_char type; enum asn_err err; if ((err = asn_get_header(b, &type, len)) != ASN_ERR_OK) return (err); if (type != (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED)) { asn_error(b, "bad sequence type %u", type); return (ASN_ERR_TAG); } if (*len > b->asn_len) { asn_error(b, "truncated sequence"); return (ASN_ERR_EOBUF); } return (ASN_ERR_OK); } /* * Application types * * 0x40 4 MSB 2MSB 2LSB LSB */ enum asn_err asn_get_ipaddress_raw(struct asn_buf *b, asn_len_t len, u_char *addr) { u_int i; if (b->asn_len < len) { asn_error(b, "truncated ip-address"); return (ASN_ERR_EOBUF); } if (len < 4) { asn_error(b, "short length for ip-Address %u", len); for (i = 0; i < len; i++) *addr++ = *b->asn_cptr++; while (i++ < len) *addr++ = 0; b->asn_len -= len; return (ASN_ERR_BADLEN); } for (i = 0; i < 4; i++) *addr++ = *b->asn_cptr++; b->asn_cptr += len - 4; b->asn_len -= len; return (ASN_ERR_OK); } enum asn_err asn_get_ipaddress(struct asn_buf *b, u_char *addr) { u_char type; asn_len_t len; enum asn_err err; if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) return (err); if (type != (ASN_CLASS_APPLICATION|ASN_APP_IPADDRESS)) { asn_error(b, "bad type for ip-address %u", type); return (ASN_ERR_TAG); } return (asn_get_ipaddress_raw(b, len, addr)); } enum asn_err asn_put_ipaddress(struct asn_buf *b, const u_char *addr) { enum asn_err err; if ((err = asn_put_header(b, ASN_CLASS_APPLICATION|ASN_APP_IPADDRESS, 4)) != ASN_ERR_OK) return (err); if (b->asn_len < 4) return (ASN_ERR_EOBUF); memcpy(b->asn_ptr, addr, 4); b->asn_ptr += 4; b->asn_len -= 4; return (ASN_ERR_OK); } /* * UNSIGNED32 * * 0x42|0x41 ... */ enum asn_err asn_get_uint32_raw(struct asn_buf *b, asn_len_t len, uint32_t *vp) { uint64_t v; enum asn_err err; if ((err = asn_get_real_unsigned(b, len, &v)) == ASN_ERR_OK) { if (v > UINT32_MAX) { asn_error(b, "uint32 too large %llu", v); err = ASN_ERR_RANGE; } *vp = (uint32_t)v; } return (err); } enum asn_err asn_put_uint32(struct asn_buf *b, u_char type, uint32_t val) { uint64_t v = val; return (asn_put_real_unsigned(b, ASN_CLASS_APPLICATION|type, v)); } /* * COUNTER64 * 0x46 ... */ enum asn_err asn_get_counter64_raw(struct asn_buf *b, asn_len_t len, uint64_t *vp) { return (asn_get_real_unsigned(b, len, vp)); } enum asn_err asn_put_counter64(struct asn_buf *b, uint64_t val) { return (asn_put_real_unsigned(b, ASN_CLASS_APPLICATION | ASN_APP_COUNTER64, val)); } /* * TimeTicks * 0x43 ... */ enum asn_err asn_get_timeticks(struct asn_buf *b, uint32_t *vp) { asn_len_t len; u_char type; enum asn_err err; if ((err = asn_get_header(b, &type, &len)) != ASN_ERR_OK) return (err); if (type != (ASN_CLASS_APPLICATION|ASN_APP_TIMETICKS)) { asn_error(b, "bad type for timeticks %u", type); return (ASN_ERR_TAG); } return (asn_get_uint32_raw(b, len, vp)); } enum asn_err asn_put_timeticks(struct asn_buf *b, uint32_t val) { uint64_t v = val; return (asn_put_real_unsigned(b, ASN_CLASS_APPLICATION | ASN_APP_TIMETICKS, v)); } /* * Construct a new OID by taking a range of sub ids of the original oid. */ void asn_slice_oid(struct asn_oid *dest, const struct asn_oid *src, u_int from, u_int to) { if (from >= to) { dest->len = 0; return; } dest->len = to - from; memcpy(dest->subs, &src->subs[from], dest->len * sizeof(dest->subs[0])); } /* * Append from to to */ void asn_append_oid(struct asn_oid *to, const struct asn_oid *from) { memcpy(&to->subs[to->len], &from->subs[0], from->len * sizeof(from->subs[0])); to->len += from->len; } /* * Skip a value */ enum asn_err asn_skip(struct asn_buf *b, asn_len_t len) { if (b->asn_len < len) return (ASN_ERR_EOBUF); b->asn_cptr += len; b->asn_len -= len; return (ASN_ERR_OK); } /* * Add a padding */ enum asn_err asn_pad(struct asn_buf *b, asn_len_t len) { if (b->asn_len < len) return (ASN_ERR_EOBUF); b->asn_ptr += 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) { - __thread static char str[ASN_OIDSTRLEN]; + 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/snmpclient.c b/contrib/bsnmp/lib/snmpclient.c index e49105918416..c22d8e125a14 100644 --- a/contrib/bsnmp/lib/snmpclient.c +++ b/contrib/bsnmp/lib/snmpclient.c @@ -1,2298 +1,2298 @@ /* * Copyright (c) 2004-2005,2018-2019 * Hartmut Brandt. * All rights reserved. * Copyright (c) 2001-2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Harti Brandt * Kendy Kutzner * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/lib/snmpclient.c,v 1.36 2005/10/06 07:14:58 brandt_h Exp $ * * Support functions for SNMP clients. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDINT_H #include #elif defined(HAVE_INTTYPES_H) #include #endif #include #ifdef HAVE_ERR_H #include #endif #include #include "support.h" #include "asn1.h" #include "snmp.h" #include "snmpclient.h" #include "snmppriv.h" #define DEBUG_PARSE 0 /* global context */ -__thread struct snmp_client snmp_client; +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); -__thread static struct sent_pdu_list sent_pdus; +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; uint64_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; uint32_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(struct snmp_client *sc, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(sc->error, sizeof(sc->error), fmt, ap); va_end(ap); } /* * 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 & ((uint64_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(&snmp_client, "no memory for table entry"); return (NULL); } if ((w = malloc(sizeof(*w))) == NULL) { seterr(&snmp_client, "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(&snmp_client, "bad index: need integer"); goto err; } if (var->subs[p] > INT32_MAX) { seterr(&snmp_client, "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(&snmp_client, "bad index: need string length"); goto err; } len = var->subs[p++]; if (var->len < p + len) { seterr(&snmp_client, "bad index: string too short"); goto err; } if ((ptr = malloc(len + 1)) == NULL) { seterr(&snmp_client, "no memory for index string"); goto err; } for (j = 0; j < len; j++) { if (var->subs[p] > UCHAR_MAX) { seterr(&snmp_client, "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(&snmp_client, "bad index: need oid length"); goto err; } oid.len = var->subs[p++]; if (var->len < p + oid.len) { seterr(&snmp_client, "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(&snmp_client, "bad index: need ip-address"); goto err; } for (j = 0; j < 4; j++) { if (var->subs[p] > 0xff) { seterr(&snmp_client, "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(&snmp_client, "bad index: need unsigned"); goto err; } if (var->subs[p] > UINT32_MAX) { seterr(&snmp_client, "bad index: unsigned too large"); goto err; } *(uint32_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 |= (uint64_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 & ((uint64_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(&snmp_client, "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(&snmp_client, "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: *(uint32_t *)(void *)((u_char *)e + descr->entries[i].offset) = b->v.uint32; break; case SNMP_SYNTAX_COUNTER64: *(uint64_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 |= (uint64_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(&snmp_client, "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(&snmp_client, "last_change: bad response"); return (-1); } if (b->syntax != SNMP_SYNTAX_TIMETICKS) { seterr(&snmp_client, "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(&snmp_client, "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(&snmp_client, "max iteration count exceeded"); return (-1); } return (-2); } seterr(&snmp_client, "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 synchronous 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)) { 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(&snmp_client, "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(&snmp_client, "%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) { free(work); work = NULL; 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->trans = SNMP_TRANS_UDP; c->chost = NULL; c->cport = NULL; strcpy(c->read_community, "public"); strcpy(c->write_community, "private"); c->security_model = SNMP_SECMODEL_USM; strcpy(c->cname, ""); 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; c->engine.max_msg_size = 1500; /* XXX */ } /* * Open UDP client socket */ static int open_client_udp(const char *host, const char *port) { int error; char *ptr; struct addrinfo hints, *res0, *res; /* copy host- and portname */ if (snmp_client.chost == NULL) { if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_HOST))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } strcpy(snmp_client.chost, DEFAULT_HOST); } if (host != NULL) { if ((ptr = malloc(1 + strlen(host))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } free(snmp_client.chost); snmp_client.chost = ptr; strcpy(snmp_client.chost, host); } if (snmp_client.cport == NULL) { if ((snmp_client.cport = malloc(1 + sizeof(DEFAULT_PORT))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } strcpy(snmp_client.cport, DEFAULT_PORT); } if (port != NULL) { if ((ptr = malloc(1 + strlen(port))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } free(snmp_client.cport); snmp_client.cport = ptr; strcpy(snmp_client.cport, port); } /* open connection */ memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_family = snmp_client.trans == SNMP_TRANS_UDP ? AF_INET : AF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0); if (error != 0) { seterr(&snmp_client, "%s: %s", snmp_client.chost, gai_strerror(error)); return (-1); } res = res0; for (;;) { if ((snmp_client.fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { if ((res = res->ai_next) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); freeaddrinfo(res0); return (-1); } } else if (connect(snmp_client.fd, res->ai_addr, res->ai_addrlen) == -1) { if ((res = res->ai_next) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); freeaddrinfo(res0); (void)close(snmp_client.fd); snmp_client.fd = -1; 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; int stype; if (snmp_client.chost == NULL) { if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_LOCAL))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } strcpy(snmp_client.chost, DEFAULT_LOCAL); } if (path != NULL) { if ((ptr = malloc(1 + strlen(path))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } free(snmp_client.chost); snmp_client.chost = ptr; strcpy(snmp_client.chost, path); } if (snmp_client.trans == SNMP_TRANS_LOC_DGRAM) stype = SOCK_DGRAM; else stype = SOCK_STREAM; if ((snmp_client.fd = socket(PF_LOCAL, stype, 0)) == -1) { seterr(&snmp_client, "%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(&snmp_client, "%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(&snmp_client, "%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(&snmp_client, "%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(&snmp_client, "%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)); switch (snmp_client.trans) { case SNMP_TRANS_UDP: case SNMP_TRANS_UDP6: if (open_client_udp(host, port) != 0) return (-1); break; case SNMP_TRANS_LOC_DGRAM: case SNMP_TRANS_LOC_STREAM: if (open_client_local(host) != 0) return (-1); break; default: seterr(&snmp_client, "bad transport mapping"); return (-1); } tout.tv_sec = 0; tout.tv_usec = 0; if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_SNDTIMEO, &tout, sizeof(struct timeval)) == -1) { seterr(&snmp_client, "%s", strerror(errno)); (void)close(snmp_client.fd); snmp_client.fd = -1; if (snmp_client.local_path[0] != '\0') (void)remove(snmp_client.local_path); return (-1); } /* initialize list */ LIST_INIT(&sent_pdus); return (0); } /* * SNMP_CLOSE * * closes connection to snmp server * - function cannot fail * - clears connection * - clears list of sent pdus * * input: * void * return: * void */ void snmp_close(void) { struct sent_pdu *p1; if (snmp_client.fd != -1) { (void)close(snmp_client.fd); snmp_client.fd = -1; if (snmp_client.local_path[0] != '\0') (void)remove(snmp_client.local_path); } while(!LIST_EMPTY(&sent_pdus)){ p1 = LIST_FIRST(&sent_pdus); if (p1->timeout_id != NULL) snmp_client.timeout_stop(p1->timeout_id); LIST_REMOVE(p1, entries); free(p1); } free(snmp_client.chost); free(snmp_client.cport); } /* * initialize a snmp_pdu structure */ void snmp_pdu_create(struct snmp_pdu *pdu, u_int op) { memset(pdu, 0, sizeof(struct snmp_pdu)); if (op == SNMP_PDU_SET) 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; if (snmp_client.version != SNMP_V3) return; pdu->identifier = ++snmp_client.identifier; pdu->engine.max_msg_size = snmp_client.engine.max_msg_size; pdu->flags = 0; pdu->security_model = snmp_client.security_model; if (snmp_client.security_model == SNMP_SECMODEL_USM) { memcpy(&pdu->engine, &snmp_client.engine, sizeof(pdu->engine)); memcpy(&pdu->user, &snmp_client.user, sizeof(pdu->user)); snmp_pdu_init_secparams(pdu); } else seterr(&snmp_client, "unknown security model"); if (snmp_client.clen > 0) { memcpy(pdu->context_engine, snmp_client.cengine, snmp_client.clen); pdu->context_engine_len = snmp_client.clen; } else { memcpy(pdu->context_engine, snmp_client.engine.engine_id, snmp_client.engine.engine_len); pdu->context_engine_len = snmp_client.engine.engine_len; } strlcpy(pdu->context_name, snmp_client.cname, sizeof(pdu->context_name)); } /* 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 = calloc(1, snmp_client.txbuflen)) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } pdu->request_id = snmp_next_reqid(&snmp_client); b.asn_ptr = buf; b.asn_len = snmp_client.txbuflen; if (snmp_pdu_encode(pdu, &b)) { seterr(&snmp_client, "%s", strerror(errno)); free(buf); return (-1); } if (snmp_client.dump_pdus) snmp_pdu_dump(pdu); if ((ret = send(snmp_client.fd, buf, b.asn_ptr - buf, 0)) == -1) { seterr(&snmp_client, "%s", strerror(errno)); free(buf); return (-1); } free(buf); return (pdu->request_id); } /* * to be called when a snmp request timed out */ static void snmp_timeout(void * listentry_ptr) { struct sent_pdu *listentry = listentry_ptr; #if 0 warnx("snmp request %i timed out, attempt (%i/%i)", listentry->reqid, listentry->retrycount, snmp_client.retries); #endif listentry->retrycount++; if (listentry->retrycount > snmp_client.retries) { /* there is no answer at all */ LIST_REMOVE(listentry, entries); listentry->callback(listentry->pdu, NULL, listentry->arg); free(listentry); } else { /* try again */ /* new request with new request ID */ listentry->reqid = snmp_send_packet(listentry->pdu); listentry->timeout_id = snmp_client.timeout_start(&snmp_client.timeout, snmp_timeout, listentry); } } int32_t snmp_pdu_send(struct snmp_pdu *pdu, snmp_send_cb_f func, void *arg) { struct sent_pdu *listentry; int32_t id; if ((listentry = malloc(sizeof(struct sent_pdu))) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } /* here we really send */ if ((id = snmp_send_packet(pdu)) == -1) { free(listentry); return (-1); } /* add entry to list of sent PDUs */ listentry->pdu = pdu; if (gettimeofday(&listentry->time, NULL) == -1) warn("gettimeofday() failed"); listentry->reqid = pdu->request_id; listentry->callback = func; listentry->arg = arg; listentry->retrycount=1; listentry->timeout_id = snmp_client.timeout_start(&snmp_client.timeout, snmp_timeout, listentry); LIST_INSERT_HEAD(&sent_pdus, listentry, entries); return (id); } /* * Receive an SNMP packet. * * tv controls how we wait for a packet: if tv is a NULL pointer, * the receive blocks forever, if tv points to a structure with all * members 0 the socket is polled, in all other cases tv specifies the * maximum time to wait for a packet. * * Return: * -1 on errors * 0 on timeout * +1 if packet received */ static int snmp_receive_packet(struct snmp_pdu *pdu, struct timeval *tv) { int dopoll, setpoll; int flags; int saved_errno; u_char *buf; int ret; struct asn_buf abuf; int32_t ip; #ifdef bsdi int optlen; #else socklen_t optlen; #endif if ((buf = calloc(1, snmp_client.rxbuflen)) == NULL) { seterr(&snmp_client, "%s", strerror(errno)); return (-1); } dopoll = setpoll = 0; flags = 0; if (tv != NULL) { /* poll or timeout */ if (tv->tv_sec != 0 || tv->tv_usec != 0) { /* wait with timeout */ if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(*tv)) == -1) { seterr(&snmp_client, "setsockopt: %s", strerror(errno)); free(buf); return (-1); } optlen = sizeof(*tv); if (getsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, tv, &optlen) == -1) { seterr(&snmp_client, "getsockopt: %s", strerror(errno)); free(buf); return (-1); } /* at this point tv_sec and tv_usec may appear * as 0. This happens for timeouts lesser than * the clock granularity. The kernel rounds these to * 0 and this would result in a blocking receive. * Instead of an else we check tv_sec and tv_usec * again below and if this rounding happens, * switch to a polling receive. */ } if (tv->tv_sec == 0 && tv->tv_usec == 0) { /* poll */ dopoll = 1; if ((flags = fcntl(snmp_client.fd, F_GETFL, 0)) == -1) { seterr(&snmp_client, "fcntl: %s", strerror(errno)); free(buf); return (-1); } if (!(flags & O_NONBLOCK)) { setpoll = 1; flags |= O_NONBLOCK; if (fcntl(snmp_client.fd, F_SETFL, flags) == -1) { seterr(&snmp_client, "fcntl: %s", strerror(errno)); free(buf); return (-1); } } } } ret = recv(snmp_client.fd, buf, snmp_client.rxbuflen, 0); saved_errno = errno; if (tv != NULL) { if (dopoll) { if (setpoll) { flags &= ~O_NONBLOCK; (void)fcntl(snmp_client.fd, F_SETFL, flags); } } else { tv->tv_sec = 0; tv->tv_usec = 0; (void)setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(*tv)); } } if (ret == -1) { free(buf); if (errno == EAGAIN || errno == EWOULDBLOCK) return (0); seterr(&snmp_client, "recv: %s", strerror(saved_errno)); return (-1); } if (ret == 0) { /* this happens when we have a streaming socket and the * remote side has closed it */ free(buf); seterr(&snmp_client, "recv: socket closed by peer"); errno = EPIPE; return (-1); } abuf.asn_ptr = buf; abuf.asn_len = ret; memset(pdu, 0, sizeof(*pdu)); if (snmp_client.security_model == SNMP_SECMODEL_USM) { memcpy(&pdu->engine, &snmp_client.engine, sizeof(pdu->engine)); memcpy(&pdu->user, &snmp_client.user, sizeof(pdu->user)); snmp_pdu_init_secparams(pdu); } if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) { seterr(&snmp_client, "snmp_decode_pdu: failed %d", ret); free(buf); return (-1); } free(buf); if (snmp_client.dump_pdus) snmp_pdu_dump(pdu); snmp_client.engine.engine_time = pdu->engine.engine_time; snmp_client.engine.engine_boots = pdu->engine.engine_boots; return (+1); } static int snmp_deliver_packet(struct snmp_pdu * resp) { struct sent_pdu *listentry; if (resp->type != SNMP_PDU_RESPONSE) { warn("ignoring snmp pdu %u", resp->type); return (-1); } LIST_FOREACH(listentry, &sent_pdus, entries) if (listentry->reqid == resp->request_id) break; if (listentry == NULL) return (-1); LIST_REMOVE(listentry, entries); listentry->callback(listentry->pdu, resp, listentry->arg); snmp_client.timeout_stop(listentry->timeout_id); free(listentry); return (0); } int snmp_receive(int blocking) { int ret; struct timeval tv; struct snmp_pdu * resp; memset(&tv, 0, sizeof(tv)); resp = malloc(sizeof(struct snmp_pdu)); if (resp == NULL) { seterr(&snmp_client, "no memory for returning PDU"); return (-1) ; } if ((ret = snmp_receive_packet(resp, blocking ? NULL : &tv)) <= 0) { free(resp); return (ret); } ret = snmp_deliver_packet(resp); snmp_pdu_free(resp); free(resp); return (ret); } /* * Check a GETNEXT response. Here we have three possible outcomes: -1 an * unexpected error happened. +1 response is ok and is within the table 0 * response is ok, but is behind the table or error is NOSUCHNAME. The req * should point to a template PDU which contains the base OIDs and the * syntaxes. This is really only useful to sweep non-sparse tables. */ static int ok_getnext(const struct snmp_pdu * req, const struct snmp_pdu * resp) { u_int i; if (resp->version != req->version) { warnx("SNMP GETNEXT: response has wrong version"); return (-1); } if (resp->error_status == SNMP_ERR_NOSUCHNAME) return (0); if (resp->error_status != SNMP_ERR_NOERROR) { warnx("SNMP GETNEXT: error %d", resp->error_status); return (-1); } if (resp->nbindings != req->nbindings) { warnx("SNMP GETNEXT: bad number of bindings in response"); return (-1); } for (i = 0; i < req->nbindings; i++) { if (!asn_is_suboid(&req->bindings[i].var, &resp->bindings[i].var)) { if (i != 0) warnx("SNMP GETNEXT: inconsistent table " "response"); return (0); } if (resp->version != SNMP_V1 && resp->bindings[i].syntax == SNMP_SYNTAX_ENDOFMIBVIEW) return (0); if (resp->bindings[i].syntax != req->bindings[i].syntax) { warnx("SNMP GETNEXT: bad syntax in response"); return (0); } } return (1); } /* * Check a GET response. Here we have three possible outcomes: -1 an * unexpected error happened. +1 response is ok. 0 NOSUCHNAME The req should * point to a template PDU which contains the OIDs and the syntaxes. This * is only useful for SNMPv1 or single object GETS. */ static int ok_get(const struct snmp_pdu * req, const struct snmp_pdu * resp) { u_int i; if (resp->version != req->version) { warnx("SNMP GET: response has wrong version"); return (-1); } if (resp->error_status == SNMP_ERR_NOSUCHNAME) return (0); if (resp->error_status != SNMP_ERR_NOERROR) { warnx("SNMP GET: error %d", resp->error_status); return (-1); } if (resp->nbindings != req->nbindings) { warnx("SNMP GET: bad number of bindings in response"); return (-1); } for (i = 0; i < req->nbindings; i++) { if (asn_compare_oid(&req->bindings[i].var, &resp->bindings[i].var) != 0) { warnx("SNMP GET: bad OID in response"); return (-1); } if (snmp_client.version != SNMP_V1 && (resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHOBJECT || resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHINSTANCE)) return (0); if (resp->bindings[i].syntax != req->bindings[i].syntax) { warnx("SNMP GET: bad syntax in response"); return (-1); } } return (1); } /* * Check the response 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) { struct timeval tv = snmp_client.timeout; struct timeval end; struct snmp_pdu pdu; int ret; int32_t reqid; u_int i; /* * Make a copy of the request and replace the syntaxes by NULL * if this is a GET,GETNEXT or GETBULK. */ pdu = *req; if (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GETNEXT || pdu.type == SNMP_PDU_GETBULK) { for (i = 0; i < pdu.nbindings; i++) pdu.bindings[i].syntax = SNMP_SYNTAX_NULL; } for (i = 0; i <= snmp_client.retries; i++) { (void)gettimeofday(&end, NULL); timeradd(&end, &snmp_client.timeout, &end); if ((reqid = snmp_send_packet(&pdu)) == -1) return (-1); for (;;) { (void)gettimeofday(&tv, NULL); if (timercmp(&end, &tv, <=)) break; timersub(&end, &tv, &tv); if ((ret = snmp_receive_packet(resp, &tv)) == 0) /* timeout */ break; if (ret > 0) { if (reqid == resp->request_id) return (0); /* not for us */ (void)snmp_deliver_packet(resp); } if (ret < 0 && errno == EPIPE) /* stream closed */ return (-1); } } errno = ETIMEDOUT; seterr(&snmp_client, "retry count exceeded"); return (-1); } int snmp_discover_engine(char *passwd) { char cname[SNMP_ADM_STR32_SIZ]; enum snmp_authentication cap; enum snmp_privacy cpp; struct snmp_pdu req, resp; if (snmp_client.version != SNMP_V3) seterr(&snmp_client, "wrong version"); strlcpy(cname, snmp_client.user.sec_name, sizeof(cname)); cap = snmp_client.user.auth_proto; cpp = snmp_client.user.priv_proto; snmp_client.engine.engine_len = 0; snmp_client.engine.engine_boots = 0; snmp_client.engine.engine_time = 0; snmp_client.user.auth_proto = SNMP_AUTH_NOAUTH; snmp_client.user.priv_proto = SNMP_PRIV_NOPRIV; memset(snmp_client.user.sec_name, 0, sizeof(snmp_client.user.sec_name)); snmp_pdu_create(&req, SNMP_PDU_GET); if (snmp_dialog(&req, &resp) == -1) return (-1); if (resp.version != req.version) { seterr(&snmp_client, "wrong version"); return (-1); } if (resp.error_status != SNMP_ERR_NOERROR) { seterr(&snmp_client, "Error %d in responce", resp.error_status); return (-1); } snmp_client.engine.engine_len = resp.engine.engine_len; snmp_client.engine.max_msg_size = resp.engine.max_msg_size; memcpy(snmp_client.engine.engine_id, resp.engine.engine_id, resp.engine.engine_len); strlcpy(snmp_client.user.sec_name, cname, sizeof(snmp_client.user.sec_name)); snmp_client.user.auth_proto = cap; snmp_client.user.priv_proto = cpp; if (snmp_client.user.auth_proto == SNMP_AUTH_NOAUTH) return (0); if (passwd == NULL || snmp_passwd_to_keys(&snmp_client.user, passwd) != SNMP_CODE_OK || snmp_get_local_keys(&snmp_client.user, snmp_client.engine.engine_id, snmp_client.engine.engine_len) != SNMP_CODE_OK) return (-1); if (resp.engine.engine_boots != 0) snmp_client.engine.engine_boots = resp.engine.engine_boots; if (resp.engine.engine_time != 0) { snmp_client.engine.engine_time = resp.engine.engine_time; return (0); } snmp_pdu_free(&req); snmp_pdu_create(&req, SNMP_PDU_GET); req.engine.engine_boots = 0; req.engine.engine_time = 0; if (snmp_dialog(&req, &resp) == -1) return (-1); if (resp.version != req.version) { seterr(&snmp_client, "wrong version"); return (-1); } if (resp.error_status != SNMP_ERR_NOERROR) { seterr(&snmp_client, "Error %d in responce", resp.error_status); return (-1); } snmp_client.engine.engine_boots = resp.engine.engine_boots; snmp_client.engine.engine_time = resp.engine.engine_time; snmp_pdu_free(&req); snmp_pdu_free(&resp); return (0); } 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); } static const char *const trans_list[] = { [SNMP_TRANS_UDP] = "udp::", [SNMP_TRANS_LOC_DGRAM] = "dgram::", [SNMP_TRANS_LOC_STREAM] = "stream::", [SNMP_TRANS_UDP6] = "udp6::", }; /** * Try to get a transport identifier which is a leading alphanumeric string * terminated by a double colon. The string may not be empty. The transport * identifier is optional. Unknown transport identifiers are reject. * Be careful: a double colon can also occur in a numeric IPv6 address. * * \param sc client struct to set errors * \param strp possible start of transport; updated to point to * the next character to parse * * \return transport identifier */ static inline int get_transp(struct snmp_client *sc, const char **strp) { const char *p; size_t i; for (i = 0; i < nitems(trans_list); i++) { p = strstr(*strp, trans_list[i]); if (p == *strp) { *strp += strlen(trans_list[i]); return ((int)i); } } p = strstr(*strp, "::"); if (p == *strp) { seterr(sc, "empty transport specifier"); return (-1); } if (p == NULL) /* by default assume UDP */ return (SNMP_TRANS_UDP); /* ignore :: after [ */ const char *ob = strchr(*strp, '['); if (ob != NULL && p > ob) /* by default assume UDP */ return (SNMP_TRANS_UDP); seterr(sc, "unknown transport specifier '%.*s'", p - *strp, *strp); return (-1); } /** * Try to get community string. Eat everything up to the last @ (if there is * any) but only if it is not longer than SNMP_COMMUNITY_MAXLEN. Empty * community strings are legal. * * \param sc client struct to set errors * \param strp possible start of community; updated to the point to * the next character to parse * * \return end of community; equals *strp if there is none; NULL if there * was an error */ static inline const char * get_comm(struct snmp_client *sc, const char **strp) { const char *p = strrchr(*strp, '@'); if (p == NULL) /* no community string */ return (*strp); if (p - *strp > SNMP_COMMUNITY_MAXLEN) { seterr(sc, "community string too long '%.*s'", p - *strp, *strp); return (NULL); } *strp = p + 1; return (p); } /** * Try to get an IPv6 address. This starts with an [ and should end with an ] * and everything between should be not longer than INET6_ADDRSTRLEN and * parseable by inet_pton(). * * \param sc client struct to set errors * \param strp possible start of IPv6 address (the '['); updated to point to * the next character to parse (the one after the closing ']') * * \return end of address (equals *strp + 1 if there is none) or NULL * on errors */ static inline const char * get_ipv6(struct snmp_client *sc, const char **strp) { char str[INET6_ADDRSTRLEN + IF_NAMESIZE]; struct addrinfo hints, *res; int error; if (**strp != '[') return (*strp + 1); const char *p = *strp + 1; while (*p != ']' ) { if (*p == '\0') { seterr(sc, "unterminated IPv6 address '%.*s'", p - *strp, *strp); return (NULL); } p++; } if (p - *strp > INET6_ADDRSTRLEN + IF_NAMESIZE) { seterr(sc, "IPv6 address too long '%.*s'", p - *strp, *strp); return (NULL); } strncpy(str, *strp + 1, p - (*strp + 1)); str[p - (*strp + 1)] = '\0'; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME | AI_NUMERICHOST; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; error = getaddrinfo(str, NULL, &hints, &res); if (error != 0) { seterr(sc, "%s: %s", str, gai_strerror(error)); return (NULL); } freeaddrinfo(res); *strp = p + 1; return (p); } /** * Try to get an IPv4 address. This starts with a digit and consists of digits * and dots, is not longer INET_ADDRSTRLEN and must be parseable by * inet_aton(). * * \param sc client struct to set errors * \param strp possible start of IPv4 address; updated to point to the * next character to parse * * \return end of address (equals *strp if there is none) or NULL * on errors */ static inline const char * get_ipv4(struct snmp_client *sc, const char **strp) { const char *p = *strp; while (isascii(*p) && (isdigit(*p) || *p == '.')) p++; if (p - *strp > INET_ADDRSTRLEN) { seterr(sc, "IPv4 address too long '%.*s'", p - *strp, *strp); return (NULL); } if (*strp == p) return *strp; char str[INET_ADDRSTRLEN + 1]; strncpy(str, *strp, p - *strp); str[p - *strp] = '\0'; struct in_addr addr; if (inet_aton(str, &addr) != 1) { seterr(sc, "illegal IPv4 address '%s'", str); return (NULL); } *strp = p; return (p); } /** * Try to get a hostname. This includes everything up to but not including * the last colon (if any). There is no length restriction. * * \param sc client struct to set errors * \param strp possible start of hostname; updated to point to the next * character to parse (the trailing NUL character or the last * colon) * * \return end of address (equals *strp if there is none) */ static inline const char * get_host(struct snmp_client *sc __unused, const char **strp) { const char *p = strrchr(*strp, ':'); if (p == NULL) { *strp += strlen(*strp); return (*strp); } *strp = p; return (p); } /** * Try to get a port number. This start with a colon and extends to the end * of string. The port number must not be empty. * * \param sc client struct to set errors * \param strp possible start of port specification; if this points to a * colon there is a port specification * * \return end of port number (equals *strp if there is none); NULL * if there is no port number */ static inline const char * get_port(struct snmp_client *sc, const char **strp) { if (**strp != ':') return (*strp + 1); if ((*strp)[1] == '\0') { seterr(sc, "empty port name"); return (NULL); } *strp += strlen(*strp); return (*strp); } /** * Save the string in the range given by two pointers. * * \param sc client struct to set errors * \param s begin and end pointers * * \return freshly allocated copy of the string between s[0] and s[1] */ static inline char * save_str(struct snmp_client *sc, const char *const s[2]) { char *m; if ((m = malloc(s[1] - s[0] + 1)) == NULL) { seterr(sc, "%s: %s", __func__, strerror(errno)); return (NULL); } strncpy(m, s[0], s[1] - s[0]); m[s[1] - s[0]] = '\0'; return (m); } /** * Parse a server specification. All parts are optional: * * [::][@][][:] * * The transport string consists of letters, digits or '_' and starts with * a letter or digit. It is terminated by two colons and may not be empty. * * The community string is terminated by the last '@' and does not exceed * SNMP_COMMUNITY_MAXLEN. It may be empty. * * The host or ip is either an IPv4 address (as parsed by inet_pton()), an * IPv6 address in '[' and ']' and parseable by inet_aton() or a hostname * terminated by the last colon or by the NUL character. * * The port number may be specified numerically or symbolically and starts * with the last colon. * * The functions sets the chost, cport, trans, read_community and * write_community fields on success and the error field on errors. * The chost and cport fields are allocated by malloc(3), their previous * content is deallocated by free(3). * * The function explicitly allows mismatches between the transport and * the address type in order to support IPv4 in IPv6 addresses. * * \param sc client struct to fill * \param str string to parse * * \return 0 on success and -1 on errors */ int snmp_parse_server(struct snmp_client *sc, const char *str) { const char *const orig = str; /* parse input */ int def_trans = 0, trans = get_transp(sc, &str); if (trans < 0) return (-1); /* choose automatically */ if (orig == str) def_trans = 1; const char *const comm[2] = { str, get_comm(sc, &str), }; if (comm[1] == NULL) return (-1); const char *const ipv6[2] = { str + 1, get_ipv6(sc, &str), }; if (ipv6[1] == NULL) return (-1); const char *ipv4[2] = { str, str, }; const char *host[2] = { str, str, }; if (ipv6[0] == ipv6[1]) { ipv4[1] = get_ipv4(sc, &str); if (ipv4[0] == ipv4[1]) host[1] = get_host(sc, &str); } const char *port[2] = { str + 1, get_port(sc, &str), }; if (port[1] == NULL) return (-1); if (*str != '\0') { seterr(sc, "junk at end of server specification '%s'", str); return (-1); } #if DEBUG_PARSE printf("transp: %d (def=%d)\n", trans, def_trans); printf("comm: %zu %zu\n", comm[0] - orig, comm[1] - orig); printf("ipv6: %zu %zu\n", ipv6[0] - orig, ipv6[1] - orig); printf("ipv4: %zu %zu\n", ipv4[0] - orig, ipv4[1] - orig); printf("host: %zu %zu\n", host[0] - orig, host[1] - orig); printf("port: %zu %zu\n", port[0] - orig, port[1] - orig); #endif /* analyse and allocate */ char *chost; if (ipv6[0] != ipv6[1]) { if ((chost = save_str(sc, ipv6)) == NULL) return (-1); if (def_trans || trans == SNMP_TRANS_UDP) /* assume the user meant udp6:: */ trans = SNMP_TRANS_UDP6; } else if (ipv4[0] != ipv4[1]) { if ((chost = save_str(sc, ipv4)) == NULL) return (-1); if (def_trans) trans = SNMP_TRANS_UDP; } else { if ((chost = save_str(sc, host)) == NULL) return (-1); if (def_trans) { /* * Default transport is UDP unless the host contains * a slash in which case we default to DGRAM. */ for (const char *p = host[0]; p < host[1]; p++) if (*p == '/') { trans = SNMP_TRANS_LOC_DGRAM; break; } } } char *cport; if (port[0] == port[1] && ( trans == SNMP_TRANS_UDP || trans == SNMP_TRANS_UDP6)) { /* If port was not specified, use "snmp" name by default */ cport = strdup("snmp"); } else cport = save_str(sc, port); if (cport == NULL) { free(chost); return (-1); } /* commit */ sc->trans = trans; /* * If community string was specified and it is empty, overwrite it. * If it was not specified, use default. */ if (comm[0] != comm[1] || strrchr(comm[0], '@') != NULL) { strncpy(sc->read_community, comm[0], comm[1] - comm[0]); sc->read_community[comm[1] - comm[0]] = '\0'; strncpy(sc->write_community, comm[0], comm[1] - comm[0]); sc->write_community[comm[1] - comm[0]] = '\0'; } free(sc->chost); sc->chost = chost; free(sc->cport); sc->cport = cport; #if DEBUG_PARSE printf("Committed values:\n"); printf("trans: %d\n", sc->trans); printf("comm: '%s'/'%s'\n", sc->read_community, sc->write_community); printf("host: '%s'\n", sc->chost); printf("port: '%s'\n", sc->cport); #endif return (0); } diff --git a/contrib/bsnmp/lib/snmpclient.h b/contrib/bsnmp/lib/snmpclient.h index 1bc3780de038..a19bdb2ea653 100644 --- a/contrib/bsnmp/lib/snmpclient.h +++ b/contrib/bsnmp/lib/snmpclient.h @@ -1,202 +1,202 @@ /* * Copyright (c) 2001-2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Harti Brandt * Kendy Kutzner * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/lib/snmpclient.h,v 1.19 2005/05/23 11:10:14 brandt_h Exp $ */ #ifndef _BSNMP_SNMPCLIENT_H #define _BSNMP_SNMPCLIENT_H #include #include #include #include #include #define SNMP_STRERROR_LEN 200 #define SNMP_LOCAL_PATH "/tmp/snmpXXXXXXXXXXXXXX" /* * transport methods */ #define SNMP_TRANS_UDP 0 #define SNMP_TRANS_LOC_DGRAM 1 #define SNMP_TRANS_LOC_STREAM 2 #define SNMP_TRANS_UDP6 3 /* type of callback function for responses * this callback function is responsible for free() any memory associated with * any of the PDUs. Therefor it may call snmp_pdu_free() */ typedef void (*snmp_send_cb_f)(struct snmp_pdu *, struct snmp_pdu *, void *); /* type of callback function for timeouts */ typedef void (*snmp_timeout_cb_f)(void * ); /* timeout start function */ typedef void *(*snmp_timeout_start_f)(struct timeval *timeout, snmp_timeout_cb_f callback, void *); /* timeout stop function */ typedef void (*snmp_timeout_stop_f)(void *timeout_id); /* * Client context. */ struct snmp_client { enum snmp_version version; int trans; /* which transport to use */ /* these two are read-only for the application */ char *cport; /* port number as string */ char *chost; /* host name or IP address as string */ char read_community[SNMP_COMMUNITY_MAXLEN + 1]; char write_community[SNMP_COMMUNITY_MAXLEN + 1]; /* SNMPv3 specific fields */ int32_t identifier; int32_t security_model; struct snmp_engine engine; struct snmp_user user; /* SNMPv3 Access control - VACM*/ uint32_t clen; uint8_t cengine[SNMP_ENGINE_ID_SIZ]; char cname[SNMP_CONTEXT_NAME_SIZ]; 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 __thread struct snmp_client snmp_client; +extern struct snmp_client snmp_client; /* initizialies a snmp_client structure */ void snmp_client_init(struct snmp_client *); /* initialize fields */ int snmp_client_set_host(struct snmp_client *, const char *); int snmp_client_set_port(struct snmp_client *, const char *); /* open connection to snmp server (hostname or portname can be NULL) */ int snmp_open(const char *_hostname, const char *_portname, const char *_read_community, const char *_write_community); /* close connection */ void snmp_close(void); /* initialize a snmp_pdu structure */ void snmp_pdu_create(struct snmp_pdu *, u_int _op); /* add pairs of (struct asn_oid *, enum snmp_syntax) to an existing pdu */ int snmp_add_binding(struct snmp_pdu *, ...); /* check wheater the answer is valid or not */ int snmp_pdu_check(const struct snmp_pdu *_req, const struct snmp_pdu *_resp); int32_t snmp_pdu_send(struct snmp_pdu *_pdu, snmp_send_cb_f _func, void *_arg); /* append an index to an oid */ int snmp_oid_append(struct asn_oid *_oid, const char *_fmt, ...); /* receive a packet */ int snmp_receive(int _blocking); /* * This structure is used to describe an SNMP table that is to be fetched. * The C-structure that is produced by the fetch function must start with * a TAILQ_ENTRY and an u_int64_t. */ struct snmp_table { /* base OID of the table */ struct asn_oid table; /* type OID of the LastChange variable for the table if any */ struct asn_oid last_change; /* maximum number of iterations if table has changed */ u_int max_iter; /* size of the C-structure */ size_t entry_size; /* number of index fields */ u_int index_size; /* bit mask of required fields */ uint64_t req_mask; /* indexes and columns to fetch. Ended by a NULL syntax entry */ struct snmp_table_entry { /* the column sub-oid, ignored for index fields */ asn_subid_t subid; /* the syntax of the column or index */ enum snmp_syntax syntax; /* offset of the field into the C-structure. For octet strings * this points to an u_char * followed by a size_t */ off_t offset; #if defined(__GNUC__) && __GNUC__ < 3 } entries[0]; #else } entries[]; #endif }; /* callback type for table fetch */ typedef void (*snmp_table_cb_f)(void *_list, void *_arg, int _res); /* fetch a table. The argument points to a TAILQ_HEAD */ int snmp_table_fetch(const struct snmp_table *descr, void *); int snmp_table_fetch_async(const struct snmp_table *, void *, snmp_table_cb_f, void *); /* send a request and wait for the response */ int snmp_dialog(struct snmp_pdu *_req, struct snmp_pdu *_resp); /* discover an authorative snmpEngineId */ int snmp_discover_engine(char *); /* parse a server specification */ int snmp_parse_server(struct snmp_client *, const char *); #endif /* _BSNMP_SNMPCLIENT_H */