1 /* $OpenBSD: traphandler.c,v 1.19 2021/01/05 18:12:15 martijn Exp $ */ 2 3 /* 4 * Copyright (c) 2014 Bret Stephen Lambert <blambert@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/queue.h> 20 #include <sys/socket.h> 21 #include <sys/stat.h> 22 #include <sys/types.h> 23 #include <sys/uio.h> 24 #include <sys/wait.h> 25 26 #include <net/if.h> 27 #include <netinet/in.h> 28 #include <arpa/inet.h> 29 30 #include <ber.h> 31 #include <errno.h> 32 #include <event.h> 33 #include <fcntl.h> 34 #include <imsg.h> 35 #include <netdb.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <syslog.h> 40 #include <unistd.h> 41 #include <pwd.h> 42 43 #include "snmpd.h" 44 #include "mib.h" 45 46 char trap_path[PATH_MAX]; 47 48 void traphandler_init(struct privsep *, struct privsep_proc *, void *arg); 49 int traphandler_dispatch_parent(int, struct privsep_proc *, struct imsg *); 50 int traphandler_bind(struct address *); 51 void traphandler_recvmsg(int, short, void *); 52 int traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *); 53 int traphandler_fork_handler(struct privsep_proc *, struct imsg *); 54 int traphandler_parse(struct ber_element *, char *, struct sockaddr *); 55 struct ber_element * 56 traphandler_v1translate(struct ber_element *, char *, int); 57 int trapcmd_cmp(struct trapcmd *, struct trapcmd *); 58 void trapcmd_exec(struct trapcmd *, struct sockaddr *, 59 struct ber_element *); 60 61 char *traphandler_hostname(struct sockaddr *, int); 62 63 RB_PROTOTYPE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) 64 RB_GENERATE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) 65 66 struct trapcmd_tree trapcmd_tree = RB_INITIALIZER(&trapcmd_tree); 67 68 static struct privsep_proc procs[] = { 69 { "parent", PROC_PARENT, traphandler_dispatch_parent } 70 }; 71 72 void 73 traphandler(struct privsep *ps, struct privsep_proc *p) 74 { 75 struct snmpd *env = ps->ps_env; 76 struct address *h; 77 78 if (env->sc_traphandler) { 79 TAILQ_FOREACH(h, &env->sc_addresses, entry) { 80 if (h->type != SOCK_DGRAM) 81 continue; 82 if ((h->fd = traphandler_bind(h)) == -1) 83 fatal("could not create trap listener socket"); 84 } 85 } 86 87 proc_run(ps, p, procs, nitems(procs), traphandler_init, NULL); 88 } 89 90 void 91 traphandler_init(struct privsep *ps, struct privsep_proc *p, void *arg) 92 { 93 struct snmpd *env = ps->ps_env; 94 struct address *h; 95 96 if (pledge("stdio id proc recvfd exec", NULL) == -1) 97 fatal("pledge"); 98 99 if (!env->sc_traphandler) 100 return; 101 102 /* listen for SNMP trap messages */ 103 TAILQ_FOREACH(h, &env->sc_addresses, entry) { 104 event_set(&h->ev, h->fd, EV_READ|EV_PERSIST, 105 traphandler_recvmsg, NULL); 106 event_add(&h->ev, NULL); 107 } 108 } 109 110 int 111 traphandler_bind(struct address *addr) 112 { 113 int s; 114 char buf[512]; 115 struct sockaddr_in *sin; 116 struct sockaddr_in6 *sin6; 117 118 if (addr->ss.ss_family == AF_INET) { 119 sin = (struct sockaddr_in *)&(addr->ss); 120 sin->sin_port = htons(162); 121 } else { 122 sin6 = (struct sockaddr_in6 *)&(addr->ss); 123 sin6->sin6_port = htons(162); 124 } 125 if ((s = snmpd_socket_af(&addr->ss, SOCK_DGRAM)) == -1) 126 return (-1); 127 128 if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) 129 goto bad; 130 131 if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1) 132 goto bad; 133 134 if (print_host(&addr->ss, buf, sizeof(buf)) == NULL) 135 goto bad; 136 137 log_info("traphandler: listening on %s:%s", buf, SNMPD_TRAPPORT); 138 139 return (s); 140 bad: 141 close (s); 142 return (-1); 143 } 144 145 void 146 traphandler_shutdown(void) 147 { 148 struct address *h; 149 150 TAILQ_FOREACH(h, &snmpd_env->sc_addresses, entry) { 151 event_del(&h->ev); 152 close(h->fd); 153 } 154 } 155 156 int 157 traphandler_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) 158 { 159 switch (imsg->hdr.type) { 160 default: 161 break; 162 } 163 164 return (-1); 165 } 166 167 int 168 snmpd_dispatch_traphandler(int fd, struct privsep_proc *p, struct imsg *imsg) 169 { 170 switch (imsg->hdr.type) { 171 case IMSG_ALERT: 172 return (traphandler_priv_recvmsg(p, imsg)); 173 default: 174 break; 175 } 176 177 return (-1); 178 } 179 180 void 181 traphandler_recvmsg(int fd, short events, void *arg) 182 { 183 struct ber ber = {0}; 184 struct ber_element *msg = NULL, *pdu; 185 char buf[8196]; 186 struct sockaddr_storage ss; 187 socklen_t slen; 188 ssize_t n; 189 int vers; 190 char *community; 191 192 slen = sizeof(ss); 193 if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss, 194 &slen)) == -1) 195 return; 196 197 ober_set_application(&ber, smi_application); 198 ober_set_readbuf(&ber, buf, n); 199 200 if ((msg = ober_read_elements(&ber, NULL)) == NULL) 201 goto parsefail; 202 203 if (ober_scanf_elements(msg, "{dse", &vers, &community, &pdu) == -1) 204 goto parsefail; 205 206 switch (vers) { 207 case SNMP_V1: 208 if (pdu->be_type != SNMP_C_TRAP) 209 goto parsefail; 210 break; 211 case SNMP_V2: 212 if (pdu->be_type != SNMP_C_TRAPV2) 213 goto parsefail; 214 break; 215 default: 216 goto parsefail; 217 } 218 219 (void)traphandler_parse(pdu, community, (struct sockaddr *)&ss); 220 221 parsefail: 222 ober_free(&ber); 223 if (msg != NULL) 224 ober_free_elements(msg); 225 } 226 227 /* 228 * Validate received message 229 */ 230 int 231 traphandler_parse(struct ber_element *pdu, char *community, struct sockaddr *sa) 232 { 233 struct privsep *ps = &snmpd_env->sc_ps; 234 struct ber ber = {0}; 235 struct ber_element *vblist = NULL, *elm, *elm2; 236 struct ber_oid o1, o2, snmpTrapOIDOID; 237 struct ber_oid snmpTrapOID, sysUpTimeOID; 238 int sysUpTime; 239 struct iovec iov[2]; 240 void *buf; 241 ssize_t buflen; 242 int ret = -1; 243 244 switch (pdu->be_type) { 245 case SNMP_C_TRAP: 246 vblist = traphandler_v1translate(pdu, community, 0); 247 if (vblist == NULL) 248 goto done; 249 break; 250 case SNMP_C_TRAPV2: 251 if (ober_scanf_elements(pdu, "{SSe}", &elm) == -1) 252 goto done; 253 if (elm->be_type != BER_TYPE_INTEGER) 254 goto done; 255 vblist = ober_unlink_elements(elm); 256 break; 257 default: 258 log_warnx("unsupported SNMP trap version '%d'", pdu->be_type); 259 goto done; 260 } 261 262 (void)ober_string2oid("1.3.6.1.2.1.1.3.0", &sysUpTimeOID); 263 (void)ober_string2oid("1.3.6.1.6.3.1.1.4.1.0", &snmpTrapOIDOID); 264 if (ober_scanf_elements(vblist, "{{od}{oo}", &o1, &sysUpTime, &o2, 265 &snmpTrapOID) == -1 || 266 ober_oid_cmp(&o1, &sysUpTimeOID) != 0 || 267 ober_oid_cmp(&o2, &snmpTrapOIDOID) != 0) { 268 goto done; 269 } 270 (void)ober_scanf_elements(vblist, "{Se", &elm); 271 for (elm = elm->be_next; elm != NULL; elm = elm->be_next) { 272 if (ober_scanf_elements(elm, "{oe}", &o1, &elm2) == -1 || 273 elm2->be_next != NULL) 274 goto done; 275 } 276 277 ober_set_application(&ber, smi_application); 278 279 if ((buflen = ober_write_elements(&ber, vblist)) == -1 || 280 ober_get_writebuf(&ber, &buf) == -1) 281 goto done; 282 283 iov[0].iov_base = sa; 284 iov[0].iov_len = sa->sa_len; 285 iov[1].iov_base = buf; 286 iov[1].iov_len = buflen; 287 288 /* Forward it to the parent process */ 289 if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1) 290 goto done; 291 292 ret = 0; 293 done: 294 ober_free(&ber); 295 if (vblist != NULL) 296 ober_free_elements(vblist); 297 return ret; 298 } 299 300 struct ber_element * 301 traphandler_v1translate(struct ber_element *pdu, char *community, int proxy) 302 { 303 struct ber_oid trapoid, enterprise, oid, snmpTrapAddressOid; 304 struct ber_oid snmpTrapCommunityOid, snmpTrapEnterpriseOid; 305 struct ber_element *elm, *last, *vblist, *vb0 = NULL; 306 void *agent_addr; 307 size_t agent_addrlen; 308 int generic_trap, specific_trap, time_stamp; 309 int hasaddress = 0, hascommunity = 0, hasenterprise = 0; 310 311 if (ober_scanf_elements(pdu, "{oxddde", &enterprise, &agent_addr, 312 &agent_addrlen, &generic_trap, &specific_trap, &time_stamp, 313 &vblist) == -1 || 314 agent_addrlen != 4 || 315 vblist->be_type != BER_TYPE_SEQUENCE) { 316 errno = EINVAL; 317 return NULL; 318 } 319 switch (generic_trap) { 320 case 0: 321 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.1", &trapoid); 322 break; 323 case 1: 324 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.2", &trapoid); 325 break; 326 case 2: 327 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.3", &trapoid); 328 break; 329 case 3: 330 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.4", &trapoid); 331 break; 332 case 4: 333 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.5", &trapoid); 334 break; 335 case 5: 336 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.6", &trapoid); 337 break; 338 case 6: 339 trapoid = enterprise; 340 if (trapoid.bo_n + 2 > BER_MAX_OID_LEN) { 341 errno = EINVAL; 342 return NULL; 343 } 344 trapoid.bo_id[trapoid.bo_n++] = 0; 345 trapoid.bo_id[trapoid.bo_n++] = specific_trap; 346 break; 347 default: 348 errno = EINVAL; 349 return NULL; 350 } 351 352 /* work aronud net-snmp's snmptrap: It adds an EOC element in vblist */ 353 if (vblist->be_len != 0) 354 vb0 = ober_unlink_elements(vblist); 355 356 if ((vblist = ober_add_sequence(NULL)) == NULL) { 357 if (vb0 != NULL) 358 ober_free_elements(vb0); 359 return NULL; 360 } 361 if (ober_printf_elements(vblist, "{od}{oO}e", "1.3.6.1.2.1.1.3.0", 362 time_stamp, "1.3.6.1.6.3.1.1.4.1.0", &trapoid, vb0) == NULL) { 363 if (vb0 != 0) 364 ober_free_elements(vb0); 365 ober_free_elements(vblist); 366 return NULL; 367 } 368 369 if (proxy) { 370 (void)ober_string2oid("1.3.6.1.6.3.18.1.3.0", 371 &snmpTrapAddressOid); 372 (void)ober_string2oid("1.3.6.1.6.3.18.1.4.0", 373 &snmpTrapCommunityOid); 374 (void)ober_string2oid("1.3.6.1.6.3.1.1.4.3.0", 375 &snmpTrapEnterpriseOid); 376 for (elm = vblist->be_sub; elm != NULL; elm = elm->be_next) { 377 if (ober_get_oid(elm->be_sub, &oid) == -1) { 378 ober_free_elements(vblist); 379 return NULL; 380 } 381 if (ober_oid_cmp(&oid, &snmpTrapAddressOid) == 0) 382 hasaddress = 1; 383 else if (ober_oid_cmp(&oid, &snmpTrapCommunityOid) == 0) 384 hascommunity = 1; 385 else if (ober_oid_cmp(&oid, 386 &snmpTrapEnterpriseOid) == 0) 387 hasenterprise = 1; 388 last = elm; 389 } 390 if (!hasaddress || !hascommunity || !hasenterprise) { 391 if (ober_printf_elements(last, "{Oxt}{Os}{OO}", 392 &snmpTrapAddressOid, agent_addr, 4, 393 BER_CLASS_APPLICATION, SNMP_T_IPADDR, 394 &snmpTrapCommunityOid, community, 395 &snmpTrapEnterpriseOid, &enterprise) == NULL) { 396 ober_free_elements(vblist); 397 return NULL; 398 } 399 } 400 } 401 return vblist; 402 } 403 404 int 405 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg) 406 { 407 ssize_t n; 408 pid_t pid; 409 410 if ((n = IMSG_DATA_SIZE(imsg)) <= 0) 411 return (-1); /* XXX */ 412 413 switch ((pid = fork())) { 414 case 0: 415 traphandler_fork_handler(p, imsg); 416 /* NOTREACHED */ 417 case -1: 418 log_warn("%s: couldn't fork traphandler", __func__); 419 return (0); 420 default: 421 log_debug("forked process %i to handle trap", pid); 422 return (0); 423 } 424 /* NOTREACHED */ 425 } 426 427 int 428 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg) 429 { 430 struct privsep *ps = p->p_ps; 431 struct snmpd *env = ps->ps_env; 432 struct ber ber = {0}; 433 struct sockaddr *sa; 434 char *buf; 435 ssize_t n; 436 struct ber_element *vblist; 437 struct ber_oid trapoid; 438 struct trapcmd *cmd; 439 struct passwd *pw; 440 int verbose; 441 442 pw = ps->ps_pw; 443 verbose = log_getverbose(); 444 445 if (setgroups(1, &pw->pw_gid) || 446 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 447 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 448 fatal("traphandler_fork_handler: cannot drop privileges"); 449 450 closefrom(STDERR_FILENO + 1); 451 452 log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); 453 log_setverbose(verbose); 454 log_procinit(p->p_title); 455 456 n = IMSG_DATA_SIZE(imsg); 457 458 sa = imsg->data; 459 n -= sa->sa_len; 460 buf = (char *)imsg->data + sa->sa_len; 461 462 ober_set_application(&ber, smi_application); 463 ober_set_readbuf(&ber, buf, n); 464 465 if ((vblist = ober_read_elements(&ber, NULL)) == NULL) 466 fatalx("couldn't parse SNMP trap message"); 467 ober_free(&ber); 468 469 (void)ober_scanf_elements(vblist, "{S{So", &trapoid); 470 if ((cmd = trapcmd_lookup(&trapoid)) != NULL) 471 trapcmd_exec(cmd, sa, vblist->be_sub); 472 473 ober_free_elements(vblist); 474 475 exit(0); 476 } 477 478 void 479 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, 480 struct ber_element *vb) 481 { 482 char oidbuf[SNMP_MAX_OID_STRLEN]; 483 struct ber_oid oid; 484 struct ber_element *elm; 485 int n, s[2], status = 0; 486 char *value, *host; 487 pid_t child = -1; 488 489 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { 490 log_warn("could not create pipe for OID '%s'", 491 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 492 return; 493 } 494 495 switch (child = fork()) { 496 case 0: 497 dup2(s[1], STDIN_FILENO); 498 499 close(s[0]); 500 close(s[1]); 501 502 closefrom(STDERR_FILENO + 1); 503 504 /* path to command is in argv[0], args follow */ 505 execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); 506 507 /* this shouldn't happen */ 508 log_warn("could not exec trap command for OID '%s'", 509 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 510 _exit(1); 511 /* NOTREACHED */ 512 513 case -1: 514 log_warn("could not fork trap command for OID '%s'", 515 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 516 close(s[0]); 517 close(s[1]); 518 return; 519 } 520 521 close(s[1]); 522 523 host = traphandler_hostname(sa, 0); 524 if (dprintf(s[0], "%s\n", host) == -1) 525 goto out; 526 527 host = traphandler_hostname(sa, 1); 528 if (dprintf(s[0], "%s\n", host) == -1) 529 goto out; 530 531 for (; vb != NULL; vb = vb->be_next) { 532 if (ober_scanf_elements(vb, "{oe}", &oid, &elm) == -1) 533 goto out; 534 if ((value = smi_print_element(elm)) == NULL) 535 goto out; 536 smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); 537 n = dprintf(s[0], "%s %s\n", oidbuf, value); 538 free(value); 539 if (n == -1) 540 goto out; 541 } 542 out: 543 close(s[0]); 544 waitpid(child, &status, 0); 545 546 if (WIFSIGNALED(status)) { 547 log_warnx("child %i exited due to receipt of signal %i", 548 child, WTERMSIG(status)); 549 } else if (WEXITSTATUS(status) != 0) { 550 log_warnx("child %i exited with status %i", 551 child, WEXITSTATUS(status)); 552 } else { 553 log_debug("child %i finished", child); 554 } 555 close(s[1]); 556 557 return; 558 } 559 560 char * 561 traphandler_hostname(struct sockaddr *sa, int numeric) 562 { 563 static char buf[NI_MAXHOST]; 564 int flag = 0; 565 566 if (numeric) 567 flag = NI_NUMERICHOST; 568 569 bzero(buf, sizeof(buf)); 570 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) 571 return ("Unknown"); 572 573 return (buf); 574 } 575 576 struct trapcmd * 577 trapcmd_lookup(struct ber_oid *oid) 578 { 579 struct trapcmd key, *res; 580 581 bzero(&key, sizeof(key)); 582 key.cmd_oid = oid; 583 584 if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) 585 res = key.cmd_maybe; 586 return (res); 587 } 588 589 int 590 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) 591 { 592 int ret; 593 594 ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid); 595 switch (ret) { 596 case 2: 597 /* cmd1 is a child of cmd2 */ 598 cmd1->cmd_maybe = cmd2; 599 return (1); 600 default: 601 return (ret); 602 } 603 /* NOTREACHED */ 604 } 605 606 int 607 trapcmd_add(struct trapcmd *cmd) 608 { 609 return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); 610 } 611 612 void 613 trapcmd_free(struct trapcmd *cmd) 614 { 615 RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); 616 free(cmd->cmd_argv); 617 free(cmd->cmd_oid); 618 free(cmd); 619 } 620