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; }