1 /* $OpenBSD: traphandler.c,v 1.15 2019/10/24 12:39:27 tb 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, "{oSddd", 236 trapoid, >ype, &etype, uptime) == -1) 237 goto done; 238 traphandler_v1translate(trapoid, gtype, etype); 239 break; 240 241 case SNMP_V2: 242 if (ober_scanf_elements(elm, "{SSS{e}}", &elm) == -1 || 243 ober_scanf_elements(elm, "{Sd}{So}", 244 uptime, trapoid) == -1) 245 goto done; 246 *vbinds = elm->be_next->be_next; 247 break; 248 249 default: 250 log_warnx("unsupported SNMP trap version '%d'", vers); 251 goto done; 252 } 253 254 ober_free(&ber); 255 return (0); 256 257 done: 258 ober_free(&ber); 259 if (*req) 260 ober_free_elements(*req); 261 *req = NULL; 262 return (-1); 263 } 264 265 void 266 traphandler_v1translate(struct ber_oid *oid, u_int gtype, u_int etype) 267 { 268 /* append 'specific trap' number to 'enterprise specific' traps */ 269 if (gtype >= 6) { 270 oid->bo_id[oid->bo_n] = 0; 271 oid->bo_id[oid->bo_n + 1] = etype; 272 oid->bo_n += 2; 273 } 274 } 275 276 int 277 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg) 278 { 279 ssize_t n; 280 pid_t pid; 281 282 if ((n = IMSG_DATA_SIZE(imsg)) <= 0) 283 return (-1); /* XXX */ 284 285 switch ((pid = fork())) { 286 case 0: 287 traphandler_fork_handler(p, imsg); 288 /* NOTREACHED */ 289 case -1: 290 log_warn("%s: couldn't fork traphandler", __func__); 291 return (0); 292 default: 293 log_debug("forked process %i to handle trap", pid); 294 return (0); 295 } 296 /* NOTREACHED */ 297 } 298 299 int 300 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg) 301 { 302 struct privsep *ps = p->p_ps; 303 struct snmpd *env = ps->ps_env; 304 char oidbuf[SNMP_MAX_OID_STRLEN]; 305 struct sockaddr *sa; 306 char *buf; 307 ssize_t n; 308 struct ber_element *req, *iter; 309 struct trapcmd *cmd; 310 struct ber_oid trapoid; 311 u_int uptime; 312 struct passwd *pw; 313 int verbose; 314 315 pw = ps->ps_pw; 316 verbose = log_getverbose(); 317 318 if (setgroups(1, &pw->pw_gid) || 319 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 320 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 321 fatal("traphandler_fork_handler: cannot drop privileges"); 322 323 closefrom(STDERR_FILENO + 1); 324 325 log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); 326 log_setverbose(verbose); 327 log_procinit(p->p_title); 328 329 n = IMSG_DATA_SIZE(imsg); 330 331 sa = imsg->data; 332 n -= sa->sa_len; 333 buf = (char *)imsg->data + sa->sa_len; 334 335 if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1) 336 fatalx("couldn't parse SNMP trap message"); 337 338 smi_oid2string(&trapoid, oidbuf, sizeof(oidbuf), 0); 339 if ((cmd = trapcmd_lookup(&trapoid)) != NULL) 340 trapcmd_exec(cmd, sa, iter, oidbuf, uptime); 341 342 if (req != NULL) 343 ober_free_elements(req); 344 345 exit(0); 346 } 347 348 void 349 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, 350 struct ber_element *iter, char *trapoid, u_int uptime) 351 { 352 char oidbuf[SNMP_MAX_OID_STRLEN]; 353 struct ber_oid oid; 354 struct ber_element *elm; 355 int n, s[2], status = 0; 356 char *value, *host; 357 pid_t child = -1; 358 359 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { 360 log_warn("could not create pipe for OID '%s'", 361 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 362 return; 363 } 364 365 switch (child = fork()) { 366 case 0: 367 dup2(s[1], STDIN_FILENO); 368 369 close(s[0]); 370 close(s[1]); 371 372 closefrom(STDERR_FILENO + 1); 373 374 /* path to command is in argv[0], args follow */ 375 execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); 376 377 /* this shouldn't happen */ 378 log_warn("could not exec trap command for OID '%s'", 379 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 380 _exit(1); 381 /* NOTREACHED */ 382 383 case -1: 384 log_warn("could not fork trap command for OID '%s'", 385 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 386 close(s[0]); 387 close(s[1]); 388 return; 389 } 390 391 close(s[1]); 392 393 host = traphandler_hostname(sa, 0); 394 if (dprintf(s[0], "%s\n", host) == -1) 395 goto out; 396 397 host = traphandler_hostname(sa, 1); 398 if (dprintf(s[0], "%s\n", host) == -1) 399 goto out; 400 401 if (dprintf(s[0], 402 "iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.0 %u\n", 403 uptime) == -1) 404 goto out; 405 406 if (dprintf(s[0], 407 "iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects." 408 "snmpTrap.snmpTrapOID.0 %s\n", trapoid) == -1) 409 goto out; 410 411 for (; iter != NULL; iter = iter->be_next) { 412 if (ober_scanf_elements(iter, "{oe}", &oid, &elm) == -1) 413 goto out; 414 if ((value = smi_print_element(elm)) == NULL) 415 goto out; 416 smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); 417 n = dprintf(s[0], "%s %s\n", oidbuf, value); 418 free(value); 419 if (n == -1) 420 goto out; 421 } 422 out: 423 close(s[0]); 424 waitpid(child, &status, 0); 425 426 if (WIFSIGNALED(status)) { 427 log_warnx("child %i exited due to receipt of signal %i", 428 child, WTERMSIG(status)); 429 } else if (WEXITSTATUS(status) != 0) { 430 log_warnx("child %i exited with status %i", 431 child, WEXITSTATUS(status)); 432 } else { 433 log_debug("child %i finished", child); 434 } 435 close(s[1]); 436 437 return; 438 } 439 440 char * 441 traphandler_hostname(struct sockaddr *sa, int numeric) 442 { 443 static char buf[NI_MAXHOST]; 444 int flag = 0; 445 446 if (numeric) 447 flag = NI_NUMERICHOST; 448 449 bzero(buf, sizeof(buf)); 450 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) 451 return ("Unknown"); 452 453 return (buf); 454 } 455 456 struct trapcmd * 457 trapcmd_lookup(struct ber_oid *oid) 458 { 459 struct trapcmd key, *res; 460 461 bzero(&key, sizeof(key)); 462 key.cmd_oid = oid; 463 464 if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) 465 res = key.cmd_maybe; 466 return (res); 467 } 468 469 int 470 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) 471 { 472 int ret; 473 474 ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid); 475 switch (ret) { 476 case 2: 477 /* cmd1 is a child of cmd2 */ 478 cmd1->cmd_maybe = cmd2; 479 return (1); 480 default: 481 return (ret); 482 } 483 /* NOTREACHED */ 484 } 485 486 int 487 trapcmd_add(struct trapcmd *cmd) 488 { 489 return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); 490 } 491 492 void 493 trapcmd_free(struct trapcmd *cmd) 494 { 495 RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); 496 free(cmd->cmd_argv); 497 free(cmd->cmd_oid); 498 free(cmd); 499 } 500