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