This diff introduces setting the engineid for snmpd(8).
Although this diff might seem quite excessive at first glance, there's
a valid reason to do so.
The following things are in effect when sending an SNMPv3 trap:
- SNMP trap packets are unacknowledged; meaning that we don't get a
response -, nor report message.
- SNMPv3 packets with a trap contain the engineid of the sender.
- The key used in auth and priv are derived from the password and the
engineid.
- users are linked to an engineid
So if we're sending messages in SNMPv3 format we can't generate a random
engineid on each boot as we do now, or the trap receiver can't find the
correct user. Since I want to keep the default config as empty as
possible I've choosen to use the first 27 bytes (maximum length that
fits in the engineid) of the sha256 hash of the hostname(3). This should
give us the biggest confidence in having a consistent name that won't
clash with other agents. If someone has a better idea though, please
speak up now.
As for allowing to set the engineid: When receiving a trap admins will
need to be able to specify the engineid of the remote agent, or there
will be problems with the key generation of that user.
Given this requirement it's a small step to allow the same yacc rules
to be used for setting the global engineid and gives a little more
control to the admin. The global engineid just happens to be more
convenient to implement first.
OK?
martijn@
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v
retrieving revision 1.64
diff -u -p -r1.64 parse.y
--- parse.y 20 Jun 2021 19:55:48 -0000 1.64
+++ parse.y 23 Jul 2021 13:39:59 -0000
@@ -35,6 +35,8 @@
#include <arpa/inet.h>
#include <arpa/nameser.h>
+#include <openssl/sha.h>
+
#include <ctype.h>
#include <unistd.h>
#include <err.h>
@@ -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 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 <v.string> STRING
-%token <v.number> NUMBER
+%token <v.number> NUMBER
%type <v.string> hostcmn
%type <v.string> srcaddr port
%type <v.number> 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
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__);
@@ -1134,6 +1365,21 @@ parse_config(const char *filename, u_int
popfile();
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);
+ engineid[0] |= SNMP_ENGINEID_NEW;
+ }
/* Setup default listen addresses */
if (TAILQ_EMPTY(&conf->sc_addresses)) {
Index: snmpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.c,v
retrieving revision 1.44
diff -u -p -r1.44 snmpd.c
--- snmpd.c 27 Jan 2021 07:21:54 -0000 1.44
+++ snmpd.c 23 Jul 2021 13:39:59 -0000
@@ -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
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)
{
@@ -357,22 +332,4 @@ 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;
}
Index: snmpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.conf.5,v
retrieving revision 1.50
diff -u -p -r1.50 snmpd.conf.5
--- snmpd.conf.5 20 Jun 2021 19:59:42 -0000 1.50
+++ snmpd.conf.5 23 Jul 2021 13:39:59 -0000
@@ -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.
Index: snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.97
diff -u -p -r1.97 snmpd.h
--- snmpd.h 20 Jun 2021 19:59:42 -0000 1.97
+++ snmpd.h 23 Jul 2021 13:39:59 -0000
@@ -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,
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 */
Index: snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.72
diff -u -p -r1.72 snmpe.c
--- snmpe.c 20 Jun 2021 19:55:48 -0000 1.72
+++ snmpe.c 23 Jul 2021 13:39:59 -0000
@@ -121,6 +121,9 @@ snmpe_init(struct privsep *ps, struct pr
fatal("unveil");
if (unveil(NULL, NULL) == -1)
fatal("unveil");
+
+ log_info("snmpe %s: ready",
+ tohexstr(env->sc_engineid, env->sc_engineid_len));
}
void
Index: util.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/util.c,v
retrieving revision 1.11
diff -u -p -r1.11 util.c
--- util.c 28 Jan 2021 20:45:14 -0000 1.11
+++ util.c 23 Jul 2021 13:39:59 -0000
@@ -22,7 +22,9 @@
#include <net/if.h>
#include <ber.h>
+#include <ctype.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <event.h>
@@ -186,4 +188,43 @@ print_host(struct sockaddr_storage *ss,
return (NULL);
}
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;
}