1 /* $OpenBSD: traphandler.c,v 1.22 2021/10/21 08:17:34 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 <errno.h> 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 "snmpd.h" 44 #include "mib.h" 45 46 int traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *); 47 int traphandler_fork_handler(struct privsep_proc *, struct imsg *); 48 struct ber_element * 49 traphandler_v1translate(struct snmp_message *, int); 50 int trapcmd_cmp(struct trapcmd *, struct trapcmd *); 51 void trapcmd_exec(struct trapcmd *, struct sockaddr *, 52 struct ber_element *); 53 54 char *traphandler_hostname(struct sockaddr *, int); 55 56 RB_PROTOTYPE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) 57 RB_GENERATE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp) 58 59 struct trapcmd_tree trapcmd_tree = RB_INITIALIZER(&trapcmd_tree); 60 61 /* 62 * Validate received message 63 */ 64 int 65 traphandler_parse(struct snmp_message *msg) 66 { 67 struct privsep *ps = &snmpd_env->sc_ps; 68 struct snmp_stats *stats = &snmpd_env->sc_stats; 69 struct ber ber = {0}; 70 struct ber_element *vblist = NULL, *elm; 71 struct ber_oid o1, o2, snmpTrapOIDOID; 72 struct ber_oid snmpTrapOID, sysUpTimeOID; 73 int sysUpTime; 74 struct iovec iov[2]; 75 void *buf; 76 ssize_t buflen; 77 int ret = -1; 78 79 switch (msg->sm_pdu->be_type) { 80 case SNMP_C_TRAP: 81 if ((vblist = traphandler_v1translate(msg, 0)) == NULL) 82 goto done; 83 break; 84 case SNMP_C_TRAPV2: 85 if (ober_scanf_elements(msg->sm_pdu, "{SSe}$", &elm) == -1) { 86 stats->snmp_inasnparseerrs++; 87 goto done; 88 } 89 if (elm->be_type != BER_TYPE_INTEGER) { 90 stats->snmp_inasnparseerrs++; 91 goto done; 92 } 93 vblist = ober_unlink_elements(elm); 94 break; 95 default: 96 fatalx("%s called without proper context", __func__); 97 } 98 99 (void)ober_string2oid("1.3.6.1.2.1.1.3.0", &sysUpTimeOID); 100 (void)ober_string2oid("1.3.6.1.6.3.1.1.4.1.0", &snmpTrapOIDOID); 101 if (ober_scanf_elements(vblist, "{{od$}{oo$}", &o1, &sysUpTime, &o2, 102 &snmpTrapOID) == -1 || 103 ober_oid_cmp(&o1, &sysUpTimeOID) != 0 || 104 ober_oid_cmp(&o2, &snmpTrapOIDOID) != 0) { 105 stats->snmp_inasnparseerrs++; 106 goto done; 107 } 108 (void)ober_scanf_elements(vblist, "{Se", &elm); 109 for (elm = elm->be_next; elm != NULL; elm = elm->be_next) { 110 if (ober_scanf_elements(elm, "{oS$}", &o1) == -1) { 111 stats->snmp_inasnparseerrs++; 112 goto done; 113 } 114 } 115 116 ober_set_application(&ber, smi_application); 117 118 if ((buflen = ober_write_elements(&ber, vblist)) == -1 || 119 ober_get_writebuf(&ber, &buf) == -1) { 120 msg->sm_errstr = "failed to handle trap"; 121 goto done; 122 } 123 124 iov[0].iov_base = &(msg->sm_ss); 125 iov[0].iov_len = msg->sm_slen; 126 iov[1].iov_base = buf; 127 iov[1].iov_len = buflen; 128 129 /* Forward it to the parent process */ 130 if (proc_composev(ps, PROC_PARENT, IMSG_TRAP_EXEC, iov, 2) == -1) { 131 msg->sm_errstr = "failed to handle trap"; 132 goto done; 133 } 134 135 ret = 0; 136 done: 137 ober_free(&ber); 138 if (vblist != NULL) 139 ober_free_elements(vblist); 140 return ret; 141 } 142 143 struct ber_element * 144 traphandler_v1translate(struct snmp_message *msg, int proxy) 145 { 146 struct snmp_stats *stats = &snmpd_env->sc_stats; 147 struct ber_oid trapoid, enterprise, oid, snmpTrapAddressOid; 148 struct ber_oid snmpTrapCommunityOid, snmpTrapEnterpriseOid; 149 struct ber_element *elm, *last, *vblist, *vb0 = NULL; 150 void *agent_addr; 151 size_t agent_addrlen; 152 int generic_trap, specific_trap, time_stamp; 153 int hasaddress = 0, hascommunity = 0, hasenterprise = 0; 154 155 if (ober_scanf_elements(msg->sm_pdu, "{oxdddeS$}$", &enterprise, 156 &agent_addr, &agent_addrlen, &generic_trap, &specific_trap, 157 &time_stamp, &vblist) == -1 || 158 agent_addrlen != 4 || 159 vblist->be_type != BER_TYPE_SEQUENCE) { 160 stats->snmp_inasnparseerrs++; 161 return NULL; 162 } 163 switch (generic_trap) { 164 case 0: 165 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.1", &trapoid); 166 break; 167 case 1: 168 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.2", &trapoid); 169 break; 170 case 2: 171 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.3", &trapoid); 172 break; 173 case 3: 174 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.4", &trapoid); 175 break; 176 case 4: 177 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.5", &trapoid); 178 break; 179 case 5: 180 (void)ober_string2oid("1.3.6.1.6.3.1.1.5.6", &trapoid); 181 break; 182 case 6: 183 trapoid = enterprise; 184 /* Officially this should be 128, but BER_MAX_OID_LEN is 64 */ 185 if (trapoid.bo_n + 2 > BER_MAX_OID_LEN) { 186 stats->snmp_inasnparseerrs++; 187 return NULL; 188 } 189 trapoid.bo_id[trapoid.bo_n++] = 0; 190 trapoid.bo_id[trapoid.bo_n++] = specific_trap; 191 break; 192 default: 193 stats->snmp_inasnparseerrs++; 194 return NULL; 195 } 196 197 /* work aronud net-snmp's snmptrap: It adds an EOC element in vblist */ 198 if (vblist->be_len != 0) 199 vb0 = ober_unlink_elements(vblist); 200 201 if ((vblist = ober_add_sequence(NULL)) == NULL) { 202 msg->sm_errstr = strerror(errno); 203 if (vb0 != NULL) 204 ober_free_elements(vb0); 205 return NULL; 206 } 207 if (ober_printf_elements(vblist, "{od}{oO}e", "1.3.6.1.2.1.1.3.0", 208 time_stamp, "1.3.6.1.6.3.1.1.4.1.0", &trapoid, vb0) == NULL) { 209 msg->sm_errstr = strerror(errno); 210 if (vb0 != 0) 211 ober_free_elements(vb0); 212 ober_free_elements(vblist); 213 return NULL; 214 } 215 216 if (proxy) { 217 (void)ober_string2oid("1.3.6.1.6.3.18.1.3.0", 218 &snmpTrapAddressOid); 219 (void)ober_string2oid("1.3.6.1.6.3.18.1.4.0", 220 &snmpTrapCommunityOid); 221 (void)ober_string2oid("1.3.6.1.6.3.1.1.4.3.0", 222 &snmpTrapEnterpriseOid); 223 for (elm = vblist->be_sub; elm != NULL; elm = elm->be_next) { 224 if (ober_get_oid(elm->be_sub, &oid) == -1) { 225 msg->sm_errstr = "failed to read oid"; 226 ober_free_elements(vblist); 227 return NULL; 228 } 229 if (ober_oid_cmp(&oid, &snmpTrapAddressOid) == 0) 230 hasaddress = 1; 231 else if (ober_oid_cmp(&oid, &snmpTrapCommunityOid) == 0) 232 hascommunity = 1; 233 else if (ober_oid_cmp(&oid, 234 &snmpTrapEnterpriseOid) == 0) 235 hasenterprise = 1; 236 last = elm; 237 } 238 if (!hasaddress || !hascommunity || !hasenterprise) { 239 if (ober_printf_elements(last, "{Oxt}{Os}{OO}", 240 &snmpTrapAddressOid, agent_addr, 4, 241 BER_CLASS_APPLICATION, SNMP_T_IPADDR, 242 &snmpTrapCommunityOid, msg->sm_community, 243 &snmpTrapEnterpriseOid, &enterprise) == NULL) { 244 msg->sm_errstr = strerror(errno); 245 ober_free_elements(vblist); 246 return NULL; 247 } 248 } 249 } 250 return vblist; 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 struct privsep *ps = p->p_ps; 280 struct snmpd *env = ps->ps_env; 281 struct ber ber = {0}; 282 struct sockaddr *sa; 283 char *buf; 284 ssize_t n; 285 struct ber_element *vblist; 286 struct ber_oid trapoid; 287 struct trapcmd *cmd; 288 struct passwd *pw; 289 int verbose; 290 291 pw = ps->ps_pw; 292 verbose = log_getverbose(); 293 294 if (setgroups(1, &pw->pw_gid) || 295 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 296 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 297 fatal("traphandler_fork_handler: cannot drop privileges"); 298 299 closefrom(STDERR_FILENO + 1); 300 301 log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON); 302 log_setverbose(verbose); 303 log_procinit(p->p_title); 304 305 n = IMSG_DATA_SIZE(imsg); 306 307 sa = imsg->data; 308 n -= sa->sa_len; 309 buf = (char *)imsg->data + sa->sa_len; 310 311 ober_set_application(&ber, smi_application); 312 ober_set_readbuf(&ber, buf, n); 313 314 if ((vblist = ober_read_elements(&ber, NULL)) == NULL) 315 fatalx("couldn't parse SNMP trap message"); 316 ober_free(&ber); 317 318 (void)ober_scanf_elements(vblist, "{S{So", &trapoid); 319 if ((cmd = trapcmd_lookup(&trapoid)) != NULL) 320 trapcmd_exec(cmd, sa, vblist->be_sub); 321 322 ober_free_elements(vblist); 323 324 exit(0); 325 } 326 327 void 328 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa, 329 struct ber_element *vb) 330 { 331 char oidbuf[SNMP_MAX_OID_STRLEN]; 332 struct ber_oid oid; 333 struct ber_element *elm; 334 int n, s[2], status = 0; 335 char *value, *host; 336 pid_t child = -1; 337 338 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { 339 log_warn("could not create pipe for OID '%s'", 340 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 341 return; 342 } 343 344 switch (child = fork()) { 345 case 0: 346 dup2(s[1], STDIN_FILENO); 347 348 close(s[0]); 349 close(s[1]); 350 351 closefrom(STDERR_FILENO + 1); 352 353 /* path to command is in argv[0], args follow */ 354 execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL); 355 356 /* this shouldn't happen */ 357 log_warn("could not exec trap command for OID '%s'", 358 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 359 _exit(1); 360 /* NOTREACHED */ 361 362 case -1: 363 log_warn("could not fork trap command for OID '%s'", 364 smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0)); 365 close(s[0]); 366 close(s[1]); 367 return; 368 } 369 370 close(s[1]); 371 372 host = traphandler_hostname(sa, 0); 373 if (dprintf(s[0], "%s\n", host) == -1) 374 goto out; 375 376 host = traphandler_hostname(sa, 1); 377 if (dprintf(s[0], "%s\n", host) == -1) 378 goto out; 379 380 for (; vb != NULL; vb = vb->be_next) { 381 if (ober_scanf_elements(vb, "{oeS$}", &oid, &elm) == -1) 382 goto out; 383 if ((value = smi_print_element(elm)) == NULL) 384 goto out; 385 smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0); 386 n = dprintf(s[0], "%s %s\n", oidbuf, value); 387 free(value); 388 if (n == -1) 389 goto out; 390 } 391 out: 392 close(s[0]); 393 waitpid(child, &status, 0); 394 395 if (WIFSIGNALED(status)) { 396 log_warnx("child %i exited due to receipt of signal %i", 397 child, WTERMSIG(status)); 398 } else if (WEXITSTATUS(status) != 0) { 399 log_warnx("child %i exited with status %i", 400 child, WEXITSTATUS(status)); 401 } else { 402 log_debug("child %i finished", child); 403 } 404 close(s[1]); 405 406 return; 407 } 408 409 char * 410 traphandler_hostname(struct sockaddr *sa, int numeric) 411 { 412 static char buf[NI_MAXHOST]; 413 int flag = 0; 414 415 if (numeric) 416 flag = NI_NUMERICHOST; 417 418 bzero(buf, sizeof(buf)); 419 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0) 420 return ("Unknown"); 421 422 return (buf); 423 } 424 425 struct trapcmd * 426 trapcmd_lookup(struct ber_oid *oid) 427 { 428 struct trapcmd key, *res; 429 430 bzero(&key, sizeof(key)); 431 key.cmd_oid = oid; 432 433 if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL) 434 res = key.cmd_maybe; 435 return (res); 436 } 437 438 int 439 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2) 440 { 441 int ret; 442 443 ret = ober_oid_cmp(cmd1->cmd_oid, cmd2->cmd_oid); 444 switch (ret) { 445 case 2: 446 /* cmd1 is a child of cmd2 */ 447 cmd1->cmd_maybe = cmd2; 448 return (1); 449 default: 450 return (ret); 451 } 452 /* NOTREACHED */ 453 } 454 455 int 456 trapcmd_add(struct trapcmd *cmd) 457 { 458 return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL); 459 } 460 461 void 462 trapcmd_free(struct trapcmd *cmd) 463 { 464 RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd); 465 free(cmd->cmd_argv); 466 free(cmd->cmd_oid); 467 free(cmd); 468 } 469