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