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