On Mon, 2021-08-09 at 21:44 +0200, Martijn van Duren wrote:
> On Tue, 2021-07-27 at 21:28 +0200, Martijn van Duren wrote:
> > This diff allows sending traps in SNMPv3 messages.
> > It defaults to the global seclevel, but it can be specified on a per
> > rule basis.
> > 
> > Diff requires both previous setting engineid and ober_dup diff.
> > 
> > Tested with netsnmp's snmptrapd and my WIP diff.
> > 
> > The other 2 outstanding diffs are for receiving SNMPv3 traps.
> > 
> > OK?
> > 
> > martijn@
> > 
> Resending now that the engineid diff is in.
> 
> Still awaiting the commit of ober_dup diff[0].
> 
> OK once that one goes in?
> 
> Also, rereading the diff, splitting the trap receiver in two might be a
> bit clutch. Once again invoking the manpage gurus.
> 
> martijn@
> 
> [0] https://marc.info/?l=openbsd-tech&m=162698527126249&w=2
> 
The listen on diff committed this morning broke this patch.
Updated version

In case someone wants a quick example of how to test this:
$ cat snmpd.conf
trap receiver 127.0.0.1 user test
user test authkey test1234 auth hmac-sha256 enckey test1234 enc aes
$ doas ./snmpd -df snmpd.conf  
startup
snmpe: listening on udp 0.0.0.0:161
snmpe: listening on udp :::161
snmpe 800075cb81afa75034471524f4b8c3608f47d7f5dff8f28584e99f87d8854128: ready
^C
$ doas cat /etc/snmp/snmptrapd.conf
snmpTrapdAddr 127.0.0.1
createUser -e 
0x800075cb81afa75034471524f4b8c3608f47d7f5dff8f28584e99f87d8854128 test SHA256 
test1234 AES test1234
authUser log test
$ doas snmptrapd -fLe &
[1] 72884
trapd> NET-SNMP version 5.9
$ doas ./snmpd -df snmpd.conf
snmpd> startup
snmpd> snmpe: listening on udp 0.0.0.0:161
snmpd> snmpe: listening on udp :::161
snmpd> snmpe 800075cb81afa75034471524f4b8c3608f47d7f5dff8f28584e99f87d8854128: 
ready
trapd> 2021-08-10 12:50:21 localhost [UDP: [127.0.0.1]:42772->[0.0.0.0]:0]:
trapd> SNMPv2-MIB::sysUpTime.0 = Timeticks: (2) 0:00:00.02     
SNMPv2-MIB::snmpTrapOID.0 = OID: SNMPv2-MIB::coldStart.0

Don't forget to replace my engineid with your personal one in snmptrapd.conf.

martijn@

Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v
retrieving revision 1.66
diff -u -p -r1.66 parse.y
--- parse.y     10 Aug 2021 06:49:33 -0000      1.66
+++ parse.y     10 Aug 2021 10:57:36 -0000
@@ -134,11 +134,11 @@ typedef struct {
 %token HANDLE DEFAULT SRCADDR TCP UDP PFADDRFILTER PORT
 %token <v.string>      STRING
 %token <v.number>      NUMBER
-%type  <v.string>      hostcmn
+%type  <v.string>      usmuser community optcommunity
 %type  <v.number>      listenproto listenflag listenflags
 %type  <v.string>      srcaddr port
 %type  <v.number>      optwrite yesno seclevel
-%type  <v.data>        objtype cmd
+%type  <v.data>        objtype cmd hostauth hostauthv3 usmauthopts usmauthopt
 %type  <v.oid>         oid hostoid trapoid
 %type  <v.auth>        auth
 %type  <v.enc>         enc
@@ -243,13 +243,13 @@ main              : LISTEN ON listen_udptcp
                        free($3);
                }
                | TRAP RECEIVER host
-               | TRAP HANDLE hostcmn trapoid cmd {
-                       struct trapcmd *cmd = $5.data;
+               | TRAP HANDLE trapoid cmd {
+                       struct trapcmd *cmd = $4.data;
 
-                       cmd->cmd_oid = $4;
+                       cmd->cmd_oid = $3;
 
                        if (trapcmd_add(cmd) != 0) {
-                               free($4);
+                               free($3);
                                free(cmd);
                                yyerror("duplicate oid");
                                YYERROR;
@@ -268,8 +268,8 @@ main                : LISTEN ON listen_udptcp
                | PFADDRFILTER yesno            {
                        conf->sc_pfaddrfilter = $2;
                }
-               | SECLEVEL seclevel {
-                       conf->sc_min_seclevel = $2;
+               | seclevel {
+                       conf->sc_min_seclevel = $1;
                }
                | USER STRING                   {
                        const char *errstr;
@@ -701,15 +701,93 @@ hostoid           : /* empty */                           
{ $$ = NULL; }
                | OBJECTID oid                          { $$ = $2; }
                ;
 
-hostcmn                : /* empty */                           { $$ = NULL; }
-               | COMMUNITY STRING                      { $$ = $2; }
+usmuser                : USER STRING                           {
+                       if (strlen($2) > SNMPD_MAXUSERNAMELEN) {
+                               yyerror("User name too long: %s", $2);
+                               free($2);
+                               YYERROR;
+                       }
+                       $$ = $2;
+               }
+               ;
+
+community      : COMMUNITY STRING                      {
+                       if (strlen($2) > SNMPD_MAXCOMMUNITYLEN) {
+                               yyerror("Community too long: %s", $2);
+                               free($2);
+                               YYERROR;
+                       }
+                       $$ = $2;
+               }
+               ;
+
+optcommunity   : /* empty */                           { $$ = NULL; }
+               | community                             { $$ = $1; }
+               ;
+
+usmauthopt     : usmuser                               {
+                       $$.data = $1;
+                       $$.value = -1;
+               }
+               | seclevel                              {
+                       $$.data = 0;
+                       $$.value = $1;
+               }
+               ;
+
+usmauthopts    : /* empty */                           {
+                       $$.data = NULL;
+                       $$.value = -1;
+               }
+               | usmauthopts usmauthopt                {
+                       if ($2.data != NULL) {
+                               if ($$.data != NULL) {
+                                       yyerror("user redefined");
+                                       free($2.data);
+                                       YYERROR;
+                               }
+                               $$.data = $2.data;
+                       } else {
+                               if ($$.value != -1) {
+                                       yyerror("seclevel redefined");
+                                       YYERROR;
+                               }
+                               $$.value = $2.value;
+                       }
+               }
+               ;
+
+hostauthv3     : usmauthopts                           {
+                       if ($1.data == NULL) {
+                               yyerror("user missing");
+                               YYERROR;
+                       }
+                       $$.data = $1.data;
+                       $$.value = $1.value;
+               }
+               ;
+
+hostauth       : hostauthv3                            {
+                       $$.type = SNMP_V3;
+                       $$.data = $1.data;
+                       $$.value = $1.value;
+               }
+               | SNMPV2 optcommunity                   {
+                       $$.type = SNMP_V2;
+                       $$.data = $2;
+               }
+               | SNMPV3 hostauthv3                     {
+                       $$.type = SNMP_V3;
+                       $$.data = $2.data;
+                       $$.value = $2.value;
+               }
                ;
 
 srcaddr                : /* empty */                           { $$ = NULL; }
                | SRCADDR STRING                        { $$ = $2; }
                ;
 
-hostdef                : STRING hostoid hostcmn srcaddr        {
+hostdef                : STRING hostoid hostauth srcaddr       {
                        struct sockaddr_storage ss;
                        struct trap_address *tr;
 
@@ -722,27 +800,35 @@ hostdef           : STRING hostoid hostcmn srcadd
                                yyerror("invalid host: %s", $1);
                                free($1);
                                free($2);
-                               free($3);
+                               free($3.data);
                                free($4);
                                free(tr);
                                YYERROR;
                        }
                        free($1);
-                       memcpy(&(tr->ss), &ss, sizeof(ss));
+                       memcpy(&(tr->ta_ss), &ss, sizeof(ss));
                        if ($4 != NULL) {
                                if (host($1, "0", SOCK_DGRAM, &ss, 1) <= 0) {
                                        yyerror("invalid host: %s", $1);
                                        free($2);
-                                       free($3);
+                                       free($3.data);
                                        free($4);
                                        free(tr);
                                        YYERROR;
                                }
                                free($4);
-                               memcpy(&(tr->ss_local), &ss, sizeof(ss));
+                               memcpy(&(tr->ta_sslocal), &ss, sizeof(ss));
+                       }
+                       tr->ta_oid = $2;
+                       tr->ta_version = $3.type;
+                       if ($3.type == ADDRESS_FLAG_SNMPV2) {
+                               (void)strlcpy(tr->ta_community, $3.data,
+                                   sizeof(tr->ta_community));
+                               free($3.data);
+                       } else {
+                               tr->ta_usmusername = $3.data;
+                               tr->ta_seclevel = $3.value;
                        }
-                       tr->sa_oid = $2;
-                       tr->sa_community = $3;
                        TAILQ_INSERT_TAIL(&(conf->sc_trapreceivers), tr, entry);
                }
                ;
@@ -759,9 +845,9 @@ comma               : /* empty */
                | ','
                ;
 
-seclevel       : NONE          { $$ = 0; }
-               | AUTH          { $$ = SNMP_MSGFLAG_AUTH; }
-               | ENC           { $$ = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV; }
+seclevel       : SECLEVEL NONE { $$ = 0; }
+               | SECLEVEL AUTH { $$ = SNMP_MSGFLAG_AUTH; }
+               | SECLEVEL ENC  { $$ = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV; }
                ;
 
 userspecs      : /* empty */
@@ -1393,10 +1479,19 @@ parse_config(const char *filename, u_int
                return (NULL);
        }
 
-       if (conf->sc_trcommunity[0] == '\0') {
-               TAILQ_FOREACH(tr, &conf->sc_trapreceivers, entry) {
-                       if (tr->sa_community == NULL) {
-                               log_warnx("trap receiver: missing community");
+       TAILQ_FOREACH(tr, &conf->sc_trapreceivers, entry) {
+               if (tr->ta_version == SNMP_V2 &&
+                   tr->ta_community[0] == '\0' &&
+                   conf->sc_trcommunity[0] == '\0') {
+                       log_warnx("trap receiver: missing community");
+                       free(conf);
+                       return (NULL);
+               }
+               if (tr->ta_version == SNMP_V3) {
+                       tr->ta_usmuser = usm_finduser(tr->ta_usmusername);
+                       if (tr->ta_usmuser == NULL) {
+                               log_warnx("trap receiver: user not defined: %s",
+                                   tr->ta_usmusername);
                                free(conf);
                                return (NULL);
                        }
Index: snmpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.conf.5,v
retrieving revision 1.56
diff -u -p -r1.56 snmpd.conf.5
--- snmpd.conf.5        10 Aug 2021 07:53:57 -0000      1.56
+++ snmpd.conf.5        10 Aug 2021 10:57:36 -0000
@@ -308,19 +308,48 @@ statement with a
 flag set.
 Traps over SNMPv3 are currently unsupported.
 .It Xo
-.Ic trap receiver Ar string
+.Ic trap receiver Ar address
 .Op Ic oid Ar oid-string
+.Ic snmpv2c
 .Op Ic community Ar string
 .Op Ic source-address Ar address
 .Xc
-Specify the address or FQDN of a remote trap receiver for outgoing traps
+Specify the
+.Ar address
+or FQDN of a remote trap receiver for outgoing traps
 sent by
 .Xr snmpd 8 .
 This option may be specified multiple times.
-The daemon will send outgoing traps using the revised SNMPv2 format and the
-configured trap community.
+The daemon will send outgoing traps in
+.Ic snmpv2c
+format.
 The default community is specified by the global
 .Ic trap community
+option.
+The IPv4 or IPv6 source address of the traps can be enforced using
+.It Xo
+.Ic trap receiver Ar address
+.Op Ic oid Ar oid-string
+.Op Ic snmpv3
+.Ic user Ar name Oo Ic seclevel Ar level Oc
+.Op Ic source-address Ar address
+.Xc
+Specify the
+.Ar address
+or FQDN of a remote trap receiver for outgoing traps
+sent by
+.Xr snmpd 8 .
+This option may be specified multiple times.
+The daemon will send outgoing traps in
+.Ic snmpv3
+format.
+.Ic user
+must point to an existing global
+.Ic user .
+If
+.Ic seclevel
+is not defined it defaults to the global
+.Ic seclevel
 option.
 The IPv4 or IPv6 source address of the traps can be enforced using
 .Ic source-address .
Index: snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.99
diff -u -p -r1.99 snmpd.h
--- snmpd.h     9 Aug 2021 18:14:53 -0000       1.99
+++ snmpd.h     10 Aug 2021 10:57:36 -0000
@@ -512,10 +512,18 @@ TAILQ_HEAD(addresslist, address);
     (ADDRESS_FLAG_SNMPV1 | ADDRESS_FLAG_SNMPV2 | ADDRESS_FLAG_SNMPV3)
 
 struct trap_address {
-       struct sockaddr_storage  ss;
-       struct sockaddr_storage  ss_local;
-       char                    *sa_community;
-       struct ber_oid          *sa_oid;
+       struct sockaddr_storage  ta_ss;
+       struct sockaddr_storage  ta_sslocal;
+       int                      ta_version;
+       union {
+               char             ta_community[SNMPD_MAXCOMMUNITYLEN];
+               struct {
+                       char            *ta_usmusername;
+                       struct usmuser  *ta_usmuser;
+                       int              ta_seclevel;
+               };
+       };
+       struct ber_oid          *ta_oid;
 
        TAILQ_ENTRY(trap_address) entry;
 };
@@ -655,6 +663,7 @@ struct kif_arp      *karp_getaddr(struct sock
 void            snmpe(struct privsep *, struct privsep_proc *);
 void            snmpe_shutdown(void);
 void            snmpe_dispatchmsg(struct snmp_message *);
+void            snmpe_response(struct snmp_message *);
 int             snmp_messagecmp(struct snmp_message *, struct snmp_message *);
 RB_PROTOTYPE(snmp_messages, snmp_message, sm_entry, snmp_messagecmp)
 
Index: snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.74
diff -u -p -r1.74 snmpe.c
--- snmpe.c     9 Aug 2021 18:14:53 -0000       1.74
+++ snmpe.c     10 Aug 2021 10:57:36 -0000
@@ -45,7 +45,6 @@ const char *snmpe_pdutype2string(enum sn
 int     snmpe_parse(struct snmp_message *);
 void    snmpe_tryparse(int, struct snmp_message *);
 int     snmpe_parsevarbinds(struct snmp_message *);
-void    snmpe_response(struct snmp_message *);
 void    snmpe_sig_handler(int sig, short, void *);
 int     snmpe_bind(struct address *);
 void    snmpe_recvmsg(int fd, short, void *);
@@ -98,7 +97,6 @@ snmpe_init(struct privsep *ps, struct pr
        struct address          *h;
 
        kr_init();
-       trap_init();
        timer_init();
        usm_generate_keys();
 
@@ -124,6 +122,7 @@ snmpe_init(struct privsep *ps, struct pr
 
        log_info("snmpe %s: ready",
            tohexstr(env->sc_engineid, env->sc_engineid_len));
+       trap_init();
 }
 
 void
Index: trap.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/trap.c,v
retrieving revision 1.36
diff -u -p -r1.36 trap.c
--- trap.c      6 Sep 2020 15:51:28 -0000       1.36
+++ trap.c      10 Aug 2021 10:57:36 -0000
@@ -54,17 +54,13 @@ trap_init(void)
 int
 trap_send(struct ber_oid *oid, struct ber_element *elm)
 {
-       int                      ret = 0, s;
        struct trap_address     *tr;
-       struct ber_element      *root, *b, *c, *trap;
-       struct ber               ber;
-       char                    *cmn;
-       ssize_t                  len;
-       u_int8_t                *ptr;
+       struct ber_element      *vblist, *trap;
        struct                   ber_oid uptime = OID(MIB_sysUpTime);
        struct                   ber_oid trapoid = OID(MIB_snmpTrapOID);
        char                     ostr[SNMP_MAX_OID_STRLEN];
        struct oid               oa, ob;
+       struct snmp_message     *msg;
 
        if (TAILQ_EMPTY(&snmpd_env->sc_trapreceivers))
                return (0);
@@ -85,66 +81,72 @@ trap_send(struct ber_oid *oid, struct be
 
        /* Add mandatory varbind elements */
        trap = ober_add_sequence(NULL);
-       c = ober_printf_elements(trap, "{Odt}{OO}",
+       vblist = ober_printf_elements(trap, "{Odt}{OO}",
            &uptime, smi_getticks(),
            BER_CLASS_APPLICATION, SNMP_T_TIMETICKS,
            &trapoid, oid);
        if (elm != NULL)
-               ober_link_elements(c, elm);
-
-       bzero(&ber, sizeof(ber));
+               ober_link_elements(vblist, elm);
 
        TAILQ_FOREACH(tr, &snmpd_env->sc_trapreceivers, entry) {
-               if (tr->sa_oid != NULL && tr->sa_oid->bo_n) {
+               if (tr->ta_oid != NULL && tr->ta_oid->bo_n) {
                        /* The trap receiver may want only a specified MIB */
-                       bcopy(&tr->sa_oid->bo_id, &ob.o_oid,
+                       bcopy(&tr->ta_oid->bo_id, &ob.o_oid,
                            sizeof(ob.o_oid));
-                       ob.o_oidlen = tr->sa_oid->bo_n;
+                       ob.o_oidlen = tr->ta_oid->bo_n;
                        if (smi_oid_cmp(&oa, &ob) != 0)
                                continue;
                }
 
-               if ((s = snmpd_socket_af(&tr->ss, SOCK_DGRAM)) == -1) {
-                       ret = -1;
-                       goto done;
+               if ((msg = calloc(1, sizeof(*msg))) == NULL)
+                       fatal("malloc");
+               msg->sm_sock = snmpd_socket_af(&tr->ta_ss, SOCK_DGRAM);
+               if (msg->sm_sock == -1) {
+                       log_warn("socket");
+                       free(msg);
+                       continue;
                }
-               if (tr->ss_local.ss_family != 0) {
-                       if (bind(s, (struct sockaddr *)&(tr->ss_local),
-                           tr->ss_local.ss_len) == -1) {
-                               ret = -1;
-                               goto done;
-                       }
+               memcpy(&(msg->sm_ss), &(tr->ta_ss), sizeof(msg->sm_ss));
+               msg->sm_slen = tr->ta_ss.ss_len;
+               if (tr->ta_sslocal.ss_family != 0) {
+                       memcpy(&(msg->sm_local_ss), &(tr->ta_sslocal),
+                           sizeof(msg->sm_local_ss));
+                       msg->sm_local_slen = tr->ta_sslocal.ss_len;
                }
-
-               cmn = tr->sa_community != NULL ?
-                   tr->sa_community : snmpd_env->sc_trcommunity;
-
-               /* SNMP header */
-               root = ober_add_sequence(NULL);
-               b = ober_printf_elements(root, "ds{tddd",
-                   SNMP_V2, cmn, BER_CLASS_CONTEXT, SNMP_C_TRAPV2,
-                   arc4random(), 0, 0);
-               ober_link_elements(b, trap);
-
-#ifdef DEBUG
-               smi_debug_elements(root);
-#endif
-               len = ober_write_elements(&ber, root);
-               if (ober_get_writebuf(&ber, (void *)&ptr) > 0 &&
-                   sendto(s, ptr, len, 0, (struct sockaddr *)&tr->ss,
-                   tr->ss.ss_len) != -1) {
-                       snmpd_env->sc_stats.snmp_outpkts++;
-                       ret++;
+               msg->sm_version = tr->ta_version;
+               msg->sm_pdutype = SNMP_C_TRAPV2;
+               ober_set_application(&msg->sm_ber, smi_application);
+               msg->sm_request = arc4random();
+               if ((msg->sm_varbindresp = ober_dup(trap->be_sub)) == NULL)
+                       fatal("malloc");
+
+               switch (msg->sm_version) {
+               case SNMP_V2:
+                       (void)strlcpy(msg->sm_community, tr->ta_community,
+                           sizeof(msg->sm_community));
+                       break;
+               case SNMP_V3:
+                       msg->sm_msgid = msg->sm_request & INT32_MAX;
+                       msg->sm_max_msg_size = READ_BUF_SIZE;
+                       msg->sm_flags = tr->ta_seclevel != -1 ?
+                           tr->ta_seclevel : snmpd_env->sc_min_seclevel;
+                       msg->sm_secmodel = SNMP_SEC_USM;
+                       msg->sm_engine_time = snmpd_engine_time();
+                       msg->sm_engine_boots = snmpd_env->sc_engine_boots;
+                       memcpy(msg->sm_ctxengineid, snmpd_env->sc_engineid,
+                           snmpd_env->sc_engineid_len);
+                       msg->sm_ctxengineid_len =
+                           snmpd_env->sc_engineid_len;
+                       (void)strlcpy(msg->sm_username, tr->ta_usmusername,
+                           sizeof(msg->sm_username));
+                       msg->sm_user = tr->ta_usmuser;
+                       arc4random_buf(msg->sm_salt, sizeof(msg->sm_salt));
+                       break;
                }
 
-               close(s);
-               ober_unlink_elements(b);
-               ober_free_elements(root);
+               snmpe_response(msg);
        }
-
- done:
        ober_free_elements(trap);
-       ober_free(&ber);
 
-       return (ret);
+       return 0;
 }


Reply via email to