Previous diff failed to set the initial bit when not defining engineid
in the config.

On Fri, 2021-07-23 at 15:41 +0200, Martijn van Duren wrote:
> 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     27 Jul 2021 18:37:37 -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);
+               conf->sc_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     27 Jul 2021 18:37:37 -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        27 Jul 2021 18:37:37 -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     27 Jul 2021 18:37:37 -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     27 Jul 2021 18:37:37 -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      27 Jul 2021 18:37:37 -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;
 }


Reply via email to