diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.in | 34 | ||||
-rw-r--r-- | lib/asn1.c | 77 | ||||
-rw-r--r-- | lib/bsnmpclient.3 | 59 | ||||
-rw-r--r-- | lib/snmp.h | 3 | ||||
-rw-r--r-- | lib/snmpclient.c | 494 | ||||
-rw-r--r-- | lib/snmpclient.h | 1 | ||||
-rw-r--r-- | lib/snmpcrypto.c | 135 | ||||
-rw-r--r-- | lib/snmptc.h.in | 65 | ||||
-rw-r--r-- | lib/tc.def | 8 |
9 files changed, 612 insertions, 264 deletions
diff --git a/lib/Makefile.in b/lib/Makefile.in deleted file mode 100644 index dbf746e08b36..000000000000 --- a/lib/Makefile.in +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2003-2003 -# Fraunhofer Institute for Open Communication Systems (FhG Fokus). -# All rights reserved. -# -# Author: Harti Brandt <harti@freebsd.org> -# -# $Begemot: bsnmp/trunk/lib/Makefile.in 1487 2008-12-23 19:03:33Z brandt_h $ -# -SRCS= asn1.c snmp.c snmpagent.c snmpclient.c support.c # snmptc.c -INCS= asn1.h snmp.h snmpagent.h snmpclient.h ${builddir}/snmptc.h -MAN3= asn1.3 bsnmplib.3 bsnmpclient.3 bsnmpagent.3 -DEFS= tc.def - -LIB= libbsnmp.la -SHLIB_MAJOR= 6 -SHLIB_MINOR= 0 - -CFLAGS+= -I$(srcdir) - -$(LIB): $(SRCS:.c=.lo) snmptc.h - $(LIBTOOL) --mode=link $(CC) $(LDLAGS) -o $@ $(SRCS:.c=.lo) -rpath $(libdir) -version-info $(SHLIB_MAJOR):$(SHLIB_MINOR) - -snmptc.h : tc.def - ( \ - echo -n "/* autogenerated from tc.def; " ; \ - ls -l -D "%F %T" $(srcdir)/tc.def | awk '{printf("%s %s",$$6,$$7)}' ; \ - echo "*/"; \ - echo "#ifndef snmptc_h_1529923773" ; \ - echo "#define snmptc_h_1529923773" ; \ - ${top_builddir}/gensnmptree/gensnmptree -E -f <${srcdir}/tc.def ; \ - echo "#endif" ; \ - ) >snmptc.h - -CLEANFILES += snmptc.h *.3out diff --git a/lib/asn1.c b/lib/asn1.c index 03b5662ed22c..f1f9267c0226 100644 --- a/lib/asn1.c +++ b/lib/asn1.c @@ -65,8 +65,8 @@ asn_get_header(struct asn_buf *b, u_char *type, asn_len_t *len) 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); } @@ -100,6 +100,19 @@ asn_get_header(struct asn_buf *b, u_char *type, asn_len_t *len) *len = *b->asn_cptr++; b->asn_len--; } + +#ifdef BOGUS_CVE_2019_5610_FIX + /* + * This is the fix from CVE-2019-5610. + * + * This is the wrong place. Each of the asn functions should check + * that it has enough info for its own work. + */ + if (*len > b->asn_len) { + asn_error(b, "lenen %u exceeding asn_len %u", *len, b->asn_len); + return (ASN_ERR_EOBUF); + } +#endif return (ASN_ERR_OK); } @@ -142,7 +155,7 @@ asn_put_len(u_char *ptr, asn_len_t len) /* * 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 @@ -151,8 +164,8 @@ 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); } @@ -246,9 +259,10 @@ asn_get_real_integer(struct asn_buf *b, asn_len_t len, int64_t *vp) 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"); @@ -326,27 +340,35 @@ asn_put_real_integer(struct asn_buf *b, u_char type, int64_t ival) 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--) { @@ -400,11 +422,14 @@ asn_get_integer_raw(struct asn_buf *b, asn_len_t len, int32_t *vp) 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); @@ -584,7 +609,7 @@ asn_get_objid_raw(struct asn_buf *b, asn_len_t len, struct asn_oid *oid) 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); @@ -640,7 +665,7 @@ asn_put_objid(struct asn_buf *b, const struct asn_oid *oid) 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; @@ -652,7 +677,8 @@ asn_put_objid(struct asn_buf *b, const struct asn_oid *oid) 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; @@ -809,10 +835,7 @@ asn_get_uint32_raw(struct asn_buf *b, asn_len_t len, uint32_t *vp) 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; } diff --git a/lib/bsnmpclient.3 b/lib/bsnmpclient.3 index 10c2dc6a22a1..0a2286eb14c4 100644 --- a/lib/bsnmpclient.3 +++ b/lib/bsnmpclient.3 @@ -31,7 +31,7 @@ .\" .\" $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 @@ -177,7 +177,9 @@ If it is 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. @@ -675,7 +677,12 @@ The syntax of a server specification is .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 @@ -685,13 +692,51 @@ of a local socket, and 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 , diff --git a/lib/snmp.h b/lib/snmp.h index 61e783e76bb0..7db21ae7d8d0 100644 --- a/lib/snmp.h +++ b/lib/snmp.h @@ -42,6 +42,9 @@ #include <sys/types.h> +#define BSNMP_MAJOR 1 +#define BSNMP_MINOR 13 + #define SNMP_COMMUNITY_MAXLEN 128 #define SNMP_MAX_BINDINGS 100 #define SNMP_CONTEXT_NAME_SIZ (32 + 1) diff --git a/lib/snmpclient.c b/lib/snmpclient.c index bb711ee0c73c..c22d8e125a14 100644 --- a/lib/snmpclient.c +++ b/lib/snmpclient.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2005 + * Copyright (c) 2004-2005,2018-2019 * Hartmut Brandt. * All rights reserved. * Copyright (c) 2001-2003 @@ -34,11 +34,13 @@ * * Support functions for SNMP clients. */ -#include <sys/types.h> +#include <sys/param.h> #include <sys/time.h> #include <sys/queue.h> #include <sys/socket.h> #include <sys/un.h> +#include <net/if.h> +#include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <stddef.h> @@ -58,12 +60,16 @@ #include <err.h> #endif +#include <arpa/inet.h> + #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; @@ -474,7 +480,7 @@ table_check_response(struct tabwork *work, const struct snmp_pdu *resp) if (snmp_client.version == SNMP_V1 && resp->error_status == SNMP_ERR_NOSUCHNAME && resp->error_index == - (work->descr->last_change.len == 0) ? 1 : 2) + ((work->descr->last_change.len == 0) ? 1 : 2)) /* EOT */ return (0); /* Error */ @@ -924,7 +930,8 @@ open_client_udp(const char *host, const char *port) /* open connection */ memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; - hints.ai_family = 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); @@ -1068,6 +1075,7 @@ snmp_open(const char *host, const char *port, const char *readcomm, switch (snmp_client.trans) { case SNMP_TRANS_UDP: + case SNMP_TRANS_UDP6: if (open_client_udp(host, port) != 0) return (-1); break; @@ -1866,99 +1874,425 @@ snmp_client_set_port(struct snmp_client *cl, const char *p) return (0); } -/* - * parse a server specification +static const char *const trans_list[] = { + [SNMP_TRANS_UDP] = "udp::", + [SNMP_TRANS_LOC_DGRAM] = "dgram::", + [SNMP_TRANS_LOC_STREAM] = "stream::", + [SNMP_TRANS_UDP6] = "udp6::", +}; + +/** + * Try to get a transport identifier which is a leading alphanumeric string + * terminated by a double colon. The string may not be empty. The transport + * identifier is optional. Unknown transport identifiers are reject. + * Be careful: a double colon can also occur in a numeric IPv6 address. * - * [trans::][community@][server][:port] + * \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 */ -int -snmp_parse_server(struct snmp_client *sc, const char *str) +static inline int +get_transp(struct snmp_client *sc, const char **strp) { - const char *p, *s = str; - - /* look for a double colon */ - for (p = s; *p != '\0'; p++) { - if (*p == '\\' && p[1] != '\0') { - p++; - continue; + const char *p; + size_t i; + + for (i = 0; i < nitems(trans_list); i++) { + p = strstr(*strp, trans_list[i]); + if (p == *strp) { + *strp += strlen(trans_list[i]); + return ((int)i); } - if (*p == ':' && p[1] == ':') - break; } - if (*p != '\0') { - if (p > s) { - if (p - s == 3 && strncmp(s, "udp", 3) == 0) - sc->trans = SNMP_TRANS_UDP; - else if (p - s == 6 && strncmp(s, "stream", 6) == 0) - sc->trans = SNMP_TRANS_LOC_STREAM; - else if (p - s == 5 && strncmp(s, "dgram", 5) == 0) - sc->trans = SNMP_TRANS_LOC_DGRAM; - else { - seterr(sc, "unknown SNMP transport '%.*s'", - (int)(p - s), s); - return (-1); - } - } - s = p + 2; + + p = strstr(*strp, "::"); + if (p == *strp) { + seterr(sc, "empty transport specifier"); + return (-1); } + if (p == NULL) + /* by default assume UDP */ + return (SNMP_TRANS_UDP); - /* look for a @ */ - for (p = s; *p != '\0'; p++) { - if (*p == '\\' && p[1] != '\0') { - p++; - continue; - } - if (*p == '@') - break; + /* 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); } - if (*p != '\0') { - if (p - s > SNMP_COMMUNITY_MAXLEN) { - seterr(sc, "community string too long"); - return (-1); + *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); } - strncpy(sc->read_community, s, p - s); - sc->read_community[p - s] = '\0'; - strncpy(sc->write_community, s, p - s); - sc->write_community[p - s] = '\0'; - s = p + 1; + p++; } - /* look for a colon */ - for (p = s; *p != '\0'; p++) { - if (*p == '\\' && p[1] != '\0') { - p++; - continue; - } - if (*p == ':') - break; + if (p - *strp > INET6_ADDRSTRLEN + IF_NAMESIZE) { + seterr(sc, "IPv6 address too long '%.*s'", p - *strp, *strp); + return (NULL); } - if (*p == ':') { - if (p > s) { - /* host:port */ - free(sc->chost); - if ((sc->chost = malloc(p - s + 1)) == NULL) { - seterr(sc, "%s", strerror(errno)); - return (-1); - } - strncpy(sc->chost, s, p - s); - sc->chost[p - s] = '\0'; - } - /* port */ - free(sc->cport); - if ((sc->cport = strdup(p + 1)) == NULL) { - seterr(sc, "%s", strerror(errno)); - return (-1); - } + strncpy(str, *strp + 1, p - (*strp + 1)); + str[p - (*strp + 1)] = '\0'; - } else if (p > s) { - /* host */ - free(sc->chost); - if ((sc->chost = strdup(s)) == NULL) { - seterr(sc, "%s", strerror(errno)); + 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: + * + * [<trans>::][<comm>@][<host-or-ip>][:<port>] + * + * The transport string consists of letters, digits or '_' and starts with + * a letter or digit. It is terminated by two colons and may not be empty. + * + * The community string is terminated by the last '@' and does not exceed + * SNMP_COMMUNITY_MAXLEN. It may be empty. + * + * The host or ip is either an IPv4 address (as parsed by inet_pton()), an + * IPv6 address in '[' and ']' and parseable by inet_aton() or a hostname + * terminated by the last colon or by the NUL character. + * + * The port number may be specified numerically or symbolically and starts + * with the last colon. + * + * The functions sets the chost, cport, trans, read_community and + * write_community fields on success and the error field on errors. + * The chost and cport fields are allocated by malloc(3), their previous + * content is deallocated by free(3). + * + * The function explicitly allows mismatches between the transport and + * the address type in order to support IPv4 in IPv6 addresses. + * + * \param sc client struct to fill + * \param str string to parse + * + * \return 0 on success and -1 on errors + */ +int +snmp_parse_server(struct snmp_client *sc, const char *str) +{ + const char *const orig = str; + + /* parse input */ + int def_trans = 0, trans = get_transp(sc, &str); + if (trans < 0) + return (-1); + /* choose automatically */ + if (orig == str) + def_trans = 1; + + const char *const comm[2] = { + str, + get_comm(sc, &str), + }; + if (comm[1] == NULL) + return (-1); + + const char *const ipv6[2] = { + str + 1, + get_ipv6(sc, &str), + }; + if (ipv6[1] == NULL) + return (-1); + + const char *ipv4[2] = { + str, + str, + }; + + const char *host[2] = { + str, + str, + }; + + if (ipv6[0] == ipv6[1]) { + ipv4[1] = get_ipv4(sc, &str); + + if (ipv4[0] == ipv4[1]) + host[1] = get_host(sc, &str); + } + + const char *port[2] = { + str + 1, + get_port(sc, &str), + }; + if (port[1] == NULL) + return (-1); + + if (*str != '\0') { + seterr(sc, "junk at end of server specification '%s'", str); + return (-1); + } + +#if DEBUG_PARSE + printf("transp: %d (def=%d)\n", trans, def_trans); + printf("comm: %zu %zu\n", comm[0] - orig, comm[1] - orig); + printf("ipv6: %zu %zu\n", ipv6[0] - orig, ipv6[1] - orig); + printf("ipv4: %zu %zu\n", ipv4[0] - orig, ipv4[1] - orig); + printf("host: %zu %zu\n", host[0] - orig, host[1] - orig); + printf("port: %zu %zu\n", port[0] - orig, port[1] - orig); +#endif + + /* analyse and allocate */ + char *chost; + + if (ipv6[0] != ipv6[1]) { + if ((chost = save_str(sc, ipv6)) == NULL) + return (-1); + if (def_trans || trans == SNMP_TRANS_UDP) + /* assume the user meant udp6:: */ + trans = SNMP_TRANS_UDP6; + } else if (ipv4[0] != ipv4[1]) { + if ((chost = save_str(sc, ipv4)) == NULL) + return (-1); + if (def_trans) + trans = SNMP_TRANS_UDP; + } else { + if ((chost = save_str(sc, host)) == NULL) return (-1); + + if (def_trans) { + /* + * Default transport is UDP unless the host contains + * a slash in which case we default to DGRAM. + */ + for (const char *p = host[0]; p < host[1]; p++) + if (*p == '/') { + trans = SNMP_TRANS_LOC_DGRAM; + break; + } } } + + char *cport; + + if (port[0] == port[1] && ( + trans == SNMP_TRANS_UDP || trans == SNMP_TRANS_UDP6)) { + /* If port was not specified, use "snmp" name by default */ + cport = strdup("snmp"); + } else + cport = save_str(sc, port); + + if (cport == NULL) { + free(chost); + return (-1); + } + + /* commit */ + sc->trans = trans; + + /* + * If community string was specified and it is empty, overwrite it. + * If it was not specified, use default. + */ + if (comm[0] != comm[1] || strrchr(comm[0], '@') != NULL) { + strncpy(sc->read_community, comm[0], comm[1] - comm[0]); + sc->read_community[comm[1] - comm[0]] = '\0'; + strncpy(sc->write_community, comm[0], comm[1] - comm[0]); + sc->write_community[comm[1] - comm[0]] = '\0'; + } + + free(sc->chost); + sc->chost = chost; + free(sc->cport); + sc->cport = cport; + +#if DEBUG_PARSE + printf("Committed values:\n"); + printf("trans: %d\n", sc->trans); + printf("comm: '%s'/'%s'\n", sc->read_community, sc->write_community); + printf("host: '%s'\n", sc->chost); + printf("port: '%s'\n", sc->cport); +#endif return (0); } diff --git a/lib/snmpclient.h b/lib/snmpclient.h index e7454b104ef5..a19bdb2ea653 100644 --- a/lib/snmpclient.h +++ b/lib/snmpclient.h @@ -49,6 +49,7 @@ #define SNMP_TRANS_UDP 0 #define SNMP_TRANS_LOC_DGRAM 1 #define SNMP_TRANS_LOC_STREAM 2 +#define SNMP_TRANS_UDP6 3 /* type of callback function for responses * this callback function is responsible for free() any memory associated with diff --git a/lib/snmpcrypto.c b/lib/snmpcrypto.c index cab1684dd427..5b2e5e14ae68 100644 --- a/lib/snmpcrypto.c +++ b/lib/snmpcrypto.c @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: head/contrib/bsnmp/lib/snmpcrypto.c 310648 2016-12-27 23:32:54Z ngie $ + * $FreeBSD: head/contrib/bsnmp/lib/snmpcrypto.c 339270 2018-10-09 21:28:26Z gjb $ */ #include <sys/types.h> #include <sys/socket.h> @@ -57,11 +57,11 @@ #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) @@ -94,9 +94,14 @@ snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest) uint32_t i, keylen, olen; int32_t err; const EVP_MD *dtype; - EVP_MD_CTX ctx; + EVP_MD_CTX *ctx; - err = snmp_digest_init(&pdu->user, &ctx, &dtype, &keylen); + 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) @@ -111,29 +116,29 @@ snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest) 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) + 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) + 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_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } memcpy(digest, md, SNMP_USM_AUTH_SIZE); - EVP_MD_CTX_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); failed: - EVP_MD_CTX_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } @@ -176,7 +181,7 @@ 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; + EVP_CIPHER_CTX *ctx; err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv); if (err < 0) @@ -184,18 +189,23 @@ snmp_pdu_encrypt(const struct snmp_pdu *pdu) else if (err == 0) return (SNMP_CODE_OK); - if (EVP_EncryptInit(&ctx, ctype, pdu->user.priv_key, iv) != 1) + 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, + if (EVP_EncryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, pdu->scoped_len) != 1 || - EVP_EncryptFinal(&ctx, pdu->scoped_ptr + olen, &olen) != 1) { - EVP_CIPHER_CTX_cleanup(&ctx); - return (SNMP_CODE_FAILED); - } + EVP_EncryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1) + goto failed; - EVP_CIPHER_CTX_cleanup(&ctx); + EVP_CIPHER_CTX_free(ctx); return (SNMP_CODE_OK); + +failed: + EVP_CIPHER_CTX_free(ctx); + return (SNMP_CODE_FAILED); } enum snmp_code @@ -204,7 +214,7 @@ 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; + EVP_CIPHER_CTX *ctx; err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv); if (err < 0) @@ -212,19 +222,24 @@ snmp_pdu_decrypt(const struct snmp_pdu *pdu) else if (err == 0) return (SNMP_CODE_OK); - if (EVP_DecryptInit(&ctx, ctype, pdu->user.priv_key, iv) != 1 || - EVP_CIPHER_CTX_set_padding(&ctx, 0) != 1) - return (SNMP_CODE_EDECRYPT); + 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, + if (EVP_DecryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, pdu->scoped_len) != 1 || - EVP_DecryptFinal(&ctx, pdu->scoped_ptr + olen, &olen) != 1) { - EVP_CIPHER_CTX_cleanup(&ctx); - return (SNMP_CODE_EDECRYPT); - } + EVP_DecryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1) + goto failed; - EVP_CIPHER_CTX_cleanup(&ctx); + 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 */ @@ -234,13 +249,19 @@ 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; + EVP_MD_CTX *ctx; uint8_t authbuf[SNMP_AUTH_BUF_SIZE]; if (passwd == NULL || user == NULL) return (SNMP_CODE_FAILED); - err = snmp_digest_init(user, &ctx, &dtype, &keylen); + 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) @@ -252,18 +273,18 @@ snmp_passwd_to_keys(struct snmp_user *user, char *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) + if (EVP_DigestUpdate(ctx, authbuf, SNMP_EXTENDED_KEY_SIZ) != 1) goto failed; } - if (EVP_DigestFinal(&ctx, user->auth_key, &olen) != 1) + if (EVP_DigestFinal(ctx, user->auth_key, &olen) != 1) goto failed; - EVP_MD_CTX_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); failed: - EVP_MD_CTX_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_BADDIGEST); } @@ -274,16 +295,22 @@ 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; + 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); + 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) @@ -293,12 +320,12 @@ snmp_get_local_keys(struct snmp_user *user, uint8_t *eid, uint32_t elen) 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_cleanup(&ctx); + 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_cleanup(&ctx); + EVP_MD_CTX_free(ctx); if (user->priv_proto != SNMP_PRIV_NOPRIV) memcpy(user->priv_key, user->auth_key, sizeof(user->priv_key)); @@ -312,9 +339,15 @@ 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; + EVP_MD_CTX *ctx; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) + return (SNMP_CODE_FAILED); - err = snmp_digest_init(user, &ctx, &dtype, &keylen); + 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) @@ -326,13 +359,13 @@ snmp_calc_keychange(struct snmp_user *user, uint8_t *keychange) 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_cleanup(&ctx); + 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_cleanup(&ctx); + EVP_MD_CTX_free(ctx); return (SNMP_CODE_OK); } diff --git a/lib/snmptc.h.in b/lib/snmptc.h.in deleted file mode 100644 index 377c5e9fef87..000000000000 --- a/lib/snmptc.h.in +++ /dev/null @@ -1,65 +0,0 @@ -/*- - * Copyright (c) 2018 The FreeBSD Foundation - * All rights reserved. - * - * Author: Harti Brandt <harti@freebsd.org> - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef snmptc_h_1529923773 -#define snmptc_h_1529923773 - -@tc@ - -/** - * Check whether a row status is ok. - * - * \param s row status - * - * \return true if value is ok, false otherwise - */ -static inline int -isok_RowStatus(enum RowStatus s) -{ - return s >= RowStatus_active && s <= RowStatus_destroy; -} - -/** - * Make string out of a row status. - * - * \param s row status - * - * \return string version; if the value is not a legal status - * return "RowStatus???" - */ -static inline const char * -tostr_RowStatus(enum RowStatus s) -{ - static const char *vals[] = { STRING_RowStatus }; - - if (isok_RowStatus(s)) - return vals[(int)s - STROFF_RowStatus]; - return ("RowStatus???"); -} - -#endif diff --git a/lib/tc.def b/lib/tc.def index 1930346ecabb..1c408b844122 100644 --- a/lib/tc.def +++ b/lib/tc.def @@ -46,3 +46,11 @@ typedef StorageType ENUM ( 5 readOnly ) +typedef InetAddressType ENUM ( + 0 unknown + 1 ipv4 + 2 ipv6 + 3 ipv4z + 4 ipv6z + 16 dns +) |