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