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