xref: /openbsd-src/usr.sbin/snmpd/snmpe.c (revision c1a45aed656e7d5627c30c92421893a76f370ccb)
1 /*	$OpenBSD: snmpe.c,v 1.82 2022/01/19 11:00:56 martijn Exp $	*/
2 
3 /*
4  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
5  * Copyright (c) 2017 Marco Pfatschbacher <mpf@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/queue.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25 #include <sys/tree.h>
26 
27 #include <net/if.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <errno.h>
34 #include <event.h>
35 #include <fcntl.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <pwd.h>
39 
40 #include "application.h"
41 #include "snmpd.h"
42 #include "snmpe.h"
43 #include "mib.h"
44 
45 void	 snmpe_init(struct privsep *, struct privsep_proc *, void *);
46 int	 snmpe_parse(struct snmp_message *);
47 void	 snmpe_tryparse(int, struct snmp_message *);
48 int	 snmpe_parsevarbinds(struct snmp_message *);
49 void	 snmpe_sig_handler(int sig, short, void *);
50 int	 snmpe_bind(struct address *);
51 void	 snmpe_recvmsg(int fd, short, void *);
52 void	 snmpe_readcb(int fd, short, void *);
53 void	 snmpe_writecb(int fd, short, void *);
54 void	 snmpe_acceptcb(int fd, short, void *);
55 void	 snmpe_prepare_read(struct snmp_message *, int);
56 int	 snmpe_encode(struct snmp_message *);
57 
58 struct imsgev	*iev_parent;
59 static const struct timeval	snmpe_tcp_timeout = { 10, 0 }; /* 10s */
60 
61 struct snmp_messages snmp_messages = RB_INITIALIZER(&snmp_messages);
62 
63 static struct privsep_proc procs[] = {
64 	{ "parent",	PROC_PARENT }
65 };
66 
67 void
68 snmpe(struct privsep *ps, struct privsep_proc *p)
69 {
70 	struct snmpd		*env = ps->ps_env;
71 	struct address		*h;
72 #ifdef DEBUG
73 	char		 buf[BUFSIZ];
74 	struct oid	*oid;
75 #endif
76 
77 #ifdef DEBUG
78 	for (oid = NULL; (oid = smi_foreach(oid, 0)) != NULL;) {
79 		smi_oid2string(&oid->o_id, buf, sizeof(buf), 0);
80 		log_debug("oid %s", buf);
81 	}
82 #endif
83 
84 	/* bind SNMP UDP/TCP sockets */
85 	TAILQ_FOREACH(h, &env->sc_addresses, entry)
86 		if ((h->fd = snmpe_bind(h)) == -1)
87 			fatal("snmpe: failed to bind SNMP socket");
88 
89 	proc_run(ps, p, procs, nitems(procs), snmpe_init, NULL);
90 }
91 
92 /* ARGSUSED */
93 void
94 snmpe_init(struct privsep *ps, struct privsep_proc *p, void *arg)
95 {
96 	struct snmpd		*env = ps->ps_env;
97 	struct address		*h;
98 
99 	kr_init();
100 	timer_init();
101 	usm_generate_keys();
102 	appl_init();
103 
104 	/* listen for incoming SNMP UDP/TCP messages */
105 	TAILQ_FOREACH(h, &env->sc_addresses, entry) {
106 		if (h->type == SOCK_STREAM) {
107 			if (listen(h->fd, 5) < 0)
108 				fatalx("snmpe: failed to listen on socket");
109 			event_set(&h->ev, h->fd, EV_READ, snmpe_acceptcb, h);
110 			evtimer_set(&h->evt, snmpe_acceptcb, h);
111 		} else {
112 			event_set(&h->ev, h->fd, EV_READ|EV_PERSIST,
113 			    snmpe_recvmsg, h);
114 		}
115 		event_add(&h->ev, NULL);
116 	}
117 
118 	/* no filesystem visibility */
119 	if (unveil("/", "") == -1)
120 		fatal("unveil /");
121 	if (unveil(NULL, NULL) == -1)
122 		fatal("unveil");
123 
124 	log_info("snmpe %s: ready",
125 	    tohexstr(env->sc_engineid, env->sc_engineid_len));
126 	trap_init();
127 }
128 
129 void
130 snmpe_shutdown(void)
131 {
132 	struct address *h;
133 
134 	TAILQ_FOREACH(h, &snmpd_env->sc_addresses, entry) {
135 		event_del(&h->ev);
136 		if (h->type == SOCK_STREAM)
137 			event_del(&h->evt);
138 		close(h->fd);
139 	}
140 	kr_shutdown();
141 	appl_shutdown();
142 }
143 
144 int
145 snmpe_bind(struct address *addr)
146 {
147 	char	 buf[512];
148 	int	 val, s;
149 
150 	if ((s = snmpd_socket_af(&addr->ss, addr->type)) == -1)
151 		return (-1);
152 
153 	/*
154 	 * Socket options
155 	 */
156 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
157 		goto bad;
158 
159 	if (addr->type == SOCK_STREAM) {
160 		val = 1;
161 		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
162 		    &val, sizeof(val)) == -1)
163 			fatal("setsockopt SO_REUSEADDR");
164 	} else { /* UDP */
165 		switch (addr->ss.ss_family) {
166 		case AF_INET:
167 			val = 1;
168 			if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR,
169 			    &val, sizeof(int)) == -1) {
170 				log_warn("%s: failed to set IPv4 packet info",
171 				    __func__);
172 				goto bad;
173 			}
174 			break;
175 		case AF_INET6:
176 			val = 1;
177 			if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO,
178 			    &val, sizeof(int)) == -1) {
179 				log_warn("%s: failed to set IPv6 packet info",
180 				    __func__);
181 				goto bad;
182 			}
183 		}
184 	}
185 
186 	if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1)
187 		goto bad;
188 
189 	if (print_host(&addr->ss, buf, sizeof(buf)) == NULL)
190 		goto bad;
191 
192 	log_info("snmpe: listening on %s %s:%d",
193 	    (addr->type == SOCK_STREAM) ? "tcp" : "udp", buf, addr->port);
194 
195 	return (s);
196 
197  bad:
198 	close(s);
199 	return (-1);
200 }
201 
202 const char *
203 snmpe_pdutype2string(enum snmp_pdutype pdutype)
204 {
205 	static char unknown[sizeof("Unknown (4294967295)")];
206 
207 	switch (pdutype) {
208 	case SNMP_C_GETREQ:
209 		return "GetRequest";
210 	case SNMP_C_GETNEXTREQ:
211 		return "GetNextRequest";
212 	case SNMP_C_RESPONSE:
213 		return "Response";
214 	case SNMP_C_SETREQ:
215 		return "SetRequest";
216 	case SNMP_C_TRAP:
217 		return "Trap";
218 	case SNMP_C_GETBULKREQ:
219 		return "GetBulkRequest";
220 	case SNMP_C_INFORMREQ:
221 		return "InformRequest";
222 	case SNMP_C_TRAPV2:
223 		return "SNMPv2-Trap";
224 	case SNMP_C_REPORT:
225 		return "Report";
226 	}
227 
228 	snprintf(unknown, sizeof(unknown), "Unknown (%u)", pdutype);
229 	return unknown;
230 }
231 
232 int
233 snmpe_parse(struct snmp_message *msg)
234 {
235 	struct snmpd		*env = snmpd_env;
236 	struct snmp_stats	*stats = &env->sc_stats;
237 	struct ber_element	*a;
238 	long long		 ver, req;
239 	long long		 errval, erridx;
240 	u_int			 class;
241 	char			*comn;
242 	char			*flagstr, *ctxname, *engineid;
243 	size_t			 len;
244 	struct sockaddr_storage *ss = &msg->sm_ss;
245 	struct ber_element	*root = msg->sm_req;
246 
247 	msg->sm_errstr = "invalid message";
248 
249 	do {
250 		msg->sm_transactionid = arc4random();
251 	} while (msg->sm_transactionid == 0 ||
252 	    RB_INSERT(snmp_messages, &snmp_messages, msg) != NULL);
253 
254 	if (ober_scanf_elements(root, "{ie", &ver, &a) != 0)
255 		goto parsefail;
256 
257 	/* SNMP version and community */
258 	msg->sm_version = ver;
259 	switch (msg->sm_version) {
260 	case SNMP_V1:
261 		if (!(msg->sm_aflags & ADDRESS_FLAG_SNMPV1)) {
262 			msg->sm_errstr = "SNMPv1 disabled";
263 			goto badversion;
264 		}
265 	case SNMP_V2:
266 		if (msg->sm_version == SNMP_V2 &&
267 		    !(msg->sm_aflags & ADDRESS_FLAG_SNMPV2)) {
268 			msg->sm_errstr = "SNMPv2c disabled";
269 			goto badversion;
270 		}
271 		if (ober_scanf_elements(a, "seS$", &comn, &msg->sm_pdu) != 0)
272 			goto parsefail;
273 		if (strlcpy(msg->sm_community, comn,
274 		    sizeof(msg->sm_community)) >= sizeof(msg->sm_community) ||
275 		    msg->sm_community[0] == '\0') {
276 			stats->snmp_inbadcommunitynames++;
277 			msg->sm_errstr = "invalid community name";
278 			goto fail;
279 		}
280 		break;
281 	case SNMP_V3:
282 		if (!(msg->sm_aflags & ADDRESS_FLAG_SNMPV3)) {
283 			msg->sm_errstr = "SNMPv3 disabled";
284 			goto badversion;
285 		}
286 		if (ober_scanf_elements(a, "{iisi$}e",
287 		    &msg->sm_msgid, &msg->sm_max_msg_size, &flagstr,
288 		    &msg->sm_secmodel, &a) != 0)
289 			goto parsefail;
290 
291 		msg->sm_flags = *flagstr;
292 		if ((a = usm_decode(msg, a, &msg->sm_errstr)) == NULL)
293 			goto parsefail;
294 
295 		if (MSG_SECLEVEL(msg) < env->sc_min_seclevel ||
296 		    msg->sm_secmodel != SNMP_SEC_USM) {
297 			/* XXX currently only USM supported */
298 			msg->sm_errstr = "unsupported security model";
299 			stats->snmp_usmbadseclevel++;
300 			msg->sm_usmerr = OIDVAL_usmErrSecLevel;
301 			goto parsefail;
302 		}
303 
304 		if (ober_scanf_elements(a, "{xxeS$}$",
305 		    &engineid, &msg->sm_ctxengineid_len, &ctxname, &len,
306 		    &msg->sm_pdu) != 0)
307 			goto parsefail;
308 		if (msg->sm_ctxengineid_len > sizeof(msg->sm_ctxengineid))
309 			goto parsefail;
310 		memcpy(msg->sm_ctxengineid, engineid, msg->sm_ctxengineid_len);
311 		if (len > SNMPD_MAXCONTEXNAMELEN)
312 			goto parsefail;
313 		memcpy(msg->sm_ctxname, ctxname, len);
314 		msg->sm_ctxname[len] = '\0';
315 		break;
316 	default:
317 		msg->sm_errstr = "unsupported snmp version";
318 badversion:
319 		stats->snmp_inbadversions++;
320 		goto fail;
321 	}
322 
323 	if (ober_scanf_elements(msg->sm_pdu, "t{e", &class, &(msg->sm_pdutype),
324 	    &a) != 0)
325 		goto parsefail;
326 
327 	/* SNMP PDU context */
328 	if (class != BER_CLASS_CONTEXT)
329 		goto parsefail;
330 
331 	switch (msg->sm_pdutype) {
332 	case SNMP_C_GETBULKREQ:
333 		if (msg->sm_version == SNMP_V1) {
334 			stats->snmp_inbadversions++;
335 			msg->sm_errstr =
336 			    "invalid request for protocol version 1";
337 			goto fail;
338 		}
339 		/* FALLTHROUGH */
340 
341 	case SNMP_C_GETREQ:
342 		stats->snmp_ingetrequests++;
343 		/* FALLTHROUGH */
344 
345 	case SNMP_C_GETNEXTREQ:
346 		if (msg->sm_pdutype == SNMP_C_GETNEXTREQ)
347 			stats->snmp_ingetnexts++;
348 		if (!(msg->sm_aflags & ADDRESS_FLAG_READ)) {
349 			msg->sm_errstr = "read requests disabled";
350 			goto fail;
351 		}
352 		if (msg->sm_version != SNMP_V3 &&
353 		    strcmp(env->sc_rdcommunity, msg->sm_community) != 0 &&
354 		    strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
355 			stats->snmp_inbadcommunitynames++;
356 			msg->sm_errstr = "wrong read community";
357 			goto fail;
358 		}
359 		break;
360 
361 	case SNMP_C_SETREQ:
362 		stats->snmp_insetrequests++;
363 		if (!(msg->sm_aflags & ADDRESS_FLAG_WRITE)) {
364 			msg->sm_errstr = "write requests disabled";
365 			goto fail;
366 		}
367 		if (msg->sm_version != SNMP_V3 &&
368 		    strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
369 			if (strcmp(env->sc_rdcommunity, msg->sm_community) != 0)
370 				stats->snmp_inbadcommunitynames++;
371 			else
372 				stats->snmp_inbadcommunityuses++;
373 			msg->sm_errstr = "wrong write community";
374 			goto fail;
375 		}
376 		break;
377 
378 	case SNMP_C_RESPONSE:
379 		stats->snmp_ingetresponses++;
380 		msg->sm_errstr = "response without request";
381 		goto parsefail;
382 
383 	case SNMP_C_TRAP:
384 		if (msg->sm_version != SNMP_V1) {
385 			msg->sm_errstr = "trapv1 request on !SNMPv1 message";
386 			goto parsefail;
387 		}
388 	case SNMP_C_TRAPV2:
389 		if (msg->sm_pdutype == SNMP_C_TRAPV2 &&
390 		    !(msg->sm_version == SNMP_V2 ||
391 		    msg->sm_version == SNMP_V3)) {
392 			msg->sm_errstr = "trapv2 request on !SNMPv2C or "
393 			    "!SNMPv3 message";
394 			goto parsefail;
395 		}
396 		if (!(msg->sm_aflags & ADDRESS_FLAG_NOTIFY)) {
397 			msg->sm_errstr = "notify requests disabled";
398 			goto fail;
399 		}
400 		if (msg->sm_version == SNMP_V3) {
401 			msg->sm_errstr = "SNMPv3 doesn't support traps yet";
402 			goto fail;
403 		}
404 		if (strcmp(env->sc_trcommunity, msg->sm_community) != 0) {
405 			stats->snmp_inbadcommunitynames++;
406 			msg->sm_errstr = "wrong trap community";
407 			goto fail;
408 		}
409 		stats->snmp_intraps++;
410 		/*
411 		 * This should probably go into parsevarbinds, but that's for a
412 		 * next refactor
413 		 */
414 		if (traphandler_parse(msg) == -1)
415 			goto fail;
416 		/* Shortcircuit */
417 		return 0;
418 	default:
419 		msg->sm_errstr = "invalid context";
420 		goto parsefail;
421 	}
422 
423 	/* SNMP PDU */
424 	if (ober_scanf_elements(a, "iiie{e{}}$",
425 	    &req, &errval, &erridx, &msg->sm_pduend,
426 	    &msg->sm_varbind) != 0) {
427 		stats->snmp_silentdrops++;
428 		msg->sm_errstr = "invalid PDU";
429 		goto fail;
430 	}
431 
432 	for (a = msg->sm_varbind; a != NULL; a = a->be_next) {
433 		if (ober_scanf_elements(a, "{oS$}", NULL) == -1)
434 			goto parsefail;
435 	}
436 
437 	msg->sm_request = req;
438 	msg->sm_error = errval;
439 	msg->sm_errorindex = erridx;
440 
441 	print_host(ss, msg->sm_host, sizeof(msg->sm_host));
442 	if (msg->sm_version == SNMP_V3)
443 		log_debug("%s: %s:%hd: SNMPv3 pdutype %s, flags %#x, "
444 		    "secmodel %lld, user '%s', ctx-engine %s, ctx-name '%s', "
445 		    "request %lld", __func__, msg->sm_host, msg->sm_port,
446 		    snmpe_pdutype2string(msg->sm_pdutype), msg->sm_flags,
447 		    msg->sm_secmodel, msg->sm_username,
448 		    tohexstr(msg->sm_ctxengineid, msg->sm_ctxengineid_len),
449 		    msg->sm_ctxname, msg->sm_request);
450 	else
451 		log_debug("%s: %s:%hd: SNMPv%d '%s' pdutype %s request %lld",
452 		    __func__, msg->sm_host, msg->sm_port, msg->sm_version + 1,
453 		    msg->sm_community, snmpe_pdutype2string(msg->sm_pdutype),
454 		    msg->sm_request);
455 
456 	return (0);
457 
458  parsefail:
459 	stats->snmp_inasnparseerrs++;
460  fail:
461 	print_host(ss, msg->sm_host, sizeof(msg->sm_host));
462 	log_debug("%s: %s:%hd: %s", __func__, msg->sm_host, msg->sm_port,
463 	    msg->sm_errstr);
464 	return (-1);
465 }
466 
467 int
468 snmpe_parsevarbinds(struct snmp_message *msg)
469 {
470 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
471 	struct ber_element	*varbind, *value, *rvarbind = NULL;
472 	struct ber_element	*pvarbind = NULL, *end;
473 	char			 buf[BUFSIZ];
474 	struct ber_oid		 o;
475 	int			 i;
476 
477 	appl_processpdu(msg, msg->sm_ctxname, msg->sm_version, msg->sm_pdu);
478 	return 0;
479 	/*
480 	 * Leave code here for now so it's easier to switch back in case of
481 	 * issues.
482 	 */
483 
484 	msg->sm_errstr = "invalid varbind element";
485 
486 	varbind = msg->sm_varbind;
487 	msg->sm_varbindresp = NULL;
488 	end = NULL;
489 
490 	for (i = 1; varbind != NULL && i < SNMPD_MAXVARBIND;
491 	    varbind = varbind->be_next, i++) {
492 		if (ober_scanf_elements(varbind, "{oeS$}", &o, &value) == -1) {
493 			stats->snmp_inasnparseerrs++;
494 			msg->sm_errstr = "invalid varbind";
495 			goto varfail;
496 		}
497 		if (o.bo_n < BER_MIN_OID_LEN || o.bo_n > BER_MAX_OID_LEN)
498 			goto varfail;
499 
500 		log_debug("%s: %s:%hd: oid %s", __func__, msg->sm_host,
501 		    msg->sm_port, smi_oid2string(&o, buf, sizeof(buf), 0));
502 
503 		/*
504 		 * XXX intotalreqvars should only be incremented after all are
505 		 * succeeded
506 		 */
507 		switch (msg->sm_pdutype) {
508 		case SNMP_C_GETNEXTREQ:
509 			if ((rvarbind = ober_add_sequence(NULL)) == NULL)
510 				goto varfail;
511 			if (mps_getnextreq(msg, rvarbind, &o) != 0) {
512 				msg->sm_error = SNMP_ERROR_NOSUCHNAME;
513 				ober_free_elements(rvarbind);
514 				goto varfail;
515 			}
516 			stats->snmp_intotalreqvars++;
517 			break;
518 		case SNMP_C_GETREQ:
519 			if ((rvarbind = ober_add_sequence(NULL)) == NULL)
520 				goto varfail;
521 			if (mps_getreq(msg, rvarbind, &o,
522 			    msg->sm_version) != 0) {
523 				msg->sm_error = SNMP_ERROR_NOSUCHNAME;
524 				ober_free_elements(rvarbind);
525 				goto varfail;
526 			}
527 			stats->snmp_intotalreqvars++;
528 			break;
529 		case SNMP_C_SETREQ:
530 			/*
531 			 * XXX A set varbind should only be committed if
532 			 * all variables are staged
533 			 */
534 			if (mps_setreq(msg, value, &o) == 0) {
535 				stats->snmp_intotalsetvars++;
536 				break;
537 			}
538 			msg->sm_error = SNMP_ERROR_READONLY;
539 			goto varfail;
540 		case SNMP_C_GETBULKREQ:
541 			rvarbind = NULL;
542 			if (mps_getbulkreq(msg, &rvarbind, &end, &o,
543 			    (i <= msg->sm_nonrepeaters)
544 			    ? 1 : msg->sm_maxrepetitions) != 0) {
545 				msg->sm_error = SNMP_ERROR_NOSUCHNAME;
546 				goto varfail;
547 			}
548 			/*
549 			 * XXX This should be the amount of returned
550 			 * vars
551 			 */
552 			stats->snmp_intotalreqvars++;
553 			break;
554 
555 		default:
556 			goto varfail;
557 		}
558 		if (rvarbind == NULL)
559 			break;
560 		if (pvarbind == NULL)
561 			msg->sm_varbindresp = rvarbind;
562 		else
563 			ober_link_elements(pvarbind, rvarbind);
564 		pvarbind = end == NULL ? rvarbind : end;
565 	}
566 
567 	msg->sm_errstr = "none";
568 	msg->sm_error = 0;
569 	msg->sm_errorindex = 0;
570 
571 	return 0;
572  varfail:
573 	log_debug("%s: %s:%hd: %s, error index %d", __func__,
574 	    msg->sm_host, msg->sm_port, msg->sm_errstr, i);
575 	if (msg->sm_error == 0)
576 		msg->sm_error = SNMP_ERROR_GENERR;
577 	msg->sm_errorindex = i;
578 	return -1;
579 }
580 
581 void
582 snmpe_acceptcb(int fd, short type, void *arg)
583 {
584 	struct address		*h = arg;
585 	struct sockaddr_storage	 ss;
586 	socklen_t		 len = sizeof(ss);
587 	struct snmp_message	*msg;
588 	int afd;
589 
590 	event_add(&h->ev, NULL);
591 	if ((type & EV_TIMEOUT))
592 		return;
593 
594 	if ((afd = accept4(fd, (struct sockaddr *)&ss, &len,
595 	    SOCK_NONBLOCK|SOCK_CLOEXEC)) < 0) {
596 		/* Pause accept if we are out of file descriptors  */
597 		if (errno == ENFILE || errno == EMFILE) {
598 			struct timeval evtpause = { 1, 0 };
599 
600 			event_del(&h->ev);
601 			evtimer_add(&h->evt, &evtpause);
602 		} else if (errno != EAGAIN && errno != EINTR)
603 			log_debug("%s: accept4", __func__);
604 		return;
605 	}
606 	if ((msg = calloc(1, sizeof(*msg))) == NULL)
607 		goto fail;
608 
609 	memcpy(&(msg->sm_ss), &ss, len);
610 	msg->sm_slen = len;
611 	msg->sm_aflags = h->flags;
612 	msg->sm_port = h->port;
613 	snmpe_prepare_read(msg, afd);
614 	return;
615 fail:
616 	free(msg);
617 	close(afd);
618 	return;
619 }
620 
621 void
622 snmpe_prepare_read(struct snmp_message *msg, int fd)
623 {
624 	msg->sm_sock = fd;
625 	msg->sm_sock_tcp = 1;
626 	event_del(&msg->sm_sockev);
627 	event_set(&msg->sm_sockev, fd, EV_READ,
628 	    snmpe_readcb, msg);
629 	event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
630 }
631 
632 void
633 snmpe_tryparse(int fd, struct snmp_message *msg)
634 {
635 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
636 
637 	ober_set_application(&msg->sm_ber, smi_application);
638 	ober_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
639 	msg->sm_req = ober_read_elements(&msg->sm_ber, NULL);
640 	if (msg->sm_req == NULL) {
641 		if (errno == ECANCELED) {
642 			/* short read; try again */
643 			snmpe_prepare_read(msg, fd);
644 			return;
645 		}
646 		goto fail;
647 	}
648 
649 	if (snmpe_parse(msg) == -1) {
650 		if (msg->sm_usmerr && MSG_REPORT(msg)) {
651 			usm_make_report(msg);
652 			snmpe_response(msg);
653 			return;
654 		} else
655 			goto fail;
656 	}
657 	stats->snmp_inpkts++;
658 
659 	snmpe_dispatchmsg(msg);
660 	return;
661  fail:
662 	snmp_msgfree(msg);
663 	close(fd);
664 }
665 
666 void
667 snmpe_readcb(int fd, short type, void *arg)
668 {
669 	struct snmp_message *msg = arg;
670 	ssize_t len;
671 
672 	if (type == EV_TIMEOUT || msg->sm_datalen >= sizeof(msg->sm_data))
673 		goto fail;
674 
675 	len = read(fd, msg->sm_data + msg->sm_datalen,
676 	    sizeof(msg->sm_data) - msg->sm_datalen);
677 	if (len <= 0) {
678 		if (errno != EAGAIN && errno != EINTR)
679 			goto fail;
680 		snmpe_prepare_read(msg, fd);
681 		return;
682 	}
683 
684 	msg->sm_datalen += (size_t)len;
685 	snmpe_tryparse(fd, msg);
686 	return;
687 
688  fail:
689 	snmp_msgfree(msg);
690 	close(fd);
691 }
692 
693 void
694 snmpe_writecb(int fd, short type, void *arg)
695 {
696 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
697 	struct snmp_message	*msg = arg;
698 	struct snmp_message	*nmsg;
699 	ssize_t			 len;
700 	size_t			 reqlen;
701 	struct ber		*ber = &msg->sm_ber;
702 
703 	if (type == EV_TIMEOUT)
704 		goto fail;
705 
706 	len = ber->br_wend - ber->br_wbuf;
707 	ber->br_wptr = ber->br_wbuf;
708 
709 	log_debug("%s: write fd %d len %zd", __func__, fd, len);
710 
711 	len = write(fd, ber->br_wptr, len);
712 	if (len == -1) {
713 		if (errno == EAGAIN || errno == EINTR)
714 			return;
715 		else
716 			goto fail;
717 	}
718 
719 	ber->br_wptr += len;
720 
721 	if (ber->br_wptr < ber->br_wend) {
722 		event_del(&msg->sm_sockev);
723 		event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
724 		    snmpe_writecb, msg);
725 		event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
726 		return;
727 	}
728 
729 	stats->snmp_outpkts++;
730 
731 	if ((nmsg = calloc(1, sizeof(*nmsg))) == NULL)
732 		goto fail;
733 	memcpy(&(nmsg->sm_ss), &(msg->sm_ss), msg->sm_slen);
734 	nmsg->sm_slen = msg->sm_slen;
735 	nmsg->sm_aflags = msg->sm_aflags;
736 	nmsg->sm_port = msg->sm_port;
737 
738 	/*
739 	 * Reuse the connection.
740 	 * In case we already read data of the next message, copy it over.
741 	 */
742 	reqlen = ober_calc_len(msg->sm_req);
743 	if (msg->sm_datalen > reqlen) {
744 		memcpy(nmsg->sm_data, msg->sm_data + reqlen,
745 		    msg->sm_datalen - reqlen);
746 		nmsg->sm_datalen = msg->sm_datalen - reqlen;
747 		snmp_msgfree(msg);
748 		snmpe_prepare_read(nmsg, fd);
749 		snmpe_tryparse(fd, nmsg);
750 	} else {
751 		snmp_msgfree(msg);
752 		snmpe_prepare_read(nmsg, fd);
753 	}
754 	return;
755 
756  fail:
757 	close(fd);
758 	snmp_msgfree(msg);
759 }
760 
761 void
762 snmpe_recvmsg(int fd, short sig, void *arg)
763 {
764 	struct address		*h = arg;
765 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
766 	ssize_t			 len;
767 	struct snmp_message	*msg;
768 
769 	if ((msg = calloc(1, sizeof(*msg))) == NULL)
770 		return;
771 
772 	msg->sm_aflags = h->flags;
773 	msg->sm_sock = fd;
774 	msg->sm_slen = sizeof(msg->sm_ss);
775 	msg->sm_port = h->port;
776 	if ((len = recvfromto(fd, msg->sm_data, sizeof(msg->sm_data), 0,
777 	    (struct sockaddr *)&msg->sm_ss, &msg->sm_slen,
778 	    (struct sockaddr *)&msg->sm_local_ss, &msg->sm_local_slen)) < 1) {
779 		free(msg);
780 		return;
781 	}
782 
783 	stats->snmp_inpkts++;
784 	msg->sm_datalen = (size_t)len;
785 
786 	bzero(&msg->sm_ber, sizeof(msg->sm_ber));
787 	ober_set_application(&msg->sm_ber, smi_application);
788 	ober_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
789 
790 	msg->sm_req = ober_read_elements(&msg->sm_ber, NULL);
791 	if (msg->sm_req == NULL) {
792 		stats->snmp_inasnparseerrs++;
793 		snmp_msgfree(msg);
794 		return;
795 	}
796 
797 #ifdef DEBUG
798 	fprintf(stderr, "recv msg:\n");
799 	smi_debug_elements(msg->sm_req);
800 #endif
801 
802 	if (snmpe_parse(msg) == -1) {
803 		if (msg->sm_usmerr != 0 && MSG_REPORT(msg)) {
804 			usm_make_report(msg);
805 			snmpe_response(msg);
806 			return;
807 		} else {
808 			snmp_msgfree(msg);
809 			return;
810 		}
811 	}
812 
813 	snmpe_dispatchmsg(msg);
814 }
815 
816 void
817 snmpe_dispatchmsg(struct snmp_message *msg)
818 {
819 	if (msg->sm_pdutype == SNMP_C_TRAP ||
820 	    msg->sm_pdutype == SNMP_C_TRAPV2) {
821 		snmp_msgfree(msg);
822 		return;
823 	}
824 	/* dispatched to subagent */
825 	/* XXX Do proper error handling */
826 	(void) snmpe_parsevarbinds(msg);
827 
828 	return;
829 	/*
830 	 * Leave code here for now so it's easier to switch back in case of
831 	 * issues.
832 	 */
833 	/* respond directly */
834 	msg->sm_pdutype = SNMP_C_RESPONSE;
835 	snmpe_response(msg);
836 }
837 
838 void
839 snmpe_send(struct snmp_message *msg, enum snmp_pdutype type, int32_t requestid,
840     int32_t error, uint32_t index, struct ber_element *varbindlist)
841 {
842 	msg->sm_request = requestid;
843 	msg->sm_pdutype = type;
844 	msg->sm_error = error;
845 	msg->sm_errorindex = index;
846 	msg->sm_varbindresp = varbindlist;
847 
848 	msg->sm_pdutype = SNMP_C_RESPONSE;
849 	snmpe_response(msg);
850 }
851 
852 void
853 snmpe_response(struct snmp_message *msg)
854 {
855 	struct snmp_stats	*stats = &snmpd_env->sc_stats;
856 	u_int8_t		*ptr = NULL;
857 	ssize_t			 len;
858 
859 	if (msg->sm_varbindresp == NULL && msg->sm_pduend != NULL)
860 		msg->sm_varbindresp = ober_unlink_elements(msg->sm_pduend);
861 
862 	switch (msg->sm_error) {
863 	case SNMP_ERROR_NONE:
864 		break;
865 	case SNMP_ERROR_TOOBIG:
866 		stats->snmp_intoobigs++;
867 		break;
868 	case SNMP_ERROR_NOSUCHNAME:
869 		stats->snmp_innosuchnames++;
870 		break;
871 	case SNMP_ERROR_BADVALUE:
872 		stats->snmp_inbadvalues++;
873 		break;
874 	case SNMP_ERROR_READONLY:
875 		stats->snmp_inreadonlys++;
876 		break;
877 	case SNMP_ERROR_GENERR:
878 	default:
879 		stats->snmp_ingenerrs++;
880 		break;
881 	}
882 
883 	/* Create new SNMP packet */
884 	if (snmpe_encode(msg) < 0)
885 		goto done;
886 
887 	len = ober_write_elements(&msg->sm_ber, msg->sm_resp);
888 	if (ober_get_writebuf(&msg->sm_ber, (void *)&ptr) == -1)
889 		goto done;
890 
891 	usm_finalize_digest(msg, ptr, len);
892 	if (msg->sm_sock_tcp) {
893 		event_del(&msg->sm_sockev);
894 		event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
895 		    snmpe_writecb, msg);
896 		event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
897 		return;
898 	} else {
899 		len = sendtofrom(msg->sm_sock, ptr, len, 0,
900 		    (struct sockaddr *)&msg->sm_ss, msg->sm_slen,
901 		    (struct sockaddr *)&msg->sm_local_ss, msg->sm_local_slen);
902 		if (len != -1)
903 			stats->snmp_outpkts++;
904 	}
905 
906  done:
907 	snmp_msgfree(msg);
908 }
909 
910 void
911 snmp_msgfree(struct snmp_message *msg)
912 {
913 	if (msg->sm_transactionid != 0)
914 		RB_REMOVE(snmp_messages, &snmp_messages, msg);
915 	event_del(&msg->sm_sockev);
916 	ober_free(&msg->sm_ber);
917 	if (msg->sm_req != NULL)
918 		ober_free_elements(msg->sm_req);
919 	if (msg->sm_resp != NULL)
920 		ober_free_elements(msg->sm_resp);
921 	free(msg);
922 }
923 
924 int
925 snmpe_encode(struct snmp_message *msg)
926 {
927 	struct ber_element	*ehdr;
928 	struct ber_element	*pdu, *epdu;
929 
930 	msg->sm_resp = ober_add_sequence(NULL);
931 	if ((ehdr = ober_add_integer(msg->sm_resp, msg->sm_version)) == NULL)
932 		return -1;
933 	if (msg->sm_version == SNMP_V3) {
934 		char	f = MSG_SECLEVEL(msg);
935 
936 		if ((ehdr = ober_printf_elements(ehdr, "{iixi}", msg->sm_msgid,
937 		    msg->sm_max_msg_size, &f, sizeof(f),
938 		    msg->sm_secmodel)) == NULL)
939 			return -1;
940 
941 		/* XXX currently only USM supported */
942 		if ((ehdr = usm_encode(msg, ehdr)) == NULL)
943 			return -1;
944 	} else {
945 		if ((ehdr = ober_add_string(ehdr, msg->sm_community)) == NULL)
946 			return -1;
947 	}
948 
949 	pdu = epdu = ober_add_sequence(NULL);
950 	if (msg->sm_version == SNMP_V3) {
951 		if ((epdu = ober_printf_elements(epdu, "xs{",
952 		    snmpd_env->sc_engineid, snmpd_env->sc_engineid_len,
953 		    msg->sm_ctxname)) == NULL) {
954 			ober_free_elements(pdu);
955 			return -1;
956 		}
957 	}
958 
959 	if (!ober_printf_elements(epdu, "tiii{e}", BER_CLASS_CONTEXT,
960 	    msg->sm_pdutype, msg->sm_request,
961 	    msg->sm_error, msg->sm_errorindex,
962 	    msg->sm_varbindresp)) {
963 		ober_free_elements(pdu);
964 		return -1;
965 	}
966 
967 	if (MSG_HAS_PRIV(msg))
968 		pdu = usm_encrypt(msg, pdu);
969 	ober_link_elements(ehdr, pdu);
970 
971 #ifdef DEBUG
972 	fprintf(stderr, "resp msg:\n");
973 	smi_debug_elements(msg->sm_resp);
974 #endif
975 	return 0;
976 }
977 
978 int
979 snmp_messagecmp(struct snmp_message *m1, struct snmp_message *m2)
980 {
981 	return (m1->sm_transactionid < m2->sm_transactionid ? -1 :
982 	    m1->sm_transactionid > m2->sm_transactionid);
983 }
984 
985 RB_GENERATE(snmp_messages, snmp_message, sm_entry, snmp_messagecmp)
986