xref: /openbsd-src/usr.sbin/snmpd/traphandler.c (revision cba26e98faa2b48aa4705f205ed876af460243a2)
1 /*	$OpenBSD: traphandler.c,v 1.19 2021/01/05 18:12:15 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 char	 trap_path[PATH_MAX];
47 
48 void	 traphandler_init(struct privsep *, struct privsep_proc *, void *arg);
49 int	 traphandler_dispatch_parent(int, struct privsep_proc *, struct imsg *);
50 int	 traphandler_bind(struct address *);
51 void	 traphandler_recvmsg(int, short, void *);
52 int	 traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *);
53 int	 traphandler_fork_handler(struct privsep_proc *, struct imsg *);
54 int	 traphandler_parse(struct ber_element *, char *, struct sockaddr *);
55 struct ber_element *
56 	 traphandler_v1translate(struct ber_element *, char *, int);
57 int	 trapcmd_cmp(struct trapcmd *, struct trapcmd *);
58 void	 trapcmd_exec(struct trapcmd *, struct sockaddr *,
59 	    struct ber_element *);
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 
78 	if (env->sc_traphandler) {
79 		TAILQ_FOREACH(h, &env->sc_addresses, entry) {
80 			if (h->type != SOCK_DGRAM)
81 				continue;
82 			if ((h->fd = traphandler_bind(h)) == -1)
83 				fatal("could not create trap listener socket");
84 		}
85 	}
86 
87 	proc_run(ps, p, procs, nitems(procs), traphandler_init, NULL);
88 }
89 
90 void
91 traphandler_init(struct privsep *ps, struct privsep_proc *p, void *arg)
92 {
93 	struct snmpd		*env = ps->ps_env;
94 	struct address		*h;
95 
96 	if (pledge("stdio id proc recvfd exec", NULL) == -1)
97 		fatal("pledge");
98 
99 	if (!env->sc_traphandler)
100 		return;
101 
102 	/* listen for SNMP trap messages */
103 	TAILQ_FOREACH(h, &env->sc_addresses, entry) {
104 		event_set(&h->ev, h->fd, EV_READ|EV_PERSIST,
105 		    traphandler_recvmsg, NULL);
106 		event_add(&h->ev, NULL);
107 	}
108 }
109 
110 int
111 traphandler_bind(struct address *addr)
112 {
113 	int			 s;
114 	char			 buf[512];
115 	struct sockaddr_in	*sin;
116 	struct sockaddr_in6	*sin6;
117 
118 	if (addr->ss.ss_family == AF_INET) {
119 		sin = (struct sockaddr_in *)&(addr->ss);
120 		sin->sin_port = htons(162);
121 	} else {
122 		sin6 = (struct sockaddr_in6 *)&(addr->ss);
123 		sin6->sin6_port = htons(162);
124 	}
125 	if ((s = snmpd_socket_af(&addr->ss, SOCK_DGRAM)) == -1)
126 		return (-1);
127 
128 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
129 		goto bad;
130 
131 	if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1)
132 		goto bad;
133 
134 	if (print_host(&addr->ss, buf, sizeof(buf)) == NULL)
135 		goto bad;
136 
137 	log_info("traphandler: listening on %s:%s", buf, SNMPD_TRAPPORT);
138 
139 	return (s);
140  bad:
141 	close (s);
142 	return (-1);
143 }
144 
145 void
146 traphandler_shutdown(void)
147 {
148 	struct address		*h;
149 
150 	TAILQ_FOREACH(h, &snmpd_env->sc_addresses, entry) {
151 		event_del(&h->ev);
152 		close(h->fd);
153 	}
154 }
155 
156 int
157 traphandler_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
158 {
159 	switch (imsg->hdr.type) {
160 	default:
161 		break;
162 	}
163 
164 	return (-1);
165 }
166 
167 int
168 snmpd_dispatch_traphandler(int fd, struct privsep_proc *p, struct imsg *imsg)
169 {
170 	switch (imsg->hdr.type) {
171 	case IMSG_ALERT:
172 		return (traphandler_priv_recvmsg(p, imsg));
173 	default:
174 		break;
175 	}
176 
177 	return (-1);
178 }
179 
180 void
181 traphandler_recvmsg(int fd, short events, void *arg)
182 {
183 	struct ber		 ber = {0};
184 	struct ber_element	*msg = NULL, *pdu;
185 	char			 buf[8196];
186 	struct sockaddr_storage	 ss;
187 	socklen_t		 slen;
188 	ssize_t			 n;
189 	int			 vers;
190 	char			*community;
191 
192 	slen = sizeof(ss);
193 	if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss,
194 	    &slen)) == -1)
195 		return;
196 
197 	ober_set_application(&ber, smi_application);
198 	ober_set_readbuf(&ber, buf, n);
199 
200 	if ((msg = ober_read_elements(&ber, NULL)) == NULL)
201 		goto parsefail;
202 
203 	if (ober_scanf_elements(msg, "{dse", &vers, &community, &pdu) == -1)
204 		goto parsefail;
205 
206 	switch (vers) {
207 	case SNMP_V1:
208 		if (pdu->be_type != SNMP_C_TRAP)
209 			goto parsefail;
210 		break;
211 	case SNMP_V2:
212 		if (pdu->be_type != SNMP_C_TRAPV2)
213 			goto parsefail;
214 		break;
215 	default:
216 		goto parsefail;
217 	}
218 
219 	(void)traphandler_parse(pdu, community, (struct sockaddr *)&ss);
220 
221 parsefail:
222 	ober_free(&ber);
223 	if (msg != NULL)
224 		ober_free_elements(msg);
225 }
226 
227 /*
228  * Validate received message
229  */
230 int
231 traphandler_parse(struct ber_element *pdu, char *community, struct sockaddr *sa)
232 {
233 	struct privsep		*ps = &snmpd_env->sc_ps;
234 	struct ber		 ber = {0};
235 	struct ber_element	*vblist = NULL, *elm, *elm2;
236 	struct ber_oid		 o1, o2, snmpTrapOIDOID;
237 	struct ber_oid		 snmpTrapOID, sysUpTimeOID;
238 	int			 sysUpTime;
239 	struct iovec		 iov[2];
240 	void			*buf;
241 	ssize_t			 buflen;
242 	int			 ret = -1;
243 
244 	switch (pdu->be_type) {
245 	case SNMP_C_TRAP:
246 		vblist = traphandler_v1translate(pdu, community, 0);
247 		if (vblist == NULL)
248 			goto done;
249 		break;
250 	case SNMP_C_TRAPV2:
251 		if (ober_scanf_elements(pdu, "{SSe}", &elm) == -1)
252 			goto done;
253 		if (elm->be_type != BER_TYPE_INTEGER)
254 			goto done;
255 		vblist = ober_unlink_elements(elm);
256 		break;
257 	default:
258 		log_warnx("unsupported SNMP trap version '%d'", pdu->be_type);
259 		goto done;
260 	}
261 
262 	(void)ober_string2oid("1.3.6.1.2.1.1.3.0", &sysUpTimeOID);
263 	(void)ober_string2oid("1.3.6.1.6.3.1.1.4.1.0", &snmpTrapOIDOID);
264 	if (ober_scanf_elements(vblist, "{{od}{oo}", &o1, &sysUpTime, &o2,
265 	    &snmpTrapOID) == -1 ||
266 	    ober_oid_cmp(&o1, &sysUpTimeOID) != 0 ||
267 	    ober_oid_cmp(&o2, &snmpTrapOIDOID) != 0) {
268 		goto done;
269 	}
270 	(void)ober_scanf_elements(vblist, "{Se", &elm);
271 	for (elm = elm->be_next; elm != NULL; elm = elm->be_next) {
272 		if (ober_scanf_elements(elm, "{oe}", &o1, &elm2) == -1 ||
273 		    elm2->be_next != NULL)
274 			goto done;
275 	}
276 
277 	ober_set_application(&ber, smi_application);
278 
279 	if ((buflen = ober_write_elements(&ber, vblist)) == -1 ||
280 	    ober_get_writebuf(&ber, &buf) == -1)
281 		goto done;
282 
283 	iov[0].iov_base = sa;
284 	iov[0].iov_len = sa->sa_len;
285 	iov[1].iov_base = buf;
286 	iov[1].iov_len = buflen;
287 
288 	/* Forward it to the parent process */
289 	if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1)
290 		goto done;
291 
292 	ret = 0;
293 done:
294 	ober_free(&ber);
295 	if (vblist != NULL)
296 		ober_free_elements(vblist);
297 	return ret;
298 }
299 
300 struct ber_element *
301 traphandler_v1translate(struct ber_element *pdu, char *community, int proxy)
302 {
303 	struct ber_oid trapoid, enterprise, oid, snmpTrapAddressOid;
304 	struct ber_oid snmpTrapCommunityOid, snmpTrapEnterpriseOid;
305 	struct ber_element *elm, *last, *vblist, *vb0 = NULL;
306 	void *agent_addr;
307 	size_t agent_addrlen;
308 	int generic_trap, specific_trap, time_stamp;
309 	int hasaddress = 0, hascommunity = 0, hasenterprise = 0;
310 
311 	if (ober_scanf_elements(pdu, "{oxddde", &enterprise, &agent_addr,
312 	    &agent_addrlen, &generic_trap, &specific_trap, &time_stamp,
313 	    &vblist) == -1 ||
314 	    agent_addrlen != 4 ||
315 	    vblist->be_type != BER_TYPE_SEQUENCE) {
316 		errno = EINVAL;
317 		return NULL;
318 	}
319 	switch (generic_trap) {
320 	case 0:
321 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.1", &trapoid);
322 		break;
323 	case 1:
324 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.2", &trapoid);
325 		break;
326 	case 2:
327 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.3", &trapoid);
328 		break;
329 	case 3:
330 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.4", &trapoid);
331 		break;
332 	case 4:
333 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.5", &trapoid);
334 		break;
335 	case 5:
336 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.6", &trapoid);
337 		break;
338 	case 6:
339 		trapoid = enterprise;
340 		if (trapoid.bo_n + 2 > BER_MAX_OID_LEN) {
341 			errno = EINVAL;
342 			return NULL;
343 		}
344 		trapoid.bo_id[trapoid.bo_n++] = 0;
345 		trapoid.bo_id[trapoid.bo_n++] = specific_trap;
346 		break;
347 	default:
348 		errno = EINVAL;
349 		return NULL;
350 	}
351 
352 	/* work aronud net-snmp's snmptrap: It adds an EOC element in vblist */
353 	if (vblist->be_len != 0)
354 		vb0 = ober_unlink_elements(vblist);
355 
356 	if ((vblist = ober_add_sequence(NULL)) == NULL) {
357 		if (vb0 != NULL)
358 			ober_free_elements(vb0);
359 		return NULL;
360 	}
361 	if (ober_printf_elements(vblist, "{od}{oO}e", "1.3.6.1.2.1.1.3.0",
362 	    time_stamp, "1.3.6.1.6.3.1.1.4.1.0", &trapoid, vb0) == NULL) {
363 		if (vb0 != 0)
364 			ober_free_elements(vb0);
365 		ober_free_elements(vblist);
366 		return NULL;
367 	}
368 
369 	if (proxy) {
370 		(void)ober_string2oid("1.3.6.1.6.3.18.1.3.0",
371 		    &snmpTrapAddressOid);
372 		(void)ober_string2oid("1.3.6.1.6.3.18.1.4.0",
373 		    &snmpTrapCommunityOid);
374 		(void)ober_string2oid("1.3.6.1.6.3.1.1.4.3.0",
375 		    &snmpTrapEnterpriseOid);
376 		for (elm = vblist->be_sub; elm != NULL; elm = elm->be_next) {
377 			if (ober_get_oid(elm->be_sub, &oid) == -1) {
378 				ober_free_elements(vblist);
379 				return NULL;
380 			}
381 			if (ober_oid_cmp(&oid, &snmpTrapAddressOid) == 0)
382 				hasaddress = 1;
383 			else if (ober_oid_cmp(&oid, &snmpTrapCommunityOid) == 0)
384 				hascommunity = 1;
385 			else if (ober_oid_cmp(&oid,
386 			    &snmpTrapEnterpriseOid) == 0)
387 				hasenterprise = 1;
388 			last = elm;
389 		}
390 		if (!hasaddress || !hascommunity || !hasenterprise) {
391 			if (ober_printf_elements(last, "{Oxt}{Os}{OO}",
392 			    &snmpTrapAddressOid, agent_addr, 4,
393 			    BER_CLASS_APPLICATION, SNMP_T_IPADDR,
394 			    &snmpTrapCommunityOid, community,
395 			    &snmpTrapEnterpriseOid, &enterprise) == NULL) {
396 				ober_free_elements(vblist);
397 				return NULL;
398 			}
399 		}
400 	}
401 	return vblist;
402 }
403 
404 int
405 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg)
406 {
407 	ssize_t			 n;
408 	pid_t			 pid;
409 
410 	if ((n = IMSG_DATA_SIZE(imsg)) <= 0)
411 		return (-1);			/* XXX */
412 
413 	switch ((pid = fork())) {
414 	case 0:
415 		traphandler_fork_handler(p, imsg);
416 		/* NOTREACHED */
417 	case -1:
418 		log_warn("%s: couldn't fork traphandler", __func__);
419 		return (0);
420 	default:
421 		log_debug("forked process %i to handle trap", pid);
422 		return (0);
423 	}
424 	/* NOTREACHED */
425 }
426 
427 int
428 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg)
429 {
430 	struct privsep		*ps = p->p_ps;
431 	struct snmpd		*env = ps->ps_env;
432 	struct ber		 ber = {0};
433 	struct sockaddr		*sa;
434 	char			*buf;
435 	ssize_t			 n;
436 	struct ber_element	*vblist;
437 	struct ber_oid		 trapoid;
438 	struct trapcmd		*cmd;
439 	struct passwd		*pw;
440 	int			 verbose;
441 
442 	pw = ps->ps_pw;
443 	verbose = log_getverbose();
444 
445 	if (setgroups(1, &pw->pw_gid) ||
446 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
447 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
448 		fatal("traphandler_fork_handler: cannot drop privileges");
449 
450 	closefrom(STDERR_FILENO + 1);
451 
452 	log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON);
453 	log_setverbose(verbose);
454 	log_procinit(p->p_title);
455 
456 	n = IMSG_DATA_SIZE(imsg);
457 
458 	sa = imsg->data;
459 	n -= sa->sa_len;
460 	buf = (char *)imsg->data + sa->sa_len;
461 
462 	ober_set_application(&ber, smi_application);
463 	ober_set_readbuf(&ber, buf, n);
464 
465 	if ((vblist = ober_read_elements(&ber, NULL)) == NULL)
466 		fatalx("couldn't parse SNMP trap message");
467 	ober_free(&ber);
468 
469 	(void)ober_scanf_elements(vblist, "{S{So", &trapoid);
470 	if ((cmd = trapcmd_lookup(&trapoid)) != NULL)
471 		trapcmd_exec(cmd, sa, vblist->be_sub);
472 
473 	ober_free_elements(vblist);
474 
475 	exit(0);
476 }
477 
478 void
479 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa,
480     struct ber_element *vb)
481 {
482 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
483 	struct ber_oid		 oid;
484 	struct ber_element	*elm;
485 	int			 n, s[2], status = 0;
486 	char			*value, *host;
487 	pid_t			 child = -1;
488 
489 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
490 		log_warn("could not create pipe for OID '%s'",
491 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
492 		return;
493 	}
494 
495 	switch (child = fork()) {
496 	case 0:
497 		dup2(s[1], STDIN_FILENO);
498 
499 		close(s[0]);
500 		close(s[1]);
501 
502 		closefrom(STDERR_FILENO + 1);
503 
504 		/* path to command is in argv[0], args follow */
505 		execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL);
506 
507 		/* this shouldn't happen */
508 		log_warn("could not exec trap command for OID '%s'",
509 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
510 		_exit(1);
511 		/* NOTREACHED */
512 
513 	case -1:
514 		log_warn("could not fork trap command for OID '%s'",
515 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
516 		close(s[0]);
517 		close(s[1]);
518 		return;
519 	}
520 
521 	close(s[1]);
522 
523 	host = traphandler_hostname(sa, 0);
524 	if (dprintf(s[0], "%s\n", host) == -1)
525 		goto out;
526 
527 	host = traphandler_hostname(sa, 1);
528 	if (dprintf(s[0], "%s\n", host) == -1)
529 		goto out;
530 
531 	for (; vb != NULL; vb = vb->be_next) {
532 		if (ober_scanf_elements(vb, "{oe}", &oid, &elm) == -1)
533 			goto out;
534 		if ((value = smi_print_element(elm)) == NULL)
535 			goto out;
536 		smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
537 		n = dprintf(s[0], "%s %s\n", oidbuf, value);
538 		free(value);
539 		if (n == -1)
540 			goto out;
541 	}
542  out:
543 	close(s[0]);
544 	waitpid(child, &status, 0);
545 
546 	if (WIFSIGNALED(status)) {
547 		log_warnx("child %i exited due to receipt of signal %i",
548 		    child, WTERMSIG(status));
549 	} else if (WEXITSTATUS(status) != 0) {
550 		log_warnx("child %i exited with status %i",
551 		    child, WEXITSTATUS(status));
552 	} else {
553 		log_debug("child %i finished", child);
554 	}
555 	close(s[1]);
556 
557 	return;
558 }
559 
560 char *
561 traphandler_hostname(struct sockaddr *sa, int numeric)
562 {
563 	static char	 buf[NI_MAXHOST];
564 	int		 flag = 0;
565 
566 	if (numeric)
567 		flag = NI_NUMERICHOST;
568 
569 	bzero(buf, sizeof(buf));
570 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0)
571 		return ("Unknown");
572 
573 	return (buf);
574 }
575 
576 struct trapcmd *
577 trapcmd_lookup(struct ber_oid *oid)
578 {
579 	struct trapcmd	key, *res;
580 
581 	bzero(&key, sizeof(key));
582 	key.cmd_oid = oid;
583 
584 	if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL)
585 		res = key.cmd_maybe;
586 	return (res);
587 }
588 
589 int
590 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2)
591 {
592 	int ret;
593 
594 	ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid);
595 	switch (ret) {
596 	case 2:
597 		/* cmd1 is a child of cmd2 */
598 		cmd1->cmd_maybe = cmd2;
599 		return (1);
600 	default:
601 		return (ret);
602 	}
603 	/* NOTREACHED */
604 }
605 
606 int
607 trapcmd_add(struct trapcmd *cmd)
608 {
609 	return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL);
610 }
611 
612 void
613 trapcmd_free(struct trapcmd *cmd)
614 {
615 	RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd);
616 	free(cmd->cmd_argv);
617 	free(cmd->cmd_oid);
618 	free(cmd);
619 }
620