summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.in34
-rw-r--r--lib/asn1.c77
-rw-r--r--lib/bsnmpclient.359
-rw-r--r--lib/snmp.h3
-rw-r--r--lib/snmpclient.c494
-rw-r--r--lib/snmpclient.h1
-rw-r--r--lib/snmpcrypto.c135
-rw-r--r--lib/snmptc.h.in65
-rw-r--r--lib/tc.def8
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
+)