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