Index: head/contrib/bsnmp/lib/asn1.c =================================================================== --- head/contrib/bsnmp/lib/asn1.c (revision 359511) +++ head/contrib/bsnmp/lib/asn1.c (revision 359512) @@ -1,1027 +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) > 0x30) { - asn_error(b, "types > 0x30 not supported (%u)", + 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, "len %u exceeding asn_len %u", *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 <= 0x30) and the + * 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) > 0x30) { - asn_error(NULL, "types > 0x30 not supported (%u)", + 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) + if (len > 8) { + asn_error(b, "integer too long"); err = ASN_ERR_RANGE; - else if (len > 1 && + } else if (len > 1 && ((*b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) || (*b->asn_cptr == 0xff && (b->asn_cptr[1] & 0x80) == 0x80))) { asn_error(b, "non-minimal integer"); err = ASN_ERR_BADLEN; } if (*b->asn_cptr & 0x80) neg = 1; val = 0; while (len--) { val <<= 8; val |= neg ? (u_char)~*b->asn_cptr : *b->asn_cptr; b->asn_len--; b->asn_cptr++; } if (neg) { *vp = -(int64_t)val - 1; } else *vp = (int64_t)val; return (err); } /* * Write a signed integer with the given type. The caller has to ensure * that the actual value is ok for this type. */ static enum asn_err asn_put_real_integer(struct asn_buf *b, u_char type, int64_t ival) { int i, neg = 0; # define OCTETS 8 u_char buf[OCTETS]; uint64_t val; enum asn_err ret; if (ival < 0) { /* this may fail if |INT64_MIN| > |INT64_MAX| and * the value is between * INT64_MIN <= ival < -(INT64_MAX+1) */ val = (uint64_t)-(ival + 1); neg = 1; } else val = (uint64_t)ival; /* split the value into octets */ for (i = OCTETS - 1; i >= 0; i--) { buf[i] = val & 0xff; if (neg) buf[i] = ~buf[i]; val >>= 8; } /* no leading 9 zeroes or ones */ for (i = 0; i < OCTETS - 1; i++) if (!((buf[i] == 0xff && (buf[i + 1] & 0x80) != 0) || (buf[i] == 0x00 && (buf[i + 1] & 0x80) == 0))) break; if ((ret = asn_put_header(b, type, OCTETS - i))) return (ret); if (OCTETS - (u_int)i > b->asn_len) return (ASN_ERR_EOBUF); while (i < OCTETS) { *b->asn_ptr++ = buf[i++]; b->asn_len--; } return (ASN_ERR_OK); # undef OCTETS } /* * The same for unsigned 64-bitters. Here we have the problem, that overflow * can happen, because the value maybe 9 bytes long. In this case the * first byte must be 0. */ static enum asn_err asn_get_real_unsigned(struct asn_buf *b, asn_len_t len, uint64_t *vp) { - enum asn_err err; - + *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"); - *vp = 0; return (ASN_ERR_BADLEN); } - err = ASN_ERR_OK; - *vp = 0; - if ((*b->asn_cptr & 0x80) || (len == 9 && *b->asn_cptr != 0)) { + 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; - err = ASN_ERR_RANGE; - } else if (len > 1 && - *b->asn_cptr == 0x00 && (b->asn_cptr[1] & 0x80) == 0) { - asn_error(b, "non-minimal unsigned"); - err = ASN_ERR_BADLEN; + 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) + if (len > 4) { + asn_error(b, "integer too long"); ret = ASN_ERR_BADLEN; - else if (val > INT32_MAX || val < INT32_MIN) + } 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, "OBID subid too larger"); + 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(b, "short oid"); + 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] >= 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 (len > 5) { - asn_error(b, "uint32 too long %u", len); - err = ASN_ERR_BADLEN; - } else if (v > UINT32_MAX) { + 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) { 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"); } Index: head/contrib/bsnmp/lib/bsnmpclient.3 =================================================================== --- head/contrib/bsnmp/lib/bsnmpclient.3 (revision 359511) +++ head/contrib/bsnmp/lib/bsnmpclient.3 (revision 359512) @@ -1,706 +1,751 @@ .\" .\" Copyright (c) 2004-2005 .\" Hartmut Brandt. .\" All rights reserved. .\" Copyright (c) 2001-2003 .\" Fraunhofer Institute for Open Communication Systems (FhG Fokus). .\" All rights reserved. .\" .\" Author: Harti Brandt .\" .\" 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/bsnmpclient.3,v 1.12 2005/10/04 08:46:50 brandt_h Exp $ .\" -.Dd December 31, 2016 +.Dd March 31, 2020 .Dt BSNMPCLIENT 3 .Os .Sh NAME .Nm snmp_client , .Nm snmp_client_init , .Nm snmp_client_set_host , .Nm snmp_client_set_port , .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_parse_server , .Nm snmp_receive , .Nm snmp_table_cb_f , .Nm snmp_table_fetch , .Nm snmp_table_fetch_async , .Nm snmp_dialog , .Nm snmp_discover_engine .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_parse_server "struct snmp_client *sc" "const char *str" .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" .Ft int .Fn snmp_discover_engine "void" .Sh DESCRIPTION The SNMP library contains routines to easily build SNMP client applications that use SNMP versions 1, 2 or 3. Most of the routines use a .Vt struct snmp_client : .Bd -literal -offset indent 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)]; }; .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 trans If this is .Dv SNMP_TRANS_LOC_DGRAM a local datagram socket is used. If it is .Dv SNMP_TRANS_LOC_STREAM a local stream socket is used. For .Dv SNMP_TRANS_UDP -a UDP socket is created. +a UDPv4 socket and for +.Dv SNMP_TRANS_UDP6 +a UDPv6 socket is created. It uses the .Va chost field as the path to the server's socket for local sockets. .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 identifier The message identifier value to be used with SNMPv3 PDUs. Incremented with each transmitted PDU. .It Va security_model The security model to be used with SNMPv3 PDUs. Currently only User-Based Security model specified by RFC 3414 (value 3) is supported. .It Va engine The authoritive SNMP engine parameters to be used with SNMPv3 PDUs. .It Va user The USM SNMP user credentials to be used with SNMPv3 PDUs. .It Va clen The length of the context engine id to be used with SNMPv3 PDUs. .It Va cengine The context engine id to be used with SNMPv3 PDUs. Default is empty. .It Va cname The context name to be used with SNMPv3 PDUs. Default is .Sq "" . .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 outgoing PDUs. The default is .Li INT32_MAX . .It Va min_reqid The minimum request id to use for outgoing PDUs. Request ids are allocated linearily 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 .Pp .D1 Vt extern struct snmp_client snmp_client ; .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 necessary. 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 ( Va N Ns ) 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 occurred). .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 original 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 response 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 occurred or no packet was available (in polling mode). .Pp The next two functions are used to retrieve tables from SNMP agents. They 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; uint64_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 structures 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 being 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 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 uint64_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 uint64_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 synchronously 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 asynchronously. If either the entire table is fetch, or an error occurs 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; uint64_t found; int32_t index; u_char *ifname; size_t ifnamelen; uint32_t node_id; uint32_t pcr; int32_t media; uint32_t vpi_bits; uint32_t vci_bits; uint32_t max_vpcs; uint32_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 response could be received after all timeouts and retries, the function returns -1. If a response was received 0 is returned. .Pp The function .Fn snmp_discover_engine is used to discover the authoritative snmpEngineId of a remote SNMPv3 agent. A request PDU with empty USM user name is sent and the client's engine parameters are set according to the snmpEngine parameters received in the response PDU. If the client is configured to use authentication and/or privacy and the snmpEngineBoots and/or snmpEngineTime in the response had zero values, an additional request (possibly encrypted) with the appropriate user credentials is sent to fetch the missing values. Note, that the function blocks until the discovery process is completed. If no response could be received after all timeouts and retries, or the response contained errors the function returns -1. If the discovery process was completed 0 is returned. .Pp The function .Fn snmp_parse_server is used to parse an SNMP server specification string and fill in the fields of a .Vt struct snmp_client . The syntax of a server specification is .Pp .D1 [trans::][community@][server][:port] .Pp where .Va trans -is the transport name (one of udp, stream or dgram), +is the transport name (one of +.Qq udp , +.Qq udp6 , +.Qq stream +or +.Qq dgram ) , .Va community is the string to be used for both the read and the write community, .Va server is the server's host name in case of UDP and the path name in case of a local socket, and .Va port is the port in case of UDP transport. The function returns 0 in the case of success and return -1 and sets the error string in case of an error. +.Pp +The function +.Fn snmp_parse_serverr +fills the transport, the port number and the community strings with +reasonable default values when they are not specified. +The default transport +is +.Dv SNMP_TRANS_UDP . +If the host name contains a slash the default is modified to +.Dv SNMP_TRANS_LOC_DGRAM . +If the host name looks like a numeric IPv6 address the default is +.Dv SNMP_TRANS_UDP6 . +For numeric IPv6 addresses the transport name udp is automatically +translated as +.Dv SNMP_TRANS_UDP6 . +The default port number (for +.Dv udp +or +.Dv udp6 ) +is +.Qq snmp . +The default read community is +.Qq public +and the default write community +.Qq private . +.Pp +.Fn snmp_parse_server +recognizes path names, host names and numerical IPv4 and IPv6 addresses. +A string consisting of digits and periods is assumed to be an IPv4 address +and must be parseable by +.Fn inet_aton 3 . +An IPv6 address is any string enclosed in square brackets. +It must be parseable with +.Fn gethostinfo 3 . +.Pp +The port number for +.Fn snmp_parse_server +can be specified numerically or symbolically. +It is ignored for local sockets. .Sh DIAGNOSTICS -If an error occurs in any of the function an error indication as described +If an error occurs in any of the functions an error indication as described above is returned. -Additionally the function sets a printable error string -in the +Additionally the function sets a printable error string in the .Va error -filed of +field of .Va snmp_client . .Sh SEE ALSO .Xr gensnmptree 1 , .Xr bsnmpd 1 , .Xr bsnmpagent 3 , .Xr bsnmplib 3 .Sh STANDARDS This implementation conforms to the applicable IETF RFCs and ITU-T recommendations. .Sh AUTHORS .An Hartmut Brandt Aq harti@FreeBSD.org .An Kendy Kutzner Aq kutzner@fokus.gmd.de Index: head/contrib/bsnmp/lib/snmpclient.c =================================================================== --- head/contrib/bsnmp/lib/snmpclient.c (revision 359511) +++ head/contrib/bsnmp/lib/snmpclient.c (revision 359512) @@ -1,2285 +1,2298 @@ /* - * Copyright (c) 2004-2005,2018 + * 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 */ 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; 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: + 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. + * 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++) { - if (trans_list[i] == NULL || *trans_list[i] == '\0') - continue; p = strstr(*strp, trans_list[i]); if (p == *strp) { *strp += strlen(trans_list[i]); return ((int)i); } } - p = *strp; - if (p[0] == ':' && p[1] == ':') { + p = strstr(*strp, "::"); + if (p == *strp) { seterr(sc, "empty transport specifier"); return (-1); } - /* by default assume UDP */ - return (SNMP_TRANS_UDP); + 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 i, trans = get_transp(sc, &str); + int def_trans = 0, trans = get_transp(sc, &str); if (trans < 0) return (-1); /* choose automatically */ - i = orig == str ? -1: trans; + 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: %u\n", trans); + 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 (i == -1 || trans == SNMP_TRANS_UDP) + 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 (i == -1) + if (def_trans) trans = SNMP_TRANS_UDP; } else { if ((chost = save_str(sc, host)) == NULL) return (-1); - if (i == -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: %u\n", sc->trans); + 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); } Index: head/contrib/bsnmp/lib/snmpcrypto.c =================================================================== --- head/contrib/bsnmp/lib/snmpcrypto.c (revision 359511) +++ head/contrib/bsnmp/lib/snmpcrypto.c (revision 359512) @@ -1,435 +1,435 @@ /*- * Copyright (c) 2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Shteryana Sotirova Shopova under * sponsorship from the FreeBSD Foundation. * * 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 THE 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 THE 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. * * $FreeBSD$ */ #include #include #include #include #include #include #ifdef HAVE_STDINT_H #include #elif defined(HAVE_INTTYPES_H) #include #endif #include #include #include #include #ifdef HAVE_LIBCRYPTO #include #endif #include "asn1.h" #include "snmp.h" #include "snmppriv.h" #define SNMP_PRIV_AES_IV_SIZ 16 #define SNMP_EXTENDED_KEY_SIZ 64 #define SNMP_AUTH_KEY_LOOPCNT 1048576 #define SNMP_AUTH_BUF_SIZE 72 +#ifdef HAVE_LIBCRYPTO + static const uint8_t ipad = 0x36; static const uint8_t opad = 0x5c; - -#ifdef HAVE_LIBCRYPTO static int32_t snmp_digest_init(const struct snmp_user *user, EVP_MD_CTX *ctx, const EVP_MD **dtype, uint32_t *keylen) { if (user->auth_proto == SNMP_AUTH_HMAC_MD5) { *dtype = EVP_md5(); *keylen = SNMP_AUTH_HMACMD5_KEY_SIZ; } else if (user->auth_proto == SNMP_AUTH_HMAC_SHA) { *dtype = EVP_sha1(); *keylen = SNMP_AUTH_HMACSHA_KEY_SIZ; } else if (user->auth_proto == SNMP_AUTH_NOAUTH) return (0); else { snmp_error("unknown authentication option - %d", user->auth_proto); return (-1); } if (EVP_DigestInit(ctx, *dtype) != 1) return (-1); return (1); } enum snmp_code snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest) { uint8_t md[EVP_MAX_MD_SIZE], extkey[SNMP_EXTENDED_KEY_SIZ]; uint8_t key1[SNMP_EXTENDED_KEY_SIZ], key2[SNMP_EXTENDED_KEY_SIZ]; uint32_t i, keylen, olen; int32_t err; const EVP_MD *dtype; EVP_MD_CTX *ctx; ctx = EVP_MD_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); err = snmp_digest_init(&pdu->user, ctx, &dtype, &keylen); if (err <= 0) EVP_MD_CTX_free(ctx); if (err < 0) return (SNMP_CODE_BADDIGEST); else if (err == 0) return (SNMP_CODE_OK); memset(pdu->digest_ptr, 0, sizeof(pdu->msg_digest)); memcpy(extkey, pdu->user.auth_key, keylen); memset(extkey + keylen, 0, sizeof(extkey) - keylen); for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++) { key1[i] = extkey[i] ^ ipad; key2[i] = extkey[i] ^ opad; } if (EVP_DigestUpdate(ctx, key1, SNMP_EXTENDED_KEY_SIZ) != 1 || EVP_DigestUpdate(ctx, pdu->outer_ptr, pdu->outer_len) != 1 || EVP_DigestFinal(ctx, md, &olen) != 1) goto failed; if (EVP_DigestInit(ctx, dtype) != 1 || EVP_DigestUpdate(ctx, key2, SNMP_EXTENDED_KEY_SIZ) != 1 || EVP_DigestUpdate(ctx, md, olen) != 1 || EVP_DigestFinal(ctx, md, &olen) != 1) goto failed; if (olen < SNMP_USM_AUTH_SIZE) { snmp_error("bad digest size - %d", olen); EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } memcpy(digest, md, SNMP_USM_AUTH_SIZE); EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); failed: EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } static int32_t snmp_pdu_cipher_init(const struct snmp_pdu *pdu, int32_t len, const EVP_CIPHER **ctype, uint8_t *piv) { int i; uint32_t netint; if (pdu->user.priv_proto == SNMP_PRIV_DES) { if (len % 8 != 0) return (-1); *ctype = EVP_des_cbc(); memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt)); for (i = 0; i < 8; i++) piv[i] = piv[i] ^ pdu->user.priv_key[8 + i]; } else if (pdu->user.priv_proto == SNMP_PRIV_AES) { *ctype = EVP_aes_128_cfb128(); netint = htonl(pdu->engine.engine_boots); memcpy(piv, &netint, sizeof(netint)); piv += sizeof(netint); netint = htonl(pdu->engine.engine_time); memcpy(piv, &netint, sizeof(netint)); piv += sizeof(netint); memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt)); } else if (pdu->user.priv_proto == SNMP_PRIV_NOPRIV) return (0); else { snmp_error("unknown privacy option - %d", pdu->user.priv_proto); return (-1); } return (1); } enum snmp_code snmp_pdu_encrypt(const struct snmp_pdu *pdu) { int32_t err, olen; uint8_t iv[SNMP_PRIV_AES_IV_SIZ]; const EVP_CIPHER *ctype; EVP_CIPHER_CTX *ctx; err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv); if (err < 0) return (SNMP_CODE_EDECRYPT); else if (err == 0) return (SNMP_CODE_OK); ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); if (EVP_EncryptInit(ctx, ctype, pdu->user.priv_key, iv) != 1) goto failed; if (EVP_EncryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, pdu->scoped_len) != 1 || EVP_EncryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1) goto failed; EVP_CIPHER_CTX_free(ctx); return (SNMP_CODE_OK); failed: EVP_CIPHER_CTX_free(ctx); return (SNMP_CODE_FAILED); } enum snmp_code snmp_pdu_decrypt(const struct snmp_pdu *pdu) { int32_t err, olen; uint8_t iv[SNMP_PRIV_AES_IV_SIZ]; const EVP_CIPHER *ctype; EVP_CIPHER_CTX *ctx; err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv); if (err < 0) return (SNMP_CODE_EDECRYPT); else if (err == 0) return (SNMP_CODE_OK); ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); if (EVP_DecryptInit(ctx, ctype, pdu->user.priv_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) goto failed; if (EVP_DecryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, pdu->scoped_len) != 1 || EVP_DecryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1) goto failed; EVP_CIPHER_CTX_free(ctx); return (SNMP_CODE_OK); failed: EVP_CIPHER_CTX_free(ctx); return (SNMP_CODE_EDECRYPT); } /* [RFC 3414] - A.2. Password to Key Algorithm */ enum snmp_code snmp_passwd_to_keys(struct snmp_user *user, char *passwd) { int err, loop, i, pwdlen; uint32_t keylen, olen; const EVP_MD *dtype; EVP_MD_CTX *ctx; uint8_t authbuf[SNMP_AUTH_BUF_SIZE]; if (passwd == NULL || user == NULL) return (SNMP_CODE_FAILED); ctx = EVP_MD_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); err = snmp_digest_init(user, ctx, &dtype, &keylen); if (err <= 0) EVP_MD_CTX_free(ctx); if (err < 0) return (SNMP_CODE_BADDIGEST); else if (err == 0) return (SNMP_CODE_OK); memset(user->auth_key, 0, sizeof(user->auth_key)); pwdlen = strlen(passwd); for (loop = 0; loop < SNMP_AUTH_KEY_LOOPCNT; loop += i) { for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++) authbuf[i] = passwd[(loop + i) % pwdlen]; if (EVP_DigestUpdate(ctx, authbuf, SNMP_EXTENDED_KEY_SIZ) != 1) goto failed; } if (EVP_DigestFinal(ctx, user->auth_key, &olen) != 1) goto failed; EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); failed: EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } /* [RFC 3414] - 2.6. Key Localization Algorithm */ enum snmp_code snmp_get_local_keys(struct snmp_user *user, uint8_t *eid, uint32_t elen) { int err; uint32_t keylen, olen; const EVP_MD *dtype; EVP_MD_CTX *ctx; uint8_t authbuf[SNMP_AUTH_BUF_SIZE]; if (user == NULL || eid == NULL || elen > SNMP_ENGINE_ID_SIZ) return (SNMP_CODE_FAILED); ctx = EVP_MD_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); memset(user->priv_key, 0, sizeof(user->priv_key)); memset(authbuf, 0, sizeof(authbuf)); err = snmp_digest_init(user, ctx, &dtype, &keylen); if (err <= 0) EVP_MD_CTX_free(ctx); if (err < 0) return (SNMP_CODE_BADDIGEST); else if (err == 0) return (SNMP_CODE_OK); memcpy(authbuf, user->auth_key, keylen); memcpy(authbuf + keylen, eid, elen); memcpy(authbuf + keylen + elen, user->auth_key, keylen); if (EVP_DigestUpdate(ctx, authbuf, 2 * keylen + elen) != 1 || EVP_DigestFinal(ctx, user->auth_key, &olen) != 1) { EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } EVP_MD_CTX_free(ctx); if (user->priv_proto != SNMP_PRIV_NOPRIV) memcpy(user->priv_key, user->auth_key, sizeof(user->priv_key)); return (SNMP_CODE_OK); } enum snmp_code snmp_calc_keychange(struct snmp_user *user, uint8_t *keychange) { int32_t err, rvalue[SNMP_AUTH_HMACSHA_KEY_SIZ / 4]; uint32_t i, keylen, olen; const EVP_MD *dtype; EVP_MD_CTX *ctx; ctx = EVP_MD_CTX_new(); if (ctx == NULL) return (SNMP_CODE_FAILED); err = snmp_digest_init(user, ctx, &dtype, &keylen); if (err <= 0) EVP_MD_CTX_free(ctx); if (err < 0) return (SNMP_CODE_BADDIGEST); else if (err == 0) return (SNMP_CODE_OK); for (i = 0; i < keylen / 4; i++) rvalue[i] = random(); memcpy(keychange, user->auth_key, keylen); memcpy(keychange + keylen, rvalue, keylen); if (EVP_DigestUpdate(ctx, keychange, 2 * keylen) != 1 || EVP_DigestFinal(ctx, keychange, &olen) != 1) { EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); } #else /* !HAVE_LIBCRYPTO */ enum snmp_code snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest __unused) { if (pdu->user.auth_proto != SNMP_AUTH_NOAUTH) return (SNMP_CODE_BADSECLEVEL); return (SNMP_CODE_OK); } enum snmp_code snmp_pdu_encrypt(const struct snmp_pdu *pdu) { if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV) return (SNMP_CODE_BADSECLEVEL); return (SNMP_CODE_OK); } enum snmp_code snmp_pdu_decrypt(const struct snmp_pdu *pdu) { if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV) return (SNMP_CODE_BADSECLEVEL); return (SNMP_CODE_OK); } enum snmp_code snmp_passwd_to_keys(struct snmp_user *user, char *passwd __unused) { if (user->auth_proto == SNMP_AUTH_NOAUTH && user->priv_proto == SNMP_PRIV_NOPRIV) return (SNMP_CODE_OK); errno = EPROTONOSUPPORT; return (SNMP_CODE_FAILED); } enum snmp_code snmp_get_local_keys(struct snmp_user *user, uint8_t *eid __unused, uint32_t elen __unused) { if (user->auth_proto == SNMP_AUTH_NOAUTH && user->priv_proto == SNMP_PRIV_NOPRIV) return (SNMP_CODE_OK); errno = EPROTONOSUPPORT; return (SNMP_CODE_FAILED); } enum snmp_code snmp_calc_keychange(struct snmp_user *user __unused, uint8_t *keychange __unused) { errno = EPROTONOSUPPORT; return (SNMP_CODE_FAILED); } #endif /* HAVE_LIBCRYPTO */ Index: head/contrib/bsnmp/snmp_mibII/mibII.c =================================================================== --- head/contrib/bsnmp/snmp_mibII/mibII.c (revision 359511) +++ head/contrib/bsnmp/snmp_mibII/mibII.c (revision 359512) @@ -1,1871 +1,1870 @@ /* * 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: mibII.c 516 2006-10-27 15:54:02Z brandt_h $ * * Implementation of the standard interfaces and ip MIB. */ #include "mibII.h" #include "mibII_oid.h" #include #include /*****************************/ /* our module */ static struct lmodule *module; /* routing socket */ static int route; static void *route_fd; /* if-index allocator */ static uint32_t next_if_index = 1; /* currently fetching the arp table */ static int in_update_arp; /* OR registrations */ static u_int ifmib_reg; static u_int ipmib_reg; static u_int tcpmib_reg; static u_int udpmib_reg; static u_int ipForward_reg; /*****************************/ /* list of all IP addresses */ struct mibifa_list mibifa_list = TAILQ_HEAD_INITIALIZER(mibifa_list); /* list of all interfaces */ struct mibif_list mibif_list = TAILQ_HEAD_INITIALIZER(mibif_list); /* list of dynamic interface names */ struct mibdynif_list mibdynif_list = SLIST_HEAD_INITIALIZER(mibdynif_list); /* list of all interface index mappings */ struct mibindexmap_list mibindexmap_list = STAILQ_HEAD_INITIALIZER(mibindexmap_list); /* list of all stacking entries */ struct mibifstack_list mibifstack_list = TAILQ_HEAD_INITIALIZER(mibifstack_list); /* list of all receive addresses */ struct mibrcvaddr_list mibrcvaddr_list = TAILQ_HEAD_INITIALIZER(mibrcvaddr_list); /* list of all NetToMedia entries */ struct mibarp_list mibarp_list = TAILQ_HEAD_INITIALIZER(mibarp_list); /* number of interfaces */ int32_t mib_if_number; /* last change of table */ uint64_t mib_iftable_last_change; /* last change of stack table */ uint64_t mib_ifstack_last_change; /* if this is set, one of our lists may be bad. refresh them when idle */ int mib_iflist_bad; /* network socket */ int mib_netsock; /* last time refreshed */ uint64_t mibarpticks; /* info on system clocks */ struct clockinfo clockinfo; /* list of all New if registrations */ static struct newifreg_list newifreg_list = TAILQ_HEAD_INITIALIZER(newifreg_list); /* baud rate of fastest interface */ uint64_t mibif_maxspeed; /* user-forced update interval */ u_int mibif_force_hc_update_interval; /* current update interval */ u_int mibif_hc_update_interval; /* HC update timer handle */ static void *hc_update_timer; /* Idle poll timer */ static void *mibII_poll_timer; /* interfaces' data poll interval */ u_int mibII_poll_ticks; /* Idle poll hook */ static void mibII_idle(void *arg __unused); /*****************************/ static const struct asn_oid oid_ifMIB = OIDX_ifMIB; static const struct asn_oid oid_ipMIB = OIDX_ipMIB; static const struct asn_oid oid_tcpMIB = OIDX_tcpMIB; static const struct asn_oid oid_udpMIB = OIDX_udpMIB; static const struct asn_oid oid_ipForward = OIDX_ipForward; static const struct asn_oid oid_linkDown = OIDX_linkDown; static const struct asn_oid oid_linkUp = OIDX_linkUp; static const struct asn_oid oid_ifIndex = OIDX_ifIndex; /*****************************/ /* * Find an interface */ struct mibif * mib_find_if(u_int idx) { struct mibif *ifp; TAILQ_FOREACH(ifp, &mibif_list, link) if (ifp->index == idx) return (ifp); return (NULL); } struct mibif * mib_find_if_sys(u_int sysindex) { struct mibif *ifp; TAILQ_FOREACH(ifp, &mibif_list, link) if (ifp->sysindex == sysindex) return (ifp); return (NULL); } struct mibif * mib_find_if_name(const char *name) { struct mibif *ifp; TAILQ_FOREACH(ifp, &mibif_list, link) if (strcmp(ifp->name, name) == 0) return (ifp); return (NULL); } /* * Check whether an interface is dynamic. The argument may include the * unit number. This assumes, that the name part does NOT contain digits. */ int mib_if_is_dyn(const char *name) { size_t len; struct mibdynif *d; for (len = 0; name[len] != '\0' && isalpha(name[len]) ; len++) ; SLIST_FOREACH(d, &mibdynif_list, link) if (strlen(d->name) == len && strncmp(d->name, name, len) == 0) return (1); return (0); } /* set an interface name to dynamic mode */ void mib_if_set_dyn(const char *name) { struct mibdynif *d; SLIST_FOREACH(d, &mibdynif_list, link) if (strcmp(name, d->name) == 0) return; if ((d = malloc(sizeof(*d))) == NULL) err(1, NULL); strlcpy(d->name, name, sizeof(d->name)); SLIST_INSERT_HEAD(&mibdynif_list, d, link); } /* * register for interface creations */ int mib_register_newif(int (*func)(struct mibif *), const struct lmodule *mod) { struct newifreg *reg; TAILQ_FOREACH(reg, &newifreg_list, link) if (reg->mod == mod) { reg->func = func; return (0); } if ((reg = malloc(sizeof(*reg))) == NULL) { syslog(LOG_ERR, "newifreg: %m"); return (-1); } reg->mod = mod; reg->func = func; TAILQ_INSERT_TAIL(&newifreg_list, reg, link); return (0); } void mib_unregister_newif(const struct lmodule *mod) { struct newifreg *reg; TAILQ_FOREACH(reg, &newifreg_list, link) if (reg->mod == mod) { TAILQ_REMOVE(&newifreg_list, reg, link); free(reg); return; } } struct mibif * mib_first_if(void) { return (TAILQ_FIRST(&mibif_list)); } struct mibif * mib_next_if(const struct mibif *ifp) { return (TAILQ_NEXT(ifp, link)); } /* * Change the admin status of an interface */ int mib_if_admin(struct mibif *ifp, int up) { struct ifreq ifr; strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (ioctl(mib_netsock, SIOCGIFFLAGS, &ifr) == -1) { syslog(LOG_ERR, "SIOCGIFFLAGS(%s): %m", ifp->name); return (-1); } if (up) ifr.ifr_flags |= IFF_UP; else ifr.ifr_flags &= ~IFF_UP; if (ioctl(mib_netsock, SIOCSIFFLAGS, &ifr) == -1) { syslog(LOG_ERR, "SIOCSIFFLAGS(%s): %m", ifp->name); return (-1); } (void)mib_fetch_ifmib(ifp); return (0); } /* * Generate a link up/down trap */ static void link_trap(struct mibif *ifp, int up) { struct snmp_value ifindex; ifindex.var = oid_ifIndex; ifindex.var.subs[ifindex.var.len++] = ifp->index; ifindex.syntax = SNMP_SYNTAX_INTEGER; ifindex.v.integer = ifp->index; snmp_send_trap(up ? &oid_linkUp : &oid_linkDown, &ifindex, (struct snmp_value *)NULL); } /** * Fetch the GENERIC IFMIB and update the HC counters */ static int fetch_generic_mib(struct mibif *ifp, const struct ifmibdata *old) { int name[6]; size_t len; struct mibif_private *p = ifp->private; name[0] = CTL_NET; name[1] = PF_LINK; name[2] = NETLINK_GENERIC; name[3] = IFMIB_IFDATA; name[4] = ifp->sysindex; name[5] = IFDATA_GENERAL; len = sizeof(ifp->mib); if (sysctl(name, nitems(name), &ifp->mib, &len, NULL, 0) == -1) { if (errno != ENOENT) syslog(LOG_WARNING, "sysctl(ifmib, %s) failed %m", ifp->name); return (-1); } /* * Assume that one of the two following compounds is optimized away */ if (ULONG_MAX >= 0xffffffffffffffffULL) { p->hc_inoctets = ifp->mib.ifmd_data.ifi_ibytes; p->hc_outoctets = ifp->mib.ifmd_data.ifi_obytes; p->hc_omcasts = ifp->mib.ifmd_data.ifi_omcasts; p->hc_opackets = ifp->mib.ifmd_data.ifi_opackets; p->hc_imcasts = ifp->mib.ifmd_data.ifi_imcasts; p->hc_ipackets = ifp->mib.ifmd_data.ifi_ipackets; } else if (ULONG_MAX >= 0xffffffff) { #define UPDATE(HC, MIB) \ if (old->ifmd_data.MIB > ifp->mib.ifmd_data.MIB) \ p->HC += (0x100000000ULL + \ ifp->mib.ifmd_data.MIB) - \ old->ifmd_data.MIB; \ else \ p->HC += ifp->mib.ifmd_data.MIB - \ old->ifmd_data.MIB; UPDATE(hc_inoctets, ifi_ibytes) UPDATE(hc_outoctets, ifi_obytes) UPDATE(hc_omcasts, ifi_omcasts) UPDATE(hc_opackets, ifi_opackets) UPDATE(hc_imcasts, ifi_imcasts) UPDATE(hc_ipackets, ifi_ipackets) #undef UPDATE } else abort(); return (0); } /** * Update the 64-bit interface counters */ static void update_hc_counters(void *arg __unused) { struct mibif *ifp; struct ifmibdata oldmib; TAILQ_FOREACH(ifp, &mibif_list, link) { oldmib = ifp->mib; (void)fetch_generic_mib(ifp, &oldmib); } } /** * Recompute the poll timer for the HC counters */ void mibif_reset_hc_timer(void) { u_int ticks; if ((ticks = mibif_force_hc_update_interval) == 0) { if (mibif_maxspeed <= IF_Mbps(10)) { /* at 10Mbps overflow needs 3436 seconds */ ticks = 3000 * 100; /* 50 minutes */ } else if (mibif_maxspeed <= IF_Mbps(100)) { /* at 100Mbps overflow needs 343 seconds */ ticks = 300 * 100; /* 5 minutes */ } else if (mibif_maxspeed < IF_Mbps(622)) { /* at 622Mbps overflow needs 53 seconds */ ticks = 40 * 100; /* 40 seconds */ } else if (mibif_maxspeed <= IF_Mbps(1000)) { /* at 1Gbps overflow needs 34 seconds */ ticks = 20 * 100; /* 20 seconds */ } else { /* at 10Gbps overflow needs 3.4 seconds */ ticks = 100; /* 1 seconds */ } } if (ticks == mibif_hc_update_interval) return; if (hc_update_timer != NULL) { timer_stop(hc_update_timer); hc_update_timer = NULL; } update_hc_counters(NULL); if ((hc_update_timer = timer_start_repeat(ticks, ticks, update_hc_counters, NULL, module)) == NULL) { syslog(LOG_ERR, "timer_start(%u): %m", ticks); return; } mibif_hc_update_interval = ticks; } /** * Restart the idle poll timer. */ void mibif_restart_mibII_poll_timer(void) { if (mibII_poll_timer != NULL) timer_stop(mibII_poll_timer); if ((mibII_poll_timer = timer_start_repeat(mibII_poll_ticks * 10, mibII_poll_ticks * 10, mibII_idle, NULL, module)) == NULL) syslog(LOG_ERR, "timer_start(%u): %m", mibII_poll_ticks); } /* * Fetch new MIB data. */ int mib_fetch_ifmib(struct mibif *ifp) { static int kmib[2] = { -1, 0 }; /* for sysctl net.ifdescr_maxlen */ int name[6]; size_t kmiblen = nitems(kmib); size_t len; void *newmib; struct ifmibdata oldmib = ifp->mib; struct ifreq irr; - unsigned int alias_maxlen = MIBIF_ALIAS_SIZE_MAX; + u_int alias_maxlen = MIBIF_ALIAS_SIZE_MAX; if (fetch_generic_mib(ifp, &oldmib) == -1) return (-1); /* * Quoting RFC2863, 3.1.15: "... LinkUp and linkDown traps are * generated just after ifOperStatus leaves, or just before it * enters, the down state, respectively;" */ if (ifp->trap_enable && ifp->mib.ifmd_data.ifi_link_state != oldmib.ifmd_data.ifi_link_state && (ifp->mib.ifmd_data.ifi_link_state == LINK_STATE_DOWN || oldmib.ifmd_data.ifi_link_state == LINK_STATE_DOWN)) link_trap(ifp, ifp->mib.ifmd_data.ifi_link_state == LINK_STATE_UP ? 1 : 0); ifp->flags &= ~(MIBIF_HIGHSPEED | MIBIF_VERYHIGHSPEED); if (ifp->mib.ifmd_data.ifi_baudrate > 20000000) { ifp->flags |= MIBIF_HIGHSPEED; if (ifp->mib.ifmd_data.ifi_baudrate > 650000000) ifp->flags |= MIBIF_VERYHIGHSPEED; } if (ifp->mib.ifmd_data.ifi_baudrate > mibif_maxspeed) { mibif_maxspeed = ifp->mib.ifmd_data.ifi_baudrate; mibif_reset_hc_timer(); } /* * linkspecific MIB */ name[0] = CTL_NET; name[1] = PF_LINK; name[2] = NETLINK_GENERIC; name[3] = IFMIB_IFDATA; name[4] = ifp->sysindex; name[5] = IFDATA_LINKSPECIFIC; if (sysctl(name, nitems(name), NULL, &len, NULL, 0) == -1) { syslog(LOG_WARNING, "sysctl linkmib estimate (%s): %m", ifp->name); if (ifp->specmib != NULL) { ifp->specmib = NULL; ifp->specmiblen = 0; } goto out; } if (len == 0) { if (ifp->specmib != NULL) { ifp->specmib = NULL; ifp->specmiblen = 0; } goto out; } if (ifp->specmiblen != len) { if ((newmib = realloc(ifp->specmib, len)) == NULL) { ifp->specmib = NULL; ifp->specmiblen = 0; goto out; } ifp->specmib = newmib; ifp->specmiblen = len; } if (sysctl(name, nitems(name), ifp->specmib, &len, NULL, 0) == -1) { syslog(LOG_WARNING, "sysctl linkmib (%s): %m", ifp->name); if (ifp->specmib != NULL) { ifp->specmib = NULL; ifp->specmiblen = 0; } } out: - /* * Find sysctl mib for net.ifdescr_maxlen (one time). * kmib[0] == -1 at first call to mib_fetch_ifmib(). * Then kmib[0] > 0 if we found sysctl mib for net.ifdescr_maxlen. * Else, kmib[0] == 0 (unexpected error from a kernel). */ if (kmib[0] < 0 && sysctlnametomib("net.ifdescr_maxlen", kmib, &kmiblen) < 0) { kmib[0] = 0; syslog(LOG_WARNING, "sysctlnametomib net.ifdescr_maxlen: %m"); } /* * Fetch net.ifdescr_maxlen value every time to catch up with changes. */ len = sizeof(alias_maxlen); if (kmib[0] > 0 && sysctl(kmib, 2, &alias_maxlen, &len, NULL, 0) < 0) { /* unexpected error from the kernel, use default value */ alias_maxlen = MIBIF_ALIAS_SIZE_MAX; syslog(LOG_WARNING, "sysctl net.ifdescr_maxlen: %m"); } /* * Kernel limit might be decreased after interfaces got * their descriptions assigned. Try to obtain them anyway. */ if (alias_maxlen == 0) alias_maxlen = MIBIF_ALIAS_SIZE_MAX; /* * Allocate maximum memory for a buffer and later reallocate * to free extra memory. */ if ((ifp->alias = malloc(alias_maxlen)) == NULL) { syslog(LOG_WARNING, "malloc(%d) failed: %m", (int)alias_maxlen); goto fin; } strlcpy(irr.ifr_name, ifp->name, sizeof(irr.ifr_name)); irr.ifr_buffer.buffer = ifp->alias; irr.ifr_buffer.length = alias_maxlen; if (ioctl(mib_netsock, SIOCGIFDESCR, &irr) == -1) { free(ifp->alias); ifp->alias = NULL; if (errno != ENOMSG) syslog(LOG_WARNING, "SIOCGIFDESCR (%s): %m", ifp->name); } else if (irr.ifr_buffer.buffer == NULL) { free(ifp->alias); ifp->alias = NULL; syslog(LOG_WARNING, "SIOCGIFDESCR (%s): too long (%zu)", ifp->name, irr.ifr_buffer.length); } else { ifp->alias_size = strnlen(ifp->alias, alias_maxlen) + 1; if (ifp->alias_size > MIBIF_ALIAS_SIZE) ifp->alias_size = MIBIF_ALIAS_SIZE; if (ifp->alias_size < alias_maxlen) ifp->alias = realloc(ifp->alias, ifp->alias_size); } -fin: + fin: ifp->mibtick = get_ticks(); return (0); } /* find first/next address for a given interface */ struct mibifa * mib_first_ififa(const struct mibif *ifp) { struct mibifa *ifa; TAILQ_FOREACH(ifa, &mibifa_list, link) if (ifp->index == ifa->ifindex) return (ifa); return (NULL); } struct mibifa * mib_next_ififa(struct mibifa *ifa0) { struct mibifa *ifa; ifa = ifa0; while ((ifa = TAILQ_NEXT(ifa, link)) != NULL) if (ifa->ifindex == ifa0->ifindex) return (ifa); return (NULL); } /* * Allocate a new IFA */ static struct mibifa * alloc_ifa(u_int ifindex, struct in_addr addr) { struct mibifa *ifa; uint32_t ha; if ((ifa = malloc(sizeof(struct mibifa))) == NULL) { syslog(LOG_ERR, "ifa: %m"); return (NULL); } ifa->inaddr = addr; ifa->ifindex = ifindex; ha = ntohl(ifa->inaddr.s_addr); ifa->index.len = 4; ifa->index.subs[0] = (ha >> 24) & 0xff; ifa->index.subs[1] = (ha >> 16) & 0xff; ifa->index.subs[2] = (ha >> 8) & 0xff; ifa->index.subs[3] = (ha >> 0) & 0xff; ifa->flags = 0; ifa->inbcast.s_addr = 0; ifa->inmask.s_addr = 0xffffffff; INSERT_OBJECT_OID(ifa, &mibifa_list); return (ifa); } /* * Delete an interface address */ static void destroy_ifa(struct mibifa *ifa) { TAILQ_REMOVE(&mibifa_list, ifa, link); free(ifa); } /* * Helper routine to extract the sockaddr structures from a routing * socket message. */ void mib_extract_addrs(int addrs, u_char *info, struct sockaddr **out) { u_int i; for (i = 0; i < RTAX_MAX; i++) { if ((addrs & (1 << i)) != 0) { *out = (struct sockaddr *)(void *)info; info += roundup((*out)->sa_len, sizeof(long)); } else *out = NULL; out++; } } /* * save the phys address of an interface. Handle receive address entries here. */ static void get_physaddr(struct mibif *ifp, struct sockaddr_dl *sdl, u_char *ptr) { u_char *np; struct mibrcvaddr *rcv; if (sdl->sdl_alen == 0) { /* no address */ if (ifp->physaddrlen != 0) { if ((rcv = mib_find_rcvaddr(ifp->index, ifp->physaddr, ifp->physaddrlen)) != NULL) mib_rcvaddr_delete(rcv); free(ifp->physaddr); ifp->physaddr = NULL; ifp->physaddrlen = 0; } return; } if (ifp->physaddrlen != sdl->sdl_alen) { /* length changed */ if (ifp->physaddrlen) { /* delete olf receive address */ if ((rcv = mib_find_rcvaddr(ifp->index, ifp->physaddr, ifp->physaddrlen)) != NULL) mib_rcvaddr_delete(rcv); } if ((np = realloc(ifp->physaddr, sdl->sdl_alen)) == NULL) { free(ifp->physaddr); ifp->physaddr = NULL; ifp->physaddrlen = 0; return; } ifp->physaddr = np; ifp->physaddrlen = sdl->sdl_alen; } else if (memcmp(ifp->physaddr, ptr, ifp->physaddrlen) == 0) { /* no change */ return; } else { /* address changed */ /* delete olf receive address */ if ((rcv = mib_find_rcvaddr(ifp->index, ifp->physaddr, ifp->physaddrlen)) != NULL) mib_rcvaddr_delete(rcv); } memcpy(ifp->physaddr, ptr, ifp->physaddrlen); /* make new receive address */ if ((rcv = mib_rcvaddr_create(ifp, ifp->physaddr, ifp->physaddrlen)) != NULL) rcv->flags |= MIBRCVADDR_HW; } /* * Free an interface */ static void mibif_free(struct mibif *ifp) { struct mibif *ifp1; struct mibindexmap *map; struct mibifa *ifa, *ifa1; struct mibrcvaddr *rcv, *rcv1; struct mibarp *at, *at1; if (ifp->xnotify != NULL) (*ifp->xnotify)(ifp, MIBIF_NOTIFY_DESTROY, ifp->xnotify_data); (void)mib_ifstack_delete(ifp, NULL); (void)mib_ifstack_delete(NULL, ifp); TAILQ_REMOVE(&mibif_list, ifp, link); /* if this was the fastest interface - recompute this */ if (ifp->mib.ifmd_data.ifi_baudrate == mibif_maxspeed) { mibif_maxspeed = ifp->mib.ifmd_data.ifi_baudrate; TAILQ_FOREACH(ifp1, &mibif_list, link) if (ifp1->mib.ifmd_data.ifi_baudrate > mibif_maxspeed) mibif_maxspeed = ifp1->mib.ifmd_data.ifi_baudrate; mibif_reset_hc_timer(); } if (ifp->alias != NULL) { free(ifp->alias); ifp->alias = NULL; } free(ifp->private); ifp->private = NULL; free(ifp->physaddr); ifp->physaddr = NULL; free(ifp->specmib); ifp->specmib = NULL; STAILQ_FOREACH(map, &mibindexmap_list, link) if (map->mibif == ifp) { map->mibif = NULL; break; } /* purge interface addresses */ ifa = TAILQ_FIRST(&mibifa_list); while (ifa != NULL) { ifa1 = TAILQ_NEXT(ifa, link); if (ifa->ifindex == ifp->index) destroy_ifa(ifa); ifa = ifa1; } /* purge receive addresses */ rcv = TAILQ_FIRST(&mibrcvaddr_list); while (rcv != NULL) { rcv1 = TAILQ_NEXT(rcv, link); if (rcv->ifindex == ifp->index) mib_rcvaddr_delete(rcv); rcv = rcv1; } /* purge ARP entries */ at = TAILQ_FIRST(&mibarp_list); while (at != NULL) { at1 = TAILQ_NEXT(at, link); if (at->index.subs[0] == ifp->index) mib_arp_delete(at); at = at1; } free(ifp); ifp = NULL; mib_if_number--; mib_iftable_last_change = this_tick; } /* * Create a new interface */ static struct mibif * mibif_create(u_int sysindex, const char *name) { struct mibif *ifp; struct mibindexmap *map; if ((ifp = malloc(sizeof(*ifp))) == NULL) { syslog(LOG_WARNING, "%s: %m", __func__); return (NULL); } memset(ifp, 0, sizeof(*ifp)); if ((ifp->private = malloc(sizeof(struct mibif_private))) == NULL) { syslog(LOG_WARNING, "%s: %m", __func__); free(ifp); return (NULL); } memset(ifp->private, 0, sizeof(struct mibif_private)); ifp->sysindex = sysindex; strlcpy(ifp->name, name, sizeof(ifp->name)); strlcpy(ifp->descr, name, sizeof(ifp->descr)); ifp->spec_oid = oid_zeroDotZero; map = NULL; if (!mib_if_is_dyn(ifp->name)) { /* non-dynamic. look whether we know the interface */ STAILQ_FOREACH(map, &mibindexmap_list, link) if (strcmp(map->name, ifp->name) == 0) { ifp->index = map->ifindex; map->mibif = ifp; break; } /* assume it has a connector if it is not dynamic */ ifp->has_connector = 1; ifp->trap_enable = 1; } if (map == NULL) { /* new interface - get new index */ if (next_if_index > 0x7fffffff) errx(1, "ifindex wrap"); if ((map = malloc(sizeof(*map))) == NULL) { syslog(LOG_ERR, "ifmap: %m"); free(ifp); return (NULL); } map->ifindex = next_if_index++; map->sysindex = ifp->sysindex; strcpy(map->name, ifp->name); map->mibif = ifp; STAILQ_INSERT_TAIL(&mibindexmap_list, map, link); } else { /* re-instantiate. Introduce a counter discontinuity */ ifp->counter_disc = get_ticks(); } ifp->index = map->ifindex; ifp->mib.ifmd_data.ifi_link_state = LINK_STATE_UNKNOWN; INSERT_OBJECT_INT(ifp, &mibif_list); mib_if_number++; mib_iftable_last_change = this_tick; /* instantiate default ifStack entries */ (void)mib_ifstack_create(ifp, NULL); (void)mib_ifstack_create(NULL, ifp); return (ifp); } /* * Inform all interested parties about a new interface */ static void notify_newif(struct mibif *ifp) { struct newifreg *reg; TAILQ_FOREACH(reg, &newifreg_list, link) if ((*reg->func)(ifp)) return; } /* * This is called for new interfaces after we have fetched the interface * MIB. If this is a broadcast interface try to guess the broadcast address * depending on the interface type. */ static void check_llbcast(struct mibif *ifp) { static u_char ether_bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; struct mibrcvaddr *rcv; if (!(ifp->mib.ifmd_flags & IFF_BROADCAST)) return; switch (ifp->mib.ifmd_data.ifi_type) { case IFT_ETHER: case IFT_FDDI: case IFT_ISO88025: case IFT_L2VLAN: if (mib_find_rcvaddr(ifp->index, ether_bcast, 6) == NULL && (rcv = mib_rcvaddr_create(ifp, ether_bcast, 6)) != NULL) rcv->flags |= MIBRCVADDR_BCAST; break; } } /* * Retrieve the current interface list from the system. */ void mib_refresh_iflist(void) { struct mibif *ifp, *ifp1; size_t len; u_short idx; int name[6]; int count; struct ifmibdata mib; TAILQ_FOREACH(ifp, &mibif_list, link) ifp->flags &= ~MIBIF_FOUND; len = sizeof(count); if (sysctlbyname("net.link.generic.system.ifcount", &count, &len, NULL, 0) == -1) { syslog(LOG_ERR, "ifcount: %m"); return; } name[0] = CTL_NET; name[1] = PF_LINK; name[2] = NETLINK_GENERIC; name[3] = IFMIB_IFDATA; name[5] = IFDATA_GENERAL; for (idx = 1; idx <= count; idx++) { name[4] = idx; len = sizeof(mib); if (sysctl(name, nitems(name), &mib, &len, NULL, 0) == -1) { if (errno == ENOENT) continue; syslog(LOG_ERR, "ifmib(%u): %m", idx); return; } if ((ifp = mib_find_if_sys(idx)) != NULL) { ifp->flags |= MIBIF_FOUND; continue; } /* Unknown interface - create */ if ((ifp = mibif_create(idx, mib.ifmd_name)) != NULL) { ifp->flags |= MIBIF_FOUND; (void)mib_fetch_ifmib(ifp); check_llbcast(ifp); notify_newif(ifp); } } /* * Purge interfaces that disappeared */ ifp = TAILQ_FIRST(&mibif_list); while (ifp != NULL) { ifp1 = TAILQ_NEXT(ifp, link); if (!(ifp->flags & MIBIF_FOUND)) mibif_free(ifp); ifp = ifp1; } } /* * Find an interface address */ struct mibifa * mib_find_ifa(struct in_addr addr) { struct mibifa *ifa; TAILQ_FOREACH(ifa, &mibifa_list, link) if (ifa->inaddr.s_addr == addr.s_addr) return (ifa); return (NULL); } /* * Process a new ARP entry */ static void process_arp(const struct rt_msghdr *rtm, const struct sockaddr_dl *sdl, const struct sockaddr_in *sa) { struct mibif *ifp; struct mibarp *at; /* IP arp table entry */ if (sdl->sdl_alen == 0) return; if ((ifp = mib_find_if_sys(sdl->sdl_index)) == NULL) return; /* have a valid entry */ if ((at = mib_find_arp(ifp, sa->sin_addr)) == NULL && (at = mib_arp_create(ifp, sa->sin_addr, sdl->sdl_data + sdl->sdl_nlen, sdl->sdl_alen)) == NULL) return; if (rtm->rtm_rmx.rmx_expire == 0) at->flags |= MIBARP_PERM; else at->flags &= ~MIBARP_PERM; at->flags |= MIBARP_FOUND; } /* * Handle a routing socket message. */ static void handle_rtmsg(struct rt_msghdr *rtm) { struct sockaddr *addrs[RTAX_MAX]; struct if_msghdr *ifm; struct ifa_msghdr ifam, *ifamp; struct ifma_msghdr *ifmam; #ifdef RTM_IFANNOUNCE struct if_announcemsghdr *ifan; #endif struct mibif *ifp; struct sockaddr_dl *sdl; struct sockaddr_in *sa; struct mibifa *ifa; struct mibrcvaddr *rcv; u_char *ptr; if (rtm->rtm_version != RTM_VERSION) { syslog(LOG_ERR, "Bogus RTM version %u", rtm->rtm_version); return; } switch (rtm->rtm_type) { case RTM_NEWADDR: ifamp = (struct ifa_msghdr *)rtm; memcpy(&ifam, ifamp, sizeof(ifam)); mib_extract_addrs(ifam.ifam_addrs, (u_char *)(ifamp + 1), addrs); if (addrs[RTAX_IFA] == NULL || addrs[RTAX_NETMASK] == NULL) break; sa = (struct sockaddr_in *)(void *)addrs[RTAX_IFA]; if ((ifa = mib_find_ifa(sa->sin_addr)) == NULL) { /* unknown address */ if ((ifp = mib_find_if_sys(ifam.ifam_index)) == NULL) { syslog(LOG_WARNING, "RTM_NEWADDR for unknown " "interface %u", ifam.ifam_index); break; } if ((ifa = alloc_ifa(ifp->index, sa->sin_addr)) == NULL) break; } sa = (struct sockaddr_in *)(void *)addrs[RTAX_NETMASK]; ifa->inmask = sa->sin_addr; if (addrs[RTAX_BRD] != NULL) { sa = (struct sockaddr_in *)(void *)addrs[RTAX_BRD]; ifa->inbcast = sa->sin_addr; } ifa->flags |= MIBIFA_FOUND; break; case RTM_DELADDR: ifamp = (struct ifa_msghdr *)rtm; memcpy(&ifam, ifamp, sizeof(ifam)); mib_extract_addrs(ifam.ifam_addrs, (u_char *)(ifamp + 1), addrs); if (addrs[RTAX_IFA] == NULL) break; sa = (struct sockaddr_in *)(void *)addrs[RTAX_IFA]; if ((ifa = mib_find_ifa(sa->sin_addr)) != NULL) { ifa->flags |= MIBIFA_FOUND; if (!(ifa->flags & MIBIFA_DESTROYED)) destroy_ifa(ifa); } break; case RTM_NEWMADDR: ifmam = (struct ifma_msghdr *)rtm; mib_extract_addrs(ifmam->ifmam_addrs, (u_char *)(ifmam + 1), addrs); if (addrs[RTAX_IFA] == NULL || addrs[RTAX_IFA]->sa_family != AF_LINK) break; sdl = (struct sockaddr_dl *)(void *)addrs[RTAX_IFA]; if ((rcv = mib_find_rcvaddr(sdl->sdl_index, sdl->sdl_data + sdl->sdl_nlen, sdl->sdl_alen)) == NULL) { /* unknown address */ if ((ifp = mib_find_if_sys(sdl->sdl_index)) == NULL) { syslog(LOG_WARNING, "RTM_NEWMADDR for unknown " "interface %u", sdl->sdl_index); break; } if ((rcv = mib_rcvaddr_create(ifp, sdl->sdl_data + sdl->sdl_nlen, sdl->sdl_alen)) == NULL) break; rcv->flags |= MIBRCVADDR_VOLATILE; } rcv->flags |= MIBRCVADDR_FOUND; break; case RTM_DELMADDR: ifmam = (struct ifma_msghdr *)rtm; mib_extract_addrs(ifmam->ifmam_addrs, (u_char *)(ifmam + 1), addrs); if (addrs[RTAX_IFA] == NULL || addrs[RTAX_IFA]->sa_family != AF_LINK) break; sdl = (struct sockaddr_dl *)(void *)addrs[RTAX_IFA]; if ((rcv = mib_find_rcvaddr(sdl->sdl_index, sdl->sdl_data + sdl->sdl_nlen, sdl->sdl_alen)) != NULL) mib_rcvaddr_delete(rcv); break; case RTM_IFINFO: ifm = (struct if_msghdr *)(void *)rtm; mib_extract_addrs(ifm->ifm_addrs, (u_char *)(ifm + 1), addrs); if ((ifp = mib_find_if_sys(ifm->ifm_index)) == NULL) break; if (addrs[RTAX_IFP] != NULL && addrs[RTAX_IFP]->sa_family == AF_LINK) { sdl = (struct sockaddr_dl *)(void *)addrs[RTAX_IFP]; ptr = sdl->sdl_data + sdl->sdl_nlen; get_physaddr(ifp, sdl, ptr); } (void)mib_fetch_ifmib(ifp); break; #ifdef RTM_IFANNOUNCE case RTM_IFANNOUNCE: ifan = (struct if_announcemsghdr *)rtm; ifp = mib_find_if_sys(ifan->ifan_index); switch (ifan->ifan_what) { case IFAN_ARRIVAL: if (ifp == NULL && (ifp = mibif_create(ifan->ifan_index, ifan->ifan_name)) != NULL) { (void)mib_fetch_ifmib(ifp); check_llbcast(ifp); notify_newif(ifp); } break; case IFAN_DEPARTURE: if (ifp != NULL) mibif_free(ifp); break; } break; #endif case RTM_GET: case RTM_ADD: mib_extract_addrs(rtm->rtm_addrs, (u_char *)(rtm + 1), addrs); if (rtm->rtm_flags & RTF_LLINFO) { if (addrs[RTAX_DST] == NULL || addrs[RTAX_GATEWAY] == NULL || addrs[RTAX_DST]->sa_family != AF_INET || addrs[RTAX_GATEWAY]->sa_family != AF_LINK) break; process_arp(rtm, (struct sockaddr_dl *)(void *)addrs[RTAX_GATEWAY], (struct sockaddr_in *)(void *)addrs[RTAX_DST]); } else { if (rtm->rtm_errno == 0 && (rtm->rtm_flags & RTF_UP)) mib_sroute_process(rtm, addrs[RTAX_GATEWAY], addrs[RTAX_DST], addrs[RTAX_NETMASK]); } break; case RTM_DELETE: mib_extract_addrs(rtm->rtm_addrs, (u_char *)(rtm + 1), addrs); if (rtm->rtm_errno == 0 && (rtm->rtm_flags & RTF_UP)) mib_sroute_process(rtm, addrs[RTAX_GATEWAY], addrs[RTAX_DST], addrs[RTAX_NETMASK]); break; } } /* * send a routing message */ void mib_send_rtmsg(struct rt_msghdr *rtm, struct sockaddr *gw, struct sockaddr *dst, struct sockaddr *mask) { size_t len; struct rt_msghdr *msg; char *cp; ssize_t sent; len = sizeof(*rtm) + SA_SIZE(gw) + SA_SIZE(dst) + SA_SIZE(mask); if ((msg = malloc(len)) == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return; } cp = (char *)(msg + 1); memset(msg, 0, sizeof(*msg)); msg->rtm_flags = 0; msg->rtm_version = RTM_VERSION; msg->rtm_addrs = RTA_DST | RTA_GATEWAY; memcpy(cp, dst, SA_SIZE(dst)); cp += SA_SIZE(dst); memcpy(cp, gw, SA_SIZE(gw)); cp += SA_SIZE(gw); if (mask != NULL) { memcpy(cp, mask, SA_SIZE(mask)); cp += SA_SIZE(mask); msg->rtm_addrs |= RTA_NETMASK; } msg->rtm_msglen = cp - (char *)msg; msg->rtm_type = RTM_GET; if ((sent = write(route, msg, msg->rtm_msglen)) == -1) { syslog(LOG_ERR, "%s: write: %m", __func__); free(msg); return; } if (sent != msg->rtm_msglen) { syslog(LOG_ERR, "%s: short write", __func__); free(msg); return; } free(msg); } /* * Fetch the routing table via sysctl */ u_char * mib_fetch_rtab(int af, int info, int arg, size_t *lenp) { int name[6]; u_char *buf, *newbuf; name[0] = CTL_NET; name[1] = PF_ROUTE; name[2] = 0; name[3] = af; name[4] = info; name[5] = arg; *lenp = 0; /* initial estimate */ if (sysctl(name, nitems(name), NULL, lenp, NULL, 0) == -1) { syslog(LOG_ERR, "sysctl estimate (%d,%d,%d,%d,%d,%d): %m", name[0], name[1], name[2], name[3], name[4], name[5]); return (NULL); } if (*lenp == 0) return (NULL); buf = NULL; for (;;) { if ((newbuf = realloc(buf, *lenp)) == NULL) { syslog(LOG_ERR, "sysctl buffer: %m"); free(buf); return (NULL); } buf = newbuf; if (sysctl(name, nitems(name), buf, lenp, NULL, 0) == 0) break; if (errno != ENOMEM) { syslog(LOG_ERR, "sysctl get: %m"); free(buf); return (NULL); } *lenp += *lenp / 8 + 1; } return (buf); } /* * Update the following info: interface, interface addresses, interface * receive addresses, arp-table. * This does not change the interface list itself. */ static void update_ifa_info(void) { u_char *buf, *next; struct rt_msghdr *rtm; struct mibifa *ifa, *ifa1; struct mibrcvaddr *rcv, *rcv1; size_t needed; static const int infos[][3] = { { 0, NET_RT_IFLIST, 0 }, #ifdef NET_RT_IFMALIST { AF_LINK, NET_RT_IFMALIST, 0 }, #endif }; u_int i; TAILQ_FOREACH(ifa, &mibifa_list, link) ifa->flags &= ~MIBIFA_FOUND; TAILQ_FOREACH(rcv, &mibrcvaddr_list, link) rcv->flags &= ~MIBRCVADDR_FOUND; for (i = 0; i < sizeof(infos) / sizeof(infos[0]); i++) { if ((buf = mib_fetch_rtab(infos[i][0], infos[i][1], infos[i][2], &needed)) == NULL) continue; next = buf; while (next < buf + needed) { rtm = (struct rt_msghdr *)(void *)next; next += rtm->rtm_msglen; handle_rtmsg(rtm); } free(buf); } /* * Purge the address list of unused entries. These may happen for * interface aliases that are on the same subnet. We don't receive * routing socket messages for them. */ ifa = TAILQ_FIRST(&mibifa_list); while (ifa != NULL) { ifa1 = TAILQ_NEXT(ifa, link); if (!(ifa->flags & MIBIFA_FOUND)) destroy_ifa(ifa); ifa = ifa1; } rcv = TAILQ_FIRST(&mibrcvaddr_list); while (rcv != NULL) { rcv1 = TAILQ_NEXT(rcv, link); if (!(rcv->flags & (MIBRCVADDR_FOUND | MIBRCVADDR_BCAST | MIBRCVADDR_HW))) mib_rcvaddr_delete(rcv); rcv = rcv1; } } /* * Update arp table */ void mib_arp_update(void) { struct mibarp *at, *at1; size_t needed; u_char *buf, *next; struct rt_msghdr *rtm; if (in_update_arp) return; /* Aaargh */ in_update_arp = 1; TAILQ_FOREACH(at, &mibarp_list, link) at->flags &= ~MIBARP_FOUND; if ((buf = mib_fetch_rtab(AF_INET, NET_RT_FLAGS, 0, &needed)) == NULL) { in_update_arp = 0; return; } next = buf; while (next < buf + needed) { rtm = (struct rt_msghdr *)(void *)next; next += rtm->rtm_msglen; handle_rtmsg(rtm); } free(buf); at = TAILQ_FIRST(&mibarp_list); while (at != NULL) { at1 = TAILQ_NEXT(at, link); if (!(at->flags & MIBARP_FOUND)) mib_arp_delete(at); at = at1; } mibarpticks = get_ticks(); in_update_arp = 0; } /* * Input on the routing socket. */ static void route_input(int fd, void *udata __unused) { u_char buf[1024 * 16]; ssize_t n; struct rt_msghdr *rtm; if ((n = read(fd, buf, sizeof(buf))) == -1) err(1, "read(rt_socket)"); if (n == 0) errx(1, "EOF on rt_socket"); rtm = (struct rt_msghdr *)(void *)buf; if ((size_t)n != rtm->rtm_msglen) errx(1, "n=%zu, rtm_msglen=%u", (size_t)n, rtm->rtm_msglen); handle_rtmsg(rtm); } /* * execute and SIOCAIFADDR */ static int siocaifaddr(char *ifname, struct in_addr addr, struct in_addr mask, struct in_addr bcast) { struct ifaliasreq addreq; struct sockaddr_in *sa; memset(&addreq, 0, sizeof(addreq)); strlcpy(addreq.ifra_name, ifname, sizeof(addreq.ifra_name)); sa = (struct sockaddr_in *)(void *)&addreq.ifra_addr; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr = addr; sa = (struct sockaddr_in *)(void *)&addreq.ifra_mask; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr = mask; sa = (struct sockaddr_in *)(void *)&addreq.ifra_broadaddr; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr = bcast; return (ioctl(mib_netsock, SIOCAIFADDR, &addreq)); } /* * Exececute a SIOCDIFADDR */ static int siocdifaddr(const char *ifname, struct in_addr addr) { struct ifreq delreq; struct sockaddr_in *sa; memset(&delreq, 0, sizeof(delreq)); strlcpy(delreq.ifr_name, ifname, sizeof(delreq.ifr_name)); sa = (struct sockaddr_in *)(void *)&delreq.ifr_addr; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr = addr; return (ioctl(mib_netsock, SIOCDIFADDR, &delreq)); } /* * Verify an interface address without fetching the entire list */ static int verify_ifa(const char *name, struct mibifa *ifa) { struct ifreq req; struct sockaddr_in *sa; memset(&req, 0, sizeof(req)); strlcpy(req.ifr_name, name, sizeof(req.ifr_name)); sa = (struct sockaddr_in *)(void *)&req.ifr_addr; sa->sin_family = AF_INET; sa->sin_len = sizeof(*sa); sa->sin_addr = ifa->inaddr; if (ioctl(mib_netsock, SIOCGIFADDR, &req) == -1) return (-1); if (ifa->inaddr.s_addr != sa->sin_addr.s_addr) { syslog(LOG_ERR, "%s: address mismatch", __func__); return (-1); } if (ioctl(mib_netsock, SIOCGIFNETMASK, &req) == -1) return (-1); if (ifa->inmask.s_addr != sa->sin_addr.s_addr) { syslog(LOG_ERR, "%s: netmask mismatch", __func__); return (-1); } return (0); } /* * Restore a deleted interface address. Don't wait for the routing socket * to update us. */ void mib_undestroy_ifa(struct mibifa *ifa) { struct mibif *ifp; if ((ifp = mib_find_if(ifa->ifindex)) == NULL) /* keep it destroyed */ return; if (siocaifaddr(ifp->name, ifa->inaddr, ifa->inmask, ifa->inbcast)) /* keep it destroyed */ return; ifa->flags &= ~MIBIFA_DESTROYED; } /* * Destroy an interface address */ int mib_destroy_ifa(struct mibifa *ifa) { struct mibif *ifp; if ((ifp = mib_find_if(ifa->ifindex)) == NULL) { /* ups. */ mib_iflist_bad = 1; return (-1); } if (siocdifaddr(ifp->name, ifa->inaddr)) { /* ups. */ syslog(LOG_ERR, "SIOCDIFADDR: %m"); mib_iflist_bad = 1; return (-1); } ifa->flags |= MIBIFA_DESTROYED; return (0); } /* * Rollback the modification of an address. Don't bother to wait for * the routing socket. */ void mib_unmodify_ifa(struct mibifa *ifa) { struct mibif *ifp; if ((ifp = mib_find_if(ifa->ifindex)) == NULL) { /* ups. */ mib_iflist_bad = 1; return; } if (siocaifaddr(ifp->name, ifa->inaddr, ifa->inmask, ifa->inbcast)) { /* ups. */ mib_iflist_bad = 1; return; } } /* * Modify an IFA. */ int mib_modify_ifa(struct mibifa *ifa) { struct mibif *ifp; if ((ifp = mib_find_if(ifa->ifindex)) == NULL) { /* ups. */ mib_iflist_bad = 1; return (-1); } if (siocaifaddr(ifp->name, ifa->inaddr, ifa->inmask, ifa->inbcast)) { /* ups. */ mib_iflist_bad = 1; return (-1); } if (verify_ifa(ifp->name, ifa)) { /* ups. */ mib_iflist_bad = 1; return (-1); } return (0); } /* * Destroy a freshly created interface address. Don't bother to wait for * the routing socket. */ void mib_uncreate_ifa(struct mibifa *ifa) { struct mibif *ifp; if ((ifp = mib_find_if(ifa->ifindex)) == NULL) { /* ups. */ mib_iflist_bad = 1; return; } if (siocdifaddr(ifp->name, ifa->inaddr)) { /* ups. */ mib_iflist_bad = 1; return; } destroy_ifa(ifa); } /* * Create a new ifa and verify it */ struct mibifa * mib_create_ifa(u_int ifindex, struct in_addr addr, struct in_addr mask, struct in_addr bcast) { struct mibif *ifp; struct mibifa *ifa; if ((ifp = mib_find_if(ifindex)) == NULL) return (NULL); if ((ifa = alloc_ifa(ifindex, addr)) == NULL) return (NULL); ifa->inmask = mask; ifa->inbcast = bcast; if (siocaifaddr(ifp->name, ifa->inaddr, ifa->inmask, ifa->inbcast)) { syslog(LOG_ERR, "%s: %m", __func__); destroy_ifa(ifa); return (NULL); } if (verify_ifa(ifp->name, ifa)) { destroy_ifa(ifa); return (NULL); } return (ifa); } /* * Get all cloning interfaces and make them dynamic. * Hah! Whe should probably do this on a periodic basis (XXX). */ static void get_cloners(void) { struct if_clonereq req; char *buf, *cp; int i; memset(&req, 0, sizeof(req)); if (ioctl(mib_netsock, SIOCIFGCLONERS, &req) == -1) { syslog(LOG_ERR, "get cloners: %m"); return; } if ((buf = malloc(req.ifcr_total * IFNAMSIZ)) == NULL) { syslog(LOG_ERR, "%m"); return; } req.ifcr_count = req.ifcr_total; req.ifcr_buffer = buf; if (ioctl(mib_netsock, SIOCIFGCLONERS, &req) == -1) { syslog(LOG_ERR, "get cloners: %m"); free(buf); return; } for (cp = buf, i = 0; i < req.ifcr_total; i++, cp += IFNAMSIZ) mib_if_set_dyn(cp); free(buf); } /* * Idle function */ static void mibII_idle(void *arg __unused) { struct mibifa *ifa; if (mib_iflist_bad) { TAILQ_FOREACH(ifa, &mibifa_list, link) ifa->flags &= ~MIBIFA_DESTROYED; /* assume, that all cloning interfaces are dynamic */ get_cloners(); mib_refresh_iflist(); update_ifa_info(); mib_arp_update(); mib_iflist_bad = 0; } mib_arp_update(); } /* * Start the module */ static void mibII_start(void) { if ((route_fd = fd_select(route, route_input, NULL, module)) == NULL) { syslog(LOG_ERR, "fd_select(route): %m"); return; } mib_refresh_iflist(); update_ifa_info(); mib_arp_update(); (void)mib_fetch_route(); mib_iftable_last_change = 0; mib_ifstack_last_change = 0; ifmib_reg = or_register(&oid_ifMIB, "The MIB module to describe generic objects for network interface" " sub-layers.", module); ipmib_reg = or_register(&oid_ipMIB, "The MIB module for managing IP and ICMP implementations, but " "excluding their management of IP routes.", module); tcpmib_reg = or_register(&oid_tcpMIB, "The MIB module for managing TCP implementations.", module); udpmib_reg = or_register(&oid_udpMIB, "The MIB module for managing UDP implementations.", module); ipForward_reg = or_register(&oid_ipForward, "The MIB module for the display of CIDR multipath IP Routes.", module); mibII_poll_timer = NULL; mibII_poll_ticks = MIBII_POLL_TICKS; mibif_restart_mibII_poll_timer(); } /* * Initialize the module */ static int mibII_init(struct lmodule *mod, int argc __unused, char *argv[] __unused) { size_t len; module = mod; len = sizeof(clockinfo); if (sysctlbyname("kern.clockrate", &clockinfo, &len, NULL, 0) == -1) { syslog(LOG_ERR, "kern.clockrate: %m"); return (-1); } if (len != sizeof(clockinfo)) { syslog(LOG_ERR, "kern.clockrate: wrong size"); return (-1); } if ((route = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC)) == -1) { syslog(LOG_ERR, "PF_ROUTE: %m"); return (-1); } if ((mib_netsock = socket(PF_INET, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "PF_INET: %m"); (void)close(route); return (-1); } (void)shutdown(mib_netsock, SHUT_RDWR); /* assume, that all cloning interfaces are dynamic */ get_cloners(); return (0); } static int mibII_fini(void) { if (mibII_poll_timer != NULL ) { timer_stop(mibII_poll_timer); mibII_poll_timer = NULL; } if (route_fd != NULL) fd_deselect(route_fd); if (route != -1) (void)close(route); if (mib_netsock != -1) (void)close(mib_netsock); /* XXX free memory */ or_unregister(ipForward_reg); or_unregister(udpmib_reg); or_unregister(tcpmib_reg); or_unregister(ipmib_reg); or_unregister(ifmib_reg); return (0); } static void mibII_loading(const struct lmodule *mod, int loaded) { struct mibif *ifp; if (loaded == 1) return; TAILQ_FOREACH(ifp, &mibif_list, link) if (ifp->xnotify_mod == mod) { ifp->xnotify_mod = NULL; ifp->xnotify_data = NULL; ifp->xnotify = NULL; } mib_unregister_newif(mod); } extern const struct snmp_module config; const struct snmp_module config = { "This module implements the interface and ip groups.", mibII_init, mibII_fini, NULL, /* idle */ NULL, /* dump */ NULL, /* config */ mibII_start, NULL, mibII_ctree, mibII_CTREE_SIZE, mibII_loading }; /* * Should have a list of these attached to each interface. */ void * mibif_notify(struct mibif *ifp, const struct lmodule *mod, mibif_notify_f func, void *data) { ifp->xnotify = func; ifp->xnotify_data = data; ifp->xnotify_mod = mod; return (ifp); } void mibif_unnotify(void *arg) { struct mibif *ifp = arg; ifp->xnotify = NULL; ifp->xnotify_data = NULL; ifp->xnotify_mod = NULL; } Index: head/contrib/bsnmp/snmp_mibII/mibII.h =================================================================== --- head/contrib/bsnmp/snmp_mibII/mibII.h (revision 359511) +++ head/contrib/bsnmp/snmp_mibII/mibII.h (revision 359512) @@ -1,277 +1,276 @@ /* * 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/snmp_mibII/mibII.h,v 1.16 2006/02/14 09:04:19 brandt_h Exp $ * * Implementation of the interfaces and IP groups of MIB-II. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asn1.h" #include "snmp.h" #include "snmpmod.h" #include "snmp_mibII.h" #include "mibII_tree.h" -/* maximum size of the interface alias unless overridden with net.ifdescr_maxlen */ +/* maximum size of interface alias unless overridden with net.ifdescr_maxlen */ #define MIBIF_ALIAS_SIZE (64 + 1) #define MIBIF_ALIAS_SIZE_MAX 1024 /* * Interface list and flags. */ TAILQ_HEAD(mibif_list, mibif); enum { MIBIF_FOUND = 0x0001, MIBIF_HIGHSPEED = 0x0002, MIBIF_VERYHIGHSPEED = 0x0004, }; /* * Private mibif data - hang off from the mibif. */ struct mibif_private { uint64_t hc_inoctets; uint64_t hc_outoctets; uint64_t hc_omcasts; uint64_t hc_opackets; uint64_t hc_imcasts; uint64_t hc_ipackets; - }; #define MIBIF_PRIV(IFP) ((struct mibif_private *)((IFP)->private)) /* * Interface addresses. */ TAILQ_HEAD(mibifa_list, mibifa); enum { MIBIFA_FOUND = 0x0001, MIBIFA_DESTROYED = 0x0002, }; /* * Receive addresses */ TAILQ_HEAD(mibrcvaddr_list, mibrcvaddr); enum { MIBRCVADDR_FOUND = 0x00010000, }; /* * Interface index mapping. The problem here is, that if the same interface * is reinstantiated (for examble by unloading and loading the hardware driver) * we must use the same index for this interface. For dynamic interfaces * (clip, lane) we must use a fresh index, each time a new interface is created. * To differentiate between these types of interfaces we use the following table * which contains an entry for each dynamic interface type. All other interface * types are supposed to be static. The mibindexmap contains an entry for * all interfaces. The mibif pointer is NULL, if the interface doesn't exist * anymore. */ struct mibdynif { SLIST_ENTRY(mibdynif) link; char name[IFNAMSIZ]; }; SLIST_HEAD(mibdynif_list, mibdynif); struct mibindexmap { STAILQ_ENTRY(mibindexmap) link; u_short sysindex; u_int ifindex; struct mibif *mibif; /* may be NULL */ char name[IFNAMSIZ]; }; STAILQ_HEAD(mibindexmap_list, mibindexmap); /* * Interface stacking. The generic code cannot know how the interfaces stack. * For this reason it instantiates only the x.0 and 0.x table elements. All * others have to be instantiated by the interface specific modules. * The table is read-only. */ struct mibifstack { TAILQ_ENTRY(mibifstack) link; struct asn_oid index; }; TAILQ_HEAD(mibifstack_list, mibifstack); /* * NetToMediaTable (ArpTable) */ struct mibarp { TAILQ_ENTRY(mibarp) link; struct asn_oid index; /* contains both the ifindex and addr */ u_char phys[128]; /* the physical address */ u_int physlen; /* and its length */ u_int flags; }; TAILQ_HEAD(mibarp_list, mibarp); enum { MIBARP_FOUND = 0x00010000, MIBARP_PERM = 0x00000001, }; /* * New if registrations */ struct newifreg { TAILQ_ENTRY(newifreg) link; const struct lmodule *mod; int (*func)(struct mibif *); }; TAILQ_HEAD(newifreg_list, newifreg); /* list of all IP addresses */ extern struct mibifa_list mibifa_list; /* list of all interfaces */ extern struct mibif_list mibif_list; /* list of dynamic interface names */ extern struct mibdynif_list mibdynif_list; /* list of all interface index mappings */ extern struct mibindexmap_list mibindexmap_list; /* list of all stacking entries */ extern struct mibifstack_list mibifstack_list; /* list of all receive addresses */ extern struct mibrcvaddr_list mibrcvaddr_list; /* list of all NetToMedia entries */ extern struct mibarp_list mibarp_list; /* number of interfaces */ extern int32_t mib_if_number; /* last change of interface table */ extern uint64_t mib_iftable_last_change; /* last change of stack table */ extern uint64_t mib_ifstack_last_change; /* if this is set, one of our lists may be bad. refresh them when idle */ extern int mib_iflist_bad; /* last time refreshed */ extern uint64_t mibarpticks; /* info on system clocks */ extern struct clockinfo clockinfo; /* baud rate of fastest interface */ extern uint64_t mibif_maxspeed; /* user-forced update interval */ extern u_int mibif_force_hc_update_interval; /* current update interval */ extern u_int mibif_hc_update_interval; /* re-compute update interval */ void mibif_reset_hc_timer(void); /* interfaces' data poll interval */ extern u_int mibII_poll_ticks; /* restart the data poll timer */ void mibif_restart_mibII_poll_timer(void); #define MIBII_POLL_TICKS 100 /* get interfaces and interface addresses. */ void mib_fetch_interfaces(void); /* check whether this interface(type) is dynamic */ int mib_if_is_dyn(const char *name); /* destroy an interface address */ int mib_destroy_ifa(struct mibifa *); /* restituate a deleted interface address */ void mib_undestroy_ifa(struct mibifa *); /* change interface address */ int mib_modify_ifa(struct mibifa *); /* undo if address modification */ void mib_unmodify_ifa(struct mibifa *); /* create an interface address */ struct mibifa * mib_create_ifa(u_int ifindex, struct in_addr addr, struct in_addr mask, struct in_addr bcast); /* delete a freshly created address */ void mib_uncreate_ifa(struct mibifa *); /* create/delete arp entries */ struct mibarp *mib_arp_create(const struct mibif *, struct in_addr, const u_char *, size_t); void mib_arp_delete(struct mibarp *); /* find arp entry */ struct mibarp *mib_find_arp(const struct mibif *, struct in_addr); /* update arp table */ void mib_arp_update(void); /* fetch routing table */ u_char *mib_fetch_rtab(int af, int info, int arg, size_t *lenp); /* process routing message */ void mib_sroute_process(struct rt_msghdr *, struct sockaddr *, struct sockaddr *, struct sockaddr *); /* send a routing message */ void mib_send_rtmsg(struct rt_msghdr *, struct sockaddr *, struct sockaddr *, struct sockaddr *); /* extract addresses from routing message */ void mib_extract_addrs(int, u_char *, struct sockaddr **); /* fetch routing table */ int mib_fetch_route(void); Index: head/contrib/bsnmp/snmp_usm/usm_snmp.c =================================================================== --- head/contrib/bsnmp/snmp_usm/usm_snmp.c (revision 359511) +++ head/contrib/bsnmp/snmp_usm/usm_snmp.c (revision 359512) @@ -1,620 +1,620 @@ /*- - * Copyright (c) 2010 The FreeBSD Foundation + * Copyright (c) 2010,2018 The FreeBSD Foundation * All rights reserved. * * This software was developed by Shteryana Sotirova Shopova under * sponsorship from the FreeBSD Foundation. * * 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 THE 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 THE 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include "asn1.h" #include "snmp.h" #include "snmpmod.h" #define SNMPTREE_TYPES #include "usm_tree.h" #include "usm_oid.h" static struct lmodule *usm_module; /* For the registration. */ static const struct asn_oid oid_usm = OIDX_snmpUsmMIB; static const struct asn_oid oid_usmNoAuthProtocol = OIDX_usmNoAuthProtocol; static const struct asn_oid oid_usmHMACMD5AuthProtocol = \ OIDX_usmHMACMD5AuthProtocol; static const struct asn_oid oid_usmHMACSHAAuthProtocol = \ OIDX_usmHMACSHAAuthProtocol; static const struct asn_oid oid_usmNoPrivProtocol = OIDX_usmNoPrivProtocol; static const struct asn_oid oid_usmDESPrivProtocol = OIDX_usmDESPrivProtocol; static const struct asn_oid oid_usmAesCfb128Protocol = OIDX_usmAesCfb128Protocol; static const struct asn_oid oid_usmUserSecurityName = OIDX_usmUserSecurityName; /* The registration. */ static uint reg_usm; static int32_t usm_lock; static struct usm_user * usm_get_user(const struct asn_oid *, uint); static struct usm_user * usm_get_next_user(const struct asn_oid *, uint); static void usm_append_userindex(struct asn_oid *, uint, const struct usm_user *); static int usm_user_index_decode(const struct asn_oid *, uint, uint8_t *, uint32_t *, char *); int op_usm_stats(struct snmp_context *ctx __unused, struct snmp_value *val, uint32_t sub __unused, uint32_t iidx __unused, enum snmp_op op) { struct snmpd_usmstat *usmstats; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if ((usmstats = bsnmpd_get_usm_stats()) == NULL) return (SNMP_ERR_GENERR); if (op == SNMP_OP_GET) { switch (val->var.subs[sub - 1]) { case LEAF_usmStatsUnsupportedSecLevels: val->v.uint32 = usmstats->unsupported_seclevels; break; case LEAF_usmStatsNotInTimeWindows: val->v.uint32 = usmstats->not_in_time_windows; break; case LEAF_usmStatsUnknownUserNames: val->v.uint32 = usmstats->unknown_users; break; case LEAF_usmStatsUnknownEngineIDs: val->v.uint32 = usmstats->unknown_engine_ids; break; case LEAF_usmStatsWrongDigests: val->v.uint32 = usmstats->wrong_digests; break; case LEAF_usmStatsDecryptionErrors: val->v.uint32 = usmstats->decrypt_errors; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int op_usm_lock(struct snmp_context *ctx __unused, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { if (val->var.subs[sub - 1] != LEAF_usmUserSpinLock) return (SNMP_ERR_NOSUCHNAME); switch (op) { case SNMP_OP_GET: if (++usm_lock == INT32_MAX) usm_lock = 0; val->v.integer = usm_lock; break; case SNMP_OP_GETNEXT: abort(); case SNMP_OP_SET: if (val->v.integer != usm_lock) return (SNMP_ERR_INCONS_VALUE); break; case SNMP_OP_ROLLBACK: /* FALLTHROUGH */ case SNMP_OP_COMMIT: break; } return (SNMP_ERR_NOERROR); } int op_usm_users(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { uint32_t elen; struct usm_user *uuser, *clone; char uname[SNMP_ADM_STR32_SIZ]; uint8_t eid[SNMP_ENGINE_ID_SIZ]; switch (op) { case SNMP_OP_GET: if ((uuser = usm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_GETNEXT: if ((uuser = usm_get_next_user(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); usm_append_userindex(&val->var, sub, uuser); break; case SNMP_OP_SET: if ((uuser = usm_get_user(&val->var, sub)) == NULL && val->var.subs[sub - 1] != LEAF_usmUserStatus && val->var.subs[sub - 1] != LEAF_usmUserCloneFrom) return (SNMP_ERR_NOSUCHNAME); /* * XXX (ngie): need to investigate the MIB to determine how * this is possible given some of the transitions below. */ if (community != COMM_INITIALIZE && uuser != NULL && uuser->type == StorageType_readOnly) return (SNMP_ERR_NOT_WRITEABLE); switch (val->var.subs[sub - 1]) { case LEAF_usmUserSecurityName: return (SNMP_ERR_NOT_WRITEABLE); case LEAF_usmUserCloneFrom: if (uuser != NULL || usm_user_index_decode(&val->var, sub, eid, &elen, uname) < 0 || !(asn_is_suboid(&oid_usmUserSecurityName, &val->v.oid))) return (SNMP_ERR_WRONG_VALUE); if ((clone = usm_get_user(&val->v.oid, sub)) == NULL) return (SNMP_ERR_INCONS_VALUE); if ((uuser = usm_new_user(eid, elen, uname)) == NULL) return (SNMP_ERR_GENERR); uuser->status = RowStatus_notReady; if (community != COMM_INITIALIZE) uuser->type = StorageType_volatile; else uuser->type = StorageType_readOnly; uuser->suser.auth_proto = clone->suser.auth_proto; uuser->suser.priv_proto = clone->suser.priv_proto; memcpy(uuser->suser.auth_key, clone->suser.auth_key, sizeof(uuser->suser.auth_key)); memcpy(uuser->suser.priv_key, clone->suser.priv_key, sizeof(uuser->suser.priv_key)); ctx->scratch->int1 = RowStatus_createAndWait; break; case LEAF_usmUserAuthProtocol: ctx->scratch->int1 = uuser->suser.auth_proto; if (asn_compare_oid(&oid_usmNoAuthProtocol, &val->v.oid) == 0) uuser->suser.auth_proto = SNMP_AUTH_NOAUTH; else if (asn_compare_oid(&oid_usmHMACMD5AuthProtocol, &val->v.oid) == 0) uuser->suser.auth_proto = SNMP_AUTH_HMAC_MD5; else if (asn_compare_oid(&oid_usmHMACSHAAuthProtocol, &val->v.oid) == 0) uuser->suser.auth_proto = SNMP_AUTH_HMAC_SHA; else return (SNMP_ERR_WRONG_VALUE); break; case LEAF_usmUserAuthKeyChange: case LEAF_usmUserOwnAuthKeyChange: if (val->var.subs[sub - 1] == LEAF_usmUserOwnAuthKeyChange && (usm_user == NULL || strcmp(uuser->suser.sec_name, usm_user->suser.sec_name) != 0)) return (SNMP_ERR_NO_ACCESS); if (val->v.octetstring.len > SNMP_AUTH_KEY_SIZ) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->ptr1 = malloc(SNMP_AUTH_KEY_SIZ); if (ctx->scratch->ptr1 == NULL) return (SNMP_ERR_GENERR); memcpy(ctx->scratch->ptr1, uuser->suser.auth_key, SNMP_AUTH_KEY_SIZ); memcpy(uuser->suser.auth_key, val->v.octetstring.octets, val->v.octetstring.len); break; case LEAF_usmUserPrivProtocol: ctx->scratch->int1 = uuser->suser.priv_proto; if (asn_compare_oid(&oid_usmNoPrivProtocol, &val->v.oid) == 0) uuser->suser.priv_proto = SNMP_PRIV_NOPRIV; else if (asn_compare_oid(&oid_usmDESPrivProtocol, &val->v.oid) == 0) uuser->suser.priv_proto = SNMP_PRIV_DES; else if (asn_compare_oid(&oid_usmAesCfb128Protocol, &val->v.oid) == 0) uuser->suser.priv_proto = SNMP_PRIV_AES; else return (SNMP_ERR_WRONG_VALUE); break; case LEAF_usmUserPrivKeyChange: case LEAF_usmUserOwnPrivKeyChange: if (val->var.subs[sub - 1] == LEAF_usmUserOwnPrivKeyChange && (usm_user == NULL || strcmp(uuser->suser.sec_name, usm_user->suser.sec_name) != 0)) return (SNMP_ERR_NO_ACCESS); if (val->v.octetstring.len > SNMP_PRIV_KEY_SIZ) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->ptr1 = malloc(SNMP_PRIV_KEY_SIZ); if (ctx->scratch->ptr1 == NULL) return (SNMP_ERR_GENERR); memcpy(ctx->scratch->ptr1, uuser->suser.priv_key, sizeof(uuser->suser.priv_key)); memcpy(uuser->suser.priv_key, val->v.octetstring.octets, val->v.octetstring.len); break; case LEAF_usmUserPublic: if (val->v.octetstring.len > SNMP_ADM_STR32_SIZ) return (SNMP_ERR_INCONS_VALUE); if (uuser->user_public_len > 0) { ctx->scratch->ptr2 = malloc(uuser->user_public_len); if (ctx->scratch->ptr2 == NULL) return (SNMP_ERR_GENERR); memcpy(ctx->scratch->ptr2, uuser->user_public, uuser->user_public_len); ctx->scratch->int2 = uuser->user_public_len; } if (val->v.octetstring.len > 0) { memcpy(uuser->user_public, val->v.octetstring.octets, val->v.octetstring.len); uuser->user_public_len = val->v.octetstring.len; } else { memset(uuser->user_public, 0, sizeof(uuser->user_public)); uuser->user_public_len = 0; } break; case LEAF_usmUserStorageType: return (SNMP_ERR_INCONS_VALUE); case LEAF_usmUserStatus: if (uuser == NULL) { if (val->v.integer != RowStatus_createAndWait || usm_user_index_decode(&val->var, sub, eid, &elen, uname) < 0) return (SNMP_ERR_INCONS_VALUE); uuser = usm_new_user(eid, elen, uname); if (uuser == NULL) return (SNMP_ERR_GENERR); uuser->status = RowStatus_notReady; if (community != COMM_INITIALIZE) uuser->type = StorageType_volatile; else uuser->type = StorageType_readOnly; } else if (val->v.integer != RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); uuser->status = val->v.integer; break; } return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: switch (val->var.subs[sub - 1]) { case LEAF_usmUserAuthKeyChange: case LEAF_usmUserOwnAuthKeyChange: case LEAF_usmUserPrivKeyChange: case LEAF_usmUserOwnPrivKeyChange: free(ctx->scratch->ptr1); break; case LEAF_usmUserPublic: if (ctx->scratch->ptr2 != NULL) free(ctx->scratch->ptr2); break; case LEAF_usmUserStatus: if (val->v.integer != RowStatus_destroy) break; if ((uuser = usm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); usm_delete_user(uuser); break; default: break; } return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: if ((uuser = usm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->var.subs[sub - 1]) { case LEAF_usmUserAuthProtocol: uuser->suser.auth_proto = ctx->scratch->int1; break; case LEAF_usmUserAuthKeyChange: case LEAF_usmUserOwnAuthKeyChange: memcpy(uuser->suser.auth_key, ctx->scratch->ptr1, sizeof(uuser->suser.auth_key)); free(ctx->scratch->ptr1); break; case LEAF_usmUserPrivProtocol: uuser->suser.priv_proto = ctx->scratch->int1; break; case LEAF_usmUserPrivKeyChange: case LEAF_usmUserOwnPrivKeyChange: memcpy(uuser->suser.priv_key, ctx->scratch->ptr1, sizeof(uuser->suser.priv_key)); free(ctx->scratch->ptr1); break; case LEAF_usmUserPublic: if (ctx->scratch->ptr2 != NULL) { memcpy(uuser->user_public, ctx->scratch->ptr2, ctx->scratch->int2); uuser->user_public_len = ctx->scratch->int2; free(ctx->scratch->ptr2); } else { memset(uuser->user_public, 0, sizeof(uuser->user_public)); uuser->user_public_len = 0; } break; case LEAF_usmUserCloneFrom: case LEAF_usmUserStatus: if (ctx->scratch->int1 == RowStatus_createAndWait) usm_delete_user(uuser); break; default: break; } return (SNMP_ERR_NOERROR); default: abort(); } switch (val->var.subs[sub - 1]) { case LEAF_usmUserSecurityName: return (string_get(val, uuser->suser.sec_name, -1)); case LEAF_usmUserCloneFrom: memcpy(&val->v.oid, &oid_zeroDotZero, sizeof(oid_zeroDotZero)); break; case LEAF_usmUserAuthProtocol: switch (uuser->suser.auth_proto) { case SNMP_AUTH_HMAC_MD5: memcpy(&val->v.oid, &oid_usmHMACMD5AuthProtocol, sizeof(oid_usmHMACMD5AuthProtocol)); break; case SNMP_AUTH_HMAC_SHA: memcpy(&val->v.oid, &oid_usmHMACSHAAuthProtocol, sizeof(oid_usmHMACSHAAuthProtocol)); break; default: memcpy(&val->v.oid, &oid_usmNoAuthProtocol, sizeof(oid_usmNoAuthProtocol)); break; } break; case LEAF_usmUserAuthKeyChange: case LEAF_usmUserOwnAuthKeyChange: return (string_get(val, (char *)uuser->suser.auth_key, 0)); case LEAF_usmUserPrivProtocol: switch (uuser->suser.priv_proto) { case SNMP_PRIV_DES: memcpy(&val->v.oid, &oid_usmDESPrivProtocol, sizeof(oid_usmDESPrivProtocol)); break; case SNMP_PRIV_AES: memcpy(&val->v.oid, &oid_usmAesCfb128Protocol, sizeof(oid_usmAesCfb128Protocol)); break; default: memcpy(&val->v.oid, &oid_usmNoPrivProtocol, sizeof(oid_usmNoPrivProtocol)); break; } break; case LEAF_usmUserPrivKeyChange: case LEAF_usmUserOwnPrivKeyChange: return (string_get(val, (char *)uuser->suser.priv_key, 0)); case LEAF_usmUserPublic: return (string_get(val, uuser->user_public, uuser->user_public_len)); case LEAF_usmUserStorageType: val->v.integer = uuser->type; break; case LEAF_usmUserStatus: val->v.integer = uuser->status; break; } return (SNMP_ERR_NOERROR); } static int usm_user_index_decode(const struct asn_oid *oid, uint sub, uint8_t *engine, uint32_t *elen, char *uname) { uint32_t i, nlen; int uname_off; if (oid->subs[sub] > SNMP_ENGINE_ID_SIZ) return (-1); for (i = 0; i < oid->subs[sub]; i++) engine[i] = oid->subs[sub + i + 1]; *elen = i; uname_off = sub + oid->subs[sub] + 1; if ((nlen = oid->subs[uname_off]) >= SNMP_ADM_STR32_SIZ) return (-1); for (i = 0; i < nlen; i++) uname[i] = oid->subs[uname_off + i + 1]; uname[nlen] = '\0'; return (0); } static void usm_append_userindex(struct asn_oid *oid, uint sub, const struct usm_user *uuser) { uint32_t i; oid->len = sub + uuser->user_engine_len + strlen(uuser->suser.sec_name); oid->len += 2; oid->subs[sub] = uuser->user_engine_len; for (i = 1; i < uuser->user_engine_len + 1; i++) oid->subs[sub + i] = uuser->user_engine_id[i - 1]; sub += uuser->user_engine_len + 1; oid->subs[sub] = strlen(uuser->suser.sec_name); for (i = 1; i <= oid->subs[sub]; i++) oid->subs[sub + i] = uuser->suser.sec_name[i - 1]; } static struct usm_user * usm_get_user(const struct asn_oid *oid, uint sub) { uint32_t enginelen; char username[SNMP_ADM_STR32_SIZ]; uint8_t engineid[SNMP_ENGINE_ID_SIZ]; if (usm_user_index_decode(oid, sub, engineid, &enginelen, username) < 0) return (NULL); return (usm_find_user(engineid, enginelen, username)); } static struct usm_user * usm_get_next_user(const struct asn_oid *oid, uint sub) { uint32_t enginelen; char username[SNMP_ADM_STR32_SIZ]; uint8_t engineid[SNMP_ENGINE_ID_SIZ]; struct usm_user *uuser; if (oid->len - sub == 0) return (usm_first_user()); if (usm_user_index_decode(oid, sub, engineid, &enginelen, username) < 0) return (NULL); if ((uuser = usm_find_user(engineid, enginelen, username)) != NULL) return (usm_next_user(uuser)); return (NULL); } /* * USM snmp module initialization hook. * Returns 0 on success, < 0 on error. */ static int usm_init(struct lmodule * mod, int argc __unused, char *argv[] __unused) { usm_module = mod; usm_lock = random(); bsnmpd_reset_usm_stats(); return (0); } /* * USM snmp module finalization hook. */ static int usm_fini(void) { usm_flush_users(); or_unregister(reg_usm); return (0); } /* * USM snmp module start operation. */ static void usm_start(void) { reg_usm = or_register(&oid_usm, "The MIB module for managing SNMP User-Based Security Model.", usm_module); } static void usm_dump(void) { struct usm_user *uuser; struct snmpd_usmstat *usmstats; const char *const authstr[] = { "noauth", "md5", "sha", NULL }; const char *const privstr[] = { "nopriv", "des", "aes", NULL }; if ((usmstats = bsnmpd_get_usm_stats()) != NULL) { syslog(LOG_ERR, "UnsupportedSecLevels\t\t%u", usmstats->unsupported_seclevels); syslog(LOG_ERR, "NotInTimeWindows\t\t%u", usmstats->not_in_time_windows); syslog(LOG_ERR, "UnknownUserNames\t\t%u", usmstats->unknown_users); syslog(LOG_ERR, "UnknownEngineIDs\t\t%u", usmstats->unknown_engine_ids); syslog(LOG_ERR, "WrongDigests\t\t%u", usmstats->wrong_digests); syslog(LOG_ERR, "DecryptionErrors\t\t%u", usmstats->decrypt_errors); } syslog(LOG_ERR, "USM users"); for (uuser = usm_first_user(); uuser != NULL; (uuser = usm_next_user(uuser))) syslog(LOG_ERR, "user %s\t\t%s, %s", uuser->suser.sec_name, authstr[uuser->suser.auth_proto], privstr[uuser->suser.priv_proto]); } -static const char usm_comment[] = \ +static const char usm_comment[] = "This module implements SNMP User-based Security Model defined in RFC 3414."; extern const struct snmp_module config; const struct snmp_module config = { .comment = usm_comment, .init = usm_init, .fini = usm_fini, .start = usm_start, .tree = usm_ctree, .dump = usm_dump, .tree_size = usm_CTREE_SIZE, }; Index: head/contrib/bsnmp/snmp_vacm/vacm_snmp.c =================================================================== --- head/contrib/bsnmp/snmp_vacm/vacm_snmp.c (revision 359511) +++ head/contrib/bsnmp/snmp_vacm/vacm_snmp.c (revision 359512) @@ -1,1028 +1,1028 @@ /*- * Copyright (c) 2010,2018 The FreeBSD Foundation * All rights reserved. * * This software was developed by Shteryana Sotirova Shopova under * sponsorship from the FreeBSD Foundation. * * 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 THE 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 THE 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include "asn1.h" #include "snmp.h" #include "snmpmod.h" #define SNMPTREE_TYPES #include "vacm_tree.h" #include "vacm_oid.h" static struct lmodule *vacm_module; /* For the registration. */ static const struct asn_oid oid_vacm = OIDX_snmpVacmMIB; static uint reg_vacm; static int32_t vacm_lock; /* * Internal datastructures and forward declarations. */ static void vacm_append_userindex(struct asn_oid *, uint, const struct vacm_user *); static int vacm_user_index_decode(const struct asn_oid *, uint, int32_t *, char *); static struct vacm_user *vacm_get_user(const struct asn_oid *, uint); static struct vacm_user *vacm_get_next_user(const struct asn_oid *, uint); static void vacm_append_access_rule_index(struct asn_oid *, uint, const struct vacm_access *); static int vacm_access_rule_index_decode(const struct asn_oid *, uint, char *, char *, int32_t *, int32_t *); static struct vacm_access * vacm_get_access_rule(const struct asn_oid *, uint); static struct vacm_access * vacm_get_next_access_rule(const struct asn_oid *, uint); static int vacm_view_index_decode(const struct asn_oid *, uint, char *, struct asn_oid *); static void vacm_append_viewindex(struct asn_oid *, uint, const struct vacm_view *); static struct vacm_view *vacm_get_view(const struct asn_oid *, uint); static struct vacm_view *vacm_get_next_view(const struct asn_oid *, uint); static struct vacm_view *vacm_get_view_by_name(u_char *, u_int); static struct vacm_context *vacm_get_context(const struct asn_oid *, uint); static struct vacm_context *vacm_get_next_context(const struct asn_oid *, uint); static void vacm_append_ctxindex(struct asn_oid *, uint, const struct vacm_context *); int op_vacm_context(struct snmp_context *ctx __unused, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { char cname[SNMP_ADM_STR32_SIZ]; size_t cnamelen; struct vacm_context *vacm_ctx; if (val->var.subs[sub - 1] != LEAF_vacmContextName) abort(); switch (op) { case SNMP_OP_GET: if ((vacm_ctx = vacm_get_context(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_GETNEXT: if ((vacm_ctx = vacm_get_next_context(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); vacm_append_ctxindex(&val->var, sub, vacm_ctx); break; case SNMP_OP_SET: if ((vacm_ctx = vacm_get_context(&val->var, sub)) != NULL) return (SNMP_ERR_WRONG_VALUE); if (community != COMM_INITIALIZE) return (SNMP_ERR_NOT_WRITEABLE); if (val->var.subs[sub] >= SNMP_ADM_STR32_SIZ) return (SNMP_ERR_WRONG_VALUE); if (index_decode(&val->var, sub, iidx, &cname, &cnamelen)) return (SNMP_ERR_GENERR); cname[cnamelen] = '\0'; if ((vacm_ctx = vacm_add_context(cname, reg_vacm)) == NULL) return (SNMP_ERR_GENERR); return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: /* FALLTHROUGH*/ case SNMP_OP_ROLLBACK: return (SNMP_ERR_NOERROR); default: abort(); } return (string_get(val, vacm_ctx->ctxname, -1)); } int op_vacm_security_to_group(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { int32_t smodel; char uname[SNMP_ADM_STR32_SIZ]; struct vacm_user *user; switch (op) { case SNMP_OP_GET: if ((user = vacm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_GETNEXT: if ((user = vacm_get_next_user(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); vacm_append_userindex(&val->var, sub, user); break; case SNMP_OP_SET: if ((user = vacm_get_user(&val->var, sub)) == NULL && val->var.subs[sub - 1] != LEAF_vacmSecurityToGroupStatus) return (SNMP_ERR_NOSUCHNAME); if (user != NULL) { if (community != COMM_INITIALIZE && user->type == StorageType_readOnly) return (SNMP_ERR_NOT_WRITEABLE); if (user->status == RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); } switch (val->var.subs[sub - 1]) { case LEAF_vacmGroupName: ctx->scratch->ptr1 = user->group->groupname; ctx->scratch->int1 = strlen(user->group->groupname); return (vacm_user_set_group(user, val->v.octetstring.octets,val->v.octetstring.len)); case LEAF_vacmSecurityToGroupStorageType: return (SNMP_ERR_INCONS_VALUE); case LEAF_vacmSecurityToGroupStatus: if (user == NULL) { if (val->v.integer != RowStatus_createAndGo || vacm_user_index_decode(&val->var, sub, &smodel, uname) < 0) return (SNMP_ERR_INCONS_VALUE); user = vacm_new_user(smodel, uname); if (user == NULL) return (SNMP_ERR_GENERR); user->status = RowStatus_destroy; if (community != COMM_INITIALIZE) user->type = StorageType_volatile; else user->type = StorageType_readOnly; } else if (val->v.integer != RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = user->status; user->status = val->v.integer; break; } return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: if (val->var.subs[sub - 1] != LEAF_vacmSecurityToGroupStatus) return (SNMP_ERR_NOERROR); if ((user = vacm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->v.integer) { case RowStatus_destroy: return (vacm_delete_user(user)); case RowStatus_createAndGo: user->status = RowStatus_active; break; default: break; } return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: if ((user = vacm_get_user(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->var.subs[sub - 1]) { case LEAF_vacmGroupName: return (vacm_user_set_group(user, ctx->scratch->ptr1, ctx->scratch->int1)); case LEAF_vacmSecurityToGroupStatus: if (ctx->scratch->int1 == RowStatus_destroy) return (vacm_delete_user(user)); user->status = ctx->scratch->int1; break; default: break; } return (SNMP_ERR_NOERROR); default: abort(); } switch (val->var.subs[sub - 1]) { case LEAF_vacmGroupName: return (string_get(val, user->group->groupname, -1)); case LEAF_vacmSecurityToGroupStorageType: val->v.integer = user->type; break; case LEAF_vacmSecurityToGroupStatus: val->v.integer = user->status; break; default: abort(); } return (SNMP_ERR_NOERROR); } int op_vacm_access(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { int32_t smodel, slevel; char gname[SNMP_ADM_STR32_SIZ], cprefix[SNMP_ADM_STR32_SIZ]; struct vacm_access *acl; switch (op) { case SNMP_OP_GET: if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_GETNEXT: if ((acl = vacm_get_next_access_rule(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); vacm_append_access_rule_index(&val->var, sub, acl); break; case SNMP_OP_SET: if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL && val->var.subs[sub - 1] != LEAF_vacmAccessStatus) return (SNMP_ERR_NOSUCHNAME); if (acl != NULL && community != COMM_INITIALIZE && acl->type == StorageType_readOnly) return (SNMP_ERR_NOT_WRITEABLE); switch (val->var.subs[sub - 1]) { case LEAF_vacmAccessContextMatch: ctx->scratch->int1 = acl->ctx_match; if (val->v.integer == vacmAccessContextMatch_exact) acl->ctx_match = 1; else if (val->v.integer == vacmAccessContextMatch_prefix) acl->ctx_match = 0; else return (SNMP_ERR_WRONG_VALUE); break; case LEAF_vacmAccessReadViewName: ctx->scratch->ptr1 = acl->read_view; acl->read_view = vacm_get_view_by_name(val->v.octetstring.octets, val->v.octetstring.len); if (acl->read_view == NULL) { acl->read_view = ctx->scratch->ptr1; return (SNMP_ERR_INCONS_VALUE); } return (SNMP_ERR_NOERROR); case LEAF_vacmAccessWriteViewName: ctx->scratch->ptr1 = acl->write_view; if ((acl->write_view = vacm_get_view_by_name(val->v.octetstring.octets, val->v.octetstring.len)) == NULL) { acl->write_view = ctx->scratch->ptr1; return (SNMP_ERR_INCONS_VALUE); } break; case LEAF_vacmAccessNotifyViewName: ctx->scratch->ptr1 = acl->notify_view; if ((acl->notify_view = vacm_get_view_by_name(val->v.octetstring.octets, val->v.octetstring.len)) == NULL) { acl->notify_view = ctx->scratch->ptr1; return (SNMP_ERR_INCONS_VALUE); } break; case LEAF_vacmAccessStorageType: return (SNMP_ERR_INCONS_VALUE); case LEAF_vacmAccessStatus: if (acl == NULL) { if (val->v.integer != RowStatus_createAndGo || vacm_access_rule_index_decode(&val->var, sub, gname, cprefix, &smodel, &slevel) < 0) return (SNMP_ERR_INCONS_VALUE); if ((acl = vacm_new_access_rule(gname, cprefix, smodel, slevel)) == NULL) return (SNMP_ERR_GENERR); acl->status = RowStatus_destroy; if (community != COMM_INITIALIZE) acl->type = StorageType_volatile; else acl->type = StorageType_readOnly; } else if (val->v.integer != RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = acl->status; acl->status = val->v.integer; break; } return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: if (val->var.subs[sub - 1] != LEAF_vacmAccessStatus) return (SNMP_ERR_NOERROR); if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); if (val->v.integer == RowStatus_destroy) return (vacm_delete_access_rule(acl)); else acl->status = RowStatus_active; return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->var.subs[sub - 1]) { case LEAF_vacmAccessContextMatch: acl->ctx_match = ctx->scratch->int1; break; case LEAF_vacmAccessReadViewName: acl->read_view = ctx->scratch->ptr1; break; case LEAF_vacmAccessWriteViewName: acl->write_view = ctx->scratch->ptr1; break; case LEAF_vacmAccessNotifyViewName: acl->notify_view = ctx->scratch->ptr1; break; case LEAF_vacmAccessStatus: if (ctx->scratch->int1 == RowStatus_destroy) return (vacm_delete_access_rule(acl)); default: break; } return (SNMP_ERR_NOERROR); default: abort(); } switch (val->var.subs[sub - 1]) { case LEAF_vacmAccessContextMatch: return (string_get(val, acl->ctx_prefix, -1)); case LEAF_vacmAccessReadViewName: if (acl->read_view != NULL) return (string_get(val, acl->read_view->viewname, -1)); else return (string_get(val, NULL, 0)); case LEAF_vacmAccessWriteViewName: if (acl->write_view != NULL) return (string_get(val, acl->write_view->viewname, -1)); else return (string_get(val, NULL, 0)); case LEAF_vacmAccessNotifyViewName: if (acl->notify_view != NULL) return (string_get(val, acl->notify_view->viewname, -1)); else return (string_get(val, NULL, 0)); case LEAF_vacmAccessStorageType: val->v.integer = acl->type; break; case LEAF_vacmAccessStatus: val->v.integer = acl->status; break; default: abort(); } return (SNMP_ERR_NOERROR); } int op_vacm_view_lock(struct snmp_context *ctx __unused, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { if (val->var.subs[sub - 1] != LEAF_vacmViewSpinLock) return (SNMP_ERR_NOSUCHNAME); switch (op) { case SNMP_OP_GET: if (++vacm_lock == INT32_MAX) vacm_lock = 0; val->v.integer = vacm_lock; break; case SNMP_OP_GETNEXT: abort(); case SNMP_OP_SET: if (val->v.integer != vacm_lock) return (SNMP_ERR_INCONS_VALUE); break; case SNMP_OP_ROLLBACK: /* FALLTHROUGH */ case SNMP_OP_COMMIT: break; } return (SNMP_ERR_NOERROR); } int op_vacm_view(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, uint32_t iidx __unused, enum snmp_op op) { char vname[SNMP_ADM_STR32_SIZ]; struct asn_oid oid; struct vacm_view *view; switch (op) { case SNMP_OP_GET: if ((view = vacm_get_view(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_GETNEXT: if ((view = vacm_get_next_view(&val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); vacm_append_viewindex(&val->var, sub, view); break; case SNMP_OP_SET: if ((view = vacm_get_view(&val->var, sub)) == NULL && val->var.subs[sub - 1] != LEAF_vacmViewTreeFamilyStatus) return (SNMP_ERR_NOSUCHNAME); if (view != NULL) { if (community != COMM_INITIALIZE && view->type == StorageType_readOnly) return (SNMP_ERR_NOT_WRITEABLE); if (view->status == RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); } switch (val->var.subs[sub - 1]) { case LEAF_vacmViewTreeFamilyMask: if (val->v.octetstring.len > sizeof(view->mask)) ctx->scratch->ptr1 = malloc(sizeof(view->mask)); if (ctx->scratch->ptr1 == NULL) return (SNMP_ERR_GENERR); memset(ctx->scratch->ptr1, 0, sizeof(view->mask)); memcpy(ctx->scratch->ptr1, view->mask, sizeof(view->mask)); memset(view->mask, 0, sizeof(view->mask)); memcpy(view->mask, val->v.octetstring.octets, val->v.octetstring.len); break; case LEAF_vacmViewTreeFamilyType: ctx->scratch->int1 = view->exclude; if (val->v.integer == vacmViewTreeFamilyType_included) view->exclude = 0; else if (val->v.integer == vacmViewTreeFamilyType_excluded) view->exclude = 1; else return (SNMP_ERR_WRONG_VALUE); break; case LEAF_vacmViewTreeFamilyStorageType: return (SNMP_ERR_INCONS_VALUE); case LEAF_vacmViewTreeFamilyStatus: if (view == NULL) { if (val->v.integer != RowStatus_createAndGo || vacm_view_index_decode(&val->var, sub, vname, &oid) < 0) return (SNMP_ERR_INCONS_VALUE); if ((view = vacm_new_view(vname, &oid)) == NULL) return (SNMP_ERR_GENERR); view->status = RowStatus_destroy; if (community != COMM_INITIALIZE) view->type = StorageType_volatile; else view->type = StorageType_readOnly; } else if (val->v.integer != RowStatus_active && val->v.integer != RowStatus_destroy) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = view->status; view->status = val->v.integer; break; } return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: switch (val->var.subs[sub - 1]) { case LEAF_vacmViewTreeFamilyMask: free(ctx->scratch->ptr1); break; case LEAF_vacmViewTreeFamilyStatus: if ((view = vacm_get_view(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->v.integer) { case RowStatus_destroy: return (vacm_delete_view(view)); case RowStatus_createAndGo: view->status = RowStatus_active; break; default: /* NOTREACHED*/ return (SNMP_ERR_GENERR); } default: break; } return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: if ((view = vacm_get_view(&val->var, sub)) == NULL) return (SNMP_ERR_GENERR); switch (val->var.subs[sub - 1]) { case LEAF_vacmViewTreeFamilyMask: memcpy(view->mask, ctx->scratch->ptr1, sizeof(view->mask)); free(ctx->scratch->ptr1); break; case LEAF_vacmViewTreeFamilyType: view->exclude = ctx->scratch->int1; break; case LEAF_vacmViewTreeFamilyStatus: if (ctx->scratch->int1 == RowStatus_destroy) return (vacm_delete_view(view)); break; default: break; } return (SNMP_ERR_NOERROR); default: abort(); } switch (val->var.subs[sub - 1]) { case LEAF_vacmViewTreeFamilyMask: return (string_get(val, view->mask, sizeof(view->mask))); case LEAF_vacmViewTreeFamilyType: if (view->exclude) val->v.integer = vacmViewTreeFamilyType_excluded; else val->v.integer = vacmViewTreeFamilyType_included; break; case LEAF_vacmViewTreeFamilyStorageType: val->v.integer = view->type; break; case LEAF_vacmViewTreeFamilyStatus: val->v.integer = view->status; break; default: abort(); } return (SNMP_ERR_NOERROR); } static void vacm_append_userindex(struct asn_oid *oid, uint sub, const struct vacm_user *user) { uint32_t i; oid->len = sub + strlen(user->secname) + 2; oid->subs[sub++] = user->sec_model; oid->subs[sub] = strlen(user->secname); for (i = 1; i <= strlen(user->secname); i++) oid->subs[sub + i] = user->secname[i - 1]; } static int vacm_user_index_decode(const struct asn_oid *oid, uint sub, int32_t *smodel, char *uname) { uint32_t i; *smodel = oid->subs[sub++]; if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (-1); for (i = 0; i < oid->subs[sub]; i++) uname[i] = oid->subs[sub + i + 1]; uname[i] = '\0'; return (0); } static struct vacm_user * vacm_get_user(const struct asn_oid *oid, uint sub) { int32_t smodel; char uname[SNMP_ADM_STR32_SIZ]; struct vacm_user *user; if (vacm_user_index_decode(oid, sub, &smodel, uname) < 0) return (NULL); for (user = vacm_first_user(); user != NULL; user = vacm_next_user(user)) if (strcmp(uname, user->secname) == 0 && user->sec_model == smodel) return (user); return (NULL); } static struct vacm_user * vacm_get_next_user(const struct asn_oid *oid, uint sub) { int32_t smodel; char uname[SNMP_ADM_STR32_SIZ]; struct vacm_user *user; if (oid->len - sub == 0) return (vacm_first_user()); if (vacm_user_index_decode(oid, sub, &smodel, uname) < 0) return (NULL); for (user = vacm_first_user(); user != NULL; user = vacm_next_user(user)) if (strcmp(uname, user->secname) == 0 && user->sec_model == smodel) return (vacm_next_user(user)); return (NULL); } static void vacm_append_access_rule_index(struct asn_oid *oid, uint sub, const struct vacm_access *acl) { uint32_t i; oid->len = sub + strlen(acl->group->groupname) + strlen(acl->ctx_prefix) + 4; oid->subs[sub] = strlen(acl->group->groupname); for (i = 1; i <= strlen(acl->group->groupname); i++) oid->subs[sub + i] = acl->group->groupname[i - 1]; sub += strlen(acl->group->groupname) + 1; oid->subs[sub] = strlen(acl->ctx_prefix); for (i = 1; i <= strlen(acl->ctx_prefix); i++) oid->subs[sub + i] = acl->ctx_prefix[i - 1]; sub += strlen(acl->ctx_prefix) + 1; oid->subs[sub++] = acl->sec_model; oid->subs[sub] = acl->sec_level; } static int vacm_access_rule_index_decode(const struct asn_oid *oid, uint sub, char *gname, char *cprefix, int32_t *smodel, int32_t *slevel) { uint32_t i; if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (-1); for (i = 0; i < oid->subs[sub]; i++) gname[i] = oid->subs[sub + i + 1]; gname[i] = '\0'; sub += strlen(gname) + 1; if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (-1); for (i = 0; i < oid->subs[sub]; i++) cprefix[i] = oid->subs[sub + i + 1]; cprefix[i] = '\0'; sub += strlen(cprefix) + 1; *smodel = oid->subs[sub++]; *slevel = oid->subs[sub]; return (0); } struct vacm_access * vacm_get_access_rule(const struct asn_oid *oid, uint sub) { int32_t smodel, slevel; char gname[SNMP_ADM_STR32_SIZ], prefix[SNMP_ADM_STR32_SIZ]; struct vacm_access *acl; if (vacm_access_rule_index_decode(oid, sub, gname, prefix, &smodel, &slevel) < 0) return (NULL); for (acl = vacm_first_access_rule(); acl != NULL; acl = vacm_next_access_rule(acl)) if (strcmp(gname, acl->group->groupname) == 0 && strcmp(prefix, acl->ctx_prefix) == 0 && smodel == acl->sec_model && slevel == acl->sec_level) return (acl); return (NULL); } struct vacm_access * vacm_get_next_access_rule(const struct asn_oid *oid __unused, uint sub __unused) { int32_t smodel, slevel; char gname[SNMP_ADM_STR32_SIZ], prefix[SNMP_ADM_STR32_SIZ]; struct vacm_access *acl; if (oid->len - sub == 0) return (vacm_first_access_rule()); if (vacm_access_rule_index_decode(oid, sub, gname, prefix, &smodel, &slevel) < 0) return (NULL); for (acl = vacm_first_access_rule(); acl != NULL; acl = vacm_next_access_rule(acl)) if (strcmp(gname, acl->group->groupname) == 0 && strcmp(prefix, acl->ctx_prefix) == 0 && smodel == acl->sec_model && slevel == acl->sec_model) return (vacm_next_access_rule(acl)); return (NULL); } static int vacm_view_index_decode(const struct asn_oid *oid, uint sub, char *vname, struct asn_oid *view_oid) { uint32_t i; int viod_off; if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (-1); for (i = 0; i < oid->subs[sub]; i++) vname[i] = oid->subs[sub + i + 1]; vname[i] = '\0'; viod_off = sub + oid->subs[sub] + 1; if ((view_oid->len = oid->subs[viod_off]) > ASN_MAXOIDLEN) return (-1); memcpy(&view_oid->subs[0], &oid->subs[viod_off + 1], view_oid->len * sizeof(view_oid->subs[0])); return (0); } static void vacm_append_viewindex(struct asn_oid *oid, uint sub, const struct vacm_view *view) { uint32_t i; oid->len = sub + strlen(view->viewname) + 1; oid->subs[sub] = strlen(view->viewname); for (i = 1; i <= strlen(view->viewname); i++) oid->subs[sub + i] = view->viewname[i - 1]; sub += strlen(view->viewname) + 1; oid->subs[sub] = view->subtree.len; oid->len++; asn_append_oid(oid, &view->subtree); } struct vacm_view * vacm_get_view(const struct asn_oid *oid, uint sub) { char vname[SNMP_ADM_STR32_SIZ]; struct asn_oid subtree; struct vacm_view *view; if (vacm_view_index_decode(oid, sub, vname, &subtree) < 0) return (NULL); for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) if (strcmp(vname, view->viewname) == 0 && asn_compare_oid(&subtree, &view->subtree)== 0) return (view); return (NULL); } struct vacm_view * vacm_get_next_view(const struct asn_oid *oid, uint sub) { char vname[SNMP_ADM_STR32_SIZ]; struct asn_oid subtree; struct vacm_view *view; if (oid->len - sub == 0) return (vacm_first_view()); if (vacm_view_index_decode(oid, sub, vname, &subtree) < 0) return (NULL); for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) if (strcmp(vname, view->viewname) == 0 && asn_compare_oid(&subtree, &view->subtree)== 0) return (vacm_next_view(view)); return (NULL); } static struct vacm_view * vacm_get_view_by_name(u_char *octets, u_int len) { struct vacm_view *view; for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) if (strlen(view->viewname) == len && memcmp(octets, view->viewname, len) == 0) return (view); return (NULL); } static struct vacm_context * vacm_get_context(const struct asn_oid *oid, uint sub) { char cname[SNMP_ADM_STR32_SIZ]; size_t cnamelen; u_int index_count; struct vacm_context *vacm_ctx; if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (NULL); index_count = 0; index_count = SNMP_INDEX(index_count, 1); if (index_decode(oid, sub, index_count, &cname, &cnamelen)) return (NULL); for (vacm_ctx = vacm_first_context(); vacm_ctx != NULL; vacm_ctx = vacm_next_context(vacm_ctx)) if (strcmp(cname, vacm_ctx->ctxname) == 0) return (vacm_ctx); return (NULL); } static struct vacm_context * vacm_get_next_context(const struct asn_oid *oid, uint sub) { char cname[SNMP_ADM_STR32_SIZ]; size_t cnamelen; u_int index_count; struct vacm_context *vacm_ctx; if (oid->len - sub == 0) return (vacm_first_context()); if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) return (NULL); index_count = 0; index_count = SNMP_INDEX(index_count, 1); if (index_decode(oid, sub, index_count, &cname, &cnamelen)) return (NULL); for (vacm_ctx = vacm_first_context(); vacm_ctx != NULL; vacm_ctx = vacm_next_context(vacm_ctx)) if (strcmp(cname, vacm_ctx->ctxname) == 0) return (vacm_next_context(vacm_ctx)); return (NULL); } static void vacm_append_ctxindex(struct asn_oid *oid, uint sub, const struct vacm_context *ctx) { uint32_t i; oid->len = sub + strlen(ctx->ctxname) + 1; oid->subs[sub] = strlen(ctx->ctxname); for (i = 1; i <= strlen(ctx->ctxname); i++) oid->subs[sub + i] = ctx->ctxname[i - 1]; } /* * VACM snmp module initialization hook. * Returns 0 on success, < 0 on error. */ static int vacm_init(struct lmodule *mod, int argc __unused, char *argv[] __unused) { vacm_module = mod; vacm_lock = random(); vacm_groups_init(); /* XXX: TODO - initialize structures */ return (0); } /* * VACM snmp module finalization hook. */ static int vacm_fini(void) { /* XXX: TODO - cleanup */ vacm_flush_contexts(reg_vacm); or_unregister(reg_vacm); return (0); } /* * VACM snmp module start operation. */ static void vacm_start(void) { static char dflt_ctx[] = ""; reg_vacm = or_register(&oid_vacm, "The MIB module for managing SNMP View-based Access Control Model.", vacm_module); (void)vacm_add_context(dflt_ctx, reg_vacm); } static void vacm_dump(void) { struct vacm_context *vacmctx; struct vacm_user *vuser; struct vacm_access *vacl; struct vacm_view *view; static char oidbuf[ASN_OIDSTRLEN]; syslog(LOG_ERR, "\n"); syslog(LOG_ERR, "Context list:"); for (vacmctx = vacm_first_context(); vacmctx != NULL; vacmctx = vacm_next_context(vacmctx)) syslog(LOG_ERR, "Context \"%s\", module id %d", vacmctx->ctxname, vacmctx->regid); syslog(LOG_ERR, "VACM users:"); for (vuser = vacm_first_user(); vuser != NULL; vuser = vacm_next_user(vuser)) syslog(LOG_ERR, "Uname %s, Group %s, model %d", vuser->secname, vuser->group!= NULL?vuser->group->groupname:"Unknown", vuser->sec_model); syslog(LOG_ERR, "VACM Access rules:"); for (vacl = vacm_first_access_rule(); vacl != NULL; vacl = vacm_next_access_rule(vacl)) syslog(LOG_ERR, "Group %s, CtxPrefix %s, Model %d, Level %d, " "RV %s, WR %s, NV %s", vacl->group!=NULL? vacl->group->groupname:"Unknown", vacl->ctx_prefix, vacl->sec_model, vacl->sec_level, vacl->read_view!=NULL? vacl->read_view->viewname:"None", vacl->write_view!=NULL? vacl->write_view->viewname:"None", vacl->notify_view!=NULL? vacl->notify_view->viewname:"None"); syslog(LOG_ERR, "VACM Views:"); for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) syslog(LOG_ERR, "View %s, Tree %s - %s", view->viewname, asn_oid2str_r(&view->subtree, oidbuf), view->exclude? "excluded":"included"); } -static const char vacm_comment[] = \ +static const char vacm_comment[] = "This module implements SNMP View-based Access Control Model defined in RFC 3415."; extern const struct snmp_module config; const struct snmp_module config = { .comment = vacm_comment, .init = vacm_init, .fini = vacm_fini, .start = vacm_start, .tree = vacm_ctree, .dump = vacm_dump, .tree_size = vacm_CTREE_SIZE, }; Index: head/contrib/bsnmp/snmpd/snmpd.config =================================================================== --- head/contrib/bsnmp/snmpd/snmpd.config (revision 359511) +++ head/contrib/bsnmp/snmpd/snmpd.config (revision 359512) @@ -1,114 +1,183 @@ # # Copyright (c) 2001-2003 # Fraunhofer Institute for Open Communication Systems (FhG Fokus). # All rights reserved. # # Author: Harti Brandt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $Begemot: bsnmp/snmpd/snmpd.config,v 1.16 2006/02/14 09:04:20 brandt_h Exp $ # -# Example configuration file. +# Example configuration file for testing. # # # Set some common variables # host := foo.bar.com location := "Room 200" contact := "sysmeister@bar.com" system := 1 # FreeBSD -traphost := noc.bar.com +traphost := localhost trapport := 162 read := "public" -# Uncomment the line below that sets the community string -# to enable write access. -write := "geheim" +write := "geheim" # take care - this allows writing trap := "mytrap" +securityModelSNMPv1 := 1 +securityModelSNMPv2c := 2 + +noAuthNoPriv := 1 + # # Configuration # %snmpd begemotSnmpdDebugDumpPdus = 2 begemotSnmpdDebugSyslogPri = 7 +begemotSnmpdDebugSnmpTrace = 0 # -# Set the read and write communities. +# Set community strings. # -# The default value of the community strings is NULL (note, that this is -# different from the empty string). This disables both read and write access. -# To enable read access only the read community string must be set. Setting -# the write community string enables both read and write access with that -# string. +# Each community string has a permission attached to it - 1 for read only +# and 2 for read/write. Default is 1. Community strings must be unique. # # Be sure to understand the security implications of SNMPv2 - the community # strings are readable on the wire! # begemotSnmpdCommunityString.0.1 = $(read) -# begemotSnmpdCommunityString.0.2 = $(write) -# begemotSnmpdCommunityString.0.3 = "otherPublic" +begemotSnmpdCommunityPermission.0.1 = 1 +#begemotSnmpdCommunityString.0.2 = $(write) +#begemotSnmpdCommunityPermission.0.2 = 2 +#begemotSnmpdCommunityString.0.3 = "otherPublic" begemotSnmpdCommunityDisable = 1 # open standard SNMP ports -# begemotSnmpdPortStatus.[$(host)].161 = 1 -# begemotSnmpdPortStatus.127.0.0.1.161 = 1 +# 0.0.0.0:161 +begemotSnmpdTransInetStatus.1.4.0.0.0.0.161.1 = 4 -# UDP over IPv4: 127.0.0.1:161 -begemotSnmpdTransInetStatus.1.4.127.0.0.1.161.1 = 4 +# test the port table; IPv4 address +# 127.0.0.1:10161 +begemotSnmpdTransInetStatus.1.4.127.0.0.1.10161.1 = 4 -# UDP over IPv6: ::1:161 -begemotSnmpdTransInetStatus.2.16.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.161.1 = 4 +# test the port table; IPv6 address +# ::1:10162 +begemotSnmpdTransInetStatus.2.16.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.10162.1 = 4 +# :::10163 +begemotSnmpdTransInetStatus.2.16.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.10163.1 = 4 +# fe80::1%1:10164 - requires inet fe80::1%em0/64 +begemotSnmpdTransInetStatus.4.20.254.128.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.1.10164.1 = 4 +# fe80::1%2:10164 - requires inet fe80::1%em1/64 +begemotSnmpdTransInetStatus.4.20.254.128.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.2.10164.1 = 4 +# fe80::1:10170 - should fail (no scope index) +# begemotSnmpdTransInetStatus.2.16.254.128.0.0.0.0.0.0.0.0.0.0.0.0.0.1.10170.1 = 4 +# fe80::1%0:10170 - should fail (default scope index for link local address) +# begemotSnmpdTransInetStatus.4.20.254.128.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.10170.1 = 4 -# Use domain name and IPv6 link-local address with scope zone id as address -# begemotSnmpdTransInetStatus.16."localhost".161.1 = 4 -# begemotSnmpdTransInetStatus.16."fe80::1%em0".161.1 = 4 +# test the port table; DNS address +# :10165 UDPv4 and UDPv6 +begemotSnmpdTransInetStatus.16.0.10165.1 = 4 +# 127.0.0.1:10166 +# ::1:10166 +begemotSnmpdTransInetStatus.16."localhost".10166.1 = 4 +# ::1:10167 +begemotSnmpdTransInetStatus.16."localhost6".10167.1 = 4 +# fe80::1%em0:10168 - requires inet fe80::$em0/64 +begemotSnmpdTransInetStatus.16."fe80::1%em0".10168.1 = 4 +# fe80::1%em1:10169 - requires inet fe80::$em1/64 +begemotSnmpdTransInetStatus.16."fe80::1%em1".10169.1 = 4 # open a unix domain socket -begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1 -begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4 +# begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1 +# begemotSnmpdLocalPortType."/var/run/snmpd.sock" = 4 # send traps to the traphost begemotTrapSinkStatus.[$(traphost)].$(trapport) = 4 begemotTrapSinkVersion.[$(traphost)].$(trapport) = 2 begemotTrapSinkComm.[$(traphost)].$(trapport) = $(trap) sysContact = $(contact) sysLocation = $(location) sysObjectId = 1.3.6.1.4.1.12325.1.1.2.1.$(system) snmpEnableAuthenTraps = 2 # # Load MIB-2 module # +#begemotSnmpdModulePath."mibII" = "../snmp_mibII/.libs/snmp_mibII.so" begemotSnmpdModulePath."mibII" = "/usr/local/lib/snmp_mibII.so" # +# SNMPv3 notification targets +# +#begemotSnmpdModulePath."target" = "../snmp_target/.libs/snmp_target.so" +begemotSnmpdModulePath."target" = "/usr/local/lib/snmp_target.so" + +# +# SNMPv3 user-based security module +# +#begemotSnmpdModulePath."usm" = "../snmp_usm/.libs/snmp_usm.so" +begemotSnmpdModulePath."usm" = "/usr/local/lib/snmp_usm.so" + +# +# SNMPv3 view-based access control module +# +#begemotSnmpdModulePath."vacm" = "../snmp_vacm/.libs/snmp_vacm.so" +begemotSnmpdModulePath."vacm" = "/usr/local/lib/snmp_vacm.so" + +# # Netgraph module # -begemotSnmpdModulePath."netgraph" = "/usr/local/lib/snmp_netgraph.so" +# begemotSnmpdModulePath."netgraph" = "/usr/local/lib/snmp_netgraph.so" +# %netgraph +# begemotNgControlNodeName = "snmpd" -%netgraph -begemotNgControlNodeName = "snmpd" +%vacm + +internetoid := 1.3.6.1 +internetoidlen := 4 + +vacmSecurityToGroupStatus.$(securityModelSNMPv1).$(read) = 4 +vacmGroupName.$(securityModelSNMPv1).$(read) = $(read) + +vacmSecurityToGroupStatus.$(securityModelSNMPv2c).$(read) = 4 +vacmGroupName.$(securityModelSNMPv2c).$(read) = $(read) + +vacmSecurityToGroupStatus.$(securityModelSNMPv2c).$(write) = 4 +vacmGroupName.$(securityModelSNMPv2c).$(write) = $(write) + +vacmViewTreeFamilyStatus."internet".$(internetoidlen).$(internetoid) = 4 + +vacmAccessStatus.$(read)."".$(securityModelSNMPv1).$(noAuthNoPriv) = 4 +vacmAccessReadViewName.$(read)."".$(securityModelSNMPv1).$(noAuthNoPriv) = "internet" + +vacmAccessStatus.$(write)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = 4 +vacmAccessStatus.$(read)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = 4 +vacmAccessReadViewName.$(write)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = "internet" +vacmAccessReadViewName.$(read)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = "internet" +vacmAccessWriteViewName.$(write)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = "internet" +vacmAccessWriteViewName.$(read)."".$(securityModelSNMPv2c).$(noAuthNoPriv) = "internet" + Index: head/contrib/bsnmp/snmpd/trans_inet.c =================================================================== --- head/contrib/bsnmp/snmpd/trans_inet.c (revision 359511) +++ head/contrib/bsnmp/snmpd/trans_inet.c (revision 359512) @@ -1,1340 +1,1341 @@ /* * Copyright (c) 2018 * Hartmut Brandt. * 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/snmpd/trans_udp.c,v 1.5 2005/10/04 08:46:56 brandt_h Exp $ * * Internet transport */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asn1.h" #include "snmp.h" #include "snmpmod.h" #include "snmpd.h" #define SNMPTREE_TYPES #define SNMPENUM_FUNCS #include "tree.h" #include "oid.h" extern const struct transport_def inet_trans; struct inet_port; struct inet_port_params; struct port_sock; typedef int create_func(struct inet_port *, struct inet_port_params *); typedef void input_func(int, void *); typedef int activate_func(struct inet_port *); typedef void deactivate_func(struct inet_port *); typedef void parse_ctrl_func(struct port_sock *, const struct msghdr *); typedef void setsrc_func(struct port_sock *, struct msghdr *); static create_func ipv4_create; static input_func ipv4_input; static activate_func ipv4_activate; static deactivate_func ipv4_deactivate; static parse_ctrl_func ipv4_parse_ctrl; static setsrc_func ipv4_setsrc; static create_func ipv6_create; static input_func ipv6_input; static activate_func ipv6_activate; static deactivate_func ipv6_deactivate; static parse_ctrl_func ipv6_parse_ctrl; static setsrc_func ipv6_setsrc; static create_func ipv6z_create; static create_func dns_create; static activate_func dns_activate; static deactivate_func dns_deactivate; struct port_sock { /* common input stuff; must be first */ struct port_input input; /** link field */ TAILQ_ENTRY(port_sock) link; /** pointer to parent */ struct inet_port *port; /** bind address */ struct sockaddr_storage bind_addr; /** reply destination */ struct sockaddr_storage ret_dest; /** need to set source address in reply; set for INADDR_ANY */ bool set_ret_source; /** address of the receive interface */ union { /** IPv4 case */ struct in_addr a4; /** IPv6 case */ struct in6_pktinfo a6; } ret_source; /** parse control message */ parse_ctrl_func *parse_ctrl; /** set source address for a send() */ setsrc_func *setsrc; }; static_assert(offsetof(struct port_sock, input) == 0, "input not first in port_sock"); /** * Table row for the inet ports. * * When actived each row can have one or several open sockets. For ipv6, * ipv4 and ipv6z addresses it is always one, for dns addresses more than * one socket can be open. */ struct inet_port { /** common i/o port stuff (must be first) */ struct tport tport; /** transport protocol */ enum BegemotSnmpdTransportProto proto; /** row status */ enum RowStatus row_status; /** socket list */ TAILQ_HEAD(, port_sock) socks; /** value for InetAddressType::dns */ char *dns_addr; /** port number in dns case; network byte order */ uint16_t dns_port; /** create a port */ create_func *create; /** activate a port */ activate_func *activate; /** deactivate port */ deactivate_func *deactivate; }; static_assert(offsetof(struct inet_port, tport) == 0, "tport not first in inet_port"); /** to be used in bind_addr field */ #define AF_DNS AF_VENDOR00 /** registered transport */ static struct transport *my_trans; /** set operation */ enum { SET_CREATED, SET_ACTIVATED, SET_DEACTIVATE, SET_DESTROY, }; /** length of the control data buffer */ static const size_t RECV_CBUF_SIZE = MAX(CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + CMSG_SPACE(sizeof(struct in6_pktinfo))); /** length of the control data buffer */ static const size_t XMIT_CBUF_SIZE = MAX(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo))); /** * Start the transport. This registers the transport with the * transport table. * * \return SNMP error code */ static int inet_start(void) { return (trans_register(&inet_trans, &my_trans)); } /** * Stop the transport. This tries to unregister the transport which * in turn fails if the list of ports is not empty. * * \return SNMP error code */ static int inet_stop(int force __unused) { if (my_trans != NULL) if (trans_unregister(my_trans) != 0) return (SNMP_ERR_GENERR); return (SNMP_ERR_NOERROR); } /** * Deactivate SNMP port. * * \param tp port to close */ static void deactivate_port(struct inet_port *p) { p->deactivate(p); } /* * This function activates a port. For ports opened via the config files * this is called just before entering the event loop. For ports create * during runtime this is called when the RowStatus is set to Active or * as second step for CreateAndGo. * * \param tp transport port * * \return SNMP error code */ static int inet_activate(struct tport *tp) { struct inet_port *port = (struct inet_port *)tp; return (port->activate(port)); } /* * Close the SNMP port if it is open and destroy it. * * \param tp port to close */ static void inet_destroy_port(struct tport *tp) { struct inet_port *port = (struct inet_port *)tp; deactivate_port(port); trans_remove_port(tp); free(port->dns_addr); free(port); } /** * If the input struct has no buffer allocated yet, do it now. If allocation * fails, read the data into a local buffer and drop it. * * \param pi input struct * * \return -1 if allocation fails, 0 otherwise */ static int inet_alloc_buf(struct port_input *pi) { char drop_buf[2000]; if (pi->buf == NULL) { if ((pi->buf = buf_alloc(0)) == NULL) { (void)recvfrom(pi->fd, drop_buf, sizeof(drop_buf), 0, NULL, NULL); return (-1); } pi->buflen = buf_size(0); } return (0); } /** * Read message into input buffer. Get also the source address and any * control data that is available. If the message is truncated, increment * corresponding statistics. * * \param pi input object * \param msg message object to fill * \param cbuf control data buffer * * \return -1 when something goes wrong, 0 othersise */ static int inet_read_msg(struct port_input *pi, struct msghdr *msg, char *cbuf) { struct iovec iov[1]; iov[0].iov_base = pi->buf; iov[0].iov_len = pi->buflen; msg->msg_name = pi->peer; msg->msg_namelen = pi->peerlen; msg->msg_iov = iov; msg->msg_iovlen = 1; msg->msg_control = cbuf; msg->msg_controllen = RECV_CBUF_SIZE; msg->msg_flags = 0; memset(cbuf, 0, RECV_CBUF_SIZE); const ssize_t len = recvmsg(pi->fd, msg, 0); if (len == -1 || len == 0) /* receive error */ return (-1); if (msg->msg_flags & MSG_TRUNC) { /* truncated - drop */ snmpd_stats.silentDrops++; snmpd_stats.inTooLong++; return (-1); } pi->length = (size_t)len; return (0); } /* * Input available on socket. * * \param tp transport port * \param pi input struct * * \return number of bytes received */ static ssize_t inet_recv(struct tport *tp, struct port_input *pi) { struct inet_port *port = __containerof(tp, struct inet_port, tport); struct port_sock *sock = __containerof(pi, struct port_sock, input); assert(port->proto == BegemotSnmpdTransportProto_udp); if (inet_alloc_buf(pi) == -1) return (-1); char cbuf[RECV_CBUF_SIZE]; struct msghdr msg; if (inet_read_msg(pi, &msg, cbuf) == -1) return (-1); sock->parse_ctrl(sock, &msg); return (0); } /* * Send message. * * \param tp port * \param buf data to send * \param len number of bytes to send * \param addr destination address * \param addlen destination address length * * \return number of bytes sent */ static ssize_t inet_send2(struct tport *tp, const u_char *buf, size_t len, struct port_input *pi) { struct inet_port *p = __containerof(tp, struct inet_port, tport); struct port_sock *s = (pi == NULL) ? TAILQ_FIRST(&p->socks) : __containerof(pi, struct port_sock, input); struct iovec iov; iov.iov_base = __DECONST(void*, buf); iov.iov_len = len; struct msghdr msg; msg.msg_flags = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = (void *)pi->peer; msg.msg_namelen = pi->peerlen; msg.msg_control = NULL; msg.msg_controllen = 0; char cbuf[XMIT_CBUF_SIZE]; if (s->set_ret_source) { msg.msg_control = cbuf; s->setsrc(s, &msg); } return (sendmsg(s->input.fd, &msg, 0)); } /** exported to daemon */ const struct transport_def inet_trans = { "inet", OIDX_begemotSnmpdTransInet, inet_start, inet_stop, inet_destroy_port, inet_activate, NULL, inet_recv, inet_send2, }; struct inet_port_params { /** index oid */ struct asn_oid index; /** internet address type */ enum InetAddressType type; /** internet address */ u_char *addr; /** length of address */ size_t addr_len; /** port number */ uint32_t port; /** protocol */ enum BegemotSnmpdTransportProto proto; }; /** * IPv4 creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv4_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 4) return (SNMP_ERR_INCONS_VALUE); struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); snmpd_input_init(&sock->input); TAILQ_INSERT_HEAD(&port->socks, sock, link); struct sockaddr_in *sin = (struct sockaddr_in *)&sock->bind_addr; sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = AF_INET; sin->sin_port = htons(params->port); memcpy(&sin->sin_addr, params->addr, 4); /* network byte order */ sock->port = port; return (SNMP_ERR_NOERROR); } /* * An IPv4 inet port is ready. Delegate to the generic code to read the data * and react. * * \param fd file descriptor that is ready * \param udata inet_port pointer */ static void ipv4_input(int fd __unused, void *udata) { struct port_sock *sock = udata; sock->input.peerlen = sizeof(struct sockaddr_in); snmpd_input(&sock->input, &sock->port->tport); } /** * Activate an IPv4 socket. * * \param sock thhe socket to activate * * \return error code */ static int ipv4_activate_sock(struct port_sock *sock) { if ((sock->input.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "creating UDP4 socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } const struct sockaddr_in *sin = (const struct sockaddr_in *)&sock->bind_addr; if (sin->sin_addr.s_addr == INADDR_ANY) { /* need to know from which address to return */ static const int on = 1; if (setsockopt(sock->input.fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m"); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->set_ret_source = true; } if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) { if (errno == EADDRNOTAVAIL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } syslog(LOG_ERR, "bind: %s:%u %m", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } if ((sock->input.id = fd_select(sock->input.fd, ipv4_input, sock, NULL)) == NULL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->input.peer = (struct sockaddr *)&sock->ret_dest; sock->parse_ctrl = ipv4_parse_ctrl; sock->setsrc = ipv4_setsrc; return (SNMP_ERR_NOERROR); } /** * Open an IPv4 socket. Make the socket, bind it and put it on the select * list. The socket struct has already been created during creation. * * \param p inet port * * \return SNMP error code */ static int ipv4_activate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); const int ret = ipv4_activate_sock(sock); if (ret == SNMP_ERR_NOERROR) p->row_status = RowStatus_active; return (ret); } /** * Close an IPv4 socket. Keep the sock object. * * \param p inet port */ static void ipv4_deactivate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); snmpd_input_close(&sock->input); p->row_status = RowStatus_notInService; } /** * Parse the control data received with a UDPv4 packet. This may contain * credentials (for a local connection) and the address of the interface * the message was received on. If there are credentials set the priv flag * if the effective UID is zero. * * \param sock the sock object * \param msg the received message */ static void ipv4_parse_ctrl(struct port_sock *sock, const struct msghdr *msg) { struct sockcred *cred = NULL; for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { memcpy(&sock->ret_source.a4, CMSG_DATA(cmsg), sizeof(struct in_addr)); } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) { cred = (struct sockcred *)(void *)CMSG_DATA(cmsg); } } sock->input.priv = 0; if (sock->input.cred && cred) /* remote end has sent credentials */ sock->input.priv = (cred->sc_euid == 0); } /** * Set the source address option for IPv4 sockets. * * \param sock socket object * \param msg message */ static void ipv4_setsrc(struct port_sock *sock, struct msghdr *msg) { struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); /* select outgoing interface by setting source address */ cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_SENDSRCADDR; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); memcpy(CMSG_DATA(cmsg), &sock->ret_source.a4, sizeof(struct in_addr)); msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr)); } /** * Common part of IPv6 creation. This is used by both ipv6_create() and * ipv6z_create(). * * \param port the table row * \param params creation parameters * \param scope_id scope_id (0 or from index) * * \return SNMP_ERR_NOERROR if port has been created, error code otherwise */ static int ipv6_create_common(struct inet_port *port, struct inet_port_params *params, u_int scope_id) { struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&sock->bind_addr; sin->sin6_len = sizeof(struct sockaddr_in6); sin->sin6_family = AF_INET6; sin->sin6_port = htons(params->port); sin->sin6_flowinfo = 0; sin->sin6_scope_id = scope_id; memcpy(sin->sin6_addr.s6_addr, params->addr, 16); if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) && scope_id == 0) { char buf[INET6_ADDRSTRLEN]; syslog(LOG_INFO, "%s: link local address used without scope " "index: %s", __func__, inet_ntop(AF_INET6, &sin->sin6_addr, buf, sizeof(buf))); free(sock); return (SNMP_ERR_NO_CREATION); } sock->port = port; snmpd_input_init(&sock->input); TAILQ_INSERT_HEAD(&port->socks, sock, link); return (SNMP_ERR_NOERROR); } /** * IPv6 creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv6_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 16) return (SNMP_ERR_INCONS_VALUE); const int ret = ipv6_create_common(port, params, 0); if (ret != SNMP_ERR_NOERROR) return (ret); return (SNMP_ERR_NOERROR); } /* * An IPv6 inet port is ready. Delegate to the generic code to read the data * and react. * * \param fd file descriptor that is ready * \param udata inet_port pointer */ static void ipv6_input(int fd __unused, void *udata) { struct port_sock *sock = udata; sock->input.peerlen = sizeof(struct sockaddr_in6); snmpd_input(&sock->input, &sock->port->tport); } /** * Activate an IPv6 socket. * * \param sock thhe socket to activate * * \return error code */ static int ipv6_activate_sock(struct port_sock *sock) { if ((sock->input.fd = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "creating UDP6 socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } const struct sockaddr_in6 *sin = (const struct sockaddr_in6 *)&sock->bind_addr; if (memcmp(&sin->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) { /* need to know from which address to return */ static const int on = 1; if (setsockopt(sock->input.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(IP6_PKTINFO): %m"); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->set_ret_source = true; } if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) { if (community != COMM_INITIALIZE && errno == EADDRNOTAVAIL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } char buf[INET6_ADDRSTRLEN]; syslog(LOG_ERR, "bind: %s:%u:%u %m", inet_ntop(AF_INET6, &sin->sin6_addr, buf, sizeof(buf)), sin->sin6_scope_id, ntohs(sin->sin6_port)); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } if ((sock->input.id = fd_select(sock->input.fd, ipv6_input, sock, NULL)) == NULL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->input.peer = (struct sockaddr *)&sock->ret_dest; sock->parse_ctrl = ipv6_parse_ctrl; sock->setsrc = ipv6_setsrc; return (SNMP_ERR_NOERROR); } /** * Open an IPv6 socket. * * \param port inet port * * \return SNMP error code */ static int ipv6_activate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); const int ret = ipv6_activate_sock(sock); if (ret == SNMP_ERR_NOERROR) p->row_status = RowStatus_active; return (ret); } /** * Close an IPv6 socket. Keep the sock object. * * \param p inet port */ static void ipv6_deactivate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); snmpd_input_close(&sock->input); p->row_status = RowStatus_notInService; } /** * IPv6 specific part of message processing. The control data may contain * credentials and packet info that contains the destination address of * the packet. * * \param sock the sock object * \param msg the received message */ static void ipv6_parse_ctrl(struct port_sock *sock, const struct msghdr *msg) { struct sockcred *cred = NULL; for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { const struct in6_pktinfo *info = (const struct in6_pktinfo *)(const void *) CMSG_DATA(cmsg); sock->ret_source.a6.ipi6_addr = info->ipi6_addr; sock->ret_source.a6.ipi6_ifindex = !IN6_IS_ADDR_LINKLOCAL(&info->ipi6_addr) ? 0: info->ipi6_ifindex; + } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) { cred = (struct sockcred *)(void *)CMSG_DATA(cmsg); } } sock->input.priv = 0; if (sock->input.cred && cred) /* remote end has sent credentials */ sock->input.priv = (cred->sc_euid == 0); } /** * Set the source address option for IPv4 sockets. * * \param sock socket object * \param msg message */ static void ipv6_setsrc(struct port_sock *sock, struct msghdr *msg) { struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); /* select outgoing interface by setting source address */ cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); memcpy(CMSG_DATA(cmsg), &sock->ret_source.a6, sizeof(struct in6_pktinfo)); msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); } /** * IPv6z creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv6z_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 20) return (SNMP_ERR_INCONS_VALUE); u_int scope_id = 0; for (u_int i = 16; i < 20; i++) { scope_id <<= 8; scope_id |= params->addr[i]; } const int ret = ipv6_create_common(port, params, scope_id); if (ret != SNMP_ERR_NOERROR) return (ret); return (SNMP_ERR_NOERROR); } /** * DNS name creation stuff. Parse the index and save the string. * This does not create a socket struct. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int dns_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len > 64) return (SNMP_ERR_INCONS_VALUE); if (strnlen(params->addr, params->addr_len) != params->addr_len) return (SNMP_ERR_INCONS_VALUE); if ((port->dns_addr = realloc(params->addr, params->addr_len + 1)) == NULL) return (SNMP_ERR_GENERR); port->dns_addr[params->addr_len] = '\0'; params->addr = NULL; port->dns_port = htons(params->port); return (SNMP_ERR_NOERROR); } /** * Open sockets. This loops through all the addresses returned by getaddrinfo * and opens a socket for each of them. * * \param port inet port * * \return SNMP error code */ static int dns_activate(struct inet_port *port) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; // XXX udp-only hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_NUMERICSERV; char portbuf[8]; sprintf(portbuf, "%hu", ntohs(port->dns_port)); struct addrinfo *res0; int error = getaddrinfo(port->dns_addr[0] == '\0' ? NULL : port->dns_addr, portbuf, &hints, &res0); if (error) { syslog(LOG_ERR, "cannot resolve address '%s': %s", port->dns_addr, gai_strerror(error)); return (SNMP_ERR_GENERR); } for (struct addrinfo *res = res0; res != NULL; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); snmpd_input_init(&sock->input); sock->port = port; int ret = SNMP_ERR_NOERROR; if (res->ai_family == AF_INET) { *(struct sockaddr_in *)&sock->bind_addr = *(struct sockaddr_in *)(void *)res->ai_addr; ret = ipv4_activate_sock(sock); } else { *(struct sockaddr_in6 *)&sock->bind_addr = *(struct sockaddr_in6 *)(void *)res->ai_addr; ret = ipv6_activate_sock(sock); } if (ret != SNMP_ERR_NOERROR) free(sock); else TAILQ_INSERT_HEAD(&port->socks, sock, link); } if (!TAILQ_EMPTY(&port->socks)) port->row_status = RowStatus_active; freeaddrinfo(res0); return (SNMP_ERR_GENERR); } /** * Deactive the socket. Close all open sockets and delete all sock objects. * * \param port inet port */ static void dns_deactivate(struct inet_port *port) { while (!TAILQ_EMPTY(&port->socks)) { struct port_sock *sock = TAILQ_FIRST(&port->socks); TAILQ_REMOVE(&port->socks, sock, link); snmpd_input_close(&sock->input); free(sock); } port->row_status = RowStatus_notInService; } static int inet_create(struct inet_port_params *params, struct inet_port **pp) { int err = SNMP_ERR_NOERROR; struct inet_port *port = NULL; if (params->port > 0xffff) { err = SNMP_ERR_NO_CREATION; goto fail; } if ((port = malloc(sizeof(*port))) == NULL) { err = SNMP_ERR_GENERR; goto fail; } memset(port, 0, sizeof(*port)); TAILQ_INIT(&port->socks); port->proto = params->proto; port->tport.index = params->index; switch (params->type) { case InetAddressType_ipv4: port->create = ipv4_create; port->activate = ipv4_activate; port->deactivate = ipv4_deactivate; break; case InetAddressType_ipv6: port->create = ipv6_create; port->activate = ipv6_activate; port->deactivate = ipv6_deactivate; break; case InetAddressType_ipv6z: port->create = ipv6z_create; port->activate = ipv6_activate; port->deactivate = ipv6_deactivate; break; case InetAddressType_dns: port->create = dns_create; port->activate = dns_activate; port->deactivate = dns_deactivate; break; default: err = SNMP_ERR_NO_CREATION; goto fail; } if ((err = port->create(port, params)) != SNMP_ERR_NOERROR) goto fail; *pp = port; trans_insert_port(my_trans, &port->tport); return (err); fail: free(port->dns_addr); free(port); return (err); } static int create_and_go(struct snmp_context *ctx, struct inet_port_params *params) { int err; struct inet_port *port; if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR) return (err); port->row_status = RowStatus_createAndGo; ctx->scratch->ptr1 = port; if (community == COMM_INITIALIZE) return (err); return (inet_activate(&port->tport)); } static int create_and_wait(struct snmp_context *ctx, struct inet_port_params *params) { int err; struct inet_port *port; if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR) return (err); port->row_status = RowStatus_createAndWait; ctx->scratch->ptr1 = port; return (err); } /** * This is called to set a RowStatus value in the port table during * SET processing. * * When this is called during initialization time and the RowStatus is set * to CreateAndGo, the port is actually not activated. This is done when * the main code calls the init() for all ports later. */ static int inet_port_set(struct snmp_context *ctx, struct inet_port *port, struct inet_port_params *params, enum RowStatus nstatus) { switch (nstatus) { case RowStatus_createAndGo: if (port != NULL) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = SET_CREATED; return (create_and_go(ctx, params)); case RowStatus_createAndWait: if (port != NULL) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = SET_CREATED; return (create_and_wait(ctx, params)); case RowStatus_active: if (port == NULL) return (SNMP_ERR_INCONS_VALUE); switch (port->row_status) { case RowStatus_notReady: /* this can not happend */ abort(); case RowStatus_notInService: ctx->scratch->int1 = SET_ACTIVATED; return (inet_activate(&port->tport)); case RowStatus_active: return (SNMP_ERR_NOERROR); case RowStatus_createAndGo: case RowStatus_createAndWait: case RowStatus_destroy: abort(); } break; case RowStatus_notInService: if (port == NULL) return (SNMP_ERR_INCONS_VALUE); switch (port->row_status) { case RowStatus_notReady: /* this can not happend */ abort(); case RowStatus_notInService: return (SNMP_ERR_NOERROR); case RowStatus_active: /* this is done during commit */ ctx->scratch->int1 = SET_DEACTIVATE; return (SNMP_ERR_NOERROR); case RowStatus_createAndGo: case RowStatus_createAndWait: case RowStatus_destroy: abort(); } break; case RowStatus_destroy: /* this is done during commit */ ctx->scratch->int1 = SET_DESTROY; return (SNMP_ERR_NOERROR); case RowStatus_notReady: return (SNMP_ERR_WRONG_VALUE); } abort(); } /* * Port table */ int op_snmp_trans_inet(struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op) { asn_subid_t which = value->var.subs[sub - 1]; struct inet_port *port; struct inet_port_params params; int ret; switch (op) { case SNMP_OP_GETNEXT: if ((port = (struct inet_port *)trans_next_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); index_append(&value->var, sub, &port->tport.index); goto fetch; case SNMP_OP_GET: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); goto fetch; case SNMP_OP_SET: port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub); if (which != LEAF_begemotSnmpdTransInetStatus) abort(); if (!isok_RowStatus(value->v.integer)) return (SNMP_ERR_WRONG_VALUE); if (index_decode(&value->var, sub, iidx, ¶ms.type, ¶ms.addr, ¶ms.addr_len, ¶ms.port, ¶ms.proto)) return (SNMP_ERR_NO_CREATION); asn_slice_oid(¶ms.index, &value->var, sub, value->var.len); ret = inet_port_set(ctx, port, ¶ms, (enum RowStatus)value->v.integer); free(params.addr); return (ret); case SNMP_OP_ROLLBACK: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) /* cannot happen */ abort(); switch (ctx->scratch->int1) { case SET_CREATED: /* newly created */ assert(port != NULL); inet_destroy_port(&port->tport); return (SNMP_ERR_NOERROR); case SET_DESTROY: /* do it now */ assert(port != NULL); return (SNMP_ERR_NOERROR); case SET_ACTIVATED: deactivate_port(port); return (SNMP_ERR_NOERROR); case SET_DEACTIVATE: return (SNMP_ERR_NOERROR); } abort(); case SNMP_OP_COMMIT: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) /* cannot happen */ abort(); switch (ctx->scratch->int1) { case SET_CREATED: /* newly created */ assert(port != NULL); return (SNMP_ERR_NOERROR); case SET_DESTROY: /* do it now */ assert(port != NULL); inet_destroy_port(&port->tport); return (SNMP_ERR_NOERROR); case SET_ACTIVATED: return (SNMP_ERR_NOERROR); case SET_DEACTIVATE: deactivate_port(port); return (SNMP_ERR_NOERROR); } abort(); } abort(); fetch: switch (which) { case LEAF_begemotSnmpdTransInetStatus: value->v.integer = port->row_status; break; default: abort(); } return (SNMP_ERR_NOERROR); } Property changes on: head/contrib/bsnmp/snmpd/trans_inet.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bsnmp/snmpd/trans_inet.h =================================================================== --- head/contrib/bsnmp/snmpd/trans_inet.h (revision 359511) +++ head/contrib/bsnmp/snmpd/trans_inet.h (revision 359512) Property changes on: head/contrib/bsnmp/snmpd/trans_inet.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bsnmp/tests/asn1.cc =================================================================== --- head/contrib/bsnmp/tests/asn1.cc (nonexistent) +++ head/contrib/bsnmp/tests/asn1.cc (revision 359512) @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2020 + * Hartmut Brandt + * All rights reserved. + * + * 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. + * + * :se ts=4 + */ + +#include "constbuf.h" + +extern "C" { +#include "asn1.h" +} + +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace test::literals; + +template +static std::enable_if_t, asn_buf> +mk_asn_buf(const T &b) +{ + asn_buf abuf; + + abuf.asn_cptr = b.data(); + abuf.asn_len = b.size(); + + return abuf; +} + +static asn_buf +mk_asn_buf(asn_len_t len) +{ + asn_buf abuf; + + abuf.asn_ptr = new u_char[len]; + abuf.asn_len = len; + + return abuf; +} + +static std::string g_errstr; + +static void +save_g_errstr(const struct asn_buf *b, const char *fmt, ...) +{ + va_list ap; + + char sbuf[20000]; + va_start(ap, fmt); + vsprintf(sbuf, fmt, ap); + va_end(ap); + + if (b != NULL) { + strcat(sbuf, " at"); + for (u_int i = 0; b->asn_len > i; i++) + sprintf(sbuf + strlen(sbuf), " %02x", b->asn_cptr[i]); + } + strcat(sbuf, "\n"); + + g_errstr = sbuf; +} + +/** + * Encapsulate an ASN.1 parse buffer and the parse header fields. + * Constructing parses the header. + */ +struct Asn_value +{ + /** parse buffer */ + struct asn_buf buf; + + /** error from header parsing */ + asn_err err; + + /** ASN.1 tag byte */ + uint8_t type; + + /** value length */ + asn_len_t alen; + + /** + * Construct a parse buffer and parse the header. + * + * \tparam Tbuf input buffer type + * + * \param ibuf input buffer + */ + template + explicit + Asn_value(const Tbuf &ibuf) + : buf {mk_asn_buf(ibuf)}, err {asn_get_header(&buf, &type, &alen)} + { + } +}; + +/** + * Parse the ASN.1 header and check the error code. If the error is not + * ASN_ERR_OK then check the error string. + * + * \tparam Tbuf input buffer type + * + * \param buf input buffer + * \param err expected error code (default ASN_ERR_OK) + * \param errstr expected error string (default empty) + * + * \return the parse buffer + */ +template +static auto +check_header(const Tbuf &buf, asn_err err = ASN_ERR_OK, + std::string_view errstr = {}) +{ + g_errstr.clear(); + auto r = Asn_value(buf); + REQUIRE(r.err == err); + if (r.err != ASN_ERR_OK) + REQUIRE(g_errstr == errstr); + else + REQUIRE(g_errstr == ""); + return r; +} + +/** + * Parse the ASN.1 header and expect it not to fail. The check the tag. + * + * \tparam Tbuf input buffer type + * + * \param buf input buffer + * \param tag expected type tag + * + * \return the parse buffer + */ +template +static auto +check_header(const Tbuf &buf, uint8_t type) +{ + auto r = check_header(buf); + REQUIRE(r.type == type); + return r; +} + +/** + * Parse the ASN.1 header and expect it not to fail. The check the tag and + * the length. + * + * \tparam Tbuf input buffer type + * + * \param buf input buffer + * \param tag expected type tag + * \param alen expected value length + * + * \return the parse buffer + */ +template +static auto +check_header(const Tbuf &buf, uint8_t type, asn_len_t alen) +{ + auto r = check_header(buf); + REQUIRE(r.type == type); + REQUIRE(r.alen == alen); + return r; +} + +template +static void +check_buf(const asn_buf &s, const Tbuf &exp, bool print = false) +{ + if (print) { + for (auto c : exp) + std::printf(":%02x", c); + std::printf("\n"); + + for (size_t i = 0; i < size(exp); i++) + std::printf(":%02x", s.asn_ptr[i]); + std::printf("\n"); + } + REQUIRE(std::equal(begin(exp), end(exp), s.asn_ptr)); +} + +TEST_CASE("ASN.1 header parsing", "[asn1][parse]") +{ + asn_error = save_g_errstr; + + SECTION("empty buffer") { + check_header(std::vector{}, ASN_ERR_EOBUF, + "no identifier for header at\n"); + } + SECTION("tag too large") { + check_header("x1f:06:01:7f"_cbuf, ASN_ERR_FAILED, + "tags > 0x1e not supported (0x1f) at 1f 06 01 7f\n"); + } + SECTION("no length field") { + check_header("x46"_cbuf, ASN_ERR_EOBUF, "no length field at\n"); + } + SECTION("indefinite length") { + check_header("x46:80:02:04:06"_cbuf, ASN_ERR_FAILED, + "indefinite length not supported at 02 04 06\n"); + } + SECTION("long length") { + check_header("x46:83:00:00:02:7f:12"_cbuf, ASN_ERR_FAILED, + "long length too long (3) at 00 00 02 7f 12\n"); + } + SECTION("truncated length field") { + check_header("x46:82:00"_cbuf, ASN_ERR_EOBUF, + "long length truncated at 00\n"); + } + SECTION("correct long length") { + check_header("x04:81:00"_cbuf, ASN_TYPE_OCTETSTRING, 0); +#ifndef BOGUS_CVE_2019_5610_FIX + check_header("x04:81:04:00"_cbuf, ASN_TYPE_OCTETSTRING, 4); + check_header("x04:81:ff:00"_cbuf, ASN_TYPE_OCTETSTRING, 255); +#endif + check_header("x04:82:00:00"_cbuf, ASN_TYPE_OCTETSTRING, 0); +#ifndef BOGUS_CVE_2019_5610_FIX + check_header("x04:82:00:80"_cbuf, ASN_TYPE_OCTETSTRING, 128); + check_header("x04:82:01:80"_cbuf, ASN_TYPE_OCTETSTRING, 384); + check_header("x04:82:ff:ff"_cbuf, ASN_TYPE_OCTETSTRING, 65535); +#endif + } + SECTION("short length") { + check_header("x04:00:00"_cbuf, ASN_TYPE_OCTETSTRING, 0); + check_header("x04:01:00"_cbuf, ASN_TYPE_OCTETSTRING, 1); +#ifndef BOGUS_CVE_2019_5610_FIX + check_header("x04:40:00"_cbuf, ASN_TYPE_OCTETSTRING, 64); + check_header("x04:7f:00"_cbuf, ASN_TYPE_OCTETSTRING, 127); +#endif + } +} + +TEST_CASE("ASN.1 header building", "[asn1][build]") +{ + asn_error = save_g_errstr; + + const auto conv_err = [] (asn_len_t alen, asn_len_t vlen, uint8_t type, + asn_err err, std::string_view errstr) { + auto b = mk_asn_buf(alen); + g_errstr.clear(); + REQUIRE(asn_put_header(&b, type, vlen) == err); + REQUIRE(g_errstr == errstr); + }; + + const auto conv = [] (asn_len_t alen, asn_len_t vlen, uint8_t type, + const auto &cbuf) { + auto b = mk_asn_buf(alen); + auto t = b; + REQUIRE(asn_put_header(&b, type, vlen) == ASN_ERR_OK); + REQUIRE(b.asn_len == (size_t)0); + check_buf(t, cbuf); + }; + + SECTION("no space for tag") { + conv_err(0, 0, ASN_TYPE_OCTETSTRING, ASN_ERR_EOBUF, ""); + } + SECTION("no space for length") { + conv_err(1, 0, ASN_TYPE_OCTETSTRING, ASN_ERR_EOBUF, ""); + conv_err(2, 128, ASN_TYPE_OCTETSTRING, ASN_ERR_EOBUF, ""); + } + SECTION("bad tag") { + conv_err(2, 0, 0x1f, ASN_ERR_FAILED, + "types > 0x1e not supported (0x1f)\n"); + conv_err(2, 0, 0xff, ASN_ERR_FAILED, + "types > 0x1e not supported (0x1f)\n"); + } + SECTION("ok") { + conv(2, 0, ASN_TYPE_OCTETSTRING, "x04:00"_cbuf); + } +} + +TEST_CASE("Counter64 parsing", "[asn1][parse]") +{ + asn_error = save_g_errstr; + + /** + * Sucessfully parse a COUNTER64 value. + * + * \param buf buffer to parse + * \param xval expected value + */ + const auto conv = [] (const auto &buf, uint64_t xval) { + auto r = check_header(buf, ASN_APP_COUNTER64 | ASN_CLASS_APPLICATION); + + uint64_t val; + REQUIRE(asn_get_counter64_raw(&r.buf, r.alen, &val) == ASN_ERR_OK); + REQUIRE(val == xval); + }; + + /** + * Parse COUNTER64 with error. + * + * \param buf buffer to parse + * \param err expected error from value parser + * \param errstr expected error string + */ + const auto conv_err = [] (const auto &buf, asn_err err, + std::string_view errstr) { + auto r = check_header(buf, ASN_APP_COUNTER64 | ASN_CLASS_APPLICATION); + + g_errstr.clear(); + uint64_t val; + REQUIRE(asn_get_counter64_raw(&r.buf, r.alen, &val) == err); + REQUIRE(g_errstr == errstr); + }; + + SECTION("correct encoding") { + + conv("x46:01:00"_cbuf, 0x0ULL); + conv("x46:01:01"_cbuf, 0x1ULL); + conv("x46:01:7f"_cbuf, 0x7fULL); + + conv("x46:02:00:80"_cbuf, 0x80ULL); + conv("x46:02:00:ff"_cbuf, 0xffULL); + conv("x46:02:7f:ff"_cbuf, 0x7fffULL); + + conv("x46:03:00:80:00"_cbuf, 0x8000ULL); + conv("x46:03:00:ff:ff"_cbuf, 0xffffULL); + conv("x46:03:7f:ff:ff"_cbuf, 0x7fffffULL); + + conv("x46:04:00:80:00:00"_cbuf, 0x800000ULL); + conv("x46:04:00:ff:ff:ff"_cbuf, 0xffffffULL); + conv("x46:04:7f:ff:ff:ff"_cbuf, 0x7fffffffULL); + + conv("x46:05:00:80:00:00:00"_cbuf, 0x80000000ULL); + conv("x46:05:00:ff:ff:ff:ff"_cbuf, 0xffffffffULL); + conv("x46:05:7f:ff:ff:ff:ff"_cbuf, 0x7fffffffffULL); + + conv("x46:06:00:80:00:00:00:00"_cbuf, 0x8000000000ULL); + conv("x46:06:00:ff:ff:ff:ff:ff"_cbuf, 0xffffffffffULL); + conv("x46:06:7f:ff:ff:ff:ff:ff"_cbuf, 0x7fffffffffffULL); + + conv("x46:07:00:80:00:00:00:00:00"_cbuf, 0x800000000000ULL); + conv("x46:07:00:ff:ff:ff:ff:ff:ff"_cbuf, 0xffffffffffffULL); + conv("x46:07:7f:ff:ff:ff:ff:ff:ff"_cbuf, 0x7fffffffffffffULL); + + conv("x46:08:00:80:00:00:00:00:00:00"_cbuf, 0x80000000000000ULL); + conv("x46:08:00:ff:ff:ff:ff:ff:ff:ff"_cbuf, 0xffffffffffffffULL); + conv("x46:08:7f:ff:ff:ff:ff:ff:ff:ff"_cbuf, 0x7fffffffffffffffULL); + + conv("x46:09:00:80:00:00:00:00:00:00:00"_cbuf, 0x8000000000000000ULL); + conv("x46:09:00:ff:ff:ff:ff:ff:ff:ff:ff"_cbuf, 0xffffffffffffffffULL); + } + + SECTION("zero length") { + conv_err("x46:00"_cbuf, ASN_ERR_BADLEN, + "zero-length integer at\n"); + } + + SECTION("non minimal encoding") { + conv_err("x46:02:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00\n"); + conv_err("x46:02:00:7f"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 7f\n"); + conv_err("x46:03:00:00:80"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 80\n"); + conv_err("x46:04:00:00:80:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 80 00\n"); + conv_err("x46:0a:00:00:00:00:00:00:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 00 00 00 00 00 00 00 00\n"); + conv_err("x46:0a:00:01:00:00:00:00:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 01 00 00 00 00 00 00 00 00\n"); + } + + SECTION("out of range") { + conv_err("x46:09:01:00:00:00:00:00:00:00:00"_cbuf, ASN_ERR_RANGE, + "unsigned too large or negative at 01 00 00 00 00 00 00 00 00\n"); + conv_err("x46:0a:01:00:00:00:00:00:00:00:00:00"_cbuf, ASN_ERR_RANGE, + "unsigned too large or negative at 01 00 00 00 00 00 00 00 00 00\n"); + conv_err("x46:01:80"_cbuf, ASN_ERR_RANGE, + "unsigned too large or negative at 80\n"); + conv_err("x46:02:80:00"_cbuf, ASN_ERR_RANGE, + "unsigned too large or negative at 80 00\n"); + conv_err("x46:03:80:00:00"_cbuf, ASN_ERR_RANGE, + "unsigned too large or negative at 80 00 00\n"); + } + +#ifndef BOGUS_CVE_2019_5610_FIX + SECTION("truncated value") { + conv_err("x46:02:00"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 00\n"); + conv_err("x46:09:00:80:00:00:00"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 00 80 00 00 00\n"); + conv_err("x46:09:00:ff:ff:ff:ff:ff:ff:ff"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 00 ff ff ff ff ff ff ff\n"); + } +#endif +} + +TEST_CASE("Counter64 building", "[asn1][build]") +{ + asn_error = save_g_errstr; + + const auto conv = [] (asn_len_t alen, uint64_t val, const auto &buf) { + auto b = mk_asn_buf(alen); + auto s = b; + REQUIRE(asn_put_counter64(&b, val) == ASN_ERR_OK); + REQUIRE(b.asn_len == (size_t)0); + check_buf(s, buf); + }; + + const auto conv_err = [] (asn_len_t alen, uint64_t val, asn_err err, + std::string_view errstr) { + auto b = mk_asn_buf(alen); + g_errstr.clear(); + REQUIRE(asn_put_counter64(&b, val) == err); + REQUIRE(g_errstr == errstr); + }; + + conv(3, 0x0, "x46:01:00"_cbuf); + conv(3, 0x1, "x46:01:01"_cbuf); + conv(3, 0x7f, "x46:01:7f"_cbuf); + + conv(4, 0x80, "x46:02:00:80"_cbuf); + conv(4, 0xff, "x46:02:00:ff"_cbuf); + conv(4, 0x7fff, "x46:02:7f:ff"_cbuf); + + conv(5, 0x8000, "x46:03:00:80:00"_cbuf); + conv(5, 0xffff, "x46:03:00:ff:ff"_cbuf); + conv(5, 0x7fffff, "x46:03:7f:ff:ff"_cbuf); + + conv(6, 0x800000, "x46:04:00:80:00:00"_cbuf); + conv(6, 0xffffff, "x46:04:00:ff:ff:ff"_cbuf); + conv(6, 0x7fffffff, "x46:04:7f:ff:ff:ff"_cbuf); + + conv(7, 0x80000000, "x46:05:00:80:00:00:00"_cbuf); + conv(7, 0xffffffff, "x46:05:00:ff:ff:ff:ff"_cbuf); + conv(7, 0x7fffffffff, "x46:05:7f:ff:ff:ff:ff"_cbuf); + + conv(8, 0x8000000000, "x46:06:00:80:00:00:00:00"_cbuf); + conv(8, 0xffffffffff, "x46:06:00:ff:ff:ff:ff:ff"_cbuf); + conv(8, 0x7fffffffffff, "x46:06:7f:ff:ff:ff:ff:ff"_cbuf); + + conv(9, 0x800000000000, "x46:07:00:80:00:00:00:00:00"_cbuf); + conv(9, 0xffffffffffff, "x46:07:00:ff:ff:ff:ff:ff:ff"_cbuf); + conv(9, 0x7fffffffffffff, "x46:07:7f:ff:ff:ff:ff:ff:ff"_cbuf); + + conv(10, 0x80000000000000, "x46:08:00:80:00:00:00:00:00:00"_cbuf); + conv(10, 0xffffffffffffff, "x46:08:00:ff:ff:ff:ff:ff:ff:ff"_cbuf); + conv(10, 0x7fffffffffffffff, "x46:08:7f:ff:ff:ff:ff:ff:ff:ff"_cbuf); + + conv(11, 0x8000000000000000, "x46:09:00:80:00:00:00:00:00:00:00"_cbuf); + conv(11, 0xffffffffffffffff, "x46:09:00:ff:ff:ff:ff:ff:ff:ff:ff"_cbuf); + + SECTION("empty buffer") { + conv_err(0, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short for length field") { + conv_err(1, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short") { + conv_err(2, 0, ASN_ERR_EOBUF, ""); + conv_err(3, 0x80, ASN_ERR_EOBUF, ""); + conv_err(4, 0x8000, ASN_ERR_EOBUF, ""); + conv_err(5, 0x800000, ASN_ERR_EOBUF, ""); + conv_err(6, 0x80000000, ASN_ERR_EOBUF, ""); + conv_err(7, 0x8000000000, ASN_ERR_EOBUF, ""); + conv_err(8, 0x800000000000, ASN_ERR_EOBUF, ""); + conv_err(9, 0x80000000000000, ASN_ERR_EOBUF, ""); + conv_err(10, 0x8000000000000000, ASN_ERR_EOBUF, ""); + } +} + +TEST_CASE("Unsigned32 parsing", "[asn1][parse]") +{ + asn_error = save_g_errstr; + + /** + * Sucessfully parse a COUNTER value. + * + * \param buf buffer to parse + * \param xval expected value + */ + const auto conv = [] (const auto &buf, uint32_t xval) { + auto r = check_header(buf, ASN_APP_COUNTER | ASN_CLASS_APPLICATION); + + uint32_t val; + REQUIRE(asn_get_uint32_raw(&r.buf, r.alen, &val) == ASN_ERR_OK); + REQUIRE(val == xval); + }; + + /** + * Parse COUNTER with error. + * + * \param buf buffer to parse + * \param err expected error from value parser + * \param errstr expected error string + */ + const auto conv_err = [] (const auto &buf, asn_err err, + std::string_view errstr) { + auto r = check_header(buf, ASN_APP_COUNTER | ASN_CLASS_APPLICATION); + + g_errstr.clear(); + uint32_t val; + REQUIRE(asn_get_uint32_raw(&r.buf, r.alen, &val) == err); + REQUIRE(g_errstr == errstr); + }; + + SECTION("correct encoding") { + conv("x41:01:00"_cbuf, 0x0U); + conv("x41:01:01"_cbuf, 0x1U); + conv("x41:01:7f"_cbuf, 0x7fU); + + conv("x41:02:00:80"_cbuf, 0x80U); + conv("x41:02:00:ff"_cbuf, 0xffU); + conv("x41:02:7f:ff"_cbuf, 0x7fffU); + + conv("x41:03:00:80:00"_cbuf, 0x8000U); + conv("x41:03:00:ff:ff"_cbuf, 0xffffU); + conv("x41:03:7f:ff:ff"_cbuf, 0x7fffffU); + + conv("x41:04:00:80:00:00"_cbuf, 0x800000U); + conv("x41:04:00:ff:ff:ff"_cbuf, 0xffffffU); + conv("x41:04:7f:ff:ff:ff"_cbuf, 0x7fffffffU); + + conv("x41:05:00:80:00:00:00"_cbuf, 0x80000000U); + conv("x41:05:00:ff:ff:ff:ff"_cbuf, 0xffffffffU); + } + SECTION("zero length") { + + conv_err("x41:00"_cbuf, ASN_ERR_BADLEN, + "zero-length integer at\n"); + } + + SECTION("non minimal encoding") { + conv_err("x41:02:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00\n"); + conv_err("x41:02:00:7f"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 7f\n"); + conv_err("x41:03:00:00:80"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 80\n"); + conv_err("x41:04:00:00:80:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 80 00\n"); + conv_err("x41:06:00:00:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 00 00 00 00 00\n"); + conv_err("x41:06:00:01:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal unsigned at 00 01 00 00 00 00\n"); + } + + SECTION("out of range") { + conv_err("x41:05:01:00:00:00:00"_cbuf, + ASN_ERR_RANGE, "uint32 too large 4294967296 at\n"); + conv_err("x41:06:01:00:00:00:00:00"_cbuf, + ASN_ERR_RANGE, "uint32 too large 1099511627776 at\n"); + conv_err("x41:01:80"_cbuf, + ASN_ERR_RANGE, "unsigned too large or negative at 80\n"); + conv_err("x41:02:80:00"_cbuf, + ASN_ERR_RANGE, "unsigned too large or negative at 80 00\n"); + conv_err("x41:03:80:00:00"_cbuf, + ASN_ERR_RANGE, "unsigned too large or negative at 80 00 00\n"); + } + +#ifndef BOGUS_CVE_2019_5610_FIX + SECTION("truncated value") { + conv_err("x41:01"_cbuf, ASN_ERR_EOBUF, + "truncated integer at\n"); + conv_err("x41:02:01"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 01\n"); + conv_err("x41:05:00:80:"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 00 80\n"); + conv_err("x41:05:00:ff:ff:ff"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 00 ff ff ff\n"); + } +#endif +} + +TEST_CASE("Unsigned32 building", "[asn1][build]") +{ + asn_error = save_g_errstr; + + const auto conv = [] (asn_len_t alen, uint32_t val, const auto &buf) { + auto b = mk_asn_buf(alen); + auto s = b; + REQUIRE(asn_put_uint32(&b, ASN_APP_COUNTER, val) == ASN_ERR_OK); + REQUIRE(b.asn_len == (size_t)0); + check_buf(s, buf); + }; + + const auto conv_err = [] (asn_len_t alen, uint32_t val, asn_err err, + std::string_view errstr) { + auto b = mk_asn_buf(alen); + g_errstr.clear(); + REQUIRE(asn_put_uint32(&b, ASN_APP_COUNTER, val) == err); + REQUIRE(g_errstr == errstr); + }; + + conv(3, 0x0, "x41:01:00"_cbuf); + conv(3, 0x1, "x41:01:01"_cbuf); + conv(3, 0x7f, "x41:01:7f"_cbuf); + + conv(4, 0x80, "x41:02:00:80"_cbuf); + conv(4, 0xff, "x41:02:00:ff"_cbuf); + conv(4, 0x7fff, "x41:02:7f:ff"_cbuf); + + conv(5, 0x8000, "x41:03:00:80:00"_cbuf); + conv(5, 0xffff, "x41:03:00:ff:ff"_cbuf); + conv(5, 0x7fffff, "x41:03:7f:ff:ff"_cbuf); + + conv(6, 0x800000, "x41:04:00:80:00:00"_cbuf); + conv(6, 0xffffff, "x41:04:00:ff:ff:ff"_cbuf); + conv(6, 0x7fffffff, "x41:04:7f:ff:ff:ff"_cbuf); + + conv(7, 0x80000000, "x41:05:00:80:00:00:00"_cbuf); + conv(7, 0xffffffff, "x41:05:00:ff:ff:ff:ff"_cbuf); + + SECTION("empty buffer") { + conv_err(0, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short for length field") { + conv_err(1, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short") { + conv_err(2, 0, ASN_ERR_EOBUF, ""); + conv_err(3, 0x80, ASN_ERR_EOBUF, ""); + conv_err(4, 0x8000, ASN_ERR_EOBUF, ""); + conv_err(5, 0x800000, ASN_ERR_EOBUF, ""); + conv_err(6, 0x80000000, ASN_ERR_EOBUF, ""); + } +} + +TEST_CASE("Integer parsing", "[asn1][parse]") +{ + asn_error = save_g_errstr; + + /** + * Sucessfully parse a INTEGER value. + * + * \param buf buffer to parse + * \param xval expected value + */ + const auto conv = [] (const auto &buf, int32_t xval) { + auto r = check_header(buf, ASN_TYPE_INTEGER); + + int32_t val; + REQUIRE(asn_get_integer_raw(&r.buf, r.alen, &val) == ASN_ERR_OK); + REQUIRE(val == xval); + }; + + /** + * Parse INTEGER with error. + * + * \param buf buffer to parse + * \param err expected error from value parser + * \param errstr expected error string + */ + const auto conv_err = [] (const auto &buf, asn_err err, + std::string_view errstr) { + auto r = check_header(buf, ASN_TYPE_INTEGER); + + g_errstr.clear(); + int32_t val; + REQUIRE(asn_get_integer_raw(&r.buf, r.alen, &val) == err); + REQUIRE(g_errstr == errstr); + }; + + SECTION("correct encoding") { + conv("x02:01:00"_cbuf, 0x0); + conv("x02:01:01"_cbuf, 0x1); + conv("x02:01:7f"_cbuf, 0x7f); + conv("x02:01:ff"_cbuf, -0x1); + conv("x02:01:80"_cbuf, -0x80); + + conv("x02:02:00:80"_cbuf, 0x80); + conv("x02:02:00:ff"_cbuf, 0xff); + conv("x02:02:7f:ff"_cbuf, 0x7fff); + conv("x02:02:ff:7f"_cbuf, -0x81); + conv("x02:02:ff:01"_cbuf, -0xff); + conv("x02:02:ff:00"_cbuf, -0x100); + conv("x02:02:80:00"_cbuf, -0x8000); + + conv("x02:03:00:80:00"_cbuf, 0x8000); + conv("x02:03:00:ff:ff"_cbuf, 0xffff); + conv("x02:03:7f:ff:ff"_cbuf, 0x7fffff); + conv("x02:03:ff:7f:ff"_cbuf, -0x8001); + conv("x02:03:ff:00:01"_cbuf, -0xffff); + conv("x02:03:ff:00:00"_cbuf, -0x10000); + conv("x02:03:80:00:00"_cbuf, -0x800000); + + conv("x02:04:00:80:00:00"_cbuf, 0x800000); + conv("x02:04:00:ff:ff:ff"_cbuf, 0xffffff); + conv("x02:04:7f:ff:ff:ff"_cbuf, 0x7fffffff); + conv("x02:04:ff:7f:ff:ff"_cbuf, -0x800001); + conv("x02:04:ff:00:00:01"_cbuf, -0xffffff); + conv("x02:04:ff:00:00:00"_cbuf, -0x1000000); + conv("x02:04:80:00:00:00"_cbuf, -0x80000000); + } + + SECTION("zero length") { + conv_err("x02:00"_cbuf, ASN_ERR_BADLEN, + "zero-length integer at\n"); + } + SECTION("too long") { + conv_err("x02:05:01:02:03:04:05"_cbuf, ASN_ERR_BADLEN, + "integer too long at\n"); + } + + SECTION("non minimal encoding") { + conv_err("x02:02:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 00\n"); + conv_err("x02:02:00:7f"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 7f\n"); + conv_err("x02:03:00:00:80"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 00 80\n"); + conv_err("x02:04:00:00:80:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 00 80 00\n"); + conv_err("x02:06:00:00:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 00 00 00 00 00\n"); + conv_err("x02:06:00:01:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at 00 01 00 00 00 00\n"); + conv_err("x02:02:ff:80"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff 80\n"); + conv_err("x02:02:ff:ff"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff ff\n"); + conv_err("x02:03:ff:80:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff 80 00\n"); + conv_err("x02:03:ff:ff:ff"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff ff ff\n"); + conv_err("x02:04:ff:80:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff 80 00 00\n"); + conv_err("x02:04:ff:ff:ff:ff"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff ff ff ff\n"); + conv_err("x02:06:ff:80:00:00:00:00"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff 80 00 00 00 00\n"); + conv_err("x02:06:ff:ff:ff:ff:ff:ff"_cbuf, ASN_ERR_BADLEN, + "non-minimal integer at ff ff ff ff ff ff\n"); + } + +#ifndef BOGUS_CVE_2019_5610_FIX + SECTION("truncated value") { + conv_err("x02:01"_cbuf, ASN_ERR_EOBUF, + "truncated integer at\n"); + conv_err("x02:02:ff"_cbuf, ASN_ERR_EOBUF, + "truncated integer at ff\n"); + conv_err("x02:05:ff:00:03:01"_cbuf, ASN_ERR_EOBUF, + "truncated integer at ff 00 03 01\n"); + conv_err("x02:04:7f:ff:"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 7f ff\n"); + conv_err("x02:04:80:00:00"_cbuf, ASN_ERR_EOBUF, + "truncated integer at 80 00 00\n"); + } +#endif +} + +TEST_CASE("Integer32 building", "[asn1][build]") +{ + asn_error = save_g_errstr; + + const auto conv = [] (asn_len_t alen, int32_t val, const auto &buf) { + auto b = mk_asn_buf(alen); + auto s = b; + REQUIRE(asn_put_integer(&b, val) == ASN_ERR_OK); + REQUIRE(b.asn_len == (size_t)0); + check_buf(s, buf); + }; + + const auto conv_err = [] (asn_len_t alen, int32_t val, asn_err err, + std::string_view errstr) { + auto b = mk_asn_buf(alen); + g_errstr.clear(); + REQUIRE(asn_put_integer(&b, val) == err); + REQUIRE(g_errstr == errstr); + }; + + conv(3, 0x0, "x02:01:00"_cbuf); + conv(3, 0x1, "x02:01:01"_cbuf); + conv(3, 0x7f, "x02:01:7f"_cbuf); + conv(3, -0x1, "x02:01:ff"_cbuf); + conv(3, -0x80, "x02:01:80"_cbuf); + + conv(4, 0x80, "x02:02:00:80"_cbuf); + conv(4, 0xff, "x02:02:00:ff"_cbuf); + conv(4, 0x7fff, "x02:02:7f:ff"_cbuf); + conv(4, -0x81, "x02:02:ff:7f"_cbuf); + conv(4, -0xff, "x02:02:ff:01"_cbuf); + conv(4, -0x100, "x02:02:ff:00"_cbuf); + conv(4, -0x8000, "x02:02:80:00"_cbuf); + + conv(5, 0x8000, "x02:03:00:80:00"_cbuf); + conv(5, 0xffff, "x02:03:00:ff:ff"_cbuf); + conv(5, 0x7fffff, "x02:03:7f:ff:ff"_cbuf); + conv(5, -0x8001, "x02:03:ff:7f:ff"_cbuf); + conv(5, -0xffff, "x02:03:ff:00:01"_cbuf); + conv(5, -0x10000, "x02:03:ff:00:00"_cbuf); + conv(5, -0x800000, "x02:03:80:00:00"_cbuf); + + conv(6, 0x800000, "x02:04:00:80:00:00"_cbuf); + conv(6, 0xffffff, "x02:04:00:ff:ff:ff"_cbuf); + conv(6, 0x7fffffff, "x02:04:7f:ff:ff:ff"_cbuf); + conv(6, -0x800001, "x02:04:ff:7f:ff:ff"_cbuf); + conv(6, -0xffffff, "x02:04:ff:00:00:01"_cbuf); + conv(6, -0x1000000, "x02:04:ff:00:00:00"_cbuf); + conv(6, -0x80000000, "x02:04:80:00:00:00"_cbuf); + + SECTION("empty buffer") { + conv_err(0, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short for length field") { + conv_err(1, 0, ASN_ERR_EOBUF, ""); + } + SECTION("buffer too short") { + conv_err(2, 0, ASN_ERR_EOBUF, ""); + conv_err(3, 0xff, ASN_ERR_EOBUF, ""); + conv_err(4, 0xffff, ASN_ERR_EOBUF, ""); + conv_err(5, 0xffffff, ASN_ERR_EOBUF, ""); + conv_err(5, 0x7fffffff, ASN_ERR_EOBUF, ""); + conv_err(2, -0x80, ASN_ERR_EOBUF, ""); + conv_err(3, -0x8000, ASN_ERR_EOBUF, ""); + conv_err(4, -0x800000, ASN_ERR_EOBUF, ""); + conv_err(5, -0x80000000, ASN_ERR_EOBUF, ""); + } +} + +TEST_CASE("Oid parsing", "[asn1][parse]") +{ + asn_error = save_g_errstr; + + /** + * Sucessfully parse a INTEGER value. + * + * \param buf buffer to parse + * \param xval expected value + */ + const auto conv = [] (const auto &buf, const asn_oid &xval) { + auto r = check_header(buf, ASN_TYPE_OBJID); + + struct asn_oid val; + REQUIRE(asn_get_objid_raw(&r.buf, r.alen, &val) == ASN_ERR_OK); + REQUIRE(asn_compare_oid(&val, &xval) == 0); + }; + + /** + * Parse INTEGER with error. + * + * \param buf buffer to parse + * \param err expected error from value parser + * \param errstr expected error string + */ + const auto conv_err = [] (const auto &buf, asn_err err, + std::string_view errstr) { + auto r = check_header(buf, ASN_TYPE_OBJID); + + g_errstr.clear(); + struct asn_oid val; + REQUIRE(asn_get_objid_raw(&r.buf, r.alen, &val) == err); + REQUIRE(g_errstr == errstr); + }; + + conv("x06:01:00"_cbuf, asn_oid {2, {0, 0}}); + conv("x06:01:28"_cbuf, asn_oid {2, {1, 0}}); + conv("x06:01:50"_cbuf, asn_oid {2, {2, 0}}); + + conv("x06:01:27"_cbuf, asn_oid {2, {0, 39}}); + conv("x06:01:4f"_cbuf, asn_oid {2, {1, 39}}); + conv("x06:01:7f"_cbuf, asn_oid {2, {2, 47}}); + + conv("x06:02:81:00"_cbuf, asn_oid {2, {2, 48}}); + conv("x06:02:ff:7f"_cbuf, asn_oid {2, {2, 16303}}); + conv("x06:03:ff:ff:7f"_cbuf, asn_oid {2, {2, 2097071}}); + conv("x06:04:ff:ff:ff:7f"_cbuf, asn_oid {2, {2, 268435375}}); + conv("x06:05:8f:ff:ff:ff:7f"_cbuf, asn_oid {2, {2, 4294967215}}); + + /* maximum OID */ + conv("x06:82:02:7b:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f"_cbuf, asn_oid {128, { + 2, 4294967215, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + }}); + + SECTION("truncated OID") { +#ifndef BOGUS_CVE_2019_5610_FIX + conv_err("x06:02:01"_cbuf, ASN_ERR_EOBUF, + "truncated OBJID at 01\n"); +#endif + conv_err("x06:01:8f"_cbuf, ASN_ERR_EOBUF, + "unterminated subid at\n"); + conv_err("x06:04:07:7f:82:8e"_cbuf, ASN_ERR_EOBUF, + "unterminated subid at\n"); + } + SECTION("short OID") { + conv_err("x06:00"_cbuf, ASN_ERR_BADLEN, + "short OBJID at\n"); + } + SECTION("too long") { + conv_err("x06:81:80:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c:7c"_cbuf, ASN_ERR_BADLEN, "OID too long (128) at 7c\n"); + } + SECTION("subid too large") { + conv_err("x06:06:20:90:82:83:84:75"_cbuf, ASN_ERR_RANGE, + "OID subid too larger at 75\n"); + } +} + +TEST_CASE("Objid building", "[asn1][build]") +{ + asn_error = save_g_errstr; + + const auto conv = [] (asn_len_t alen, const asn_oid &val, const auto &buf) { + auto b = mk_asn_buf(alen); + auto s = b; + REQUIRE(asn_put_objid(&b, &val) == ASN_ERR_OK); + REQUIRE(b.asn_len == (size_t)0); + check_buf(s, buf); + }; + + const auto conv_err = [] (asn_len_t alen, const asn_oid &val, asn_err err, + std::string_view errstr) { + auto b = mk_asn_buf(alen); + g_errstr.clear(); + REQUIRE(asn_put_objid(&b, &val) == err); + REQUIRE(g_errstr == errstr); + }; + + conv(3, asn_oid {2, {0, 0}}, "x06:01:00"_cbuf); + conv(3, asn_oid {2, {1, 0}}, "x06:01:28"_cbuf); + conv(3, asn_oid {2, {2, 0}}, "x06:01:50"_cbuf); + + conv(3, asn_oid {2, {0, 39}}, "x06:01:27"_cbuf); + conv(3, asn_oid {2, {1, 39}}, "x06:01:4f"_cbuf); + conv(3, asn_oid {2, {2, 47}}, "x06:01:7f"_cbuf); + + conv(4, asn_oid {2, {2, 48}}, "x06:02:81:00"_cbuf); + conv(4, asn_oid {2, {2, 16303}}, "x06:02:ff:7f"_cbuf); + conv(5, asn_oid {2, {2, 2097071}}, "x06:03:ff:ff:7f"_cbuf); + conv(6, asn_oid {2, {2, 268435375}}, "x06:04:ff:ff:ff:7f"_cbuf); + conv(7, asn_oid {2, {2, 4294967215}}, "x06:05:8f:ff:ff:ff:7f"_cbuf); + + SECTION("sub-id too large") { + conv_err(3, asn_oid {2, {3, 0}}, ASN_ERR_RANGE, + "oid out of range (3,0)\n"); + conv_err(3, asn_oid {2, {0, 40}}, ASN_ERR_RANGE, + "oid out of range (0,40)\n"); + conv_err(3, asn_oid {2, {1, 40}}, ASN_ERR_RANGE, + "oid out of range (1,40)\n"); + conv_err(3, asn_oid {2, {2, 4294967216}}, ASN_ERR_RANGE, + "oid out of range (2,4294967216)\n"); + } + SECTION("oid too long") { + conv_err(200, asn_oid {129, {}}, ASN_ERR_RANGE, + "oid too long 129\n"); + } + SECTION("oid too short") { + conv_err(3, asn_oid {0, {}}, ASN_ERR_RANGE, + "short oid\n"); + conv_err(3, asn_oid {1, {0}}, ASN_ERR_RANGE, + "short oid\n"); + conv_err(3, asn_oid {1, {3}}, ASN_ERR_RANGE, + "oid[0] too large (3)\n"); + } + + /* maximum OID */ + conv(5 * (128 - 1) + 4, asn_oid {128, { + 2, 4294967215, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + 4294967295, 4294967295, 4294967295, 4294967295, + }}, "x06:82:02:7b:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f:8f:ff:ff:ff:7f"_cbuf); +} + +/* loop tests */ Property changes on: head/contrib/bsnmp/tests/asn1.cc ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bsnmp/tests/catch.hpp =================================================================== --- head/contrib/bsnmp/tests/catch.hpp (nonexistent) +++ head/contrib/bsnmp/tests/catch.hpp (revision 359512) @@ -0,0 +1,17597 @@ +/* + * Catch v2.11.0 + * Generated: 2019-11-15 15:01:56.628356 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 11 +#define CATCH_VERSION_PATCH 0 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if defined(__UCLIBC__) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template