xref: /openbsd-src/usr.sbin/snmpd/traphandler.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: traphandler.c,v 1.5 2016/08/16 18:41:57 tedu 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/socketvar.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <sys/uio.h>
25 #include <sys/wait.h>
26 
27 #include <net/if.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 
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 "ber.h"
43 #include "snmpd.h"
44 #include "mib.h"
45 
46 int	 trapsock;
47 struct event trapev;
48 char	 trap_path[PATH_MAX];
49 
50 void	 traphandler_init(struct privsep *, struct privsep_proc *, void *arg);
51 int	 traphandler_dispatch_parent(int, struct privsep_proc *, struct imsg *);
52 int	 traphandler_bind(struct address *);
53 void	 traphandler_recvmsg(int, short, void *);
54 int	 traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *);
55 int	 traphandler_fork_handler(struct privsep_proc *, struct imsg *);
56 int	 traphandler_parse(char *, size_t, struct ber_element **,
57 	    struct ber_element **, u_int *, struct ber_oid *);
58 void	 traphandler_v1translate(struct ber_oid *, u_int, u_int);
59 
60 int	 trapcmd_cmp(struct trapcmd *, struct trapcmd *);
61 void	 trapcmd_exec(struct trapcmd *, struct sockaddr *,
62 	    struct ber_element *, char *, u_int);
63 
64 char	*traphandler_hostname(struct sockaddr *, int);
65 
66 RB_PROTOTYPE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp)
67 RB_GENERATE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp)
68 
69 struct trapcmd_tree trapcmd_tree = RB_INITIALIZER(&trapcmd_tree);
70 
71 static struct privsep_proc procs[] = {
72 	{ "parent",	PROC_PARENT,	traphandler_dispatch_parent }
73 };
74 
75 pid_t
76 traphandler(struct privsep *ps, struct privsep_proc *p)
77 {
78 	struct snmpd		*env = ps->ps_env;
79 
80 	if (env->sc_traphandler &&
81 	    (trapsock = traphandler_bind(&env->sc_address)) == -1)
82 		fatal("could not create trap listener socket");
83 
84 	return (proc_run(ps, p, procs, nitems(procs), traphandler_init,
85 	    NULL));
86 }
87 
88 void
89 traphandler_init(struct privsep *ps, struct privsep_proc *p, void *arg)
90 {
91 	struct snmpd		*env = ps->ps_env;
92 
93 	if (!env->sc_traphandler)
94 		return;
95 
96 	/* listen for SNMP trap messages */
97 	event_set(&trapev, trapsock, EV_READ|EV_PERSIST, traphandler_recvmsg,
98 	    ps);
99 	event_add(&trapev, NULL);
100 }
101 
102 int
103 traphandler_bind(struct address *addr)
104 {
105 	int			 s;
106 
107 	if ((s = snmpd_socket_af(&addr->ss, htons(SNMPD_TRAPPORT))) == -1)
108 		return (-1);
109 
110 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
111 		goto bad;
112 
113 	if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1)
114 		goto bad;
115 
116 	return (s);
117  bad:
118 	close (s);
119 	return (-1);
120 }
121 
122 void
123 traphandler_shutdown(void)
124 {
125 	event_del(&trapev);
126 	close(trapsock);
127 }
128 
129 int
130 traphandler_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
131 {
132 	switch (imsg->hdr.type) {
133 	default:
134 		break;
135 	}
136 
137 	return (-1);
138 }
139 
140 int
141 snmpd_dispatch_traphandler(int fd, struct privsep_proc *p, struct imsg *imsg)
142 {
143 	switch (imsg->hdr.type) {
144 	case IMSG_ALERT:
145 		return (traphandler_priv_recvmsg(p, imsg));
146 	default:
147 		break;
148 	}
149 
150 	return (-1);
151 }
152 
153 void
154 traphandler_recvmsg(int fd, short events, void *arg)
155 {
156 	struct privsep		*ps = arg;
157 	char			 buf[8196];
158 	struct iovec		 iov[2];
159 	struct sockaddr_storage	 ss;
160 	socklen_t		 slen;
161 	ssize_t			 n;
162 	struct ber_element	*req, *iter;
163 	struct ber_oid		 trapoid;
164 	u_int			 uptime;
165 
166 	slen = sizeof(ss);
167 	if ((n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss,
168 	    &slen)) == -1)
169 		return;
170 
171 	if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1)
172 		goto done;
173 
174 	iov[0].iov_base = &ss;
175 	iov[0].iov_len = ss.ss_len;
176 	iov[1].iov_base = buf;
177 	iov[1].iov_len = n;
178 
179 	/* Forward it to the parent process */
180 	if (proc_composev(ps, PROC_PARENT, IMSG_ALERT, iov, 2) == -1)
181 		goto done;
182 
183  done:
184 	if (req != NULL)
185 		ber_free_elements(req);
186 	return;
187 }
188 
189 /*
190  * Validate received message
191  */
192 int
193 traphandler_parse(char *buf, size_t n, struct ber_element **req,
194     struct ber_element **vbinds, u_int *uptime, struct ber_oid *trapoid)
195 {
196 	struct ber		 ber;
197 	struct ber_element	*elm;
198 	u_int			 vers, gtype, etype;
199 
200 	bzero(&ber, sizeof(ber));
201 	ber.fd = -1;
202 	ber_set_application(&ber, smi_application);
203 	ber_set_readbuf(&ber, buf, n);
204 
205 	if ((*req = ber_read_elements(&ber, NULL)) == NULL)
206 		goto done;
207 
208 	if (ber_scanf_elements(*req, "{dSe", &vers, &elm) == -1)
209 		goto done;
210 
211 	switch (vers) {
212 	case SNMP_V1:
213 		if (ber_scanf_elements(elm, "{oSddd",
214 		    trapoid, &gtype, &etype, uptime) == -1)
215 			goto done;
216 		traphandler_v1translate(trapoid, gtype, etype);
217 		break;
218 
219 	case SNMP_V2:
220 		if (ber_scanf_elements(elm, "{SSSS{e}}", &elm) == -1 ||
221 		    ber_scanf_elements(elm, "{SdS}{So}e",
222 		    uptime, trapoid, vbinds) == -1)
223 			goto done;
224 		break;
225 
226 	default:
227 		log_warnx("unsupported SNMP trap version '%d'", vers);
228 		goto done;
229 	}
230 
231 	ber_free(&ber);
232 	return (0);
233 
234  done:
235 	ber_free(&ber);
236 	if (*req)
237 		ber_free_elements(*req);
238 	*req = NULL;
239 	return (-1);
240 }
241 
242 void
243 traphandler_v1translate(struct ber_oid *oid, u_int gtype, u_int etype)
244 {
245 	/* append 'specific trap' number to 'enterprise specific' traps */
246 	if (gtype >= 6) {
247 		oid->bo_id[oid->bo_n] = 0;
248 		oid->bo_id[oid->bo_n + 1] = etype;
249 		oid->bo_n += 2;
250 	}
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 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
280 	struct sockaddr		*sa;
281 	char			*buf;
282 	ssize_t			 n;
283 	struct ber_element	*req, *iter;
284 	struct trapcmd		*cmd;
285 	struct ber_oid		 trapoid;
286 	u_int			 uptime;
287 	struct passwd		*pw;
288 	extern int		 debug;
289 
290 	pw = p->p_ps->ps_pw;
291 
292 	if (setgroups(1, &pw->pw_gid) ||
293 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
294 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
295 		fatal("traphandler_fork_handler: cannot drop privileges");
296 
297 	closefrom(STDERR_FILENO + 1);
298 
299 	log_init(debug, LOG_DAEMON);
300 	log_procinit(p->p_title);
301 
302 	n = IMSG_DATA_SIZE(imsg);
303 
304 	sa = imsg->data;
305 	n -= sa->sa_len;
306 	buf = (char *)imsg->data + sa->sa_len;
307 
308 	if (traphandler_parse(buf, n, &req, &iter, &uptime, &trapoid) == -1)
309 		fatalx("couldn't parse SNMP trap message");
310 
311 	smi_oid2string(&trapoid, oidbuf, sizeof(oidbuf), 0);
312 	if ((cmd = trapcmd_lookup(&trapoid)) != NULL)
313 		trapcmd_exec(cmd, sa, iter, oidbuf, uptime);
314 
315 	if (req != NULL)
316 		ber_free_elements(req);
317 
318 	exit(0);
319 }
320 
321 void
322 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa,
323     struct ber_element *iter, char *trapoid, u_int uptime)
324 {
325 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
326 	struct ber_oid		 oid;
327 	struct ber_element	*elm;
328 	int			 n, s[2], status = 0;
329 	char			*value, *host;
330 	pid_t			 child = -1;
331 
332 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
333 		log_warn("could not create pipe for OID '%s'",
334 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
335 		return;
336 	}
337 
338 	switch (child = fork()) {
339 	case 0:
340 		dup2(s[1], STDIN_FILENO);
341 
342 		close(s[0]);
343 		close(s[1]);
344 
345 		closefrom(STDERR_FILENO + 1);
346 
347 		/* path to command is in argv[0], args follow */
348 		execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL);
349 
350 		/* this shouldn't happen */
351 		log_warn("could not exec trap command for OID '%s'",
352 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
353 		_exit(1);
354 		/* NOTREACHED */
355 
356 	case -1:
357 		log_warn("could not fork trap command for OID '%s'",
358 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
359 		close(s[0]);
360 		close(s[1]);
361 		return;
362 	}
363 
364 	close(s[1]);
365 
366 	host = traphandler_hostname(sa, 0);
367 	if (dprintf(s[0], "%s\n", host) == -1)
368 		goto out;
369 
370 	host = traphandler_hostname(sa, 1);
371 	if (dprintf(s[0], "%s\n", host) == -1)
372 		goto out;
373 
374 	if (dprintf(s[0],
375 	    "iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.0 %u\n",
376 	    uptime) == -1)
377 		goto out;
378 
379 	if (dprintf(s[0],
380 	    "iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects."
381 	    "snmpTrap.snmpTrapOID.0 %s\n", trapoid) == -1)
382 		goto out;
383 
384 	for (; iter != NULL; iter = iter->be_next) {
385 		if (ber_scanf_elements(iter, "{oe}", &oid, &elm) == -1)
386 			goto out;
387 		if ((value = smi_print_element(elm)) == NULL)
388 			goto out;
389 		smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
390 		n = dprintf(s[0], "%s %s\n", oidbuf, value);
391 		free(value);
392 		if (n == -1)
393 			goto out;
394 	}
395  out:
396 	close(s[0]);
397 	waitpid(child, &status, 0);
398 
399 	if (WIFSIGNALED(status)) {
400 		log_warnx("child %i exited due to receipt of signal %i",
401 		    child, WTERMSIG(status));
402 	} else if (WEXITSTATUS(status) != 0) {
403 		log_warnx("child %i exited with status %i",
404 		    child, WEXITSTATUS(status));
405 	} else {
406 		log_debug("child %i finished", child);
407 	}
408 		close(s[1]);
409 
410 	return;
411 }
412 
413 char *
414 traphandler_hostname(struct sockaddr *sa, int numeric)
415 {
416 	static char	 buf[NI_MAXHOST];
417 	int		 flag = 0;
418 
419 	if (numeric)
420 		flag = NI_NUMERICHOST;
421 
422 	bzero(buf, sizeof(buf));
423 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0)
424 		return ("Unknown");
425 
426 	return (buf);
427 }
428 
429 struct trapcmd *
430 trapcmd_lookup(struct ber_oid *oid)
431 {
432 	struct trapcmd	key, *res;
433 
434 	bzero(&key, sizeof(key));
435 	key.cmd_oid = oid;
436 
437 	if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL)
438 		res = key.cmd_maybe;
439 	return (res);
440 }
441 
442 int
443 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2)
444 {
445 	int ret;
446 
447 	ret = ber_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid);
448 	switch (ret) {
449 	case 2:
450 		/* cmd1 is a child of cmd2 */
451 		cmd1->cmd_maybe = cmd2;
452 		return (1);
453 	default:
454 		return (ret);
455 	}
456 	/* NOTREACHED */
457 }
458 
459 int
460 trapcmd_add(struct trapcmd *cmd)
461 {
462 	return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL);
463 }
464 
465 void
466 trapcmd_free(struct trapcmd *cmd)
467 {
468 	RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd);
469 	free(cmd->cmd_argv);
470 	free(cmd->cmd_oid);
471 	free(cmd);
472 }
473