xref: /openbsd-src/usr.sbin/snmpd/traphandler.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
1 /*	$OpenBSD: traphandler.c,v 1.18 2020/09/06 15:51:28 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 <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 "snmpd.h"
43 #include "mib.h"
44 
45 char	 trap_path[PATH_MAX];
46 
47 void	 traphandler_init(struct privsep *, struct privsep_proc *, void *arg);
48 int	 traphandler_dispatch_parent(int, struct privsep_proc *, struct imsg *);
49 int	 traphandler_bind(struct address *);
50 void	 traphandler_recvmsg(int, short, void *);
51 int	 traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *);
52 int	 traphandler_fork_handler(struct privsep_proc *, struct imsg *);
53 int	 traphandler_parse(char *, size_t, struct ber_element **,
54 	    struct ber_element **, u_int *, struct ber_oid *);
55 void	 traphandler_v1translate(struct ber_oid *, u_int, u_int);
56 
57 int	 trapcmd_cmp(struct trapcmd *, struct trapcmd *);
58 void	 trapcmd_exec(struct trapcmd *, struct sockaddr *,
59 	    struct ber_element *, char *, u_int);
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, ps);
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 privsep		*ps = arg;
184 	char			 buf[8196];
185 	struct iovec		 iov[2];
186 	struct sockaddr_storage	 ss;
187 	socklen_t		 slen;
188 	ssize_t			 n;
189 	struct ber_element	*req, *iter;
190 	struct ber_oid		 trapoid;
191 	u_int			 uptime;
192 
193 	slen = sizeof(ss);
194 	if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss,
195 	    &slen)) == -1)
196 		return;
197 
198 	if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1)
199 		goto done;
200 
201 	iov[0].iov_base = &ss;
202 	iov[0].iov_len = ss.ss_len;
203 	iov[1].iov_base = buf;
204 	iov[1].iov_len = n;
205 
206 	/* Forward it to the parent process */
207 	if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1)
208 		goto done;
209 
210  done:
211 	if (req != NULL)
212 		ober_free_elements(req);
213 	return;
214 }
215 
216 /*
217  * Validate received message
218  */
219 int
220 traphandler_parse(char *buf, size_t n, struct ber_element **req,
221     struct ber_element **vbinds, u_int *uptime, struct ber_oid *trapoid)
222 {
223 	struct ber		 ber;
224 	struct ber_element	*elm;
225 	u_int			 vers, gtype, etype;
226 
227 	bzero(&ber, sizeof(ber));
228 	ober_set_application(&ber, smi_application);
229 	ober_set_readbuf(&ber, buf, n);
230 
231 	if ((*req = ober_read_elements(&ber, NULL)) == NULL)
232 		goto done;
233 
234 	if (ober_scanf_elements(*req, "{dSe", &vers, &elm) == -1)
235 		goto done;
236 
237 	switch (vers) {
238 	case SNMP_V1:
239 		if (ober_scanf_elements(elm, "{oSddde",
240 		    trapoid, &gtype, &etype, uptime, &elm) == -1)
241 			goto done;
242 		traphandler_v1translate(trapoid, gtype, etype);
243 		if (elm->be_type != BER_TYPE_SEQUENCE)
244 			goto done;
245 		*vbinds = elm->be_sub;
246 		break;
247 
248 	case SNMP_V2:
249 		if (ober_scanf_elements(elm, "{SSS{e}}", &elm) == -1 ||
250 		    ober_scanf_elements(elm, "{Sd}{So}",
251 		    uptime, trapoid) == -1)
252 			goto done;
253 		*vbinds = elm->be_next->be_next;
254 		break;
255 
256 	default:
257 		log_warnx("unsupported SNMP trap version '%d'", vers);
258 		goto done;
259 	}
260 
261 	ober_free(&ber);
262 	return (0);
263 
264  done:
265 	ober_free(&ber);
266 	if (*req)
267 		ober_free_elements(*req);
268 	*req = NULL;
269 	return (-1);
270 }
271 
272 void
273 traphandler_v1translate(struct ber_oid *oid, u_int gtype, u_int etype)
274 {
275 	/* append 'specific trap' number to 'enterprise specific' traps */
276 	if (gtype >= 6) {
277 		oid->bo_id[oid->bo_n] = 0;
278 		oid->bo_id[oid->bo_n + 1] = etype;
279 		oid->bo_n += 2;
280 	}
281 }
282 
283 int
284 traphandler_priv_recvmsg(struct privsep_proc *p, struct imsg *imsg)
285 {
286 	ssize_t			 n;
287 	pid_t			 pid;
288 
289 	if ((n = IMSG_DATA_SIZE(imsg)) <= 0)
290 		return (-1);			/* XXX */
291 
292 	switch ((pid = fork())) {
293 	case 0:
294 		traphandler_fork_handler(p, imsg);
295 		/* NOTREACHED */
296 	case -1:
297 		log_warn("%s: couldn't fork traphandler", __func__);
298 		return (0);
299 	default:
300 		log_debug("forked process %i to handle trap", pid);
301 		return (0);
302 	}
303 	/* NOTREACHED */
304 }
305 
306 int
307 traphandler_fork_handler(struct privsep_proc *p, struct imsg *imsg)
308 {
309 	struct privsep		*ps = p->p_ps;
310 	struct snmpd		*env = ps->ps_env;
311 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
312 	struct sockaddr		*sa;
313 	char			*buf;
314 	ssize_t			 n;
315 	struct ber_element	*req, *iter;
316 	struct trapcmd		*cmd;
317 	struct ber_oid		 trapoid;
318 	u_int			 uptime;
319 	struct passwd		*pw;
320 	int			 verbose;
321 
322 	pw = ps->ps_pw;
323 	verbose = log_getverbose();
324 
325 	if (setgroups(1, &pw->pw_gid) ||
326 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
327 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
328 		fatal("traphandler_fork_handler: cannot drop privileges");
329 
330 	closefrom(STDERR_FILENO + 1);
331 
332 	log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON);
333 	log_setverbose(verbose);
334 	log_procinit(p->p_title);
335 
336 	n = IMSG_DATA_SIZE(imsg);
337 
338 	sa = imsg->data;
339 	n -= sa->sa_len;
340 	buf = (char *)imsg->data + sa->sa_len;
341 
342 	if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1)
343 		fatalx("couldn't parse SNMP trap message");
344 
345 	smi_oid2string(&trapoid, oidbuf, sizeof(oidbuf), 0);
346 	if ((cmd = trapcmd_lookup(&trapoid)) != NULL)
347 		trapcmd_exec(cmd, sa, iter, oidbuf, uptime);
348 
349 	if (req != NULL)
350 		ober_free_elements(req);
351 
352 	exit(0);
353 }
354 
355 void
356 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa,
357     struct ber_element *iter, char *trapoid, u_int uptime)
358 {
359 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
360 	struct ber_oid		 oid;
361 	struct ber_element	*elm;
362 	int			 n, s[2], status = 0;
363 	char			*value, *host;
364 	pid_t			 child = -1;
365 
366 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
367 		log_warn("could not create pipe for OID '%s'",
368 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
369 		return;
370 	}
371 
372 	switch (child = fork()) {
373 	case 0:
374 		dup2(s[1], STDIN_FILENO);
375 
376 		close(s[0]);
377 		close(s[1]);
378 
379 		closefrom(STDERR_FILENO + 1);
380 
381 		/* path to command is in argv[0], args follow */
382 		execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL);
383 
384 		/* this shouldn't happen */
385 		log_warn("could not exec trap command for OID '%s'",
386 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
387 		_exit(1);
388 		/* NOTREACHED */
389 
390 	case -1:
391 		log_warn("could not fork trap command for OID '%s'",
392 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
393 		close(s[0]);
394 		close(s[1]);
395 		return;
396 	}
397 
398 	close(s[1]);
399 
400 	host = traphandler_hostname(sa, 0);
401 	if (dprintf(s[0], "%s\n", host) == -1)
402 		goto out;
403 
404 	host = traphandler_hostname(sa, 1);
405 	if (dprintf(s[0], "%s\n", host) == -1)
406 		goto out;
407 
408 	if (dprintf(s[0],
409 	    "iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.0 %u\n",
410 	    uptime) == -1)
411 		goto out;
412 
413 	if (dprintf(s[0],
414 	    "iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects."
415 	    "snmpTrap.snmpTrapOID.0 %s\n", trapoid) == -1)
416 		goto out;
417 
418 	for (; iter != NULL; iter = iter->be_next) {
419 		if (ober_scanf_elements(iter, "{oe}", &oid, &elm) == -1)
420 			goto out;
421 		if ((value = smi_print_element(elm)) == NULL)
422 			goto out;
423 		smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
424 		n = dprintf(s[0], "%s %s\n", oidbuf, value);
425 		free(value);
426 		if (n == -1)
427 			goto out;
428 	}
429  out:
430 	close(s[0]);
431 	waitpid(child, &status, 0);
432 
433 	if (WIFSIGNALED(status)) {
434 		log_warnx("child %i exited due to receipt of signal %i",
435 		    child, WTERMSIG(status));
436 	} else if (WEXITSTATUS(status) != 0) {
437 		log_warnx("child %i exited with status %i",
438 		    child, WEXITSTATUS(status));
439 	} else {
440 		log_debug("child %i finished", child);
441 	}
442 		close(s[1]);
443 
444 	return;
445 }
446 
447 char *
448 traphandler_hostname(struct sockaddr *sa, int numeric)
449 {
450 	static char	 buf[NI_MAXHOST];
451 	int		 flag = 0;
452 
453 	if (numeric)
454 		flag = NI_NUMERICHOST;
455 
456 	bzero(buf, sizeof(buf));
457 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0)
458 		return ("Unknown");
459 
460 	return (buf);
461 }
462 
463 struct trapcmd *
464 trapcmd_lookup(struct ber_oid *oid)
465 {
466 	struct trapcmd	key, *res;
467 
468 	bzero(&key, sizeof(key));
469 	key.cmd_oid = oid;
470 
471 	if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL)
472 		res = key.cmd_maybe;
473 	return (res);
474 }
475 
476 int
477 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2)
478 {
479 	int ret;
480 
481 	ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid);
482 	switch (ret) {
483 	case 2:
484 		/* cmd1 is a child of cmd2 */
485 		cmd1->cmd_maybe = cmd2;
486 		return (1);
487 	default:
488 		return (ret);
489 	}
490 	/* NOTREACHED */
491 }
492 
493 int
494 trapcmd_add(struct trapcmd *cmd)
495 {
496 	return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL);
497 }
498 
499 void
500 trapcmd_free(struct trapcmd *cmd)
501 {
502 	RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd);
503 	free(cmd->cmd_argv);
504 	free(cmd->cmd_oid);
505 	free(cmd);
506 }
507