/* $OpenBSD: traphandler.c,v 1.24 2022/12/28 21:30:19 jmc Exp $ */ /* * Copyright (c) 2014 Bret Stephen Lambert * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snmpd.h" #include "mib.h" int traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *); int traphandler_fork_handler(struct privsep_proc *, struct imsg *); struct ber_element * traphandler_v1translate(struct snmp_message *, int); int trapcmd_cmp(struct trapcmd *, struct trapcmd *); void trapcmd_exec(struct trapcmd *, struct sockaddr *, struct ber_element *); char *traphandler_hostname(struct sockaddr *, int); RB_PROTOTYPE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) RB_GENERATE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) struct trapcmd_tree trapcmd_tree = RB_INITIALIZER(&trapcmd_tree); /* * Validate received message */ int traphandler_parse(struct snmp_message *msg) { struct privsep *ps = &snmpd_env->sc_ps; struct snmp_stats *stats = &snmpd_env->sc_stats; struct ber ber = {0}; struct ber_element *vblist = NULL, *elm; struct ber_oid o1, o2, snmpTrapOIDOID; struct ber_oid snmpTrapOID, sysUpTimeOID; int sysUpTime; struct iovec iov[2]; void *buf; ssize_t buflen; int ret = -1; switch (msg->sm_pdu->be_type) { case SNMP_C_TRAP: if ((vblist = traphandler_v1translate(msg, 0)) == NULL) goto done; break; case SNMP_C_TRAPV2: if (ober_scanf_elements(msg->sm_pdu, "{SSe}$", &elm) == -1) { stats->snmp_inasnparseerrs++; goto done; } if (elm->be_type != BER_TYPE_INTEGER) { stats->snmp_inasnparseerrs++; goto done; } vblist = ober_unlink_elements(elm); break; default: fatalx("%s called without proper context", __func__); } (void)ober_string2oid("1.3.6.1.2.1.1.3.0", &sysUpTimeOID); (void)ober_string2oid("1.3.6.1.6.3.1.1.4.1.0", &snmpTrapOIDOID); if (ober_scanf_elements(vblist, "{{od$}{oo$}", &o1, &sysUpTime, &o2, &snmpTrapOID) == -1 || ober_oid_cmp(&o1, &sysUpTimeOID) != 0 || ober_oid_cmp(&o2, &snmpTrapOIDOID) != 0) { stats->snmp_inasnparseerrs++; goto done; } (void)ober_scanf_elements(vblist, "{Se", &elm); for (elm = elm->be_next; elm != NULL; elm = elm->be_next) { if (ober_scanf_elements(elm, "{oS$}", &o1) == -1) { stats->snmp_inasnparseerrs++; goto done; } } ober_set_application(&ber, smi_application); if ((buflen = ober_write_elements(&ber, vblist)) == -1 || ober_get_writebuf(&ber, &buf) == -1) { msg->sm_errstr = "failed to handle trap"; goto done; } iov[0].iov_base = &(msg->sm_ss); iov[0].iov_len = msg->sm_slen; iov[1].iov_base = buf; iov[1].iov_len = buflen; /* Forward it to the parent process */ if (proc_composev(ps, PROC_PARENT, IMSG_TRAP_EXEC, iov, 2) == -1) { msg->sm_errstr = "failed to handle trap"; goto done; } ret = 0; done: ober_free(&ber); if (vblist != NULL) ober_free_elements(vblist); return ret; } struct ber_element * traphandler_v1translate(struct snmp_message *msg, int proxy) { struct snmp_stats *stats = &snmpd_env->sc_stats; struct ber_oid trapoid, enterprise, oid, snmpTrapAddressOid; struct ber_oid snmpTrapCommunityOid, snmpTrapEnterpriseOid; struct ber_element *elm, *last, *vblist, *vb0 = NULL; void *agent_addr; size_t agent_addrlen; int generic_trap, specific_trap, time_stamp; int hasaddress = 0, hascommunity = 0, hasenterprise = 0; if (ober_scanf_elements(msg->sm_pdu, "{oxdddeS$}$", &enterprise, &agent_addr, &agent_addrlen, &generic_trap, &specific_trap, &time_stamp, &vblist) == -1 || agent_addrlen != 4 || vblist->be_type != BER_TYPE_SEQUENCE) { stats->snmp_inasnparseerrs++; return NULL; } switch (generic_trap) { case 0: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.1", &trapoid); break; case 1: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.2", &trapoid); break; case 2: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.3", &trapoid); break; case 3: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.4", &trapoid); break; case 4: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.5", &trapoid); break; case 5: (void)ober_string2oid("1.3.6.1.6.3.1.1.5.6", &trapoid); break; case 6: trapoid = enterprise; /* Officially this should be 128, but BER_MAX_OID_LEN is 64 */ if (trapoid.bo_n + 2 > BER_MAX_OID_LEN) { stats->snmp_inasnparseerrs++; return NULL; } trapoid.bo_id[trapoid.bo_n++] = 0; trapoid.bo_id[trapoid.bo_n++] = specific_trap; break; default: stats->snmp_inasnparseerrs++; return NULL; } /* work around net-snmp's snmptrap: It adds an EOC element in vblist */ if (vblist->be_len != 0) vb0 = ober_unlink_elements(vblist); if ((vblist = ober_add_sequence(NULL)) == NULL) { msg->sm_errstr = strerror(errno); if (vb0 != NULL) ober_free_elements(vb0); return NULL; } if (ober_printf_elements(vblist, "{od}{oO}e", "1.3.6.1.2.1.1.3.0", time_stamp, "1.3.6.1.6.3.1.1.4.1.0", &trapoid, vb0) == NULL) { msg->sm_errstr = strerror(errno); if (vb0 != 0) ober_free_elements(vb0); ober_free_elements(vblist); return NULL; } if (proxy) { (void)ober_string2oid("1.3.6.1.6.3.18.1.3.0", &snmpTrapAddressOid); (void)ober_string2oid("1.3.6.1.6.3.18.1.4.0", &snmpTrapCommunityOid); (void)ober_string2oid("1.3.6.1.6.3.1.1.4.3.0", &snmpTrapEnterpriseOid); for (elm = vblist->be_sub; elm != NULL; elm = elm->be_next) { if (ober_get_oid(elm->be_sub, &oid) == -1) { msg->sm_errstr = "failed to read oid"; ober_free_elements(vblist); return NULL; } if (ober_oid_cmp(&oid, &snmpTrapAddressOid) == 0) hasaddress = 1; else if (ober_oid_cmp(&oid, &snmpTrapCommunityOid) == 0) hascommunity = 1; else if (ober_oid_cmp(&oid, &snmpTrapEnterpriseOid) == 0) hasenterprise = 1; last = elm; } if (!hasaddress || !hascommunity || !hasenterprise) { if (ober_printf_elements(last, "{Oxt}{Os}{OO}", &snmpTrapAddressOid, agent_addr, 4, BER_CLASS_APPLICATION, SNMP_T_IPADDR, &snmpTrapCommunityOid, msg->sm_community, &snmpTrapEnterpriseOid, &enterprise) == NULL) { msg->sm_errstr = strerror(errno); ober_free_elements(vblist); return NULL; } } } return vblist; } int traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg) { ssize_t n; pid_t pid; if ((n = IMSG_DATA_SIZE(imsg)) <= 0) return (-1); /* XXX */ switch ((pid = fork())) { case 0: traphandler_fork_handler(p, imsg); /* NOTREACHED */ case -1: log_warn("%s: couldn't fork traphandler", __func__); return (0); default: log_debug("forked process %i to handle trap", pid); return (0); } /* NOTREACHED */ } int traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg) { struct privsep *ps = p->p_ps; struct snmpd *env = ps->ps_env; struct ber ber = {0}; struct sockaddr *sa; char *buf; ssize_t n; struct ber_element *vblist; struct ber_oid trapoid; struct trapcmd *cmd; struct passwd *pw; int verbose; pw = ps->ps_pw; verbose = log_getverbose(); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("traphandler_fork_handler: cannot drop privileges"); closefrom(STDERR_FILENO + 1); log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); log_setverbose(verbose); log_procinit(p->p_title); n = IMSG_DATA_SIZE(imsg); sa = imsg->data; n -= sa->sa_len; buf = (char *)imsg->data + sa->sa_len; ober_set_application(&ber, smi_application); ober_set_readbuf(&ber, buf, n); if ((vblist = ober_read_elements(&ber, NULL)) == NULL) fatalx("couldn't parse SNMP trap message"); ober_free(&ber); (void)ober_scanf_elements(vblist, "{S{So", &trapoid); if ((cmd = trapcmd_lookup(&trapoid)) != NULL) trapcmd_exec(cmd, sa, vblist->be_sub); ober_free_elements(vblist); exit(0); } void trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, struct ber_element *vb) { char oidbuf[SNMP_MAX_OID_STRLEN]; struct ber_oid oid; struct ber_element *elm; int n, s[2], status = 0; char *value, *host; pid_t child = -1; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { log_warn("could not create pipe for OID '%s'", smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); return; } switch (child = fork()) { case 0: dup2(s[1], STDIN_FILENO); close(s[0]); close(s[1]); closefrom(STDERR_FILENO + 1); /* path to command is in argv[0], args follow */ execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); /* this shouldn't happen */ log_warn("could not exec trap command for OID '%s'", smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); _exit(1); /* NOTREACHED */ case -1: log_warn("could not fork trap command for OID '%s'", smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); close(s[0]); close(s[1]); return; } close(s[1]); host = traphandler_hostname(sa, 0); if (dprintf(s[0], "%s\n", host) == -1) goto out; host = traphandler_hostname(sa, 1); if (dprintf(s[0], "%s\n", host) == -1) goto out; for (; vb != NULL; vb = vb->be_next) { if (ober_scanf_elements(vb, "{oeS$}", &oid, &elm) == -1) goto out; if ((value = smi_print_element_legacy(elm)) == NULL) goto out; smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); n = dprintf(s[0], "%s %s\n", oidbuf, value); free(value); if (n == -1) goto out; } out: close(s[0]); waitpid(child, &status, 0); if (WIFSIGNALED(status)) { log_warnx("child %i exited due to receipt of signal %i", child, WTERMSIG(status)); } else if (WEXITSTATUS(status) != 0) { log_warnx("child %i exited with status %i", child, WEXITSTATUS(status)); } else { log_debug("child %i finished", child); } close(s[1]); return; } char * traphandler_hostname(struct sockaddr *sa, int numeric) { static char buf[NI_MAXHOST]; int flag = 0; if (numeric) flag = NI_NUMERICHOST; bzero(buf, sizeof(buf)); if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) return ("Unknown"); return (buf); } struct trapcmd * trapcmd_lookup(struct ber_oid *oid) { struct trapcmd key, *res; bzero(&key, sizeof(key)); key.cmd_oid = oid; if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) res = key.cmd_maybe; return (res); } int trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) { int ret; ret = ober_oid_cmp(cmd1->cmd_oid, cmd2->cmd_oid); switch (ret) { case 2: /* cmd1 is a child of cmd2 */ cmd1->cmd_maybe = cmd2; return (1); default: return (ret); } /* NOTREACHED */ } int trapcmd_add(struct trapcmd *cmd) { return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); } void trapcmd_free(struct trapcmd *cmd) { RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); free(cmd->cmd_argv); free(cmd->cmd_oid); free(cmd); }