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