xref: /openbsd-src/usr.sbin/snmpd/traphandler.c (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
1 /*	$OpenBSD: traphandler.c,v 1.21 2021/02/22 11:31:09 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 int	 traphandler_priv_recvmsg(struct privsep_proc *, struct imsg *);
47 int	 traphandler_fork_handler(struct privsep_proc *, struct imsg *);
48 struct ber_element *
49 	 traphandler_v1translate(struct snmp_message *, int);
50 int	 trapcmd_cmp(struct trapcmd *, struct trapcmd *);
51 void	 trapcmd_exec(struct trapcmd *, struct sockaddr *,
52 	    struct ber_element *);
53 
54 char	*traphandler_hostname(struct sockaddr *, int);
55 
56 RB_PROTOTYPE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp)
57 RB_GENERATE(trapcmd_tree, trapcmd, cmd_entry, trapcmd_cmp)
58 
59 struct trapcmd_tree trapcmd_tree = RB_INITIALIZER(&trapcmd_tree);
60 
61 /*
62  * Validate received message
63  */
64 int
65 traphandler_parse(struct snmp_message *msg)
66 {
67 	struct privsep		*ps = &snmpd_env->sc_ps;
68 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
69 	struct ber		 ber = {0};
70 	struct ber_element	*vblist = NULL, *elm;
71 	struct ber_oid		 o1, o2, snmpTrapOIDOID;
72 	struct ber_oid		 snmpTrapOID, sysUpTimeOID;
73 	int			 sysUpTime;
74 	struct iovec		 iov[2];
75 	void			*buf;
76 	ssize_t			 buflen;
77 	int			 ret = -1;
78 
79 	switch (msg->sm_pdu->be_type) {
80 	case SNMP_C_TRAP:
81 		if ((vblist = traphandler_v1translate(msg, 0)) == NULL)
82 			goto done;
83 		break;
84 	case SNMP_C_TRAPV2:
85 		if (ober_scanf_elements(msg->sm_pdu, "{SSe}$", &elm) == -1) {
86 			stats->snmp_inasnparseerrs++;
87 			goto done;
88 		}
89 		if (elm->be_type != BER_TYPE_INTEGER) {
90 			stats->snmp_inasnparseerrs++;
91 			goto done;
92 		}
93 		vblist = ober_unlink_elements(elm);
94 		break;
95 	default:
96 		fatalx("%s called without proper context", __func__);
97 	}
98 
99 	(void)ober_string2oid("1.3.6.1.2.1.1.3.0", &sysUpTimeOID);
100 	(void)ober_string2oid("1.3.6.1.6.3.1.1.4.1.0", &snmpTrapOIDOID);
101 	if (ober_scanf_elements(vblist, "{{od$}{oo$}", &o1, &sysUpTime, &o2,
102 	    &snmpTrapOID) == -1 ||
103 	    ober_oid_cmp(&o1, &sysUpTimeOID) != 0 ||
104 	    ober_oid_cmp(&o2, &snmpTrapOIDOID) != 0) {
105 		stats->snmp_inasnparseerrs++;
106 		goto done;
107 	}
108 	(void)ober_scanf_elements(vblist, "{Se", &elm);
109 	for (elm = elm->be_next; elm != NULL; elm = elm->be_next) {
110 		if (ober_scanf_elements(elm, "{oS$}", &o1) == -1) {
111 			stats->snmp_inasnparseerrs++;
112 			goto done;
113 		}
114 	}
115 
116 	ober_set_application(&ber, smi_application);
117 
118 	if ((buflen = ober_write_elements(&ber, vblist)) == -1 ||
119 	    ober_get_writebuf(&ber, &buf) == -1) {
120 		msg->sm_errstr = "failed to handle trap";
121 		goto done;
122 	}
123 
124 	iov[0].iov_base = &(msg->sm_ss);
125 	iov[0].iov_len = msg->sm_slen;
126 	iov[1].iov_base = buf;
127 	iov[1].iov_len = buflen;
128 
129 	/* Forward it to the parent process */
130 	if (proc_composev(ps, PROC_PARENT, IMSG_TRAP_EXEC, iov, 2) == -1) {
131 		msg->sm_errstr = "failed to handle trap";
132 		goto done;
133 	}
134 
135 	ret = 0;
136 done:
137 	ober_free(&ber);
138 	if (vblist != NULL)
139 		ober_free_elements(vblist);
140 	return ret;
141 }
142 
143 struct ber_element *
144 traphandler_v1translate(struct snmp_message *msg, int proxy)
145 {
146 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
147 	struct ber_oid trapoid, enterprise, oid, snmpTrapAddressOid;
148 	struct ber_oid snmpTrapCommunityOid, snmpTrapEnterpriseOid;
149 	struct ber_element *elm, *last, *vblist, *vb0 = NULL;
150 	void *agent_addr;
151 	size_t agent_addrlen;
152 	int generic_trap, specific_trap, time_stamp;
153 	int hasaddress = 0, hascommunity = 0, hasenterprise = 0;
154 
155 	if (ober_scanf_elements(msg->sm_pdu, "{oxdddeS$}$", &enterprise,
156 	    &agent_addr, &agent_addrlen, &generic_trap, &specific_trap,
157 	    &time_stamp, &vblist) == -1 ||
158 	    agent_addrlen != 4 ||
159 	    vblist->be_type != BER_TYPE_SEQUENCE) {
160 		stats->snmp_inasnparseerrs++;
161 		return NULL;
162 	}
163 	switch (generic_trap) {
164 	case 0:
165 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.1", &trapoid);
166 		break;
167 	case 1:
168 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.2", &trapoid);
169 		break;
170 	case 2:
171 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.3", &trapoid);
172 		break;
173 	case 3:
174 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.4", &trapoid);
175 		break;
176 	case 4:
177 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.5", &trapoid);
178 		break;
179 	case 5:
180 		(void)ober_string2oid("1.3.6.1.6.3.1.1.5.6", &trapoid);
181 		break;
182 	case 6:
183 		trapoid = enterprise;
184 		/* Officially this should be 128, but BER_MAX_OID_LEN is 64 */
185 		if (trapoid.bo_n + 2 > BER_MAX_OID_LEN) {
186 			stats->snmp_inasnparseerrs++;
187 			return NULL;
188 		}
189 		trapoid.bo_id[trapoid.bo_n++] = 0;
190 		trapoid.bo_id[trapoid.bo_n++] = specific_trap;
191 		break;
192 	default:
193 		stats->snmp_inasnparseerrs++;
194 		return NULL;
195 	}
196 
197 	/* work aronud net-snmp's snmptrap: It adds an EOC element in vblist */
198 	if (vblist->be_len != 0)
199 		vb0 = ober_unlink_elements(vblist);
200 
201 	if ((vblist = ober_add_sequence(NULL)) == NULL) {
202 		msg->sm_errstr = strerror(errno);
203 		if (vb0 != NULL)
204 			ober_free_elements(vb0);
205 		return NULL;
206 	}
207 	if (ober_printf_elements(vblist, "{od}{oO}e", "1.3.6.1.2.1.1.3.0",
208 	    time_stamp, "1.3.6.1.6.3.1.1.4.1.0", &trapoid, vb0) == NULL) {
209 		msg->sm_errstr = strerror(errno);
210 		if (vb0 != 0)
211 			ober_free_elements(vb0);
212 		ober_free_elements(vblist);
213 		return NULL;
214 	}
215 
216 	if (proxy) {
217 		(void)ober_string2oid("1.3.6.1.6.3.18.1.3.0",
218 		    &snmpTrapAddressOid);
219 		(void)ober_string2oid("1.3.6.1.6.3.18.1.4.0",
220 		    &snmpTrapCommunityOid);
221 		(void)ober_string2oid("1.3.6.1.6.3.1.1.4.3.0",
222 		    &snmpTrapEnterpriseOid);
223 		for (elm = vblist->be_sub; elm != NULL; elm = elm->be_next) {
224 			if (ober_get_oid(elm->be_sub, &oid) == -1) {
225 				msg->sm_errstr = "failed to read oid";
226 				ober_free_elements(vblist);
227 				return NULL;
228 			}
229 			if (ober_oid_cmp(&oid, &snmpTrapAddressOid) == 0)
230 				hasaddress = 1;
231 			else if (ober_oid_cmp(&oid, &snmpTrapCommunityOid) == 0)
232 				hascommunity = 1;
233 			else if (ober_oid_cmp(&oid,
234 			    &snmpTrapEnterpriseOid) == 0)
235 				hasenterprise = 1;
236 			last = elm;
237 		}
238 		if (!hasaddress || !hascommunity || !hasenterprise) {
239 			if (ober_printf_elements(last, "{Oxt}{Os}{OO}",
240 			    &snmpTrapAddressOid, agent_addr, 4,
241 			    BER_CLASS_APPLICATION, SNMP_T_IPADDR,
242 			    &snmpTrapCommunityOid, msg->sm_community,
243 			    &snmpTrapEnterpriseOid, &enterprise) == NULL) {
244 				msg->sm_errstr = strerror(errno);
245 				ober_free_elements(vblist);
246 				return NULL;
247 			}
248 		}
249 	}
250 	return vblist;
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 	struct privsep		*ps = p->p_ps;
280 	struct snmpd		*env = ps->ps_env;
281 	struct ber		 ber = {0};
282 	struct sockaddr		*sa;
283 	char			*buf;
284 	ssize_t			 n;
285 	struct ber_element	*vblist;
286 	struct ber_oid		 trapoid;
287 	struct trapcmd		*cmd;
288 	struct passwd		*pw;
289 	int			 verbose;
290 
291 	pw = ps->ps_pw;
292 	verbose = log_getverbose();
293 
294 	if (setgroups(1, &pw->pw_gid) ||
295 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
296 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
297 		fatal("traphandler_fork_handler: cannot drop privileges");
298 
299 	closefrom(STDERR_FILENO + 1);
300 
301 	log_init((env->sc_flags & SNMPD_F_DEBUG) ? 1 : 0, LOG_DAEMON);
302 	log_setverbose(verbose);
303 	log_procinit(p->p_title);
304 
305 	n = IMSG_DATA_SIZE(imsg);
306 
307 	sa = imsg->data;
308 	n -= sa->sa_len;
309 	buf = (char *)imsg->data + sa->sa_len;
310 
311 	ober_set_application(&ber, smi_application);
312 	ober_set_readbuf(&ber, buf, n);
313 
314 	if ((vblist = ober_read_elements(&ber, NULL)) == NULL)
315 		fatalx("couldn't parse SNMP trap message");
316 	ober_free(&ber);
317 
318 	(void)ober_scanf_elements(vblist, "{S{So", &trapoid);
319 	if ((cmd = trapcmd_lookup(&trapoid)) != NULL)
320 		trapcmd_exec(cmd, sa, vblist->be_sub);
321 
322 	ober_free_elements(vblist);
323 
324 	exit(0);
325 }
326 
327 void
328 trapcmd_exec(struct trapcmd *cmd, struct sockaddr *sa,
329     struct ber_element *vb)
330 {
331 	char			 oidbuf[SNMP_MAX_OID_STRLEN];
332 	struct ber_oid		 oid;
333 	struct ber_element	*elm;
334 	int			 n, s[2], status = 0;
335 	char			*value, *host;
336 	pid_t			 child = -1;
337 
338 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
339 		log_warn("could not create pipe for OID '%s'",
340 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
341 		return;
342 	}
343 
344 	switch (child = fork()) {
345 	case 0:
346 		dup2(s[1], STDIN_FILENO);
347 
348 		close(s[0]);
349 		close(s[1]);
350 
351 		closefrom(STDERR_FILENO + 1);
352 
353 		/* path to command is in argv[0], args follow */
354 		execve(cmd->cmd_argv[0], cmd->cmd_argv, NULL);
355 
356 		/* this shouldn't happen */
357 		log_warn("could not exec trap command for OID '%s'",
358 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
359 		_exit(1);
360 		/* NOTREACHED */
361 
362 	case -1:
363 		log_warn("could not fork trap command for OID '%s'",
364 		    smi_oid2string(cmd->cmd_oid, oidbuf, sizeof(oidbuf), 0));
365 		close(s[0]);
366 		close(s[1]);
367 		return;
368 	}
369 
370 	close(s[1]);
371 
372 	host = traphandler_hostname(sa, 0);
373 	if (dprintf(s[0], "%s\n", host) == -1)
374 		goto out;
375 
376 	host = traphandler_hostname(sa, 1);
377 	if (dprintf(s[0], "%s\n", host) == -1)
378 		goto out;
379 
380 	for (; vb != NULL; vb = vb->be_next) {
381 		if (ober_scanf_elements(vb, "{oeS$}", &oid, &elm) == -1)
382 			goto out;
383 		if ((value = smi_print_element(elm)) == NULL)
384 			goto out;
385 		smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
386 		n = dprintf(s[0], "%s %s\n", oidbuf, value);
387 		free(value);
388 		if (n == -1)
389 			goto out;
390 	}
391  out:
392 	close(s[0]);
393 	waitpid(child, &status, 0);
394 
395 	if (WIFSIGNALED(status)) {
396 		log_warnx("child %i exited due to receipt of signal %i",
397 		    child, WTERMSIG(status));
398 	} else if (WEXITSTATUS(status) != 0) {
399 		log_warnx("child %i exited with status %i",
400 		    child, WEXITSTATUS(status));
401 	} else {
402 		log_debug("child %i finished", child);
403 	}
404 	close(s[1]);
405 
406 	return;
407 }
408 
409 char *
410 traphandler_hostname(struct sockaddr *sa, int numeric)
411 {
412 	static char	 buf[NI_MAXHOST];
413 	int		 flag = 0;
414 
415 	if (numeric)
416 		flag = NI_NUMERICHOST;
417 
418 	bzero(buf, sizeof(buf));
419 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, flag) != 0)
420 		return ("Unknown");
421 
422 	return (buf);
423 }
424 
425 struct trapcmd *
426 trapcmd_lookup(struct ber_oid *oid)
427 {
428 	struct trapcmd	key, *res;
429 
430 	bzero(&key, sizeof(key));
431 	key.cmd_oid = oid;
432 
433 	if ((res = RB_FIND(trapcmd_tree, &trapcmd_tree, &key)) == NULL)
434 		res = key.cmd_maybe;
435 	return (res);
436 }
437 
438 int
439 trapcmd_cmp(struct trapcmd *cmd1, struct trapcmd *cmd2)
440 {
441 	int ret;
442 
443 	ret = ober_oid_cmp(cmd2->cmd_oid, cmd1->cmd_oid);
444 	switch (ret) {
445 	case 2:
446 		/* cmd1 is a child of cmd2 */
447 		cmd1->cmd_maybe = cmd2;
448 		return (1);
449 	default:
450 		return (ret);
451 	}
452 	/* NOTREACHED */
453 }
454 
455 int
456 trapcmd_add(struct trapcmd *cmd)
457 {
458 	return (RB_INSERT(trapcmd_tree, &trapcmd_tree, cmd) != NULL);
459 }
460 
461 void
462 trapcmd_free(struct trapcmd *cmd)
463 {
464 	RB_REMOVE(trapcmd_tree, &trapcmd_tree, cmd);
465 	free(cmd->cmd_argv);
466 	free(cmd->cmd_oid);
467 	free(cmd);
468 }
469