From 73b5c081a08ab8132aaab716c8f4da9aebb020e7 Mon Sep 17 00:00:00 2001 From: martijn Date: Mon, 9 Aug 2021 18:14:53 +0000 Subject: [PATCH] Allow setting the engineid. The previous engineid was based aronud the engine boottime and a random value, which gives problems when sending/receiving unacknowledged PDUs (trapv2) over SNMPv3 with authentication enabled, which need a consistent engineid across restarts to determine the correct user from the sender. The new default engineid takes a sha256 hash (chosen for its longer output) of gethostname(3) and places the first 27 bytes after the new format number 129. This should give us a very low probability of collisions, assuming all machines have a unique name. The other formats as specified in SNMP-FRAMEWORK-MIB (RFC3411) are also supported as well as arbitrary formats in the range 128-255 for other private enterprise numbers in hex format. OK jmatthew@ --- usr.sbin/snmpd/parse.y | 252 +++++++++++++++++++++++++++++++++++- usr.sbin/snmpd/snmpd.c | 45 +------ usr.sbin/snmpd/snmpd.conf.5 | 69 +++++++++- usr.sbin/snmpd/snmpd.h | 9 +- usr.sbin/snmpd/snmpe.c | 5 +- usr.sbin/snmpd/util.c | 43 +++++- 6 files changed, 369 insertions(+), 54 deletions(-) diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y index b5bd627f714..de88a26c70e 100644 --- a/usr.sbin/snmpd/parse.y +++ b/usr.sbin/snmpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.64 2021/06/20 19:55:48 martijn Exp $ */ +/* $OpenBSD: parse.y,v 1.65 2021/08/09 18:14:53 martijn Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter @@ -35,6 +35,8 @@ #include #include +#include + #include #include #include @@ -95,6 +97,10 @@ struct snmpd *conf = NULL; static int errors = 0; static struct usmuser *user = NULL; +static uint8_t engineid[SNMPD_MAXENGINEIDLEN]; +static int32_t enginepen; +static size_t engineidlen; + int host(const char *, const char *, int, struct sockaddr_storage *, int); int listen_add(struct sockaddr_storage *, int, int); @@ -120,13 +126,14 @@ typedef struct { %} %token INCLUDE -%token LISTEN ON READ WRITE NOTIFY SNMPV1 SNMPV2 SNMPV3 +%token LISTEN ON READ WRITE NOTIFY SNMPV1 SNMPV2 SNMPV3 +%token ENGINEID PEN OPENBSD IP4 IP6 MAC TEXT OCTETS AGENTID HOSTHASH %token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER %token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER %token SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR %token HANDLE DEFAULT SRCADDR TCP UDP PFADDRFILTER PORT %token STRING -%token NUMBER +%token NUMBER %type hostcmn %type srcaddr port %type optwrite yesno seclevel listenopt listenopts @@ -196,6 +203,14 @@ yesno : STRING { ; main : LISTEN ON listenproto + | engineid_local { + if (conf->sc_engineid_len != 0) { + yyerror("Redefinition of engineid"); + YYERROR; + } + memcpy(conf->sc_engineid, engineid, engineidlen); + conf->sc_engineid_len = engineidlen; + } | READONLY COMMUNITY STRING { if (strlcpy(conf->sc_rdcommunity, $3, sizeof(conf->sc_rdcommunity)) >= @@ -381,6 +396,210 @@ port : /* empty */ { } ; +enginefmt : IP4 STRING { + struct in_addr addr; + + engineid[engineidlen++] = SNMP_ENGINEID_FMT_IPv4; + if (inet_pton(AF_INET, $2, &addr) != 1) { + yyerror("Invalid ipv4 address: %s", $2); + free($2); + YYERROR; + } + memcpy(engineid + engineidlen, &addr, + sizeof(engineid) - engineidlen); + engineid[0] |= SNMP_ENGINEID_NEW; + engineidlen += sizeof(addr); + free($2); + } + | IP6 STRING { + struct in6_addr addr; + + engineid[engineidlen++] = SNMP_ENGINEID_FMT_IPv6; + if (inet_pton(AF_INET6, $2, &addr) != 1) { + yyerror("Invalid ipv6 address: %s", $2); + free($2); + YYERROR; + } + memcpy(engineid + engineidlen, &addr, + sizeof(engineid) - engineidlen); + engineid[0] |= SNMP_ENGINEID_NEW; + engineidlen += sizeof(addr); + free($2); + } + | MAC STRING { + size_t i; + + if (strlen($2) != 5 * 3 + 2) { + yyerror("Invalid mac address: %s", $2); + free($2); + YYERROR; + } + engineid[engineidlen++] = SNMP_ENGINEID_FMT_MAC; + for (i = 0; i < 6; i++) { + if (fromhexstr(engineid + engineidlen + i, + $2 + (i * 3), 2) == NULL || + $2[i * 3 + 2] != (i < 5 ? ':' : '\0')) { + yyerror("Invalid mac address: %s", $2); + free($2); + YYERROR; + } + } + engineid[0] |= SNMP_ENGINEID_NEW; + engineidlen += 6; + free($2); + } + | TEXT STRING { + size_t i, fmtstart; + + engineid[engineidlen++] = SNMP_ENGINEID_FMT_TEXT; + for (i = 0, fmtstart = engineidlen; + i < sizeof(engineid) - engineidlen && $2[i] != '\0'; + i++) { + if (!isprint($2[i])) { + yyerror("invalid text character"); + free($2); + YYERROR; + } + engineid[fmtstart + i] = $2[i]; + engineidlen++; + } + if (i == 0 || $2[i] != '\0') { + yyerror("Invalid text length: %s", $2); + free($2); + YYERROR; + } + engineid[0] |= SNMP_ENGINEID_NEW; + free($2); + } + | OCTETS STRING { + if (strlen($2) / 2 > sizeof(engineid) - 1) { + yyerror("Invalid octets length: %s", $2); + free($2); + YYERROR; + } + + engineid[engineidlen++] = SNMP_ENGINEID_FMT_OCT; + if (fromhexstr(engineid + engineidlen, $2, + strlen($2)) == NULL) { + yyerror("Invalid octets: %s", $2); + free($2); + YYERROR; + } + engineidlen += strlen($2) / 2; + engineid[0] |= SNMP_ENGINEID_NEW; + free($2); + } + | AGENTID STRING { + if (strlen($2) / 2 != 8) { + yyerror("Invalid agentid length: %s", $2); + free($2); + YYERROR; + } + + if (fromhexstr(engineid + engineidlen, $2, + strlen($2)) == NULL) { + yyerror("Invalid agentid: %s", $2); + free($2); + YYERROR; + } + engineidlen += 8; + engineid[0] |= SNMP_ENGINEID_OLD; + free($2); + } + | HOSTHASH STRING { + if (enginepen != PEN_OPENBSD) { + yyerror("hosthash only allowed for pen " + "openbsd"); + YYERROR; + } + engineid[engineidlen++] = SNMP_ENGINEID_FMT_HH; + memcpy(engineid + engineidlen, + SHA256($2, strlen($2), NULL), + sizeof(engineid) - engineidlen); + engineidlen = sizeof(engineid); + engineid[0] |= SNMP_ENGINEID_NEW; + free($2); + } + | NUMBER STRING { + if (enginepen == PEN_OPENBSD) { + yyerror("%lld is only allowed when pen is not " + "openbsd", $1); + YYERROR; + } + + if ($1 < 128 || $1 > 255) { + yyerror("Invalid format number: %lld\n", $1); + YYERROR; + } + if (strlen($2) / 2 > sizeof(engineid) - 1) { + yyerror("Invalid octets length: %s", $2); + free($2); + YYERROR; + } + + engineid[engineidlen++] = (uint8_t)$1; + if (fromhexstr(engineid + engineidlen, $2, + strlen($2)) == NULL) { + yyerror("Invalid octets: %s", $2); + free($2); + YYERROR; + } + engineidlen += strlen($2) / 2; + engineid[0] |= SNMP_ENGINEID_NEW; + free($2); + } + ; + +enginefmt_local : enginefmt + | HOSTHASH { + char hostname[HOST_NAME_MAX + 1]; + + if (enginepen != PEN_OPENBSD) { + yyerror("hosthash only allowed for pen " + "openbsd"); + YYERROR; + } + + if (gethostname(hostname, sizeof(hostname)) == -1) { + yyerror("gethostname: %s", strerror(errno)); + YYERROR; + } + + engineid[engineidlen++] = SNMP_ENGINEID_FMT_HH; + memcpy(engineid + engineidlen, + SHA256(hostname, strlen(hostname), NULL), + sizeof(engineid) - engineidlen); + engineidlen = sizeof(engineid); + engineid[0] |= SNMP_ENGINEID_NEW; + } + ; + +pen : /* empty */ { + enginepen = PEN_OPENBSD; + } + | PEN NUMBER { + if ($2 > INT32_MAX) { + yyerror("pen number too large"); + YYERROR; + } + if ($2 <= 0) { + yyerror("pen number too small"); + YYERROR; + } + enginepen = $2; + } + | PEN OPENBSD { + enginepen = PEN_OPENBSD; + } + ; + +engineid_local : ENGINEID pen { + uint32_t npen = htonl(enginepen); + + memcpy(engineid, &npen, sizeof(enginepen)); + engineidlen = sizeof(enginepen); + } enginefmt_local + system : SYSTEM sysmib ; @@ -707,6 +926,7 @@ lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { + { "agentid", AGENTID }, { "auth", AUTH }, { "authkey", AUTHKEY }, { "community", COMMUNITY }, @@ -715,18 +935,26 @@ lookup(char *s) { "description", DESCR }, { "enc", ENC }, { "enckey", ENCKEY }, + { "engineid", ENGINEID }, { "filter-pf-addresses", PFADDRFILTER }, { "filter-routes", RTFILTER }, { "handle", HANDLE }, + { "hosthash", HOSTHASH }, { "include", INCLUDE }, { "integer", INTEGER }, + { "ipv4", IP4 }, + { "ipv6", IP6 }, { "listen", LISTEN }, { "location", LOCATION }, + { "mac", MAC }, { "name", NAME }, { "none", NONE }, { "notify", NOTIFY }, + { "octets", OCTETS }, { "oid", OBJECTID }, { "on", ON }, + { "openbsd", OPENBSD }, + { "pen", PEN }, { "port", PORT }, { "read", READ }, { "read-only", READONLY }, @@ -741,6 +969,7 @@ lookup(char *s) { "string", OCTETSTRING }, { "system", SYSTEM }, { "tcp", TCP }, + { "text", TEXT }, { "trap", TRAP }, { "udp", UDP }, { "user", USER }, @@ -1109,7 +1338,9 @@ parse_config(const char *filename, u_int flags) struct trap_address *tr; const struct usmuser *up; const char *errstr; + char hostname[HOST_NAME_MAX + 1]; int found; + uint32_t npen = htonl(PEN_OPENBSD); if ((conf = calloc(1, sizeof(*conf))) == NULL) { log_warn("%s", __func__); @@ -1135,6 +1366,21 @@ parse_config(const char *filename, u_int flags) endservent(); + /* Must be identical to enginefmt_local:HOSTHASH */ + if (conf->sc_engineid_len == 0) { + if (gethostname(hostname, sizeof(hostname)) == -1) + fatal("gethostname"); + memcpy(conf->sc_engineid, &npen, sizeof(npen)); + conf->sc_engineid_len += sizeof(npen); + conf->sc_engineid[conf->sc_engineid_len++] |= + SNMP_ENGINEID_FMT_HH; + memcpy(conf->sc_engineid + conf->sc_engineid_len, + SHA256(hostname, strlen(hostname), NULL), + sizeof(conf->sc_engineid) - conf->sc_engineid_len); + conf->sc_engineid_len = sizeof(conf->sc_engineid); + conf->sc_engineid[0] |= SNMP_ENGINEID_NEW; + } + /* Setup default listen addresses */ if (TAILQ_EMPTY(&conf->sc_addresses)) { if (host("0.0.0.0", SNMP_PORT, SOCK_DGRAM, &ss, 1) != 1) diff --git a/usr.sbin/snmpd/snmpd.c b/usr.sbin/snmpd/snmpd.c index f8cafb6263d..750ffd7205e 100644 --- a/usr.sbin/snmpd/snmpd.c +++ b/usr.sbin/snmpd/snmpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.c,v 1.44 2021/01/27 07:21:54 deraadt Exp $ */ +/* $OpenBSD: snmpd.c,v 1.45 2021/08/09 18:14:53 martijn Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter @@ -45,7 +45,6 @@ __dead void usage(void); void snmpd_shutdown(struct snmpd *); void snmpd_sig_handler(int, short, void *); int snmpd_dispatch_snmpe(int, struct privsep_proc *, struct imsg *); -void snmpd_generate_engineid(struct snmpd *); int check_child(pid_t, const char *); struct snmpd *snmpd_env; @@ -228,7 +227,6 @@ main(int argc, char *argv[]) env->sc_engine_boots = 0; pf_init(); - snmpd_generate_engineid(env); proc_init(ps, procs, nitems(procs), debug, argc0, argv0, proc_id); if (!debug && daemon(0, 0) == -1) @@ -318,29 +316,6 @@ snmpd_socket_af(struct sockaddr_storage *ss, int type) SOCK_STREAM | SOCK_NONBLOCK : SOCK_DGRAM) | SOCK_CLOEXEC, 0); } -void -snmpd_generate_engineid(struct snmpd *env) -{ - u_int32_t oid_enterprise, rnd, tim; - - /* RFC 3411 */ - memset(env->sc_engineid, 0, sizeof(env->sc_engineid)); - oid_enterprise = htonl(OIDVAL_openBSD_eid); - memcpy(env->sc_engineid, &oid_enterprise, sizeof(oid_enterprise)); - env->sc_engineid[0] |= SNMP_ENGINEID_NEW; - env->sc_engineid_len = sizeof(oid_enterprise); - - /* XXX alternatively configure engine id via snmpd.conf */ - env->sc_engineid[(env->sc_engineid_len)++] = SNMP_ENGINEID_FMT_EID; - rnd = arc4random(); - memcpy(&env->sc_engineid[env->sc_engineid_len], &rnd, sizeof(rnd)); - env->sc_engineid_len += sizeof(rnd); - - tim = htonl(env->sc_starttime.tv_sec); - memcpy(&env->sc_engineid[env->sc_engineid_len], &tim, sizeof(tim)); - env->sc_engineid_len += sizeof(tim); -} - u_long snmpd_engine_time(void) { @@ -358,21 +333,3 @@ snmpd_engine_time(void) gettimeofday(&now, NULL); return now.tv_sec; } - -char * -tohexstr(u_int8_t *bstr, int len) -{ -#define MAXHEXSTRLEN 256 - static char hstr[2 * MAXHEXSTRLEN + 1]; - static const char hex[] = "0123456789abcdef"; - int i; - - if (len > MAXHEXSTRLEN) - len = MAXHEXSTRLEN; /* truncate */ - for (i = 0; i < len; i++) { - hstr[i + i] = hex[bstr[i] >> 4]; - hstr[i + i + 1] = hex[bstr[i] & 0x0f]; - } - hstr[i + i] = '\0'; - return hstr; -} diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 index 640afe51a83..62fb978ed4d 100644 --- a/usr.sbin/snmpd/snmpd.conf.5 +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: snmpd.conf.5,v 1.52 2021/08/08 13:41:26 sthen Exp $ +.\" $OpenBSD: snmpd.conf.5,v 1.53 2021/08/09 18:14:53 martijn Exp $ .\" .\" Copyright (c) 2007, 2008, 2012 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: August 8 2021 $ +.Dd $Mdocdate: August 9 2021 $ .Dt SNMPD.CONF 5 .Os .Sh NAME @@ -146,6 +146,71 @@ Having set requires at least one .Ic trap handle statement. +.It Ic engineid Oo Ic pen Ar enterprise Oc Ar format +Set the snmp engineid, used for instance identification and key +generation for the +.Ic user +.Ar auth +and +.Ar key . +.Ar enterprise +specifies the private enterprise number of the instance and can be either an +integer or +.Ic openbsd +.Pq default . +.Pp +.Ar format +can be one of the following: +.Bl -tag -widt Ds +.It Ic ipv4 Ar address +The engineID's format identifier is set to 1 and the ipv4 +.Ar address +is used in the format. +.It Ic ipv6 Ar address +The engineID's format identifier is set to 2 and the ipv6 +.Ar address +is used in the format. +.It Ic mac Ar address +The engineID's format identifier is set to 3 and the mac +.Ar address +is used in the format. +.It Ic text Ar text +The engineID's format identifier is set to 4 and the ASCII +.Ar text +is used in the format. +.It Ic octets Ar octetstring +The engineID's format identifier is set to 5 and the +.Ar octetstring +in hexadecimal is used in the format. +.It Ic hosthash Op Ar hostname +The engineID's format identifier is set to 129 and the first 27 bytes of the +sha256 hash of the +.Ar hostname +are used in the format. +This option is only valid for +.Ar enterprise +.Ic openbsd . +If used for the local engineID, then +.Ar hostname +defaults to the value of +.Xr hostname 1 . +This format is the default. +.It Ar number Ar octetstring +The engineID's format identifier is set to +.Ar number +and the +.Ar octetstring +in hexadecimal is used in the format. +This format is only available if +.Ar enterprise +is not +.Ic openbsd . +.It Ic agentid Ar octetstring +RFC1910 legacy format. +.Ar octetstring +must be 8 bytes +.Pq or 16 characters in hexadecimal format . +.El .It Ic read-only community Ar string Specify the name of the read-only community. There is no default value. diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index fbd9ce58f07..d5f3b75d688 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.h,v 1.98 2021/08/08 13:41:26 sthen Exp $ */ +/* $OpenBSD: snmpd.h,v 1.99 2021/08/09 18:14:53 martijn Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter @@ -77,7 +77,9 @@ #define SNMP_ENGINEID_FMT_MAC 3 #define SNMP_ENGINEID_FMT_TEXT 4 #define SNMP_ENGINEID_FMT_OCT 5 -#define SNMP_ENGINEID_FMT_EID 128 +#define SNMP_ENGINEID_FMT_HH 129 + +#define PEN_OPENBSD 30155 enum imsg_type { IMSG_NONE, @@ -733,7 +735,6 @@ void timer_init(void); /* snmpd.c */ int snmpd_socket_af(struct sockaddr_storage *, int); u_long snmpd_engine_time(void); -char *tohexstr(u_int8_t *, int); /* usm.c */ void usm_generate_keys(void); @@ -796,5 +797,7 @@ ssize_t recvfromto(int, void *, size_t, int, struct sockaddr *, socklen_t *, struct sockaddr *, socklen_t *); const char *log_in6addr(const struct in6_addr *); const char *print_host(struct sockaddr_storage *, char *, size_t); +char *tohexstr(u_int8_t *, int); +uint8_t *fromhexstr(uint8_t *, const char *, size_t); #endif /* SNMPD_H */ diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c index 5fcb9171381..093cb26ba55 100644 --- a/usr.sbin/snmpd/snmpe.c +++ b/usr.sbin/snmpd/snmpe.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpe.c,v 1.73 2021/08/01 11:36:48 martijn Exp $ */ +/* $OpenBSD: snmpe.c,v 1.74 2021/08/09 18:14:53 martijn Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter @@ -121,6 +121,9 @@ snmpe_init(struct privsep *ps, struct privsep_proc *p, void *arg) fatal("unveil"); if (unveil(NULL, NULL) == -1) fatal("unveil"); + + log_info("snmpe %s: ready", + tohexstr(env->sc_engineid, env->sc_engineid_len)); } void diff --git a/usr.sbin/snmpd/util.c b/usr.sbin/snmpd/util.c index fb8bdf20d29..e0a798fbcd7 100644 --- a/usr.sbin/snmpd/util.c +++ b/usr.sbin/snmpd/util.c @@ -1,4 +1,4 @@ -/* $OpenBSD: util.c,v 1.11 2021/01/28 20:45:14 martijn Exp $ */ +/* $OpenBSD: util.c,v 1.12 2021/08/09 18:14:53 martijn Exp $ */ /* * Copyright (c) 2014 Bret Stephen Lambert * @@ -22,7 +22,9 @@ #include #include +#include #include +#include #include #include #include @@ -187,3 +189,42 @@ print_host(struct sockaddr_storage *ss, char *buf, size_t len) } return (buf); } + +char * +tohexstr(uint8_t *bstr, int len) +{ +#define MAXHEXSTRLEN 256 + static char hstr[2 * MAXHEXSTRLEN + 1]; + static const char hex[] = "0123456789abcdef"; + int i; + + if (len > MAXHEXSTRLEN) + len = MAXHEXSTRLEN; /* truncate */ + for (i = 0; i < len; i++) { + hstr[i + i] = hex[bstr[i] >> 4]; + hstr[i + i + 1] = hex[bstr[i] & 0x0f]; + } + hstr[i + i] = '\0'; + return hstr; +} + +uint8_t * +fromhexstr(uint8_t *bstr, const char *hstr, size_t len) +{ + size_t i; + char hex[3]; + + if (len % 2 != 0) + return NULL; + + hex[2] = '\0'; + for (i = 0; i < len; i += 2) { + if (!isxdigit(hstr[i]) || !isxdigit(hstr[i + 1])) + return NULL; + hex[0] = hstr[i]; + hex[1] = hstr[i + 1]; + bstr[i / 2] = strtol(hex, NULL, 16); + } + + return bstr; +} -- 2.20.1