So here's (part of) my work from h2k21.

The end-goal is to re-introduce agentx support in snmpd.

Currently snmpd(8) has its varbind logic divided over 4 files:
- snmpe.c:      loop over the varbinds inside the pdu/snmp-package.
- smi.c:        Register all the objects and act as a lookup for the
                requested varbinds (as well as some other things that
                are irrelevant to this story)
- mib.c:        The object implementation: This is where the actual
                objects live and are being registered inside smi.c
- mps.c:        The glue between snmpe.c, smi.c and mib.c. snmpe.c
                calls the mps.c functions for the appropriate request-
                type and calls out to smi.c to find the correct backend
                which in turns calls out the callback functions which
                live in mib.c to get the actual data.

So what is wrong with this approach/the current implementation?
- The current implementation is fully synchronous, meaning that if a
  backend decides to waits a long time (snmp(1)'s default timeout is
  1 second, the snmpTargetAddrTimeout is 1500 centiseconds) and multiple
  varbinds are requested it would be really easy to go over the
  timeout.
- Because varbinds are requested in a loop it's currently not possible
  to lookup the backend for each requested varbind and cluster these
  varbinds in their subqueries. This could delay things even further.
- mps, according to snmpd.h, stands for message processing subsystem.
  However, mps according to RFC3412 means the {,un}packing of the snmp
  package, not the handling of the varbinds. According to RFC3413 that
  name should be application.
- When a variable is not found in smi.c/mps.c it returns -1 for SNMPv1
  packages. This in turn gets converted into a noSuchName by snmpe.c.
  However, mib.c can return -1 on error, which simply bubbles up from
  mps.c into snmpe.c, which still gets converted into noSuchName,
  instead of the expected genErr.
- snmpe.c calls mps_getbulkreq on each varbind, resulting in all the
  maxrepetitions being placed subsequently one after another for each
  requested varbind. However, according to RFC3416 section 4.2.3, these
  results should be interleaved. To phrase it in a different way: If
  the requested varbinds are the column names of a table, the response-
  pdu should return the results row by row, while we currently return
  the results column by column.
- While mps.c tries to take into account the NoSuchName case to properly
  support SNMPv1, it completely ignores the Counter64 case, which does
  not exist in SNMPv1. According to RFC3584 these should return
  NoSuchName for get requests and move to the next object for
  get{next,bulk}-requests and return NoSuchName on EOMV.
- The current implementation (in the current local-only constraints;
  safely) assumes that all objects are registered on a boundary and no
  overlapping regions are registered. This assumption however breaks
  when moving to agentx-space where regions can be allocated overlapping
  and with different priority. So before moving to the next object on
  the edge of the registered regions we must first look back up to see
  if there's a region that also needs to be asked to stay in
  lexicographical order.
- The current implementation completely ignores trailing sub-
  identifiers that are deemed unnecessary for the request. E.g.
  requesting sysContact.0.1 returns the value of sysContact.0 on the
  instanstance syscontact.0.1. This must be a noSuchInstance exception
  (or NoSuchName error in case of SNMPv1)

Considering all the issues mentioned above and the fact that the entire
application namespace was free it was easier to just reimplement things
from scratch. I tried to keep as much of the existing code in place as
I could, so that in the case of problems moving back to the old codepath
wouldn't mean reverting the entire diff, as well as keep the mental
gymnastics around the current code to a minimum.

Apart from the fixes mentioned above, this code has two downsides that I
think are worth accepting:
- I haven't implemented write support. It doesn't really make sense for
  our current support level. We don't have atomic support, which is
  expected for proper SNMP implementations. And the values are volatile
  and reset on the next reboot. If people really have a use case for
  write support we can always revisit this at a later point in time.
- This code is quite a bit slower than our current implementation.
  ~7.5x slower for a full walk and ~25x slower for a bulkwalk of 1.3.
  However, this speed difference mainly comes from assumptions mentioned
  before and doing some ugly hacks to short circuit the RB_* functions.
  However, this still means that I can do around 2000 metrics per
  second on my laptop, which I guess should suffice most peoples needs.
  I think it's worth having an as clean as possible implementation to
  begin with and see which short circuits we want to add on top later
  on (if any).

One final note: Even though this code supports fully async calls (I
have some a decent subset of agentx working in my tree), the current
code is still fully synchronous because of mps.c's behaviour.
Because of this I intentionally left out any backpressure mechanisms
towards snmpe.c's socket handling out of this diff.

Thoughts? Tests? OK?

martijn@

Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/Makefile,v
retrieving revision 1.17
diff -u -p -r1.17 Makefile
--- Makefile    30 Jun 2020 17:11:49 -0000      1.17
+++ Makefile    1 Nov 2021 17:47:16 -0000
@@ -2,7 +2,7 @@
 
 PROG=          snmpd
 MAN=           snmpd.8 snmpd.conf.5
-SRCS=          parse.y log.c snmpe.c \
+SRCS=          parse.y log.c snmpe.c application.c application_legacy.c \
                    mps.c trap.c mib.c smi.c kroute.c snmpd.c timer.c \
                    pf.c proc.c usm.c traphandler.c util.c
 
Index: mib.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/mib.h,v
retrieving revision 1.40
diff -u -p -r1.40 mib.h
--- mib.h       20 Jun 2018 09:20:51 -0000      1.40
+++ mib.h       1 Nov 2021 17:47:16 -0000
@@ -132,6 +132,33 @@
 #define MIB_usmStatsWrongDigests       MIB_usmStats, OIDVAL_usmErrDigest
 #define MIB_usmStatsDecryptionErrors   MIB_usmStats, OIDVAL_usmErrDecrypt
 
+/* SNMP-TARGET-MIB */
+#define MIB_snmpTargetMIB              MIB_snmpModules, 12
+#define MIB_snmpTargetObjects          MIB_snmpTargetMIB, 1
+#define MIB_snmpTargetSpinLock         MIB_snmpTargetObjects, 1
+#define MIB_snmpTargetAddrTable                MIB_snmpTargetObjects, 2
+#define MIB_snmpTargetAddrEntry                MIB_snmpTargetAddrTable, 1
+#define MIB_snmpTargetAddrName         MIB_snmpTargetAddrEntry, 1
+#define MIB_snmpTargetAddrTDomain      MIB_snmpTargetAddrEntry, 2
+#define MIB_snmpTargetAddrTAddress     MIB_snmpTargetAddrEntry, 3
+#define MIB_snmpTargetAddrTimeout      MIB_snmpTargetAddrEntry, 4
+#define MIB_snmpTargetAddrRetryCount   MIB_snmpTargetAddrEntry, 5
+#define MIB_snmpTargetAddrTagList      MIB_snmpTargetAddrEntry, 6
+#define MIB_snmpTargetAddrParams       MIB_snmpTargetAddrEntry, 7
+#define MIB_snmpTargetAddrStorageType  MIB_snmpTargetAddrEntry, 8
+#define MIB_snmpTargetAddrRowStatus    MIB_snmpTargetAddrEntry, 9
+#define MIB_snmpTargetParamsTable      MIB_snmpTargetObjects, 3
+#define MIB_snmpTargetParamsEntry      MIB_snmpTargetParamsTable, 1
+#define MIB_snmpTargetParamsName       MIB_snmpTargetParamsEntry, 1
+#define MIB_snmpTargetParamsMPModel    MIB_snmpTargetParamsEntry, 2
+#define MIB_snmpTargetParamsSecurityModel      MIB_snmpTargetParamsEntry, 3
+#define MIB_snmpTargetParamsSecurityName       MIB_snmpTargetParamsEntry, 4
+#define MIB_snmpTargetParamsSecurityLevel      MIB_snmpTargetParamsEntry, 5
+#define MIB_snmpTargetParamsStorageType        MIB_snmpTargetParamsEntry, 6
+#define MIB_snmpTargetParamsRowStatus  MIB_snmpTargetParamsEntry, 7
+#define MIB_snmpUnavailableContexts    MIB_snmpTargetObjects, 4
+#define MIB_snmpUnknownContexts                MIB_snmpTargetObjects, 5
+
 /* HOST-RESOURCES-MIB */
 #define MIB_host                       MIB_mib_2, 25
 #define MIB_hrSystem                   MIB_host, 1
Index: snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.100
diff -u -p -r1.100 snmpd.h
--- snmpd.h     2 Sep 2021 05:41:02 -0000       1.100
+++ snmpd.h     1 Nov 2021 17:47:16 -0000
@@ -36,6 +36,8 @@
 #include <imsg.h>
 
 #include "snmp.h"
+#include "smi.h"
+#include "log.h"
 
 #ifndef nitems
 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
@@ -617,28 +619,6 @@ extern struct snmpd *snmpd_env;
 struct snmpd   *parse_config(const char *, u_int);
 int             cmdline_symset(char *);
 
-/* log.c */
-void   log_init(int, int);
-void   log_procinit(const char *);
-void   log_setverbose(int);
-int    log_getverbose(void);
-void   log_warn(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-void   log_warnx(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-void   log_info(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-void   log_debug(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-void   logit(int, const char *, ...)
-           __attribute__((__format__ (printf, 2, 3)));
-void   vlog(int, const char *, va_list)
-           __attribute__((__format__ (printf, 2, 0)));
-__dead void fatal(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-__dead void fatalx(const char *, ...)
-           __attribute__((__format__ (printf, 1, 2)));
-
 /* kroute.c */
 void            kr_init(void);
 void            kr_shutdown(void);
@@ -719,7 +699,6 @@ int                  pfta_get_first(struct pfr_astats 
 
 /* smi.c */
 int             smi_init(void);
-u_long          smi_getticks(void);
 void            smi_mibtree(struct oid *);
 struct oid     *smi_find(struct oid *);
 struct oid     *smi_nfind(struct oid *);
@@ -728,7 +707,6 @@ struct oid  *smi_next(struct oid *);
 struct oid     *smi_foreach(struct oid *, u_int);
 void            smi_oidlen(struct ber_oid *);
 void            smi_scalar_oidlen(struct ber_oid *);
-char           *smi_oid2string(struct ber_oid *, char *, size_t, size_t);
 int             smi_string2oid(const char *, struct ber_oid *);
 void            smi_delete(struct oid *);
 int             smi_insert(struct oid *);
@@ -736,7 +714,6 @@ int          smi_oid_cmp(struct oid *, struct o
 int             smi_key_cmp(struct oid *, struct oid *);
 unsigned int    smi_application(struct ber_element *);
 void            smi_debug_elements(struct ber_element *);
-char           *smi_print_element(struct ber_element *);
 
 /* timer.c */
 void            timer_init(void);
Index: snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.78
diff -u -p -r1.78 snmpe.c
--- snmpe.c     21 Oct 2021 14:33:13 -0000      1.78
+++ snmpe.c     1 Nov 2021 17:47:16 -0000
@@ -37,11 +37,12 @@
 #include <unistd.h>
 #include <pwd.h>
 
+#include "application.h"
 #include "snmpd.h"
+#include "snmpe.h"
 #include "mib.h"
 
 void    snmpe_init(struct privsep *, struct privsep_proc *, void *);
-const char *snmpe_pdutype2string(enum snmp_pdutype);
 int     snmpe_parse(struct snmp_message *);
 void    snmpe_tryparse(int, struct snmp_message *);
 int     snmpe_parsevarbinds(struct snmp_message *);
@@ -53,7 +54,6 @@ void   snmpe_writecb(int fd, short, void 
 void    snmpe_acceptcb(int fd, short, void *);
 void    snmpe_prepare_read(struct snmp_message *, int);
 int     snmpe_encode(struct snmp_message *);
-void    snmp_msgfree(struct snmp_message *);
 
 struct imsgev  *iev_parent;
 static const struct timeval    snmpe_tcp_timeout = { 10, 0 }; /* 10s */
@@ -99,6 +99,7 @@ snmpe_init(struct privsep *ps, struct pr
        kr_init();
        timer_init();
        usm_generate_keys();
+       appl_init();
 
        /* listen for incoming SNMP UDP/TCP messages */
        TAILQ_FOREACH(h, &env->sc_addresses, entry) {
@@ -137,6 +138,7 @@ snmpe_shutdown(void)
                close(h->fd);
        }
        kr_shutdown();
+       appl_shutdown();
 }
 
 int
@@ -427,6 +429,11 @@ badversion:
                goto fail;
        }
 
+       for (a = msg->sm_varbind; a != NULL; a = a->be_next) {
+               if (ober_scanf_elements(a, "{oS$}", NULL) == -1)
+                       goto parsefail;
+       }
+
        msg->sm_request = req;
        msg->sm_error = errval;
        msg->sm_errorindex = erridx;
@@ -467,6 +474,13 @@ snmpe_parsevarbinds(struct snmp_message 
        struct ber_oid           o;
        int                      i;
 
+       appl_processpdu(msg, msg->sm_ctxname, msg->sm_version, msg->sm_pdu);
+       return 0;
+       /*
+        * Leave code here for now so it's easier to switch back in case of
+        * issues.
+        */
+
        msg->sm_errstr = "invalid varbind element";
 
        varbind = msg->sm_varbind;
@@ -811,7 +825,26 @@ snmpe_dispatchmsg(struct snmp_message *m
        /* XXX Do proper error handling */
        (void) snmpe_parsevarbinds(msg);
 
+       return;
+       /*
+        * Leave code here for now so it's easier to switch back in case of
+        * issues.
+        */
        /* respond directly */
+       msg->sm_pdutype = SNMP_C_RESPONSE;
+       snmpe_response(msg);
+}
+
+void
+snmpe_send(struct snmp_message *msg, enum snmp_pdutype type, int32_t requestid,
+    int32_t error, uint32_t index, struct ber_element *varbindlist)
+{
+       msg->sm_msgid = requestid;
+       msg->sm_pdutype = type;
+       msg->sm_error = error;
+       msg->sm_errorindex = index;
+       msg->sm_varbindresp = varbindlist;
+
        msg->sm_pdutype = SNMP_C_RESPONSE;
        snmpe_response(msg);
 }
Index: snmpe.h
===================================================================
RCS file: snmpe.h
diff -N snmpe.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ snmpe.h     1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,25 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Martijn van Duren <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "snmp.h"
+
+struct snmp_message;
+
+void snmpe_send(struct snmp_message *, enum snmp_pdutype, int32_t, int32_t,
+    uint32_t, struct ber_element *);
+const char *snmpe_pdutype2string(enum snmp_pdutype);
Index: smi.h
===================================================================
RCS file: smi.h
diff -N smi.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ smi.h       1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,27 @@
+/*     $OpenBSD: smi.c,v 1.28 2021/01/04 07:59:54 martijn Exp $        */
+
+/*
+ * Copyright (c) 2007, 2008 Reyk Floeter <r...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ber.h>
+#include <stdint.h>
+
+#define BER_OID(...) (struct ber_oid) { { __VA_ARGS__ }, \
+    (sizeof((u_int32_t []) { __VA_ARGS__ }) / sizeof(u_int32_t)) }
+
+char *smi_oid2string(struct ber_oid *, char *, size_t, size_t);
+u_long smi_getticks(void);
+char *smi_print_element(struct ber_element *);
Index: log.h
===================================================================
RCS file: log.h
diff -N log.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ log.h       1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,40 @@
+/*     $OpenBSD: log.c,v 1.16 2017/03/21 12:06:56 bluhm Exp $  */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henn...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+
+void   log_init(int, int);
+void   log_procinit(const char *);
+void   log_setverbose(int);
+int    log_getverbose(void);
+void   log_warn(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_warnx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_info(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_debug(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   logit(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+void   vlog(int, const char *, va_list)
+           __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
Index: application.h
===================================================================
RCS file: application.h
diff -N application.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ application.h       1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,138 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Martijn van Duren <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/tree.h>
+
+#include <ber.h>
+
+#include <stdint.h>
+
+#define APPL_OIDMAX 128 /* RFC 2578 Section 3.5 */
+#define APPL_CONTEXTNAME_MAX 32 /* RFC 3415 vacmContextName */
+
+/* Combination of RFC 3416 error-status and RFC 2741 res.error */
+enum appl_error {
+       APPL_ERROR_NOERROR              = 0,
+       APPL_ERROR_TOOBIG               = 1,
+       APPL_ERROR_NOSUCHNAME           = 2,
+       APPL_ERROR_BADVALUE             = 3,
+       APPL_ERROR_READONLY             = 4,
+       APPL_ERROR_GENERR               = 5,
+       APPL_ERROR_NOACCESS             = 6,
+       APPL_ERROR_WRONGTYPE            = 7,
+       APPL_ERROR_WRONGLENGTH          = 8,
+       APPL_ERROR_WRONGENCODING        = 9,
+       APPL_ERROR_WRONGVALUE           = 10,
+       APPL_ERROR_NOCREATION           = 11,
+       APPL_ERROR_INCONSISTENTVALUE    = 12,
+       APPL_ERROR_RESOURCEUNAVAILABLE  = 13,
+       APPL_ERROR_COMMITFAILED         = 14,
+       APPL_ERROR_UNDOFAILED           = 15,
+       APPL_ERROR_AUTHORIZATIONERROR   = 16,
+       APPL_ERROR_NOTWRITABLE          = 17,
+       APPL_ERROR_INCONSISTENTNAME     = 18,
+       APPL_ERROR_OPENfAILED           = 256,
+       APPL_ERROR_NOTOPEN              = 257,
+       APPL_ERROR_INDEXWRONGTYPE       = 258,
+       APPL_ERROR_INDEXALREADYALLOCATED= 259,
+       APPL_ERROR_INDEXNONEAVAILABLE   = 260,
+       APPL_ERROR_INDEXNOTALLOCATED    = 261,
+       APPL_ERROR_UNSUPPORTEDCONTEXT   = 262,
+       APPL_ERROR_DUPLICATEREGISTRATION= 263,
+       APPL_ERROR_UNKNOWNREGISTRATION  = 264,
+       APPL_ERROR_UNKNOWNAGENTCAPS     = 265,
+       APPL_ERROR_PARSEERROR           = 266,
+       APPL_ERROR_REQUESTDENIED        = 267,
+       APPL_ERROR_PROCESSINGERROR      = 268
+};
+
+enum appl_exception {
+       APPL_EXC_NOSUCHOBJECT           = 0,
+       APPL_EXC_NOSUCHINSTANCE         = 1,
+       APPL_EXC_ENDOFMIBVIEW           = 2
+};
+
+enum appl_close_reason {
+       APPL_CLOSE_REASONOTHER          = 1,
+       APPL_CLOSE_REASONPARSEERROR     = 2,
+       APPL_CLOSE_REASONPROTOCOLERROR  = 3,
+       APPL_CLOSE_REASONTIMEOUTS       = 4,
+       APPL_CLOSE_REASONSHUTDOWN       = 5,
+       APPL_CLOSE_REASONBYMANAGER      = 6
+};
+
+struct appl_varbind {
+       int8_t av_include; /* RFC 2741 section 5.1 */
+       struct ber_oid av_oid;
+       struct ber_oid av_oid_end;
+       struct ber_element *av_value;
+
+       struct appl_varbind *av_next;
+};
+
+struct snmp_message;
+enum snmp_version;
+struct appl_backend;
+
+struct appl_backend_functions {
+       void (*ab_close)(struct appl_backend *, enum appl_close_reason);
+       void (*ab_get)(struct appl_backend *, int32_t, int32_t, const char *,
+           struct appl_varbind *);
+       void (*ab_getnext)(struct appl_backend *, int32_t, int32_t, const char 
*,
+           struct appl_varbind *);
+       /*
+        * RFC 3416 section 3: non-repeaters/max-repetitions = 0..max-bindings
+        * max-bindings = (2^31)-1
+        * RFC 2741 section 6.2.7: non-repeaters/max-repetitions = 2 bytes
+        * Go for the lowest common denominator.
+        */
+       void (*ab_getbulk)(struct appl_backend *, int32_t, int32_t, int16_t,
+           int16_t, const char *, struct appl_varbind *);
+};
+
+struct appl_backend {
+       char *ab_name;
+       void *ab_cookie;
+       uint8_t ab_retries;
+       struct appl_backend_functions *ab_fn;
+       /*
+        * Only store downstream requests: they reference upstream and when
+        * downstream requests are done the upstream request is finalized.
+        */
+       RB_HEAD(appl_requests, appl_request_downstream) ab_requests;
+};
+
+void appl_init(void);
+void appl_shutdown(void);
+enum appl_error appl_register(const char *, uint32_t, uint8_t, struct ber_oid 
*,
+    int, int, uint8_t, uint32_t, struct appl_backend *);
+enum appl_error appl_unregister(const char *, uint8_t, struct ber_oid *,
+    uint8_t, uint32_t, struct appl_backend *);
+void appl_close(struct appl_backend *);
+void appl_processpdu(struct snmp_message *, const char *,
+    enum snmp_version , struct ber_element *);
+void appl_response(struct appl_backend *, int32_t, enum appl_error, int16_t,
+    struct appl_varbind *);
+struct ber_element *appl_exception(enum appl_exception);
+
+/* Dispatcher */
+void    snmp_msgfree(struct snmp_message *);
+
+/* application_legacy.c */
+void    appl_legacy_init(void);
+void    appl_legacy_shutdown(void);
Index: application.c
===================================================================
RCS file: application.c
diff -N application.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ application.c       1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,1451 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Martijn van Duren <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "application.h"
+#include "log.h"
+#include "smi.h"
+#include "snmp.h"
+#include "snmpe.h"
+#include "mib.h"
+
+TAILQ_HEAD(, appl_context) contexts = TAILQ_HEAD_INITIALIZER(contexts);
+
+struct appl_context {
+       char ac_name[APPL_CONTEXTNAME_MAX + 1];
+
+       RB_HEAD(appl_regions, appl_region) ac_regions;
+
+       TAILQ_ENTRY(appl_context) ac_entries;
+};
+
+struct appl_region {
+       struct ber_oid ar_oid;
+       uint8_t ar_priority;
+       int32_t ar_timeout;
+       int ar_instance;
+       int ar_subtree; /* Claim entire subtree */
+       struct appl_backend *ar_backend;
+       struct appl_region *ar_next; /* Sorted by priority */
+
+       RB_ENTRY(appl_region) ar_entry;
+};
+
+struct appl_request_upstream {
+       struct appl_context *aru_ctx;
+       struct snmp_message *aru_statereference;
+       int32_t aru_requestid; /* upstream requestid */
+       int32_t aru_transactionid; /* RFC 2741 section 6.1 */
+       int16_t aru_nonrepeaters;
+       int16_t aru_maxrepetitions;
+       struct appl_varbind_internal *aru_vblist;
+       size_t aru_varbindlen;
+       enum appl_error aru_error;
+       int16_t aru_index;
+       int aru_locked; /* Prevent recursion through appl_request_send */
+
+       enum snmp_version aru_pduversion;
+       struct ber_element *aru_pdu; /* Original requested pdu */
+};
+
+struct appl_request_downstream {
+       struct appl_request_upstream *ard_request;
+       struct appl_backend *ard_backend;
+       enum snmp_pdutype ard_requesttype;
+       int16_t ard_nonrepeaters;
+       int16_t ard_maxrepetitions;
+       int32_t ard_requestid;
+       uint8_t ard_retries;
+
+       struct appl_varbind_internal *ard_vblist;
+       struct event ard_timer;
+
+       RB_ENTRY(appl_request_downstream) ard_entry;
+};
+
+enum appl_varbind_state {
+       APPL_VBSTATE_MUSTFILL,
+       APPL_VBSTATE_NEW,
+       APPL_VBSTATE_PENDING,
+       APPL_VBSTATE_DONE
+};
+
+struct appl_varbind_internal {
+       enum appl_varbind_state avi_state;
+       struct appl_varbind avi_varbind;
+       struct appl_region *avi_region;
+       int16_t avi_index;
+       struct appl_request_upstream *avi_request_upstream;
+       struct appl_request_downstream *avi_request_downstream;
+       struct appl_varbind_internal *avi_next;
+       struct appl_varbind_internal *avi_sub;
+};
+
+/* SNMP-TARGET-MIB (RFC 3413) */
+struct snmp_target_mib {
+       uint32_t                snmp_unavailablecontexts;
+       uint32_t                snmp_unknowncontexts;
+} snmp_target_mib;
+
+enum appl_error appl_region(struct appl_context *, uint32_t, uint8_t,
+    struct ber_oid *, int, int, struct appl_backend *);
+void appl_region_free(struct appl_context *, struct appl_region *);
+struct appl_region *appl_region_find(struct appl_context *,
+    const struct ber_oid *);
+struct appl_region *appl_region_next(struct appl_context *,
+    struct ber_oid *, struct appl_region *);
+void appl_request_upstream_free(struct appl_request_upstream *);
+void appl_request_downstream_free(struct appl_request_downstream *);
+void appl_request_upstream_resolve(struct appl_request_upstream *);
+void appl_request_downstream_send(struct appl_request_downstream *);
+void appl_request_downstream_timeout(int, short, void *);
+void appl_request_upstream_reply(struct appl_request_upstream *);
+int appl_varbind_valid(struct appl_varbind *, struct appl_varbind *, int, int,
+    const char **);
+int appl_varbind_backend(struct appl_varbind_internal *);
+void appl_varbind_error(struct appl_varbind_internal *, enum appl_error);
+void appl_report(struct snmp_message *, int32_t, struct ber_oid *,
+    struct ber_element *);
+void appl_pdu_log(struct appl_backend *, enum snmp_pdutype, int32_t, uint16_t,
+    uint16_t, struct appl_varbind *);
+void ober_oid_nextsibling(struct ber_oid *);
+
+int appl_region_cmp(struct appl_region *, struct appl_region *);
+int appl_request_cmp(struct appl_request_downstream *,
+    struct appl_request_downstream *);
+
+RB_PROTOTYPE_STATIC(appl_regions, appl_region, ar_entry, appl_region_cmp);
+RB_PROTOTYPE_STATIC(appl_requests, appl_request_downstream, ard_entry,
+    appl_request_cmp);
+
+#define APPL_CONTEXT_NAME(ctx) (ctx->ac_name[0] == '\0' ? NULL : ctx->ac_name)
+
+void
+appl_init(void)
+{
+       appl_legacy_init();
+}
+
+void
+appl_shutdown(void)
+{
+       struct appl_context *ctx, *tctx;
+
+       appl_legacy_shutdown();
+
+       TAILQ_FOREACH_SAFE(ctx, &contexts, ac_entries, tctx) {
+               assert(RB_EMPTY(&(ctx->ac_regions)));
+               TAILQ_REMOVE(&contexts, ctx, ac_entries);
+               free(ctx);
+       }
+}
+
+static struct appl_context *
+appl_context(const char *name, int create)
+{
+       struct appl_context *ctx;
+
+       if (name == NULL)
+               name = "";
+
+       if (strlen(name) > APPL_CONTEXTNAME_MAX) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       TAILQ_FOREACH(ctx, &contexts, ac_entries) {
+               if (strcmp(name, ctx->ac_name) == 0)
+                       return ctx;
+       }
+
+       /* Always allow the default namespace */
+       if (!create && name[0] != '\0') {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       if ((ctx = malloc(sizeof(*ctx))) == NULL)
+               return NULL;
+
+       strlcpy(ctx->ac_name, name, sizeof(ctx->ac_name));
+       RB_INIT(&(ctx->ac_regions));
+
+       TAILQ_INSERT_TAIL(&contexts, ctx, ac_entries);
+       return ctx;
+}
+
+enum appl_error
+appl_region(struct appl_context *ctx, uint32_t timeout, uint8_t priority,
+    struct ber_oid *oid, int instance, int subtree,
+    struct appl_backend *backend)
+{
+       struct appl_region *region = NULL, *nregion, search;
+       char oidbuf[1024], regionbuf[1024], subidbuf[11];
+       size_t i;
+
+       /*
+        * Don't allow overlap when subtree flag is set.
+        * This allows us to keep control of certain regions like system.
+        */
+       region = appl_region_find(ctx, oid);
+       if (region != NULL && region->ar_subtree)
+               goto overlap;
+
+       search.ar_oid = *oid;
+       region = RB_NFIND(appl_regions, &(ctx->ac_regions), &search);
+       if (region != NULL && region->ar_subtree && (
+           appl_region_cmp(&search, region) == 0 ||
+           appl_region_cmp(&search, region) == -2))
+               goto overlap;
+
+       /* Don't use smi_oid2string, because appl_register can't use it */
+       for (i = 0; i < oid->bo_n; i++) {
+               if (i != 0)
+                       strlcat(oidbuf, ".", sizeof(oidbuf));
+               snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
+                   oid->bo_id[i]);
+               strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+       }
+       if ((nregion = malloc(sizeof(*nregion))) == NULL) {
+               log_warn("%s: Can't register %s: Processing error",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_PROCESSINGERROR;
+       }
+       nregion->ar_oid = *oid;
+       nregion->ar_priority = priority;
+       nregion->ar_timeout = timeout;
+       nregion->ar_instance = instance;
+       nregion->ar_subtree = subtree;
+       nregion->ar_backend = backend;
+       nregion->ar_next = NULL;
+
+       region = RB_INSERT(appl_regions, &(ctx->ac_regions), nregion);
+       if (region == NULL)
+               return APPL_ERROR_NOERROR;
+
+       if (region->ar_priority == priority)
+               goto duplicate;
+       if (region->ar_priority > priority) {
+               RB_REMOVE(appl_regions, &(ctx->ac_regions), region);
+               RB_INSERT(appl_regions, &(ctx->ac_regions), nregion);
+               nregion->ar_next = region;
+               return APPL_ERROR_NOERROR;
+       }
+
+       while (region->ar_next != NULL &&
+           region->ar_next->ar_priority < priority)
+               region = region->ar_next;
+       if (region->ar_next != NULL && region->ar_next->ar_priority == priority)
+               goto duplicate;
+       nregion->ar_next = region->ar_next;
+       region->ar_next = nregion;
+
+       return APPL_ERROR_NOERROR;
+ duplicate:
+       free(nregion);
+       log_info("%s: %s priority %"PRId8": Duplicate registration",
+           backend->ab_name, oidbuf, priority);
+       return APPL_ERROR_DUPLICATEREGISTRATION;
+ overlap:
+       for (i = 0; i < region->ar_oid.bo_n; i++) {
+               if (i != 0)
+                       strlcat(regionbuf, ".", sizeof(regionbuf));
+               snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
+                   region->ar_oid.bo_id[i]);
+               strlcat(regionbuf, subidbuf, sizeof(regionbuf));
+       }
+       log_info("%s: %s overlaps with %s: Request denied",
+           backend->ab_name, oidbuf, regionbuf);
+       return APPL_ERROR_REQUESTDENIED;
+}
+
+/* Name from RFC 2741 section 6.2.3 */
+enum appl_error
+appl_register(const char *ctxname, uint32_t timeout, uint8_t priority,
+    struct ber_oid *oid, int instance, int subtree, uint8_t range_subid,
+    uint32_t upper_bound, struct appl_backend *backend)
+{
+       struct appl_context *ctx;
+       struct appl_region *region, search;
+       char oidbuf[1024], subidbuf[11];
+       enum appl_error error;
+       size_t i;
+       uint32_t lower_bound;
+
+       oidbuf[0] = '\0';
+       /* smi_oid2string can't do ranges */
+       for (i = 0; i < oid->bo_n; i++) {
+               snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32, oid->bo_id[i]);
+               if (i != 0)
+                       strlcat(oidbuf, ".", sizeof(oidbuf));
+               if (range_subid == i + 1) {
+                       strlcat(oidbuf, "[", sizeof(oidbuf));
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+                       strlcat(oidbuf, "-", sizeof(oidbuf));
+                       snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
+                           upper_bound);
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+                       strlcat(oidbuf, "]", sizeof(oidbuf));
+               } else
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+       }
+
+       if (ctxname == NULL)
+               ctxname = "";
+       log_info("%s: Registering %s%s context(%s) priority(%"PRIu8") "
+           "timeout(%"PRIu32".%02us)", backend->ab_name, oidbuf,
+            instance ? "(instance)" : "", ctxname, priority,
+            timeout/100, timeout % 100);
+
+       if ((ctx = appl_context(ctxname, 0)) == NULL) {
+               if (errno == ENOMEM) {
+                       log_warn("%s: Can't register %s: Processing error",
+                           backend->ab_name, oidbuf);
+                       return APPL_ERROR_PROCESSINGERROR;
+               }
+               log_info("%s: Can't register %s: Unsupported context \"%s\"",
+                   backend->ab_name, oidbuf, ctxname);
+               return APPL_ERROR_UNSUPPORTEDCONTEXT;
+       }
+       /* Default timeouts should be handled by backend */
+       if (timeout == 0)
+               fatalx("%s: Timeout can't be 0", __func__);
+       if (priority == 0) {
+               log_warnx("%s: Can't register %s: priority can't be 0",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+       if (range_subid > oid->bo_n) {
+               log_warnx("%s: Can't regiser %s: range_subid too large",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+       if (range_subid != 0 && oid->bo_id[range_subid] >= upper_bound) {
+               log_warnx("%s: Can't register %s: upper bound smaller or equal "
+                   "to range_subid", backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+       if (range_subid != 0)
+               lower_bound = oid->bo_id[range_subid];
+
+       if (range_subid == 0)
+               return appl_region(ctx, timeout, priority, oid, instance,
+                   subtree, backend);
+
+       do {
+               if ((error = appl_region(ctx, timeout, priority, oid, instance,
+                   subtree, backend)) != APPL_ERROR_NOERROR)
+                       goto fail;
+       } while (oid->bo_id[range_subid] != upper_bound);
+       if ((error = appl_region(ctx, timeout, priority, oid, instance, subtree,
+           backend)) != APPL_ERROR_NOERROR)
+               goto fail;
+
+       return APPL_ERROR_NOERROR;
+ fail:
+       search.ar_oid = *oid;
+       if (search.ar_oid.bo_id[range_subid] == lower_bound)
+               return error;
+       
+       for (search.ar_oid.bo_id[range_subid]--;
+           search.ar_oid.bo_id[range_subid] != lower_bound;
+           search.ar_oid.bo_id[range_subid]--) {
+               region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+               while (region->ar_priority != priority)
+                       region = region->ar_next;
+               appl_region_free(ctx, region);
+       }
+       region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+       while (region->ar_priority != priority)
+               region = region->ar_next;
+       appl_region_free(ctx, region);
+       return error;
+}
+
+/* Name from RFC 2741 section 6.2.4 */
+enum appl_error
+appl_unregister(const char *ctxname, uint8_t priority, struct ber_oid *oid,
+    uint8_t range_subid, uint32_t upper_bound, struct appl_backend *backend)
+{
+       struct appl_region *region, search;
+       struct appl_context *ctx;
+       char oidbuf[1024], subidbuf[11];
+       size_t i;
+
+       oidbuf[0] = '\0';
+       for (i = 0; i < oid->bo_n; i++) {
+               snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32, oid->bo_id[i]);
+               if (i != 0)
+                       strlcat(oidbuf, ".", sizeof(oidbuf));
+               if (range_subid == i + 1) {
+                       strlcat(oidbuf, "[", sizeof(oidbuf));
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+                       strlcat(oidbuf, "-", sizeof(oidbuf));
+                       snprintf(subidbuf, sizeof(subidbuf), "%"PRIu32,
+                           upper_bound);
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+                       strlcat(oidbuf, "]", sizeof(oidbuf));
+               } else
+                       strlcat(oidbuf, subidbuf, sizeof(oidbuf));
+       }
+
+       if (ctxname == NULL)
+               ctxname = "";
+       log_info("%s: Unregistering %s context(%s) priority(%"PRIu8")",
+            backend->ab_name, oidbuf,ctxname, priority);
+
+       if ((ctx = appl_context(ctxname, 0)) == NULL) {
+               if (errno == ENOMEM) {
+                       log_warn("%s: Can't unregister %s: Processing error",
+                           backend->ab_name, oidbuf);
+                       return APPL_ERROR_PROCESSINGERROR;
+               }
+               log_info("%s: Can't unregister %s: Unsupported context \"%s\"",
+                   backend->ab_name, oidbuf, ctxname);
+               return APPL_ERROR_UNSUPPORTEDCONTEXT;
+       }
+
+       if (priority == 0) {
+               log_warnx("%s: Can't unregister %s: priority can't be 0",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+
+       if (range_subid > oid->bo_n) {
+               log_warnx("%s: Can't unregiser %s: range_subid too large",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+       if (range_subid != 0 && oid->bo_id[range_subid] >= upper_bound) {
+               log_warnx("%s: Can't unregister %s: upper bound smaller or "
+                   "equal to range_subid", backend->ab_name, oidbuf);
+               return APPL_ERROR_PARSEERROR;
+       }
+
+       search.ar_oid = *oid;
+       while (range_subid != 0 &&
+           search.ar_oid.bo_id[range_subid] != upper_bound) {
+               region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+               while (region != NULL && region->ar_priority < priority)
+                       region = region->ar_next;
+               if (region == NULL || region->ar_priority != priority) {
+                       log_warnx("%s: Can't unregister %s: region not found",
+                           backend->ab_name, oidbuf);
+                       return APPL_ERROR_UNKNOWNREGISTRATION;
+               }
+               if (region->ar_backend != backend) {
+                       log_warnx("%s: Can't unregister %s: region not owned "
+                           "by backend", backend->ab_name, oidbuf);
+                       return APPL_ERROR_UNKNOWNREGISTRATION;
+               }
+       }
+       region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+       while (region != NULL && region->ar_priority < priority)
+               region = region->ar_next;
+       if (region == NULL || region->ar_priority != priority) {
+               log_warnx("%s: Can't unregister %s: region not found",
+                   backend->ab_name, oidbuf);
+               return APPL_ERROR_UNKNOWNREGISTRATION;
+       }
+       if (region->ar_backend != backend) {
+               log_warnx("%s: Can't unregister %s: region not owned "
+                   "by backend", backend->ab_name, oidbuf);
+               return APPL_ERROR_UNKNOWNREGISTRATION;
+       }
+
+       search.ar_oid = *oid;
+       while (range_subid != 0 &&
+           search.ar_oid.bo_id[range_subid] != upper_bound) {
+               region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+               while (region != NULL && region->ar_priority != priority)
+                       region = region->ar_next;
+               appl_region_free(ctx, region);
+       }
+       region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+       while (region != NULL && region->ar_priority != priority)
+               region = region->ar_next;
+       appl_region_free(ctx, region);
+
+       return APPL_ERROR_NOERROR;
+}
+
+void
+appl_region_free(struct appl_context *ctx, struct appl_region *region)
+{
+       struct appl_region *pregion;
+
+       pregion = RB_FIND(appl_regions, &(ctx->ac_regions), region);
+
+       if (pregion == region) {
+               RB_REMOVE(appl_regions, &(ctx->ac_regions), region);
+               if (region->ar_next != NULL)
+                       RB_INSERT(appl_regions, &(ctx->ac_regions),
+                           region->ar_next);
+       } else {
+               while (pregion->ar_next != region)
+                       pregion = pregion->ar_next;
+               pregion->ar_next = region->ar_next;
+       }
+
+       free(region);
+}
+
+/* backend is owned by the sub-application, just release application.c stuff */
+void
+appl_close(struct appl_backend *backend)
+{
+       struct appl_context *ctx;
+       struct appl_region *region, *tregion, *nregion;
+       struct appl_request_downstream *request, *trequest;
+
+       TAILQ_FOREACH(ctx, &contexts, ac_entries) {
+               RB_FOREACH_SAFE(region, appl_regions,
+                   &(ctx->ac_regions), tregion) {
+                       while (region != NULL) {
+                               nregion = region->ar_next;
+                               if (region->ar_backend == backend)
+                                       appl_region_free(ctx, region);
+                               region = nregion;
+                       }
+               }
+       }
+
+       RB_FOREACH_SAFE(request, appl_requests,
+           &(backend->ab_requests), trequest)
+       appl_request_downstream_free(request);
+}
+
+struct appl_region *
+appl_region_find(struct appl_context *ctx,
+    const struct ber_oid *oid)
+{
+       struct appl_region *region, search;
+
+       search.ar_oid = *oid;
+       while (search.ar_oid.bo_n > 0) {
+               region = RB_FIND(appl_regions, &(ctx->ac_regions), &search);
+               if (region != NULL)
+                       return region;
+               search.ar_oid.bo_n--;
+       }
+       return NULL;
+}
+
+struct appl_region *
+appl_region_next(struct appl_context *ctx, struct ber_oid *oid,
+    struct appl_region *cregion)
+{
+       struct appl_region search, *nregion, *pregion;
+       int cmp;
+
+       search.ar_oid = *oid;
+       nregion = RB_NFIND(appl_regions, &(ctx->ac_regions), &search);
+
+       if (cregion == nregion)
+               nregion = RB_NEXT(appl_regions, &(ctx->ac_regions), nregion);
+       /* Past last element in tree, we might still have a parent */
+       if (nregion == NULL) {
+               search.ar_oid = cregion->ar_oid;
+               search.ar_oid.bo_n--;
+               return appl_region_find(ctx, &(search.ar_oid));
+       }
+       cmp = appl_region_cmp(cregion, nregion);
+       if (cmp >= 0)
+               fatalx("%s: wrong OID order", __func__);
+       /* Direct descendant */
+       if (cmp == -2)
+               return nregion;
+
+       /* cmp == -1 */
+       search.ar_oid = cregion->ar_oid;
+       /* Find direct next sibling */
+       ober_oid_nextsibling(&(search.ar_oid));
+       if (ober_oid_cmp(&(nregion->ar_oid), &(search.ar_oid)) == 0)
+               return nregion;
+       /* Sibling gaps go to parent, or end end at border */
+       search.ar_oid = cregion->ar_oid;
+       search.ar_oid.bo_n--;
+       pregion = appl_region_find(ctx, &(search.ar_oid));
+
+       return pregion != NULL ? pregion : nregion;
+}
+
+/* Name from RFC 3413 section 3.2 */
+void
+appl_processpdu(struct snmp_message *statereference, const char *ctxname,
+    enum snmp_version pduversion, struct ber_element *pdu)
+{
+       struct appl_context *ctx;
+       struct appl_request_upstream *ureq;
+       struct ber_oid oid;
+       struct ber_element *value, *varbind, *varbindlist;
+       long long nonrepeaters, maxrepetitions;
+       static uint32_t transactionid;
+       int32_t requestid;
+       size_t i, varbindlen = 0, repeaterlen;
+
+       /* pdu must be ASN.1 validated in snmpe.c */
+       (void) ober_scanf_elements(pdu, "{iiie", &requestid, &nonrepeaters,
+           &maxrepetitions, &varbindlist);
+
+       /* RFC 3413, section 3.2, processPDU, item 5, final bullet */
+       if ((ctx = appl_context(ctxname, 0)) == NULL) {
+               oid = BER_OID(MIB_snmpUnknownContexts, 0);
+               snmp_target_mib.snmp_unknowncontexts++;
+               if ((value = ober_add_integer(NULL,
+                   snmp_target_mib.snmp_unknowncontexts)) == NULL)
+                       fatal("ober_add_integer");
+               appl_report(statereference, requestid, &oid, value);
+               return;
+       }
+
+       if ((ureq = malloc(sizeof(*ureq))) == NULL)
+               fatal("malloc");
+
+       ureq->aru_ctx = ctx;
+       ureq->aru_statereference = statereference;
+       ureq->aru_transactionid = transactionid++;
+       ureq->aru_requestid = requestid;
+       ureq->aru_error = APPL_ERROR_NOERROR;
+       ureq->aru_index = 0;
+       ureq->aru_nonrepeaters = nonrepeaters;
+       ureq->aru_maxrepetitions = maxrepetitions;
+       ureq->aru_varbindlen = 0;
+       ureq->aru_locked = 0;
+       ureq->aru_pduversion = pduversion;
+       ureq->aru_pdu = pdu;
+
+       varbind = varbindlist->be_sub;
+       for (; varbind != NULL; varbind = varbind->be_next)
+               varbindlen++;
+
+       repeaterlen = varbindlen - nonrepeaters;
+       if (pdu->be_type == SNMP_C_GETBULKREQ)
+               ureq->aru_varbindlen = nonrepeaters +
+                   (repeaterlen * maxrepetitions);
+       else
+               ureq->aru_varbindlen = varbindlen;
+       if ((ureq->aru_vblist = calloc(ureq->aru_varbindlen,
+           sizeof(*ureq->aru_vblist))) == NULL)
+               fatal("malloc");
+
+       varbind = varbindlist->be_sub;
+       /* Use aru_varbindlen in case maxrepetitions == 0 */
+       for (i = 0; i < ureq->aru_varbindlen; i++) {
+               ureq->aru_vblist[i].avi_request_upstream = ureq;
+               ureq->aru_vblist[i].avi_index = i + 1;
+               ureq->aru_vblist[i].avi_state = APPL_VBSTATE_NEW;
+               /* This can only happen with bulkreq */
+               if (varbind == NULL) {
+                       ureq->aru_vblist[i - repeaterlen].avi_sub =
+                           &(ureq->aru_vblist[i]);
+                       ureq->aru_vblist[i].avi_state = APPL_VBSTATE_MUSTFILL;
+                       continue;
+               }
+               ober_get_oid(varbind->be_sub,
+                   &(ureq->aru_vblist[i].avi_varbind.av_oid));
+               if (i + 1 < ureq->aru_varbindlen) {
+                       ureq->aru_vblist[i].avi_next =
+                           &(ureq->aru_vblist[i + 1]);
+                       ureq->aru_vblist[i].avi_varbind.av_next =
+                           &(ureq->aru_vblist[i + 1].avi_varbind);
+               } else {
+                       ureq->aru_vblist[i].avi_next = NULL;
+                       ureq->aru_vblist[i].avi_varbind.av_next = NULL;
+               }
+               varbind = varbind->be_next;
+       }
+
+       appl_pdu_log(NULL, pdu->be_type, requestid, nonrepeaters,
+           maxrepetitions, &(ureq->aru_vblist[0].avi_varbind));
+
+       appl_request_upstream_resolve(ureq);
+}
+
+void
+appl_request_upstream_free(struct appl_request_upstream *ureq)
+{
+       size_t i;
+       struct appl_varbind_internal *vb;
+
+       if (ureq == NULL)
+               return;
+
+       for (i = 0; i < ureq->aru_varbindlen && ureq->aru_vblist != NULL; i++) {
+               vb = &(ureq->aru_vblist[i]);
+               ober_free_elements(vb->avi_varbind.av_value);
+               appl_request_downstream_free(vb->avi_request_downstream);
+       }
+       free(ureq->aru_vblist);
+
+       assert(ureq->aru_statereference == NULL);
+
+       free(ureq);
+}
+
+void
+appl_request_downstream_free(struct appl_request_downstream *dreq)
+{
+       struct appl_varbind_internal *vb;
+
+       if (dreq == NULL)
+               return;
+
+       RB_REMOVE(appl_requests, &(dreq->ard_backend->ab_requests), dreq);
+       evtimer_del(&(dreq->ard_timer));
+
+       for (vb = dreq->ard_vblist; vb != NULL; vb = vb->avi_next)
+               vb->avi_request_downstream = NULL;
+
+       free(dreq);
+}
+
+void
+appl_request_upstream_resolve(struct appl_request_upstream *ureq)
+{
+       struct appl_varbind_internal *vb, *lvb, *tvb;
+       struct appl_request_downstream *dreq;
+       struct appl_region *region, *lregion;
+       struct timeval tv;
+       int done;
+       size_t i;
+       int32_t maxrepetitions;
+       int32_t timeout;
+
+       if (ureq->aru_locked)
+               return;
+       ureq->aru_locked = 1;
+
+       if (ureq->aru_pdu->be_type == SNMP_C_SETREQ) {
+               ureq->aru_error = APPL_ERROR_NOTWRITABLE;
+               ureq->aru_index = 1;
+               appl_request_upstream_reply(ureq);
+               return;
+       }
+
+ next:
+       dreq = NULL;
+       lvb = NULL;
+       done = 1;
+       timeout = 0;
+
+       if (ureq->aru_error != APPL_ERROR_NOERROR) {
+               appl_request_upstream_reply(ureq);
+               return;
+       }
+       for (i = 0; i < ureq->aru_varbindlen; i++) {
+               vb = &(ureq->aru_vblist[i]);
+
+               switch (vb->avi_state) {
+               case APPL_VBSTATE_MUSTFILL:
+               case APPL_VBSTATE_PENDING:
+                       done = 0;
+                       continue;
+               case APPL_VBSTATE_DONE:
+                       continue;
+               case APPL_VBSTATE_NEW:
+                       break;
+               }
+               if (appl_varbind_backend(vb) == -1)
+                       fatal("appl_varbind_backend");
+               if (vb->avi_state != APPL_VBSTATE_DONE)
+                       done = 0;
+       }
+
+       for (i = 0; i < ureq->aru_varbindlen; i++) {
+               vb = &(ureq->aru_vblist[i]);
+
+               if (vb->avi_state != APPL_VBSTATE_NEW)
+                       continue;
+
+               vb = &(ureq->aru_vblist[i]);
+               region = vb->avi_region;
+               lregion = lvb != NULL ? lvb->avi_region : NULL;
+               if (lvb != NULL && region->ar_backend != lregion->ar_backend)
+                       continue;
+
+               vb->avi_varbind.av_next = NULL;
+               vb->avi_next = NULL;
+               tvb = vb;
+               for (maxrepetitions = 0; tvb != NULL; tvb = tvb->avi_sub)
+                       maxrepetitions++;
+               if (dreq == NULL) {
+                       if ((dreq = malloc(sizeof(*dreq))) == NULL)
+                               fatal("malloc");
+
+                       dreq->ard_request = ureq;
+                       dreq->ard_vblist = vb;
+                       dreq->ard_backend = vb->avi_region->ar_backend;
+                       dreq->ard_retries = dreq->ard_backend->ab_retries;
+                       dreq->ard_requesttype = ureq->aru_pdu->be_type;
+                       /*
+                        * We don't yet fully handle bulkrequest responses.
+                        * It's completely valid to map onto getrequest.
+                        * maxrepetitions calculated in preparation of support.
+                        */
+                       if (dreq->ard_requesttype == SNMP_C_GETBULKREQ &&
+                           dreq->ard_backend->ab_fn->ab_getbulk == NULL)
+                               dreq->ard_requesttype = SNMP_C_GETNEXTREQ;
+                       /*
+                        * If first varbind is nonrepeater, set maxrepetitions
+                        * to 0, so that the next varbind with
+                        * maxrepetitions > 1 determines length.
+                        */
+                       dreq->ard_maxrepetitions = maxrepetitions == 1 ?
+                           0 : maxrepetitions;
+                       dreq->ard_nonrepeaters = maxrepetitions == 1 ? 1 : 0;
+                       do {
+                               dreq->ard_requestid = arc4random();
+                       } while (RB_INSERT(appl_requests,
+                           &(dreq->ard_backend->ab_requests), dreq) != NULL);
+                       lvb = vb;
+               /* avi_sub isn't set on !bulkrequest, so we always enter here */
+               } else if (maxrepetitions == 1) {
+                       dreq->ard_nonrepeaters++;
+                       vb->avi_varbind.av_next =
+                           &(dreq->ard_vblist->avi_varbind);
+                       vb->avi_next = dreq->ard_vblist;
+                       dreq->ard_vblist = vb;
+               } else {
+                       lvb->avi_varbind.av_next = &(vb->avi_varbind);
+                       lvb->avi_next = vb;
+                       /* RFC 2741 section 7.2.1.3:
+                        * The value of g.max_repetitions in the GetBulk-PDU may
+                        * be less than (but not greater than) the value in the
+                        * original request PDU.
+                        */
+                       if (dreq->ard_maxrepetitions > maxrepetitions ||
+                           dreq->ard_maxrepetitions == 0)
+                               dreq->ard_maxrepetitions = maxrepetitions;
+                       lvb = vb;
+               }
+               vb->avi_request_downstream = dreq;
+               vb->avi_state = APPL_VBSTATE_PENDING;
+               if (region->ar_timeout > timeout)
+                       timeout = region->ar_timeout;
+       }
+
+       if (dreq == NULL) {
+               ureq->aru_locked = 0;
+               if (done)
+                       appl_request_upstream_reply(ureq);
+               return;
+       }
+
+       tv.tv_sec = timeout / 100;
+       tv.tv_usec = (timeout % 100) * 10000;
+       evtimer_set(&(dreq->ard_timer), appl_request_downstream_timeout, dreq);
+       evtimer_add(&(dreq->ard_timer), &tv);
+
+       appl_request_downstream_send(dreq);
+       goto next;
+}
+
+void
+appl_request_downstream_send(struct appl_request_downstream *dreq)
+{
+
+       appl_pdu_log(dreq->ard_backend, dreq->ard_requesttype,
+           dreq->ard_requestid, 0, 0, &(dreq->ard_vblist->avi_varbind));
+
+       if (dreq->ard_requesttype == SNMP_C_GETREQ) {
+               dreq->ard_backend->ab_fn->ab_get(dreq->ard_backend,
+                   dreq->ard_request->aru_transactionid,
+                   dreq->ard_requestid,
+                   APPL_CONTEXT_NAME(dreq->ard_request->aru_ctx),
+                   &(dreq->ard_vblist->avi_varbind));
+       } else if (dreq->ard_requesttype == SNMP_C_GETNEXTREQ) {
+               dreq->ard_backend->ab_fn->ab_getnext(dreq->ard_backend,
+                   dreq->ard_request->aru_transactionid,
+                   dreq->ard_requestid,
+                   APPL_CONTEXT_NAME(dreq->ard_request->aru_ctx),
+                   &(dreq->ard_vblist->avi_varbind));
+       }
+}
+
+void
+appl_request_downstream_timeout(__unused int fd, __unused short event,
+    void *cookie)
+{
+       struct appl_request_downstream *dreq = cookie;
+
+       log_info("%s: %"PRIu32" timed out%s",
+           dreq->ard_backend->ab_name, dreq->ard_requestid,
+           dreq->ard_retries > 0 ? ": retrying" : "");
+       if (dreq->ard_retries > 0) {
+               dreq->ard_retries--;
+               appl_request_downstream_send(dreq);
+       } else
+               appl_response(dreq->ard_backend, dreq->ard_requestid,
+                   APPL_ERROR_GENERR, 1, &(dreq->ard_vblist->avi_varbind));
+}
+
+void
+appl_request_upstream_reply(struct appl_request_upstream *ureq)
+{
+       struct ber_element *varbindlist = NULL, *varbind = NULL, *value;
+       struct appl_varbind_internal *vb;
+       size_t i, repvarbinds, varbindlen;
+       ssize_t match = -1;
+
+       /* RFC 3416 section 4.2.3: Strip excessive EOMV */
+       varbindlen = ureq->aru_varbindlen;
+       if (ureq->aru_pdu->be_type == SNMP_C_GETBULKREQ &&
+           ureq->aru_error == APPL_ERROR_NOERROR) {
+               repvarbinds = (ureq->aru_varbindlen - ureq->aru_nonrepeaters) /
+                   ureq->aru_maxrepetitions;
+               for (i = ureq->aru_nonrepeaters;
+                   i < ureq->aru_varbindlen - repvarbinds; i++) {
+                       value = ureq->aru_vblist[i].avi_varbind.av_value;
+                       if ((i - ureq->aru_nonrepeaters) % repvarbinds == 0 &&
+                           value->be_class == BER_CLASS_CONTEXT &&
+                           value->be_type == APPL_EXC_ENDOFMIBVIEW) {
+                               if (match != -1)
+                                       break;
+                               match = i;
+                       }
+                       if (value->be_class != BER_CLASS_CONTEXT ||
+                           value->be_type != APPL_EXC_ENDOFMIBVIEW)
+                               match = -1;
+               }
+               if (match != -1)
+                       varbindlen = match + repvarbinds;
+       }
+
+       i = 0;
+       for (; i < varbindlen && ureq->aru_error == APPL_ERROR_NOERROR; i++) {
+               vb = &(ureq->aru_vblist[i]);
+               vb->avi_varbind.av_next =
+                   &(ureq->aru_vblist[i + 1].avi_varbind);
+               /* RFC 3584 section 4.2.2.2 */
+               if (ureq->aru_pduversion == SNMP_V1 &&
+                   vb->avi_varbind.av_value->be_class == BER_CLASS_CONTEXT)
+                       appl_varbind_error(vb, APPL_ERROR_NOSUCHNAME);
+       }
+       /* RFC 3584 section 4.4 */
+       if (ureq->aru_pduversion == SNMP_V1) {
+               switch (ureq->aru_error) {
+               case APPL_ERROR_WRONGVALUE:
+               case APPL_ERROR_WRONGENCODING:
+               case APPL_ERROR_WRONGTYPE:
+               case APPL_ERROR_WRONGLENGTH:
+               case APPL_ERROR_INCONSISTENTVALUE:
+                       ureq->aru_error = APPL_ERROR_BADVALUE;
+                       break;
+               case APPL_ERROR_NOACCESS:
+               case APPL_ERROR_NOTWRITABLE:
+               case APPL_ERROR_NOCREATION:
+               case APPL_ERROR_INCONSISTENTNAME:
+               case APPL_ERROR_AUTHORIZATIONERROR:
+                       ureq->aru_error = APPL_ERROR_NOSUCHNAME;
+                       break;
+               case APPL_ERROR_RESOURCEUNAVAILABLE:
+               case APPL_ERROR_COMMITFAILED:
+               case APPL_ERROR_UNDOFAILED:
+                       ureq->aru_error = APPL_ERROR_GENERR;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       ureq->aru_vblist[i - 1].avi_varbind.av_next = NULL;
+       /* XXX On error, print the original string, not the half filled goop */
+       appl_pdu_log(NULL, SNMP_C_RESPONSE, ureq->aru_requestid,
+           ureq->aru_error, ureq->aru_index,
+           &(ureq->aru_vblist[0].avi_varbind));
+
+       if (ureq->aru_error != APPL_ERROR_NOERROR) {
+               ober_scanf_elements(ureq->aru_pdu, "{SSSe", &varbindlist);
+               varbindlist = ober_unlink_elements(varbindlist);
+       } else {
+               for (i = 0; i < varbindlen; i++) {
+                       varbind = ober_printf_elements(varbind, "{Oe}",
+                           &(ureq->aru_vblist[i].avi_varbind.av_oid),
+                           ureq->aru_vblist[i].avi_varbind.av_value);
+                       ureq->aru_vblist[i].avi_varbind.av_value = NULL;
+                       if (varbind == NULL)
+                               fatal("ober_printf_elements");
+                       if (varbindlist == NULL)
+                               varbindlist = varbind;
+               }
+       }
+
+       snmpe_send(ureq->aru_statereference, SNMP_C_RESPONSE,
+           ureq->aru_requestid, ureq->aru_error, ureq->aru_index, varbindlist);
+       ureq->aru_statereference = NULL;
+       appl_request_upstream_free(ureq);
+}
+
+/* Name from RFC 2741 section 6.2.16 */
+void
+appl_response(struct appl_backend *backend, int32_t requestid,
+    enum appl_error error, int16_t index, struct appl_varbind *vblist)
+{
+       struct appl_request_downstream *dreq, search;
+       struct appl_request_upstream *ureq = NULL;
+       struct appl_region *nregion;
+       const char *errstr;
+       char oidbuf[1024];
+       enum snmp_pdutype pdutype;
+       struct appl_varbind *vb;
+       struct appl_varbind_internal *origvb = NULL;
+       int invalid = 0;
+       int next = 0;
+       int32_t i;
+
+       appl_pdu_log(backend, SNMP_C_RESPONSE, requestid, error, index, vblist);
+
+       search.ard_requestid = requestid;
+       dreq = RB_FIND(appl_requests, &(backend->ab_requests), &search);
+       if (dreq == NULL) {
+               log_debug("%s: %"PRIu32" not outstanding",
+                   backend->ab_name, requestid);
+               /* Continue to verify validity */
+       } else {
+               ureq = dreq->ard_request;
+               pdutype = ureq->aru_pdu->be_type;
+               next = pdutype == SNMP_C_GETNEXTREQ ||
+                   pdutype == SNMP_C_GETBULKREQ;
+               origvb = dreq->ard_vblist;
+       }
+
+       vb = vblist;
+       for (i = 1; vb != NULL; vb = vb->av_next, i++) {
+               if (!appl_varbind_valid(vb, &(origvb->avi_varbind), next,
+                   error != APPL_ERROR_NOERROR, &errstr)) {
+                       smi_oid2string(&(vb->av_oid), oidbuf,
+                           sizeof(oidbuf), 0);
+                       log_warnx("%s: %"PRIu32" %s: %s",
+                           backend->ab_name, requestid, oidbuf, errstr);
+                       invalid = 1;
+               }
+               /* Tranfer av_value */
+               if (origvb != NULL) {
+                       if (error != APPL_ERROR_NOERROR && i == index)
+                               appl_varbind_error(origvb, error);
+                       origvb->avi_state = APPL_VBSTATE_DONE;
+                       origvb->avi_varbind.av_oid = vb->av_oid;
+                       if (vb->av_value->be_class == BER_CLASS_CONTEXT &&
+                           vb->av_value->be_type == APPL_EXC_ENDOFMIBVIEW) {
+                               nregion = appl_region_next(ureq->aru_ctx,
+                                   &(vb->av_oid), origvb->avi_region);
+                               if (nregion != NULL) {
+                                       ober_free_elements(vb->av_value);
+                                       origvb->avi_varbind.av_oid =
+                                           nregion->ar_oid;
+                                       origvb->avi_varbind.av_include = 1;
+                                       vb->av_value = NULL;
+                                       origvb->avi_state = APPL_VBSTATE_NEW;
+                               }
+                       }
+                       /* RFC 3584 section 4.2.2.1 */
+                       if (ureq->aru_pduversion == SNMP_V1 &&
+                           vb->av_value != NULL &&
+                           vb->av_value->be_class == BER_CLASS_APPLICATION &&
+                           vb->av_value->be_type == SNMP_COUNTER64) {
+                               if (ureq->aru_pdu->be_type == SNMP_C_GETREQ) {
+                                       appl_varbind_error(origvb,
+                                           APPL_ERROR_NOSUCHNAME);
+                               } else {
+                                       ober_free_elements(vb->av_value);
+                                       vb->av_value = NULL;
+                                       origvb->avi_varbind.av_oid = vb->av_oid;
+                                       origvb->avi_varbind.av_include = 0;
+                                       origvb->avi_state = APPL_VBSTATE_NEW;
+                               }
+                       }
+                       origvb->avi_varbind.av_value = vb->av_value;
+                       if (origvb->avi_varbind.av_next == NULL &&
+                           vb->av_next != NULL) {
+                               log_warnx("%s: Request %"PRIu32" returned more "
+                                   "varbinds then requested",
+                                   backend->ab_name, requestid);
+                               invalid = 1;
+                       }
+                       if (origvb->avi_sub != NULL &&
+                           origvb->avi_state == APPL_VBSTATE_DONE) {
+                               origvb->avi_sub->avi_varbind.av_oid =
+                                   origvb->avi_varbind.av_oid;
+                               origvb->avi_sub->avi_state = APPL_VBSTATE_NEW;
+                       }
+                       origvb = origvb->avi_next;
+               } else {
+                       ober_free_elements(vb->av_value);
+                       vb->av_value = NULL;
+               }
+       }
+       if (error != APPL_ERROR_NOERROR && (index <= 0 || index >= i)) {
+               log_warnx("Invalid error index");
+               invalid = 1;
+       }
+       if (error == APPL_ERROR_NOERROR && index != 0) {
+               log_warnx("error index with no error");
+               invalid = 1;
+       }
+       if (vb == NULL && origvb != NULL) {
+               log_warnx("%s: Request %"PRIu32" returned less varbinds then "
+                   "requested", backend->ab_name, requestid);
+               invalid = 1;
+       }
+
+       if (dreq != NULL) {
+               if (invalid)
+                       appl_varbind_error(dreq->ard_vblist, APPL_ERROR_GENERR);
+               appl_request_downstream_free(dreq);
+       }
+
+       if (invalid && backend->ab_fn->ab_close != NULL) {
+               log_warnx("%s: Closing: Too many parse errors",
+                   backend->ab_name);
+               backend->ab_fn->ab_close(backend, APPL_CLOSE_REASONPARSEERROR);
+       }
+
+       if (ureq != NULL)
+               appl_request_upstream_resolve(ureq);
+}
+
+int
+appl_varbind_valid(struct appl_varbind *varbind, struct appl_varbind *request,
+    int next, int null, const char **errstr)
+{
+       int cmp;
+       int eomv = 0;
+
+       if (varbind->av_value == NULL) {
+               *errstr = "missing value";
+               return 0;
+       }
+       if (varbind->av_value->be_class == BER_CLASS_UNIVERSAL) {
+               switch (varbind->av_value->be_type) {
+               case BER_TYPE_NULL:
+                       if (null)
+                               break;
+                       *errstr = "unexpected null value";
+                       return 0;
+               case BER_TYPE_INTEGER:
+               case BER_TYPE_OCTETSTRING:
+               case BER_TYPE_OBJECT:
+                       if (!null)
+                               break;
+                       /* FALLTHROUGH */
+               default:
+                       *errstr = "invalid value";
+                       return 0;
+               }
+       } else if (varbind->av_value->be_class == BER_CLASS_APPLICATION) {
+               switch (varbind->av_value->be_type) {
+               case SNMP_T_IPADDR:
+               case SNMP_T_COUNTER32:
+               case SNMP_T_GAUGE32:
+               case SNMP_T_TIMETICKS:
+               case SNMP_T_OPAQUE:
+               case SNMP_T_COUNTER64:
+                       if (!null)
+                               break;
+                       /* FALLTHROUGH */
+               default:
+                       *errstr = "expecting null value";
+                       return 0;
+               }
+       } else if (varbind->av_value->be_class == BER_CLASS_CONTEXT) {
+               switch (varbind->av_value->be_type) {
+               case APPL_EXC_NOSUCHOBJECT:
+                       if (next && request != NULL) {
+                               *errstr = "Unexpected noSuchObject";
+                               return 0;
+                       }
+                       /* FALLTHROUGH */
+               case APPL_EXC_NOSUCHINSTANCE:
+                       if (null) {
+                               *errstr = "expecting null value";
+                               return 0;
+                       }
+                       if (next && request != NULL) {
+                               *errstr = "Unexpected noSuchInstance";
+                               return 0;
+                       }
+                       break;
+               case APPL_EXC_ENDOFMIBVIEW:
+                       if (null) {
+                               *errstr = "expecting null value";
+                               return 0;
+                       }
+                       if (!next && request != NULL) {
+                               *errstr = "Unexpected endOfMibView";
+                               return 0;
+                       }
+                       eomv = 1;
+                       break;
+               default:
+                       *errstr = "invalid exception";
+                       return 0;
+               }
+       } else {
+               *errstr = "invalid value";
+               return 0;
+       }
+
+       if (request == NULL)
+               return 1;
+
+       cmp = ober_oid_cmp(&(request->av_oid), &(varbind->av_oid));
+       if (next && !eomv) {
+               if (request->av_include) {
+                       if (cmp > 0) {
+                               *errstr = "oid not incrementing";
+                               return 0;
+                       }
+               } else {
+                       if (cmp >= 0) {
+                               *errstr = "oid not incrementing";
+                               return 0;
+                       }
+               }
+       } else {
+               if (cmp != 0) {
+                       *errstr = "oids not equal";
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+int
+appl_varbind_backend(struct appl_varbind_internal *ivb)
+{
+       struct appl_request_upstream *ureq = ivb->avi_request_upstream;
+       struct appl_region search, *region, *pregion;
+       struct appl_varbind *vb = &(ivb->avi_varbind);
+       int next, cmp;
+
+       next = ureq->aru_pdu->be_type == SNMP_C_GETNEXTREQ ||
+           ureq->aru_pdu->be_type == SNMP_C_GETBULKREQ;
+
+       region = appl_region_find(ureq->aru_ctx, &(vb->av_oid));
+       if (region == NULL) {
+               if (!next) {
+                       vb->av_value = appl_exception(APPL_EXC_NOSUCHOBJECT);
+                       ivb->avi_state = APPL_VBSTATE_DONE;
+                       if (vb->av_value == NULL)
+                               return -1;
+                       return 0;
+               }
+               search.ar_oid = vb->av_oid;
+               region = RB_NFIND(appl_regions,
+                   &(ureq->aru_ctx->ac_regions), &search);
+               if (region == NULL)
+                       goto eomv;
+               vb->av_oid = region->ar_oid;
+               vb->av_include = 1;
+       }
+       cmp = ober_oid_cmp(&(region->ar_oid), &(vb->av_oid));
+       if (cmp == -2) {
+               if (region->ar_instance) {
+                       if (!next) {
+                               vb->av_value =
+                                   appl_exception(APPL_EXC_NOSUCHINSTANCE);
+                               ivb->avi_state = APPL_VBSTATE_DONE;
+                               if (vb->av_value == NULL)
+                                       return -1;
+                               return 0;
+                       }
+                       if ((region = appl_region_next(ureq->aru_ctx,
+                           &(vb->av_oid), region)) == NULL)
+                               goto eomv;
+                       vb->av_oid = region->ar_oid;
+                       vb->av_include = 1;
+               }
+       } else if (cmp == 0) {
+               if (region->ar_instance && next && !vb->av_include) {
+                       if ((region = appl_region_next(ureq->aru_ctx,
+                           &(vb->av_oid), region)) == NULL)
+                               goto eomv;
+                       vb->av_oid = region->ar_oid;
+                       vb->av_include = 1;
+               }
+       }
+       ivb->avi_region = region;
+       if (next) {
+               do {
+                       pregion = region;
+                       region = appl_region_next(ureq->aru_ctx,
+                           &(region->ar_oid), pregion);
+               } while (region != NULL &&
+                   region->ar_backend == pregion->ar_backend);
+               if (region == NULL) {
+                       vb->av_oid_end = pregion->ar_oid;
+                       if (pregion->ar_instance &&
+                           vb->av_oid_end.bo_n < BER_MAX_OID_LEN)
+                               vb->av_oid_end.bo_id[vb->av_oid_end.bo_n++] = 0;
+                       else
+                               ober_oid_nextsibling(&(vb->av_oid_end));
+               } else {
+                       if (ober_oid_cmp(&(region->ar_oid),
+                           &(ivb->avi_region->ar_oid)) == 2)
+                               vb->av_oid_end = region->ar_oid;
+                       else {
+                               vb->av_oid_end = ivb->avi_region->ar_oid;
+                               ober_oid_nextsibling(&(vb->av_oid_end));
+                       }
+               }
+       }
+       return 0;
+
+ eomv:
+       do {
+               ivb->avi_varbind.av_value =
+                   appl_exception(APPL_EXC_ENDOFMIBVIEW);
+               ivb->avi_state = APPL_VBSTATE_DONE;
+               if (ivb->avi_varbind.av_value == NULL)
+                       return -1;
+               if (ivb->avi_sub != NULL)
+                       ivb->avi_sub->avi_varbind.av_oid =
+                           ivb->avi_varbind.av_oid;
+               ivb = ivb->avi_sub;
+       } while (ivb != NULL);
+
+       return 0;
+}
+
+void
+appl_varbind_error(struct appl_varbind_internal *avi, enum appl_error error)
+{
+       struct appl_request_upstream *ureq = avi->avi_request_upstream;
+
+       if (ureq->aru_error == APPL_ERROR_GENERR)
+               return;
+       if (ureq->aru_error != APPL_ERROR_NOERROR && error != APPL_ERROR_GENERR)
+               return;
+       ureq->aru_error = error;
+       ureq->aru_index = avi->avi_index;
+}
+
+void
+appl_report(struct snmp_message *msg, int32_t requestid, struct ber_oid *oid,
+    struct ber_element *value)
+{
+       struct ber_element *varbind;
+
+       varbind = ober_printf_elements(NULL, "{Oe}", oid, value);
+       if (varbind == NULL) {
+               log_warn("%"PRId32": ober_printf_elements", requestid);
+               ober_free_elements(value);
+               snmp_msgfree(msg);
+               return;
+       }
+
+       snmpe_send(msg, SNMP_C_REPORT, requestid, 0, 0, varbind);
+}
+
+struct ber_element *
+appl_exception(enum appl_exception type)
+{
+       struct ber_element *value;
+
+       if ((value = ober_add_null(NULL)) == NULL) {
+               log_warn("malloc");
+               return NULL;
+       }
+       ober_set_header(value, BER_CLASS_CONTEXT, type);
+
+       return value;
+}
+
+void
+appl_pdu_log(struct appl_backend *backend, enum snmp_pdutype pdutype,
+    int32_t requestid, uint16_t error, uint16_t index,
+    struct appl_varbind *vblist)
+{
+       struct appl_varbind *vb;
+       char buf[1024], oidbuf[1024], *str;
+       int next;
+
+       if (log_getverbose() < 2)
+               return;
+
+       next = (pdutype == SNMP_C_GETNEXTREQ || pdutype == SNMP_C_GETBULKREQ);
+
+       buf[0] = '\0';
+       for (vb = vblist; vb != NULL; vb = vb->av_next) {
+               strlcat(buf, "{", sizeof(buf));
+               strlcat(buf, smi_oid2string(&(vb->av_oid), oidbuf,
+                   sizeof(oidbuf), 0), sizeof(buf));
+               if (next) {
+                       if (vb->av_include)
+                               strlcat(buf, "(incl)", sizeof(buf));
+                       if (vb->av_oid_end.bo_n > 0) {
+                               strlcat(buf, "-", sizeof(buf));
+                               strlcat(buf, smi_oid2string(&(vb->av_oid_end),
+                                   oidbuf, sizeof(oidbuf), 0), sizeof(buf));
+                       }
+               }
+               strlcat(buf, ":", sizeof(buf));
+               if (vb->av_value != NULL) {
+                       str = smi_print_element(vb->av_value);
+                       strlcat(buf, str == NULL ? "???" : str, sizeof(buf));
+                       free(str);
+               } else
+                       strlcat(buf, "null", sizeof(buf));
+               strlcat(buf, "}", sizeof(buf));
+       }
+       log_debug("%s%s%s{%"PRId32", %"PRIu16", %"PRIu16", {%s}}",
+           backend != NULL ? backend->ab_name : "",
+           backend != NULL ? ": " : "",
+           snmpe_pdutype2string(pdutype), requestid, error, index, buf);
+}
+
+void
+ober_oid_nextsibling(struct ber_oid *oid)
+{
+       while (oid->bo_n > 0) {
+               oid->bo_id[oid->bo_n - 1]++;
+               /* Overflow check */
+               if (oid->bo_id[oid->bo_n - 1] != 0)
+                       return;
+               oid->bo_n--;
+       }
+}
+
+int
+appl_region_cmp(struct appl_region *r1, struct appl_region *r2)
+{
+       return ober_oid_cmp(&(r1->ar_oid), &(r2->ar_oid));
+}
+
+int
+appl_request_cmp(struct appl_request_downstream *r1,
+    struct appl_request_downstream *r2)
+{
+       return r1->ard_requestid < r2->ard_requestid ? -1 :
+           r1->ard_requestid > r2->ard_requestid;
+}
+
+RB_GENERATE_STATIC(appl_regions, appl_region, ar_entry, appl_region_cmp);
+RB_GENERATE_STATIC(appl_requests, appl_request_downstream, ard_entry,
+    appl_request_cmp);
Index: application_legacy.c
===================================================================
RCS file: application_legacy.c
diff -N application_legacy.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ application_legacy.c        1 Nov 2021 17:47:16 -0000
@@ -0,0 +1,173 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Martijn van Duren <mart...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <event.h>
+#include <stdlib.h>
+
+#include "application.h"
+
+#include "snmpd.h"
+
+struct appl_varbind *appl_legacy_response(size_t);
+void appl_legacy_get(struct appl_backend *, int32_t, int32_t, const char *,
+    struct appl_varbind *);
+void appl_legacy_getnext(struct appl_backend *, int32_t, int32_t, const char *,
+    struct appl_varbind *);
+
+struct appl_backend_functions appl_legacy_functions = {
+       .ab_get = appl_legacy_get,
+       .ab_getnext = appl_legacy_getnext,
+       .ab_getbulk = NULL, /* Legacy getbulk implementation is broken */
+};
+
+struct appl_backend appl_legacy = {
+       .ab_name = "legacy backend",
+       .ab_cookie = NULL,
+       .ab_retries = 0,
+       .ab_fn = &appl_legacy_functions
+};
+
+static struct ber_element *root;
+static struct appl_varbind *response = NULL;
+static size_t responsesz = 0;
+
+void
+appl_legacy_init(void)
+{
+       struct oid *object = NULL;
+       struct ber_oid oid;
+
+       while ((object = smi_foreach(object, OID_RD)) != NULL) {
+               oid = object->o_id;
+               if (!(object->o_flags & OID_TABLE))
+                       oid.bo_id[oid.bo_n++] = 0;
+               appl_register(NULL, 150, 1, &oid,
+                   !(object->o_flags & OID_TABLE), 1, 0, 0, &appl_legacy);
+       }
+
+       if ((root = ober_add_sequence(NULL)) == NULL)
+               fatal("%s: Failed to init root", __func__);
+}
+
+void
+appl_legacy_shutdown(void)
+{
+       appl_close(&appl_legacy);
+
+       ober_free_elements(root);
+       free(response);
+}
+
+struct appl_varbind *
+appl_legacy_response(size_t nvarbind)
+{
+       struct appl_varbind *tmp;
+       size_t i;
+
+       if (responsesz < nvarbind) {
+               if ((tmp = recallocarray(response, responsesz, nvarbind,
+                   sizeof(*response))) == NULL) {
+                       log_warn(NULL);
+                       return NULL;
+               }
+               responsesz = nvarbind;
+               response = tmp;
+       }
+       for (i = 0; i < nvarbind; i++)
+               response[i].av_next = i + 1 == nvarbind ?
+                   NULL : &(response[i + 1]);
+       return response;
+}
+
+void
+appl_legacy_get(struct appl_backend *backend, __unused int32_t transactionid,
+    int32_t requestid, __unused const char *ctx, struct appl_varbind *vblist)
+{
+       size_t i;
+       struct ber_element *elm;
+       struct appl_varbind *vb, *rvb, *rvblist;
+
+       for (i = 0, vb = vblist; vb != NULL; vb = vb->av_next)
+               i++;
+       if ((rvblist = appl_legacy_response(i)) == NULL) {
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vb);
+               return;
+       }
+       rvb = rvblist;
+       for (vb = vblist; vb != NULL; vb = vb->av_next, rvb = rvb->av_next) {
+               (void)mps_getreq(NULL, root, &(vb->av_oid), 1);
+               elm = ober_unlink_elements(root);
+               ober_get_oid(elm, &(rvb->av_oid));
+               rvb->av_value = ober_unlink_elements(elm);
+               ober_free_elements(elm);
+       }
+
+       appl_response(backend, requestid, APPL_ERROR_NOERROR, 0, rvblist);
+}
+
+void
+appl_legacy_getnext(struct appl_backend *backend,
+    __unused int32_t transactionid, int32_t requestid, __unused const char 
*ctx,
+    struct appl_varbind *vblist)
+{
+       size_t i;
+       struct ber_element *elm;
+       struct appl_varbind *vb, *rvb, *rvblist;
+       struct snmp_message msg;
+       int ret;
+
+       for (i = 0, vb = vblist; vb != NULL; vb = vb->av_next)
+               i++;
+       if ((rvblist = appl_legacy_response(i)) == NULL) {
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vb);
+               return;
+       }
+       rvb = rvblist;
+       i = 1;
+       for (vb = vblist; vb != NULL; vb = vb->av_next, rvb = rvb->av_next) {
+               ret = -1;
+               if (vb->av_include) {
+                       ret = mps_getreq(NULL, root, &(vb->av_oid), 0);
+                       if (ret == -1)
+                               ober_free_elements(ober_unlink_elements(root));
+               }
+               rvb->av_oid = vb->av_oid;
+               if (ret == -1) {
+                       msg.sm_version = 1;
+                       (void)mps_getnextreq(&msg, root, &(rvb->av_oid));
+               }
+               elm = ober_unlink_elements(root);
+               ober_get_oid(elm, &(rvb->av_oid));
+               if (ober_oid_cmp(&(rvb->av_oid), &(vb->av_oid_end)) > 0) {
+                       rvb->av_oid = vb->av_oid;
+                       rvb->av_value = appl_exception(APPL_EXC_ENDOFMIBVIEW);
+                       if (rvb->av_value == NULL) {
+                               log_warn("Failed to create endOfMibView");
+                               rvb->av_next = NULL;
+                               appl_response(backend, requestid,
+                                   APPL_ERROR_GENERR, i, rvblist);
+                               return;
+                       }
+               } else
+                       rvb->av_value = ober_unlink_elements(elm);
+               ober_free_elements(elm);
+               i++;
+       }
+
+       appl_response(backend, requestid, APPL_ERROR_NOERROR, 0, rvblist);
+}

Reply via email to