1 /* $OpenBSD: traphandler.c,v 1.18 2020/09/06 15:51:28 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 <event.h> 32 #include <fcntl.h> 33 #include <imsg.h> 34 #include <netdb.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <syslog.h> 39 #include <unistd.h> 40 #include <pwd.h> 41 42 #include "snmpd.h" 43 #include "mib.h" 44 45 char trap_path[PATH_MAX]; 46 47 void traphandler_init(struct privsep *, struct privsep_proc *, void *arg); 48 int traphandler_dispatch_parent(int, struct privsep_proc *, struct imsg *); 49 int traphandler_bind(struct address *); 50 void traphandler_recvmsg(int, short, void *); 51 int traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *); 52 int traphandler_fork_handler(struct privsep_proc *, struct imsg *); 53 int traphandler_parse(char *, size_t, struct ber_element **, 54 struct ber_element **, u_int *, struct ber_oid *); 55 void traphandler_v1translate(struct ber_oid *, u_int, u_int); 56 57 int trapcmd_cmp(struct trapcmd *, struct trapcmd *); 58 void trapcmd_exec(struct trapcmd *, struct sockaddr *, 59 struct ber_element *, char *, u_int); 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, ps); 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 privsep *ps = arg; 184 char buf[8196]; 185 struct iovec iov[2]; 186 struct sockaddr_storage ss; 187 socklen_t slen; 188 ssize_t n; 189 struct ber_element *req, *iter; 190 struct ber_oid trapoid; 191 u_int uptime; 192 193 slen = sizeof(ss); 194 if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss, 195 &slen)) == -1) 196 return; 197 198 if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1) 199 goto done; 200 201 iov[0].iov_base = &ss; 202 iov[0].iov_len = ss.ss_len; 203 iov[1].iov_base = buf; 204 iov[1].iov_len = n; 205 206 /* Forward it to the parent process */ 207 if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1) 208 goto done; 209 210 done: 211 if (req != NULL) 212 ober_free_elements(req); 213 return; 214 } 215 216 /* 217 * Validate received message 218 */ 219 int 220 traphandler_parse(char *buf, size_t n, struct ber_element **req, 221 struct ber_element **vbinds, u_int *uptime, struct ber_oid *trapoid) 222 { 223 struct ber ber; 224 struct ber_element *elm; 225 u_int vers, gtype, etype; 226 227 bzero(&ber, sizeof(ber)); 228 ober_set_application(&ber, smi_application); 229 ober_set_readbuf(&ber, buf, n); 230 231 if ((*req = ober_read_elements(&ber, NULL)) == NULL) 232 goto done; 233 234 if (ober_scanf_elements(*req, "{dSe", &vers, &elm) == -1) 235 goto done; 236 237 switch (vers) { 238 case SNMP_V1: 239 if (ober_scanf_elements(elm, "{oSddde", 240 trapoid, >ype, &etype, uptime, &elm) == -1) 241 goto done; 242 traphandler_v1translate(trapoid, gtype, etype); 243 if (elm->be_type != BER_TYPE_SEQUENCE) 244 goto done; 245 *vbinds = elm->be_sub; 246 break; 247 248 case SNMP_V2: 249 if (ober_scanf_elements(elm, "{SSS{e}}", &elm) == -1 || 250 ober_scanf_elements(elm, "{Sd}{So}", 251 uptime, trapoid) == -1) 252 goto done; 253 *vbinds = elm->be_next->be_next; 254 break; 255 256 default: 257 log_warnx("unsupported SNMP trap version '%d'", vers); 258 goto done; 259 } 260 261 ober_free(&ber); 262 return (0); 263 264 done: 265 ober_free(&ber); 266 if (*req) 267 ober_free_elements(*req); 268 *req = NULL; 269 return (-1); 270 } 271 272 void 273 traphandler_v1translate(struct ber_oid *oid, u_int gtype, u_int etype) 274 { 275 /* append 'specific trap' number to 'enterprise specific' traps */ 276 if (gtype >= 6) { 277 oid->bo_id[oid->bo_n] = 0; 278 oid->bo_id[oid->bo_n + 1] = etype; 279 oid->bo_n += 2; 280 } 281 } 282 283 int 284 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg) 285 { 286 ssize_t n; 287 pid_t pid; 288 289 if ((n = IMSG_DATA_SIZE(imsg)) <= 0) 290 return (-1); /* XXX */ 291 292 switch ((pid = fork())) { 293 case 0: 294 traphandler_fork_handler(p, imsg); 295 /* NOTREACHED */ 296 case -1: 297 log_warn("%s: couldn't fork traphandler", __func__); 298 return (0); 299 default: 300 log_debug("forked process %i to handle trap", pid); 301 return (0); 302 } 303 /* NOTREACHED */ 304 } 305 306 int 307 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg) 308 { 309 struct privsep *ps = p->p_ps; 310 struct snmpd *env = ps->ps_env; 311 char oidbuf[SNMP_MAX_OID_STRLEN]; 312 struct sockaddr *sa; 313 char *buf; 314 ssize_t n; 315 struct ber_element *req, *iter; 316 struct trapcmd *cmd; 317 struct ber_oid trapoid; 318 u_int uptime; 319 struct passwd *pw; 320 int verbose; 321 322 pw = ps->ps_pw; 323 verbose = log_getverbose(); 324 325 if (setgroups(1, &pw->pw_gid) || 326 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 327 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 328 fatal("traphandler_fork_handler: cannot drop privileges"); 329 330 closefrom(STDERR_FILENO + 1); 331 332 log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); 333 log_setverbose(verbose); 334 log_procinit(p->p_title); 335 336 n = IMSG_DATA_SIZE(imsg); 337 338 sa = imsg->data; 339 n -= sa->sa_len; 340 buf = (char *)imsg->data + sa->sa_len; 341 342 if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1) 343 fatalx("couldn't parse SNMP trap message"); 344 345 smi_oid2string(&trapoid, oidbuf, sizeof(oidbuf), 0); 346 if ((cmd = trapcmd_lookup(&trapoid)) != NULL) 347 trapcmd_exec(cmd, sa, iter, oidbuf, uptime); 348 349 if (req != NULL) 350 ober_free_elements(req); 351 352 exit(0); 353 } 354 355 void 356 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, 357 struct ber_element *iter, char *trapoid, u_int uptime) 358 { 359 char oidbuf[SNMP_MAX_OID_STRLEN]; 360 struct ber_oid oid; 361 struct ber_element *elm; 362 int n, s[2], status = 0; 363 char *value, *host; 364 pid_t child = -1; 365 366 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { 367 log_warn("could not create pipe for OID '%s'", 368 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 369 return; 370 } 371 372 switch (child = fork()) { 373 case 0: 374 dup2(s[1], STDIN_FILENO); 375 376 close(s[0]); 377 close(s[1]); 378 379 closefrom(STDERR_FILENO + 1); 380 381 /* path to command is in argv[0], args follow */ 382 execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); 383 384 /* this shouldn't happen */ 385 log_warn("could not exec trap command for OID '%s'", 386 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 387 _exit(1); 388 /* NOTREACHED */ 389 390 case -1: 391 log_warn("could not fork trap command for OID '%s'", 392 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 393 close(s[0]); 394 close(s[1]); 395 return; 396 } 397 398 close(s[1]); 399 400 host = traphandler_hostname(sa, 0); 401 if (dprintf(s[0], "%s\n", host) == -1) 402 goto out; 403 404 host = traphandler_hostname(sa, 1); 405 if (dprintf(s[0], "%s\n", host) == -1) 406 goto out; 407 408 if (dprintf(s[0], 409 "iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.0 %u\n", 410 uptime) == -1) 411 goto out; 412 413 if (dprintf(s[0], 414 "iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects." 415 "snmpTrap.snmpTrapOID.0 %s\n", trapoid) == -1) 416 goto out; 417 418 for (; iter != NULL; iter = iter->be_next) { 419 if (ober_scanf_elements(iter, "{oe}", &oid, &elm) == -1) 420 goto out; 421 if ((value = smi_print_element(elm)) == NULL) 422 goto out; 423 smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); 424 n = dprintf(s[0], "%s %s\n", oidbuf, value); 425 free(value); 426 if (n == -1) 427 goto out; 428 } 429 out: 430 close(s[0]); 431 waitpid(child, &status, 0); 432 433 if (WIFSIGNALED(status)) { 434 log_warnx("child %i exited due to receipt of signal %i", 435 child, WTERMSIG(status)); 436 } else if (WEXITSTATUS(status) != 0) { 437 log_warnx("child %i exited with status %i", 438 child, WEXITSTATUS(status)); 439 } else { 440 log_debug("child %i finished", child); 441 } 442 close(s[1]); 443 444 return; 445 } 446 447 char * 448 traphandler_hostname(struct sockaddr *sa, int numeric) 449 { 450 static char buf[NI_MAXHOST]; 451 int flag = 0; 452 453 if (numeric) 454 flag = NI_NUMERICHOST; 455 456 bzero(buf, sizeof(buf)); 457 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) 458 return ("Unknown"); 459 460 return (buf); 461 } 462 463 struct trapcmd * 464 trapcmd_lookup(struct ber_oid *oid) 465 { 466 struct trapcmd key, *res; 467 468 bzero(&key, sizeof(key)); 469 key.cmd_oid = oid; 470 471 if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) 472 res = key.cmd_maybe; 473 return (res); 474 } 475 476 int 477 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) 478 { 479 int ret; 480 481 ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid); 482 switch (ret) { 483 case 2: 484 /* cmd1 is a child of cmd2 */ 485 cmd1->cmd_maybe = cmd2; 486 return (1); 487 default: 488 return (ret); 489 } 490 /* NOTREACHED */ 491 } 492 493 int 494 trapcmd_add(struct trapcmd *cmd) 495 { 496 return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); 497 } 498 499 void 500 trapcmd_free(struct trapcmd *cmd) 501 { 502 RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); 503 free(cmd->cmd_argv); 504 free(cmd->cmd_oid); 505 free(cmd); 506 } 507