1 /* $OpenBSD: traphandler.c,v 1.16 2020/03/11 06:53:42 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 struct listen_sock *so; 78 79 if (env->sc_traphandler) { 80 TAILQ_FOREACH(h, &env->sc_addresses, entry) { 81 if (h->ipproto != IPPROTO_UDP) 82 continue; 83 if ((so = calloc(1, sizeof(*so))) == NULL) 84 fatal("%s", __func__); 85 if ((so->s_fd = traphandler_bind(h)) == -1) 86 fatal("could not create trap listener socket"); 87 TAILQ_INSERT_TAIL(&env->sc_sockets, so, entry); 88 } 89 } 90 91 proc_run(ps, p, procs, nitems(procs), traphandler_init, NULL); 92 } 93 94 void 95 traphandler_init(struct privsep *ps, struct privsep_proc *p, void *arg) 96 { 97 struct snmpd *env = ps->ps_env; 98 struct listen_sock *so; 99 100 if (pledge("stdio id proc recvfd exec", NULL) == -1) 101 fatal("pledge"); 102 103 if (!env->sc_traphandler) 104 return; 105 106 /* listen for SNMP trap messages */ 107 TAILQ_FOREACH(so, &env->sc_sockets, entry) { 108 event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST, 109 traphandler_recvmsg, ps); 110 event_add(&so->s_ev, NULL); 111 } 112 } 113 114 int 115 traphandler_bind(struct address *addr) 116 { 117 int s; 118 char buf[512]; 119 120 if ((s = snmpd_socket_af(&addr->ss, htons(SNMPD_TRAPPORT), 121 IPPROTO_UDP)) == -1) 122 return (-1); 123 124 if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) 125 goto bad; 126 127 if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1) 128 goto bad; 129 130 if (print_host(&addr->ss, buf, sizeof(buf)) == NULL) 131 goto bad; 132 133 log_info("traphandler: listening on %s:%d", buf, SNMPD_TRAPPORT); 134 135 return (s); 136 bad: 137 close (s); 138 return (-1); 139 } 140 141 void 142 traphandler_shutdown(void) 143 { 144 struct listen_sock *so; 145 146 TAILQ_FOREACH(so, &snmpd_env->sc_sockets, entry) { 147 event_del(&so->s_ev); 148 close(so->s_fd); 149 } 150 } 151 152 int 153 traphandler_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) 154 { 155 switch (imsg->hdr.type) { 156 default: 157 break; 158 } 159 160 return (-1); 161 } 162 163 int 164 snmpd_dispatch_traphandler(int fd, struct privsep_proc *p, struct imsg *imsg) 165 { 166 switch (imsg->hdr.type) { 167 case IMSG_ALERT: 168 return (traphandler_priv_recvmsg(p, imsg)); 169 default: 170 break; 171 } 172 173 return (-1); 174 } 175 176 void 177 traphandler_recvmsg(int fd, short events, void *arg) 178 { 179 struct privsep *ps = arg; 180 char buf[8196]; 181 struct iovec iov[2]; 182 struct sockaddr_storage ss; 183 socklen_t slen; 184 ssize_t n; 185 struct ber_element *req, *iter; 186 struct ber_oid trapoid; 187 u_int uptime; 188 189 slen = sizeof(ss); 190 if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss, 191 &slen)) == -1) 192 return; 193 194 if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1) 195 goto done; 196 197 iov[0].iov_base = &ss; 198 iov[0].iov_len = ss.ss_len; 199 iov[1].iov_base = buf; 200 iov[1].iov_len = n; 201 202 /* Forward it to the parent process */ 203 if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1) 204 goto done; 205 206 done: 207 if (req != NULL) 208 ober_free_elements(req); 209 return; 210 } 211 212 /* 213 * Validate received message 214 */ 215 int 216 traphandler_parse(char *buf, size_t n, struct ber_element **req, 217 struct ber_element **vbinds, u_int *uptime, struct ber_oid *trapoid) 218 { 219 struct ber ber; 220 struct ber_element *elm; 221 u_int vers, gtype, etype; 222 223 bzero(&ber, sizeof(ber)); 224 ober_set_application(&ber, smi_application); 225 ober_set_readbuf(&ber, buf, n); 226 227 if ((*req = ober_read_elements(&ber, NULL)) == NULL) 228 goto done; 229 230 if (ober_scanf_elements(*req, "{dSe", &vers, &elm) == -1) 231 goto done; 232 233 switch (vers) { 234 case SNMP_V1: 235 if (ober_scanf_elements(elm, "{oSddde", 236 trapoid, >ype, &etype, uptime, &elm) == -1) 237 goto done; 238 traphandler_v1translate(trapoid, gtype, etype); 239 if (elm->be_type != BER_TYPE_SEQUENCE) 240 goto done; 241 *vbinds = elm->be_sub; 242 break; 243 244 case SNMP_V2: 245 if (ober_scanf_elements(elm, "{SSS{e}}", &elm) == -1 || 246 ober_scanf_elements(elm, "{Sd}{So}", 247 uptime, trapoid) == -1) 248 goto done; 249 *vbinds = elm->be_next->be_next; 250 break; 251 252 default: 253 log_warnx("unsupported SNMP trap version '%d'", vers); 254 goto done; 255 } 256 257 ober_free(&ber); 258 return (0); 259 260 done: 261 ober_free(&ber); 262 if (*req) 263 ober_free_elements(*req); 264 *req = NULL; 265 return (-1); 266 } 267 268 void 269 traphandler_v1translate(struct ber_oid *oid, u_int gtype, u_int etype) 270 { 271 /* append 'specific trap' number to 'enterprise specific' traps */ 272 if (gtype >= 6) { 273 oid->bo_id[oid->bo_n] = 0; 274 oid->bo_id[oid->bo_n + 1] = etype; 275 oid->bo_n += 2; 276 } 277 } 278 279 int 280 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg) 281 { 282 ssize_t n; 283 pid_t pid; 284 285 if ((n = IMSG_DATA_SIZE(imsg)) <= 0) 286 return (-1); /* XXX */ 287 288 switch ((pid = fork())) { 289 case 0: 290 traphandler_fork_handler(p, imsg); 291 /* NOTREACHED */ 292 case -1: 293 log_warn("%s: couldn't fork traphandler", __func__); 294 return (0); 295 default: 296 log_debug("forked process %i to handle trap", pid); 297 return (0); 298 } 299 /* NOTREACHED */ 300 } 301 302 int 303 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg) 304 { 305 struct privsep *ps = p->p_ps; 306 struct snmpd *env = ps->ps_env; 307 char oidbuf[SNMP_MAX_OID_STRLEN]; 308 struct sockaddr *sa; 309 char *buf; 310 ssize_t n; 311 struct ber_element *req, *iter; 312 struct trapcmd *cmd; 313 struct ber_oid trapoid; 314 u_int uptime; 315 struct passwd *pw; 316 int verbose; 317 318 pw = ps->ps_pw; 319 verbose = log_getverbose(); 320 321 if (setgroups(1, &pw->pw_gid) || 322 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 323 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 324 fatal("traphandler_fork_handler: cannot drop privileges"); 325 326 closefrom(STDERR_FILENO + 1); 327 328 log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); 329 log_setverbose(verbose); 330 log_procinit(p->p_title); 331 332 n = IMSG_DATA_SIZE(imsg); 333 334 sa = imsg->data; 335 n -= sa->sa_len; 336 buf = (char *)imsg->data + sa->sa_len; 337 338 if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1) 339 fatalx("couldn't parse SNMP trap message"); 340 341 smi_oid2string(&trapoid, oidbuf, sizeof(oidbuf), 0); 342 if ((cmd = trapcmd_lookup(&trapoid)) != NULL) 343 trapcmd_exec(cmd, sa, iter, oidbuf, uptime); 344 345 if (req != NULL) 346 ober_free_elements(req); 347 348 exit(0); 349 } 350 351 void 352 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, 353 struct ber_element *iter, char *trapoid, u_int uptime) 354 { 355 char oidbuf[SNMP_MAX_OID_STRLEN]; 356 struct ber_oid oid; 357 struct ber_element *elm; 358 int n, s[2], status = 0; 359 char *value, *host; 360 pid_t child = -1; 361 362 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { 363 log_warn("could not create pipe for OID '%s'", 364 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 365 return; 366 } 367 368 switch (child = fork()) { 369 case 0: 370 dup2(s[1], STDIN_FILENO); 371 372 close(s[0]); 373 close(s[1]); 374 375 closefrom(STDERR_FILENO + 1); 376 377 /* path to command is in argv[0], args follow */ 378 execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); 379 380 /* this shouldn't happen */ 381 log_warn("could not exec trap command for OID '%s'", 382 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 383 _exit(1); 384 /* NOTREACHED */ 385 386 case -1: 387 log_warn("could not fork trap command for OID '%s'", 388 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 389 close(s[0]); 390 close(s[1]); 391 return; 392 } 393 394 close(s[1]); 395 396 host = traphandler_hostname(sa, 0); 397 if (dprintf(s[0], "%s\n", host) == -1) 398 goto out; 399 400 host = traphandler_hostname(sa, 1); 401 if (dprintf(s[0], "%s\n", host) == -1) 402 goto out; 403 404 if (dprintf(s[0], 405 "iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.0 %u\n", 406 uptime) == -1) 407 goto out; 408 409 if (dprintf(s[0], 410 "iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects." 411 "snmpTrap.snmpTrapOID.0 %s\n", trapoid) == -1) 412 goto out; 413 414 for (; iter != NULL; iter = iter->be_next) { 415 if (ober_scanf_elements(iter, "{oe}", &oid, &elm) == -1) 416 goto out; 417 if ((value = smi_print_element(elm)) == NULL) 418 goto out; 419 smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); 420 n = dprintf(s[0], "%s %s\n", oidbuf, value); 421 free(value); 422 if (n == -1) 423 goto out; 424 } 425 out: 426 close(s[0]); 427 waitpid(child, &status, 0); 428 429 if (WIFSIGNALED(status)) { 430 log_warnx("child %i exited due to receipt of signal %i", 431 child, WTERMSIG(status)); 432 } else if (WEXITSTATUS(status) != 0) { 433 log_warnx("child %i exited with status %i", 434 child, WEXITSTATUS(status)); 435 } else { 436 log_debug("child %i finished", child); 437 } 438 close(s[1]); 439 440 return; 441 } 442 443 char * 444 traphandler_hostname(struct sockaddr *sa, int numeric) 445 { 446 static char buf[NI_MAXHOST]; 447 int flag = 0; 448 449 if (numeric) 450 flag = NI_NUMERICHOST; 451 452 bzero(buf, sizeof(buf)); 453 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) 454 return ("Unknown"); 455 456 return (buf); 457 } 458 459 struct trapcmd * 460 trapcmd_lookup(struct ber_oid *oid) 461 { 462 struct trapcmd key, *res; 463 464 bzero(&key, sizeof(key)); 465 key.cmd_oid = oid; 466 467 if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) 468 res = key.cmd_maybe; 469 return (res); 470 } 471 472 int 473 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) 474 { 475 int ret; 476 477 ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid); 478 switch (ret) { 479 case 2: 480 /* cmd1 is a child of cmd2 */ 481 cmd1->cmd_maybe = cmd2; 482 return (1); 483 default: 484 return (ret); 485 } 486 /* NOTREACHED */ 487 } 488 489 int 490 trapcmd_add(struct trapcmd *cmd) 491 { 492 return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); 493 } 494 495 void 496 trapcmd_free(struct trapcmd *cmd) 497 { 498 RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); 499 free(cmd->cmd_argv); 500 free(cmd->cmd_oid); 501 free(cmd); 502 } 503