(Re)add support for agentx in snmpd
authormartijn <martijn@openbsd.org>
Tue, 23 Aug 2022 08:56:20 +0000 (08:56 +0000)
committermartijn <martijn@openbsd.org>
Tue, 23 Aug 2022 08:56:20 +0000 (08:56 +0000)
Current omissions in protocol support are notifications,
index (de)allocation, and agent capabilities.

Help testing sthen@
Feedback/tweaks/OK jmatthew@

12 files changed:
etc/group
etc/mtree/4.4BSD.dist
usr.sbin/snmpd/Makefile
usr.sbin/snmpd/application.c
usr.sbin/snmpd/application.h
usr.sbin/snmpd/application_agentx.c [new file with mode: 0644]
usr.sbin/snmpd/ax.c [new file with mode: 0644]
usr.sbin/snmpd/ax.h [new file with mode: 0644]
usr.sbin/snmpd/parse.y
usr.sbin/snmpd/snmpd.conf.5
usr.sbin/snmpd/snmpd.h
usr.sbin/snmpd/snmpe.c

index c0aeda5..30c4313 100644 (file)
--- a/etc/group
+++ b/etc/group
@@ -59,6 +59,7 @@ _ripd:*:88:
 _relayd:*:89:
 _ospf6d:*:90:
 _snmpd:*:91:
+_agentx:*:92:
 _ypldap:*:93:
 _rad:*:94:
 _smtpd:*:95:
index d67d017..987fe73 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: 4.4BSD.dist,v 1.321 2022/02/13 00:02:16 ajacoutot Exp $
+#      $OpenBSD: 4.4BSD.dist,v 1.322 2022/08/23 08:56:20 martijn Exp $
 
 /set type=dir uname=root gname=wheel mode=0755
 
@@ -612,6 +612,8 @@ usr
 var
     account
     ..
+    agentx                     uname=root gname=wheel mode=755
+    ..
     authpf                     uname=root gname=authpf mode=0770
     ..
     empty                      mode=0755
index f49829c..c112d92 100644 (file)
@@ -1,9 +1,10 @@
-#      $OpenBSD: Makefile,v 1.19 2022/06/30 11:28:36 martijn Exp $
+#      $OpenBSD: Makefile,v 1.20 2022/08/23 08:56:20 martijn Exp $
 
 PROG=          snmpd
 MAN=           snmpd.8 snmpd.conf.5
 SRCS=          parse.y log.c snmpe.c application.c application_legacy.c \
                    application_blocklist.c \
+                   application_agentx.c ax.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 0fb8a45..335123f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: application.c,v 1.6 2022/06/30 11:28:36 martijn Exp $ */
+/*     $OpenBSD: application.c,v 1.7 2022/08/23 08:56:20 martijn Exp $ */
 
 /*
  * Copyright (c) 2021 Martijn van Duren <martijn@openbsd.org>
@@ -145,11 +145,18 @@ RB_PROTOTYPE_STATIC(appl_requests, appl_request_downstream, ard_entry,
 
 #define APPL_CONTEXT_NAME(ctx) (ctx->ac_name[0] == '\0' ? NULL : ctx->ac_name)
 
+void
+appl(void)
+{
+       appl_agentx();
+}
+
 void
 appl_init(void)
 {
        appl_blocklist_init();
        appl_legacy_init();
+       appl_agentx_init();
 }
 
 void
@@ -159,6 +166,7 @@ appl_shutdown(void)
 
        appl_blocklist_shutdown();
        appl_legacy_shutdown();
+       appl_agentx_shutdown();
 
        TAILQ_FOREACH_SAFE(ctx, &contexts, ac_entries, tctx) {
                assert(RB_EMPTY(&(ctx->ac_regions)));
@@ -1122,10 +1130,13 @@ appl_response(struct appl_backend *backend, int32_t requestid,
                log_warnx("Invalid error index");
                invalid = 1;
        }
+/* amavisd-snmp-subagent sets index to 1, no reason to crash over it. */
+#if PEDANTIC
        if (error == APPL_ERROR_NOERROR && index != 0) {
                log_warnx("error index with no error");
                invalid = 1;
        }
+#endif
        if (vb == NULL && origvb != NULL) {
                log_warnx("%s: Request %"PRIu32" returned less varbinds then "
                    "requested", backend->ab_name, requestid);
index fd5b814..7f22af8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: application.h,v 1.2 2022/06/30 11:28:36 martijn Exp $ */
+/*     $OpenBSD: application.h,v 1.3 2022/08/23 08:56:20 martijn Exp $ */
 
 /*
  * Copyright (c) 2021 Martijn van Duren <martijn@openbsd.org>
@@ -117,6 +117,7 @@ struct appl_backend {
        RB_HEAD(appl_requests, appl_request_downstream) ab_requests;
 };
 
+void appl(void);
 void appl_init(void);
 void appl_shutdown(void);
 enum appl_error appl_register(const char *, uint32_t, uint8_t, struct ber_oid *,
@@ -134,6 +135,11 @@ struct ber_element *appl_exception(enum appl_exception);
 void    appl_legacy_init(void);
 void    appl_legacy_shutdown(void);
 
+/* application_agentx.c */
+void    appl_agentx(void);
+void    appl_agentx_init(void);
+void    appl_agentx_shutdown(void);
+
 /* application_blocklist.c */
 void    appl_blocklist_init(void);
 void    appl_blocklist_shutdown(void);
diff --git a/usr.sbin/snmpd/application_agentx.c b/usr.sbin/snmpd/application_agentx.c
new file mode 100644 (file)
index 0000000..5ac8b2d
--- /dev/null
@@ -0,0 +1,879 @@
+/*     $OpenBSD: application_agentx.c,v 1.1 2022/08/23 08:56:20 martijn Exp $ */
+/*
+ * Copyright (c) 2022 Martijn van Duren <martijn@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/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "application.h"
+#include "ax.h"
+#include "log.h"
+#include "smi.h"
+#include "snmp.h"
+#include "snmpd.h"
+
+#define AGENTX_DEFAULTTIMEOUT 5
+
+struct appl_agentx_connection {
+       uint32_t conn_id;
+       struct ax *conn_ax;
+       struct event conn_rev;
+       struct event conn_wev;
+
+       TAILQ_HEAD(, appl_agentx_session) conn_sessions;
+       RB_ENTRY(appl_agentx_connection) conn_entry;
+};
+
+struct appl_agentx_session {
+       uint32_t sess_id;
+       struct appl_agentx_connection *sess_conn;
+       /*
+        * RFC 2741 section 7.1.1:
+        * All subsequent AgentX protocol operations initiated by the master
+        * agent for this session must use this byte ordering and set this bit
+        * accordingly.
+        */
+       enum ax_byte_order sess_byteorder;
+       uint8_t sess_timeout;
+       struct ax_oid sess_oid;
+       struct ax_ostring sess_descr;
+       struct appl_backend sess_backend;
+
+       RB_ENTRY(appl_agentx_session) sess_entry;
+       TAILQ_ENTRY(appl_agentx_session) sess_conn_entry;
+};
+
+void appl_agentx_listen(struct agentx_master *);
+void appl_agentx_accept(int, short, void *);
+void appl_agentx_free(struct appl_agentx_connection *);
+void appl_agentx_recv(int, short, void *);
+void appl_agentx_open(struct appl_agentx_connection *, struct ax_pdu *);
+void appl_agentx_close(struct appl_agentx_session *, struct ax_pdu *);
+void appl_agentx_forceclose(struct appl_backend *, enum appl_close_reason);
+void appl_agentx_session_free(struct appl_agentx_session *);
+void appl_agentx_register(struct appl_agentx_session *, struct ax_pdu *);
+void appl_agentx_unregister(struct appl_agentx_session *, struct ax_pdu *);
+void appl_agentx_get(struct appl_backend *, int32_t, int32_t, const char *,
+    struct appl_varbind *);
+void appl_agentx_getnext(struct appl_backend *, int32_t, int32_t, const char *,
+    struct appl_varbind *);
+void appl_agentx_response(struct appl_agentx_session *, struct ax_pdu *);
+void appl_agentx_send(int, short, void *);
+struct ber_oid *appl_agentx_oid2ber_oid(struct ax_oid *, struct ber_oid *);
+struct ber_element *appl_agentx_value2ber_element(struct ax_varbind *);
+struct ax_ostring *appl_agentx_string2ostring(const char *,
+    struct ax_ostring *);
+int appl_agentx_cmp(struct appl_agentx_connection *,
+    struct appl_agentx_connection *);
+int appl_agentx_session_cmp(struct appl_agentx_session *,
+    struct appl_agentx_session *);
+
+struct appl_backend_functions appl_agentx_functions = {
+       .ab_close = appl_agentx_forceclose,
+       .ab_get = appl_agentx_get,
+       .ab_getnext = appl_agentx_getnext,
+       .ab_getbulk = NULL, /* not properly supported in application.c and libagentx */
+};
+
+RB_HEAD(appl_agentx_conns, appl_agentx_connection) appl_agentx_conns =
+    RB_INITIALIZER(&appl_agentx_conns);
+RB_HEAD(appl_agentx_sessions, appl_agentx_session) appl_agentx_sessions =
+    RB_INITIALIZER(&appl_agentx_sessions);
+
+RB_PROTOTYPE_STATIC(appl_agentx_conns, appl_agentx_connection, conn_entry,
+    appl_agentx_cmp);
+RB_PROTOTYPE_STATIC(appl_agentx_sessions, appl_agentx_session, sess_entry,
+    appl_agentx_session_cmp);
+
+void
+appl_agentx(void)
+{
+       struct agentx_master *master;
+
+       TAILQ_FOREACH(master, &(snmpd_env->sc_agentx_masters), axm_entry)
+               appl_agentx_listen(master);
+}
+
+void
+appl_agentx_init(void)
+{
+       struct agentx_master *master;
+
+       TAILQ_FOREACH(master, &(snmpd_env->sc_agentx_masters), axm_entry) {
+               if (master->axm_fd == -1)
+                       continue;
+               event_set(&(master->axm_ev), master->axm_fd,
+                   EV_READ | EV_PERSIST, appl_agentx_accept, master);
+               event_add(&(master->axm_ev), NULL);
+               log_info("AgentX: listening on %s", master->axm_sun.sun_path);
+       }
+}
+void
+appl_agentx_listen(struct agentx_master *master)
+{
+       mode_t mask;
+
+       unlink(master->axm_sun.sun_path);
+
+       mask = umask(0777);
+       if ((master->axm_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||
+           bind(master->axm_fd, (struct sockaddr *)&(master->axm_sun),
+           sizeof(master->axm_sun)) == -1 ||
+           listen(master->axm_fd, 5)) {
+               log_warn("Agentx: listen %s", master->axm_sun.sun_path);
+               umask(mask);
+               return;
+       }
+       umask(mask);
+       if (chown(master->axm_sun.sun_path, master->axm_owner,
+           master->axm_group) == -1) {
+               log_warn("AgentX: chown %s", master->axm_sun.sun_path);
+               goto fail;
+       }
+       if (chmod(master->axm_sun.sun_path, master->axm_mode) == -1) {
+               log_warn("AgentX: chmod %s", master->axm_sun.sun_path);
+               goto fail;
+       }
+       return;
+ fail:
+       close(master->axm_fd);
+       master->axm_fd = -1;
+}
+
+void
+appl_agentx_shutdown(void)
+{
+       struct appl_agentx_connection *conn, *tconn;
+
+       RB_FOREACH_SAFE(conn, appl_agentx_conns, &appl_agentx_conns, tconn)
+               appl_agentx_free(conn);
+}
+
+void
+appl_agentx_accept(int masterfd, short event, void *cookie)
+{
+       int fd;
+       struct agentx_master *master = cookie;
+       struct sockaddr_un sun;
+       socklen_t sunlen = sizeof(sun);
+       struct appl_agentx_connection *conn = NULL;
+
+       if ((fd = accept(masterfd, (struct sockaddr *)&sun, &sunlen)) == -1) {
+               log_warn("AgentX: accept %s", master->axm_sun.sun_path);
+               return;
+       }
+
+       if ((conn = malloc(sizeof(*conn))) == NULL) {
+               log_warn(NULL);
+               goto fail;
+       }
+
+       TAILQ_INIT(&(conn->conn_sessions));
+       if ((conn->conn_ax = ax_new(fd)) == NULL) {
+               log_warn(NULL);
+               goto fail;
+       }
+
+       do {
+               conn->conn_id = arc4random();
+       } while (RB_INSERT(appl_agentx_conns,
+           &appl_agentx_conns, conn) != NULL);
+
+       event_set(&(conn->conn_rev), fd, EV_READ | EV_PERSIST,
+           appl_agentx_recv, conn);
+       event_add(&(conn->conn_rev), NULL);
+       event_set(&(conn->conn_wev), fd, EV_WRITE, appl_agentx_send, conn);
+       log_info("AgentX(%d): new connection", conn->conn_id);
+
+       return;
+ fail:
+       close(fd);
+       free(conn);
+}
+
+void
+appl_agentx_free(struct appl_agentx_connection *conn)
+{
+       struct appl_agentx_session *session;
+
+       event_del(&(conn->conn_rev));
+       event_del(&(conn->conn_wev));
+
+       while ((session = TAILQ_FIRST(&(conn->conn_sessions))) != NULL) {
+               if (conn->conn_ax == NULL)
+                       appl_agentx_session_free(session);
+               else
+                       appl_agentx_forceclose(&(session->sess_backend),
+                           APPL_CLOSE_REASONSHUTDOWN);
+       }
+
+       RB_REMOVE(appl_agentx_conns, &appl_agentx_conns, conn);
+       ax_free(conn->conn_ax);
+       free(conn);
+}
+
+void
+appl_agentx_recv(int fd, short event, void *cookie)
+{
+       struct appl_agentx_connection *conn = cookie;
+       struct appl_agentx_session *session;
+       struct ax_pdu *pdu;
+
+       if ((pdu = ax_recv(conn->conn_ax)) == NULL) {
+               if (errno == EAGAIN)
+                       return;
+               log_warn("AgentX(%"PRIu32")", conn->conn_id);
+               /*
+                * Either the connection is dead, or we had garbage on the line.
+                * Both make sure we can't continue on this stream.
+                */
+               if (errno == ECONNRESET) {
+                       ax_free(conn->conn_ax);
+                       conn->conn_ax = NULL;
+               }
+               appl_agentx_free(conn);
+               return;
+       }
+
+       conn->conn_ax->ax_byteorder = pdu->ap_header.aph_flags &
+           AX_PDU_FLAG_NETWORK_BYTE_ORDER ?
+           AX_BYTE_ORDER_BE : AX_BYTE_ORDER_LE;
+       if (pdu->ap_header.aph_type != AX_PDU_TYPE_OPEN) {
+               /* Make sure we only look for connection-local sessions */
+               TAILQ_FOREACH(session, &(conn->conn_sessions),
+                   sess_conn_entry) {
+                       if (session->sess_id == pdu->ap_header.aph_sessionid)
+                               break;
+               }
+               if (session == NULL) {
+                       log_warnx("AgentX(%"PRIu32"): Session %"PRIu32" not "
+                           "found for request", conn->conn_id,
+                           pdu->ap_header.aph_sessionid);
+                       ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
+                           pdu->ap_header.aph_transactionid,
+                           pdu->ap_header.aph_packetid,
+                           &(pdu->ap_context), smi_getticks(),
+                           APPL_ERROR_NOTOPEN, 0, NULL, 0);
+                       appl_agentx_send(-1, EV_WRITE, conn);
+                       goto fail;
+               }
+               /*
+                * RFC2741 section 7.1.1 bullet 4 is unclear on what byte order
+                * the response should be. My best guess is that it makes more
+                * sense that replies are in the same byte-order as what was
+                * requested.
+                * In practice we always have the same byte order as when we
+                * opened the session, so it's likely a non-issue, however, we
+                * can change to session byte order here.
+                */
+       }
+
+       switch (pdu->ap_header.aph_type) {
+       case AX_PDU_TYPE_OPEN:
+               appl_agentx_open(conn, pdu);
+               break;
+       case AX_PDU_TYPE_CLOSE:
+               appl_agentx_close(session, pdu);
+               break;
+       case AX_PDU_TYPE_REGISTER:
+               appl_agentx_register(session, pdu);
+               break;
+       case AX_PDU_TYPE_UNREGISTER:
+               appl_agentx_unregister(session, pdu);
+               break;
+       case AX_PDU_TYPE_GET:
+       case AX_PDU_TYPE_GETNEXT:
+       case AX_PDU_TYPE_GETBULK:
+       case AX_PDU_TYPE_TESTSET:
+       case AX_PDU_TYPE_COMMITSET:
+       case AX_PDU_TYPE_UNDOSET:
+       case AX_PDU_TYPE_CLEANUPSET:
+               appl_agentx_forceclose(&(session->sess_backend),
+                   APPL_CLOSE_REASONPROTOCOLERROR);
+               if (!TAILQ_EMPTY(&(conn->conn_sessions)))
+                       goto fail;
+
+               ax_pdu_free(pdu);
+               appl_agentx_free(conn);
+               return;
+       case AX_PDU_TYPE_NOTIFY:
+               log_warnx("%s: not supported",
+                   ax_pdutype2string(pdu->ap_header.aph_type));
+               /*
+                * RFC 2741 section 7.1.10:
+                * Note that the master agent's successful response indicates
+                * the agentx-Notify-PDU was received and validated.  It does
+                * not indicate that any particular notifications were actually
+                * generated or received by notification targets
+                */
+               /* XXX Not yet - FALLTHROUGH */
+       case AX_PDU_TYPE_PING:
+               ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
+                   pdu->ap_header.aph_transactionid,
+                   pdu->ap_header.aph_packetid, &(pdu->ap_context),
+                   smi_getticks(), APPL_ERROR_NOERROR, 0, NULL, 0);
+               appl_agentx_send(-1, EV_WRITE, conn);
+               break;
+       case AX_PDU_TYPE_INDEXALLOCATE:
+       case AX_PDU_TYPE_INDEXDEALLOCATE:
+               log_warnx("%s: not supported",
+                   ax_pdutype2string(pdu->ap_header.aph_type));
+               ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
+                   pdu->ap_header.aph_transactionid,
+                   pdu->ap_header.aph_packetid, &(pdu->ap_context),
+                   smi_getticks(), APPL_ERROR_PROCESSINGERROR, 1,
+                   pdu->ap_payload.ap_vbl.ap_varbind,
+                   pdu->ap_payload.ap_vbl.ap_nvarbind);
+               appl_agentx_send(-1, EV_WRITE, conn);
+               break;
+       case AX_PDU_TYPE_ADDAGENTCAPS:
+       case AX_PDU_TYPE_REMOVEAGENTCAPS:
+               log_warnx("%s: not supported",
+                   ax_pdutype2string(pdu->ap_header.aph_type));
+               ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
+                   pdu->ap_header.aph_transactionid,
+                   pdu->ap_header.aph_packetid, &(pdu->ap_context),
+                   smi_getticks(), APPL_ERROR_PROCESSINGERROR, 1,
+                   NULL, 0);
+               appl_agentx_send(-1, EV_WRITE, conn);
+               break;
+       case AX_PDU_TYPE_RESPONSE:
+               appl_agentx_response(session, pdu);
+               break;
+       }
+
+ fail:
+       ax_pdu_free(pdu);
+}
+
+void
+appl_agentx_open(struct appl_agentx_connection *conn, struct ax_pdu *pdu)
+{
+       struct appl_agentx_session *session;
+       struct ber_oid oid;
+       char oidbuf[1024];
+
+       if ((session = malloc(sizeof(*session))) == NULL) {
+               log_warn(NULL);
+               goto fail;
+       }
+       session->sess_descr.aos_string = NULL;
+
+       session->sess_conn = conn;
+       if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
+               session->sess_byteorder = AX_BYTE_ORDER_BE;
+       else
+               session->sess_byteorder = AX_BYTE_ORDER_LE;
+
+       /* RFC 2742 agentxSessionObjectID */
+       if (pdu->ap_payload.ap_open.ap_oid.aoi_idlen == 0) {
+               pdu->ap_payload.ap_open.ap_oid.aoi_id[0] = 0;
+               pdu->ap_payload.ap_open.ap_oid.aoi_id[1] = 0;
+               pdu->ap_payload.ap_open.ap_oid.aoi_idlen = 2;
+       } else if (pdu->ap_payload.ap_open.ap_oid.aoi_idlen == 1) {
+               log_warnx("AgentX(%"PRIu32"): Invalid oid: Open Failed",
+                   conn->conn_id);
+               goto fail;
+       }
+       /* RFC 2742 agentxSessionDescr */
+       if (pdu->ap_payload.ap_open.ap_descr.aos_slen > 255) {
+               log_warnx("AgentX(%"PRIu32"): Invalid descr (too long): Open "
+                   "Failed", conn->conn_id);
+               goto fail;
+       }
+       /*
+        * ax_ostring is always NUL-terminated, but doesn't scan for internal
+        * NUL-bytes. However, mbstowcs stops at NUL, which might be in the
+        * middle of the string.
+        */
+       if (strlen(pdu->ap_payload.ap_open.ap_descr.aos_string) !=
+           pdu->ap_payload.ap_open.ap_descr.aos_slen ||
+           mbstowcs(NULL,
+           pdu->ap_payload.ap_open.ap_descr.aos_string, 0) == (size_t)-1) {
+               log_warnx("AgentX(%"PRIu32"): Invalid descr (not UTF-8): "
+                   "Open Failed", conn->conn_id);
+               goto fail;
+       }
+
+       session->sess_timeout = pdu->ap_payload.ap_open.ap_timeout;
+       session->sess_oid = pdu->ap_payload.ap_open.ap_oid;
+       session->sess_descr.aos_slen = pdu->ap_payload.ap_open.ap_descr.aos_slen;
+       if (pdu->ap_payload.ap_open.ap_descr.aos_string != NULL) {
+               session->sess_descr.aos_string =
+                   strdup(pdu->ap_payload.ap_open.ap_descr.aos_string);
+               if (session->sess_descr.aos_string == NULL) {
+                       log_warn("AgentX(%"PRIu32"): strdup: Open Failed",
+                           conn->conn_id);
+                       goto fail;
+               }
+       }
+           
+       /* RFC 2742 agentxSessionIndex: chances of reuse, slim to none */
+       do {
+               session->sess_id = arc4random();
+       } while (RB_INSERT(appl_agentx_sessions,
+           &appl_agentx_sessions, session) != NULL);
+
+       if (asprintf(&(session->sess_backend.ab_name),
+           "AgentX(%"PRIu32"/%"PRIu32")",
+           conn->conn_id, session->sess_id) == -1) {
+               log_warn("AgentX(%d): asprintf: Open Failed",
+                   conn->conn_id);
+               goto fail;
+       }
+       session->sess_backend.ab_cookie = session;
+       session->sess_backend.ab_retries = 0;
+       session->sess_backend.ab_fn = &appl_agentx_functions;
+       RB_INIT(&(session->sess_backend.ab_requests));
+       TAILQ_INSERT_TAIL(&(conn->conn_sessions), session, sess_conn_entry);
+
+       appl_agentx_oid2ber_oid(&(session->sess_oid), &oid);
+       smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
+       log_info("%s: %s %s: Open", session->sess_backend.ab_name, oidbuf,
+           session->sess_descr.aos_string);
+
+       ax_response(conn->conn_ax, session->sess_id, pdu->ap_header.aph_transactionid,
+           pdu->ap_header.aph_packetid, NULL, smi_getticks(), APPL_ERROR_NOERROR, 0,
+           NULL, 0);
+       appl_agentx_send(-1, EV_WRITE, conn);
+
+       return;
+ fail:
+       ax_response(conn->conn_ax, 0, pdu->ap_header.aph_transactionid,
+           pdu->ap_header.aph_packetid, NULL, 0, APPL_ERROR_OPENFAILED, 0,
+           NULL, 0);
+       appl_agentx_send(-1, EV_WRITE, conn);
+       if (session != NULL)
+               free(session->sess_descr.aos_string);
+       free(session);
+}
+
+void
+appl_agentx_close(struct appl_agentx_session *session, struct ax_pdu *pdu)
+{
+       struct appl_agentx_connection *conn = session->sess_conn;
+       char name[100];
+
+       strlcpy(name, session->sess_backend.ab_name, sizeof(name));
+       appl_agentx_session_free(session);
+       log_info("%s: Closed by subagent (%s)", name,
+           ax_closereason2string(pdu->ap_payload.ap_close.ap_reason));
+
+       ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
+           pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
+           &(pdu->ap_context), smi_getticks(), APPL_ERROR_NOERROR, 0, NULL, 0);
+       appl_agentx_send(-1, EV_WRITE, conn);
+}
+
+void
+appl_agentx_forceclose(struct appl_backend *backend,
+    enum appl_close_reason reason)
+{
+       struct appl_agentx_session *session = backend->ab_cookie;
+       char name[100];
+
+       session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
+       ax_close(session->sess_conn->conn_ax, session->sess_id,
+           (enum ax_close_reason) reason);
+       appl_agentx_send(-1, EV_WRITE, session->sess_conn);
+
+       strlcpy(name, session->sess_backend.ab_name, sizeof(name));
+       appl_agentx_session_free(session);
+       log_info("%s: Closed by snmpd (%s)", name,
+           ax_closereason2string((enum ax_close_reason)reason));
+}
+
+void
+appl_agentx_session_free(struct appl_agentx_session *session)
+{
+       struct appl_agentx_connection *conn = session->sess_conn;
+
+       appl_close(&(session->sess_backend));
+
+       RB_REMOVE(appl_agentx_sessions, &appl_agentx_sessions, session);
+       TAILQ_REMOVE(&(conn->conn_sessions), session, sess_conn_entry);
+
+       free(session->sess_backend.ab_name);
+       free(session->sess_descr.aos_string);
+       free(session);
+}
+
+void
+appl_agentx_register(struct appl_agentx_session *session, struct ax_pdu *pdu)
+{
+       uint32_t timeout;
+       struct ber_oid oid;
+       enum appl_error error;
+
+       timeout = pdu->ap_payload.ap_register.ap_timeout;
+       timeout = timeout != 0 ? timeout : session->sess_timeout != 0 ?
+           session->sess_timeout : AGENTX_DEFAULTTIMEOUT;
+       timeout *= 100;
+
+       if (appl_agentx_oid2ber_oid(
+           &(pdu->ap_payload.ap_register.ap_subtree), &oid) == NULL) {
+               log_warnx("%s: Failed to register: oid too small",
+                   session->sess_backend.ab_name);
+               error = APPL_ERROR_PROCESSINGERROR;
+               goto fail;
+       }
+
+       error = appl_register(pdu->ap_context.aos_string, timeout,
+           pdu->ap_payload.ap_register.ap_priority, &oid, 
+           pdu->ap_header.aph_flags & AX_PDU_FLAG_INSTANCE_REGISTRATION, 0,
+           pdu->ap_payload.ap_register.ap_range_subid,
+           pdu->ap_payload.ap_register.ap_upper_bound,
+           &(session->sess_backend));
+
+ fail:
+       ax_response(session->sess_conn->conn_ax, session->sess_id,
+           pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
+           &(pdu->ap_context), smi_getticks(), error, 0, NULL, 0);
+       appl_agentx_send(-1, EV_WRITE, session->sess_conn);
+}
+
+void
+appl_agentx_unregister(struct appl_agentx_session *session, struct ax_pdu *pdu)
+{
+       struct ber_oid oid;
+       enum appl_error error;
+
+       if (appl_agentx_oid2ber_oid(
+           &(pdu->ap_payload.ap_unregister.ap_subtree), &oid) == NULL) {
+               log_warnx("%s: Failed to unregister: oid too small",
+                   session->sess_backend.ab_name);
+               error = APPL_ERROR_PROCESSINGERROR;
+               goto fail;
+       }
+
+       error = appl_unregister(pdu->ap_context.aos_string, 
+           pdu->ap_payload.ap_unregister.ap_priority, &oid, 
+           pdu->ap_payload.ap_unregister.ap_range_subid,
+           pdu->ap_payload.ap_unregister.ap_upper_bound,
+           &(session->sess_backend));
+
+ fail:
+       ax_response(session->sess_conn->conn_ax, session->sess_id,
+           pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
+           &(pdu->ap_context), smi_getticks(), error, 0, NULL, 0);
+       appl_agentx_send(-1, EV_WRITE, session->sess_conn);
+}
+
+#define AX_PDU_FLAG_INDEX (AX_PDU_FLAG_NEW_INDEX | AX_PDU_FLAG_ANY_INDEX)
+
+void
+appl_agentx_get(struct appl_backend *backend, int32_t transactionid,
+    int32_t requestid, const char *ctx, struct appl_varbind *vblist)
+{
+       struct appl_agentx_session *session = backend->ab_cookie;
+       struct ax_ostring *context, string;
+       struct appl_varbind *vb;
+       struct ax_searchrange *srl;
+       size_t i, j, nsr;
+
+       for (nsr = 0, vb = vblist; vb != NULL; vb = vb->av_next)
+               nsr++;
+
+       if ((srl = calloc(nsr, sizeof(*srl))) == NULL) {
+               log_warn(NULL);
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
+               return;
+       }
+
+       for (i = 0, vb = vblist; i < nsr; i++, vb = vb->av_next) {
+               srl[i].asr_start.aoi_include = vb->av_include;
+               srl[i].asr_start.aoi_idlen = vb->av_oid.bo_n;
+               for (j = 0; j < vb->av_oid.bo_n; j++)
+                       srl[i].asr_start.aoi_id[j] = vb->av_oid.bo_id[j];
+               srl[i].asr_stop.aoi_include = 0;
+               srl[i].asr_stop.aoi_idlen = 0;
+       }
+       if ((context = appl_agentx_string2ostring(ctx, &string)) == NULL) {
+               if (errno != 0) {
+                       log_warn("Failed to convert context");
+                       appl_response(backend, requestid,
+                           APPL_ERROR_GENERR, 1, vblist);
+                       free(srl);
+                       return;
+               }
+       }
+
+       session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
+       if (ax_get(session->sess_conn->conn_ax, session->sess_id, transactionid,
+           requestid, context, srl, nsr) == -1)
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
+       else
+               appl_agentx_send(-1, EV_WRITE, session->sess_conn);
+       free(srl);
+       if (context != NULL)
+               free(context->aos_string);
+}
+
+void
+appl_agentx_getnext(struct appl_backend *backend, int32_t transactionid,
+    int32_t requestid, const char *ctx, struct appl_varbind *vblist)
+{
+       struct appl_agentx_session *session = backend->ab_cookie;
+       struct ax_ostring *context, string;
+       struct appl_varbind *vb;
+       struct ax_searchrange *srl;
+       size_t i, j, nsr;
+
+       for (nsr = 0, vb = vblist; vb != NULL; vb = vb->av_next)
+               nsr++;
+
+       if ((srl = calloc(nsr, sizeof(*srl))) == NULL) {
+               log_warn(NULL);
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
+               return;
+       }
+
+       for (i = 0, vb = vblist; i < nsr; i++, vb = vb->av_next) {
+               srl[i].asr_start.aoi_include = vb->av_include;
+               srl[i].asr_start.aoi_idlen = vb->av_oid.bo_n;
+               for (j = 0; j < vb->av_oid.bo_n; j++)
+                       srl[i].asr_start.aoi_id[j] = vb->av_oid.bo_id[j];
+               srl[i].asr_stop.aoi_include = 0;
+               srl[i].asr_stop.aoi_idlen = vb->av_oid_end.bo_n;
+               for (j = 0; j < vb->av_oid.bo_n; j++)
+                       srl[i].asr_stop.aoi_id[j] = vb->av_oid_end.bo_id[j];
+       }
+       if ((context = appl_agentx_string2ostring(ctx, &string)) == NULL) {
+               if (errno != 0) {
+                       log_warn("Failed to convert context");
+                       appl_response(backend, requestid,
+                           APPL_ERROR_GENERR, 1, vblist);
+                       free(srl);
+                       return;
+               }
+       }
+
+       session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
+       if (ax_getnext(session->sess_conn->conn_ax, session->sess_id, transactionid,
+           requestid, context, srl, nsr) == -1)
+               appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
+       else
+               appl_agentx_send(-1, EV_WRITE, session->sess_conn);
+       free(srl);
+       if (context != NULL)
+               free(context->aos_string);
+}
+
+void
+appl_agentx_response(struct appl_agentx_session *session, struct ax_pdu *pdu)
+{
+       struct appl_varbind *response = NULL;
+       struct ax_varbind *vb;
+       enum appl_error error;
+       uint16_t index;
+       size_t i, nvarbind;
+
+       nvarbind = pdu->ap_payload.ap_response.ap_nvarbind;
+       if ((response = calloc(nvarbind, sizeof(*response))) == NULL) {
+               log_warn(NULL);
+               appl_response(&(session->sess_backend),
+                   pdu->ap_header.aph_packetid,
+                   APPL_ERROR_GENERR, 1, NULL);
+               return;
+       }
+
+       error = (enum appl_error)pdu->ap_payload.ap_response.ap_error;
+       index = pdu->ap_payload.ap_response.ap_index;
+       for (i = 0; i < nvarbind; i++) {
+               response[i].av_next = i + 1 == nvarbind ?
+                   NULL : &(response[i + 1]);
+               vb = &(pdu->ap_payload.ap_response.ap_varbindlist[i]);
+
+               if (appl_agentx_oid2ber_oid(&(vb->avb_oid),
+                   &(response[i].av_oid)) == NULL) {
+                       log_warnx("%s: invalid oid",
+                           session->sess_backend.ab_name);
+                       if (error != APPL_ERROR_NOERROR) {
+                               error = APPL_ERROR_GENERR;
+                               index = i + 1;
+                       }
+                       continue;
+               }
+               response[i].av_value = appl_agentx_value2ber_element(vb);
+               if (response[i].av_value == NULL) {
+                       log_warn("%s: Failed to parse response value",
+                           session->sess_backend.ab_name);
+                       if (error != APPL_ERROR_NOERROR) {
+                               error = APPL_ERROR_GENERR;
+                               index = i + 1;
+                       }
+               }
+       }
+       appl_response(&(session->sess_backend), pdu->ap_header.aph_packetid,
+           error, index, response);
+       free(response);
+}
+
+void
+appl_agentx_send(int fd, short event, void *cookie)
+{
+       struct appl_agentx_connection *conn = cookie;
+
+       switch (ax_send(conn->conn_ax)) {
+       case -1:
+               if (errno == EAGAIN)
+                       break;
+               log_warn("AgentX(%"PRIu32")", conn->conn_id);
+               ax_free(conn->conn_ax);
+               conn->conn_ax = NULL;
+               appl_agentx_free(conn);
+               return;
+       case 0:
+               return;
+       default:
+               break;
+       }
+       event_add(&(conn->conn_wev), NULL);
+}
+
+struct ber_oid *
+appl_agentx_oid2ber_oid(struct ax_oid *aoid, struct ber_oid *boid)
+{
+       size_t i;
+
+       if (aoid->aoi_idlen < BER_MIN_OID_LEN ||
+           aoid->aoi_idlen > BER_MAX_OID_LEN) {
+               errno = EINVAL;
+               return NULL;
+       }
+       
+
+       boid->bo_n = aoid->aoi_idlen;
+       for (i = 0; i < boid->bo_n; i++)
+               boid->bo_id[i] = aoid->aoi_id[i];
+       return boid;
+}
+
+struct ber_element *
+appl_agentx_value2ber_element(struct ax_varbind *vb)
+{
+       struct ber_oid oid;
+       struct ber_element *elm;
+
+       switch (vb->avb_type) {
+       case AX_DATA_TYPE_INTEGER:
+               return ober_add_integer(NULL, vb->avb_data.avb_int32);
+       case AX_DATA_TYPE_OCTETSTRING:
+               return ober_add_nstring(NULL,
+                   vb->avb_data.avb_ostring.aos_string,
+                   vb->avb_data.avb_ostring.aos_slen);
+       case AX_DATA_TYPE_NULL:
+               return ober_add_null(NULL);
+       case AX_DATA_TYPE_OID:
+               if (appl_agentx_oid2ber_oid(
+                   &(vb->avb_data.avb_oid), &oid) == NULL)
+                       return NULL;
+               return ober_add_oid(NULL, &oid);
+       case AX_DATA_TYPE_IPADDRESS:
+               if ((elm = ober_add_nstring(NULL,
+                   vb->avb_data.avb_ostring.aos_string,
+                   vb->avb_data.avb_ostring.aos_slen)) == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_IPADDR);
+               return elm;
+       case AX_DATA_TYPE_COUNTER32:
+               elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
+               if (elm == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_COUNTER32);
+               return elm;
+       case AX_DATA_TYPE_GAUGE32:
+               elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
+               if (elm == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_GAUGE32);
+               return elm;
+       case AX_DATA_TYPE_TIMETICKS:
+               elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
+               if (elm == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS);
+               return elm;
+       case AX_DATA_TYPE_OPAQUE:
+               if ((elm = ober_add_nstring(NULL,
+                   vb->avb_data.avb_ostring.aos_string,
+                   vb->avb_data.avb_ostring.aos_slen)) == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_OPAQUE);
+               return elm;
+       case AX_DATA_TYPE_COUNTER64:
+               elm = ober_add_integer(NULL, vb->avb_data.avb_uint64);
+               if (elm == NULL)
+                       return NULL;
+               ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_COUNTER64);
+               return elm;
+       case AX_DATA_TYPE_NOSUCHOBJECT:
+               return appl_exception(APPL_EXC_NOSUCHOBJECT);
+       case AX_DATA_TYPE_NOSUCHINSTANCE:
+               return appl_exception(APPL_EXC_NOSUCHINSTANCE);
+       case AX_DATA_TYPE_ENDOFMIBVIEW:
+               return appl_exception(APPL_EXC_ENDOFMIBVIEW);
+       default:
+               errno = EINVAL;
+               return NULL;
+       }
+}
+
+struct ax_ostring *
+appl_agentx_string2ostring(const char *str, struct ax_ostring *ostring)
+{
+       if (str == NULL) {
+               errno = 0;
+               return NULL;
+       }
+
+       ostring->aos_slen = strlen(str);
+       if ((ostring->aos_string = strdup(str)) == NULL)
+               return NULL;
+       return ostring;
+}
+
+int
+appl_agentx_cmp(struct appl_agentx_connection *conn1,
+    struct appl_agentx_connection *conn2)
+{
+       return conn1->conn_id < conn2->conn_id ? -1 :
+           conn1->conn_id > conn2->conn_id;
+}
+
+int
+appl_agentx_session_cmp(struct appl_agentx_session *sess1,
+    struct appl_agentx_session *sess2)
+{
+       return sess1->sess_id < sess2->sess_id ? -1 : sess1->sess_id > sess2->sess_id;
+}
+
+RB_GENERATE_STATIC(appl_agentx_conns, appl_agentx_connection, conn_entry,
+    appl_agentx_cmp);
+RB_GENERATE_STATIC(appl_agentx_sessions, appl_agentx_session, sess_entry,
+    appl_agentx_session_cmp);
diff --git a/usr.sbin/snmpd/ax.c b/usr.sbin/snmpd/ax.c
new file mode 100644 (file)
index 0000000..63add68
--- /dev/null
@@ -0,0 +1,1549 @@
+/*     $OpenBSD: ax.c,v 1.1 2022/08/23 08:56:20 martijn Exp $ */
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@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/socket.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <endian.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "ax.h"
+
+#define AX_PDU_HEADER 20
+
+static int ax_pdu_need(struct ax *, size_t);
+static int ax_pdu_header(struct ax *,
+    enum ax_pdu_type, uint8_t, uint32_t, uint32_t, uint32_t,
+    struct ax_ostring *);
+static uint32_t ax_packetid(struct ax *);
+static uint32_t ax_pdu_queue(struct ax *);
+static int ax_pdu_add_uint16(struct ax *, uint16_t);
+static int ax_pdu_add_uint32(struct ax *, uint32_t);
+static int ax_pdu_add_uint64(struct ax *, uint64_t);
+static int ax_pdu_add_oid(struct ax *, struct ax_oid *);
+static int ax_pdu_add_str(struct ax *, struct ax_ostring *);
+static int ax_pdu_add_varbindlist(struct ax *, struct ax_varbind *,
+    size_t);
+static int ax_pdu_add_searchrange(struct ax *, struct ax_searchrange *);
+static uint16_t ax_pdutoh16(struct ax_pdu_header *, uint8_t *);
+static uint32_t ax_pdutoh32(struct ax_pdu_header *, uint8_t *);
+static uint64_t ax_pdutoh64(struct ax_pdu_header *, uint8_t *);
+static ssize_t ax_pdutooid(struct ax_pdu_header *, struct ax_oid *,
+    uint8_t *, size_t);
+static ssize_t ax_pdutoostring(struct ax_pdu_header *,
+    struct ax_ostring *, uint8_t *, size_t);
+static ssize_t ax_pdutovarbind(struct ax_pdu_header *,
+    struct ax_varbind *, uint8_t *, size_t);
+
+struct ax *
+ax_new(int fd)
+{
+       struct ax *ax;
+
+       if (fd == -1) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if ((ax = calloc(1, sizeof(*ax))) == NULL)
+               return NULL;
+       ax->ax_fd = fd;
+       ax->ax_rbsize = 512;
+       if ((ax->ax_rbuf = malloc(ax->ax_rbsize)) == NULL)
+               goto fail;
+       ax->ax_byteorder = AX_BYTE_ORDER_NATIVE;
+
+       return ax;
+
+fail:
+       free(ax);
+       return NULL;
+}
+
+void
+ax_free(struct ax *ax)
+{
+       if (ax == NULL)
+               return;
+       close(ax->ax_fd);
+       free(ax->ax_rbuf);
+       free(ax->ax_wbuf);
+       free(ax->ax_packetids);
+       free(ax);
+}
+
+struct ax_pdu *
+ax_recv(struct ax *ax)
+{
+       struct ax_pdu *pdu;
+       struct ax_pdu_header header;
+       struct ax_pdu_response *response;
+       struct ax_varbind *varbind;
+       struct ax_pdu_searchrangelist *srl = NULL;
+       struct ax_pdu_varbindlist *vbl;
+       struct ax_searchrange *sr;
+       size_t rbsize, packetidx = 0, i, rawlen;
+       ssize_t nread;
+       uint8_t *u8;
+       uint8_t *rbuf;
+       int found;
+
+       /* Only read a single packet at a time to make sure libevent triggers */
+       if (ax->ax_rblen < AX_PDU_HEADER) {
+               if ((nread = read(ax->ax_fd, ax->ax_rbuf + ax->ax_rblen,
+                   AX_PDU_HEADER - ax->ax_rblen)) == 0) {
+                       errno = ECONNRESET;
+                       return NULL;
+               }
+               if (nread == -1)
+                       return NULL;
+               ax->ax_rblen += nread;
+               if (ax->ax_rblen < AX_PDU_HEADER) {
+                       errno = EAGAIN;
+                       return NULL;
+               }
+       }
+       u8 = ax->ax_rbuf;
+       header.aph_version = *u8++;
+       header.aph_type = *u8++;
+       header.aph_flags = *u8++;
+       u8++;
+       header.aph_sessionid = ax_pdutoh32(&header, u8);
+       u8 += 4;
+       header.aph_transactionid = ax_pdutoh32(&header, u8);
+       u8 += 4;
+       header.aph_packetid = ax_pdutoh32(&header, u8);
+       u8 += 4;
+       header.aph_plength = ax_pdutoh32(&header, u8);
+
+       if (header.aph_version != 1) {
+               errno = EPROTO;
+               return NULL;
+       }
+       if (ax->ax_rblen < AX_PDU_HEADER + header.aph_plength) {
+               if (AX_PDU_HEADER + header.aph_plength > ax->ax_rbsize) {
+                       rbsize = (((AX_PDU_HEADER + header.aph_plength)
+                           / 512) + 1) * 512;
+                       if ((rbuf = recallocarray(ax->ax_rbuf, ax->ax_rbsize,
+                           rbsize, sizeof(*rbuf))) == NULL)
+                               return NULL;
+                       ax->ax_rbsize = rbsize;
+                       ax->ax_rbuf = rbuf;
+               }
+               nread = read(ax->ax_fd, ax->ax_rbuf + ax->ax_rblen,
+                   header.aph_plength - (ax->ax_rblen - AX_PDU_HEADER));
+               if (nread == 0)
+                       errno = ECONNRESET;
+               if (nread <= 0)
+                       return NULL;
+               ax->ax_rblen += nread;
+               if (ax->ax_rblen < AX_PDU_HEADER + header.aph_plength) {
+                       errno = EAGAIN;
+                       return NULL;
+               }
+       }
+
+       if ((pdu = calloc(1, sizeof(*pdu))) == NULL)
+               return NULL;
+
+       memcpy(&(pdu->ap_header), &header, sizeof(header));
+
+#if defined(AX_DEBUG) && defined(AX_DEBUG_VERBOSE)
+       {
+               char chars[4];
+               int print = 1;
+
+               fprintf(stderr, "received packet:\n");
+               for (i = 0; i < pdu->ap_header.aph_plength + AX_PDU_HEADER;
+                   i++) {
+                       fprintf(stderr, "%02hhx ", ax->ax_rbuf[i]);
+                       chars[i % 4] = ax->ax_rbuf[i];
+                       if (!isprint(ax->ax_rbuf[i]))
+                               print = 0;
+                       if (i % 4 == 3) {
+                               if (print)
+                                       fprintf(stderr, "%.4s", chars);
+                               fprintf(stderr, "\n");
+                               print = 1;
+                       }
+               }
+       }
+#endif
+
+       u8 = (ax->ax_rbuf) + AX_PDU_HEADER;
+       rawlen = pdu->ap_header.aph_plength;
+       if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT) {
+               nread = ax_pdutoostring(&header, &(pdu->ap_context), u8,
+                   rawlen);
+               if (nread == -1)
+                       goto fail;
+               rawlen -= nread;
+               u8 += nread;
+       }
+
+       switch (pdu->ap_header.aph_type) {
+       case AX_PDU_TYPE_OPEN:
+               if (rawlen < 12) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               pdu->ap_payload.ap_open.ap_timeout = *u8;
+               rawlen -= 4;
+               u8 += 4;
+               if ((nread = ax_pdutooid(&header,
+                   &(pdu->ap_payload.ap_open.ap_oid), u8, rawlen)) == -1)
+                       goto fail;
+               rawlen -= nread;
+               u8 += nread;
+               if ((nread = ax_pdutoostring(&header,
+                   &(pdu->ap_payload.ap_open.ap_descr), u8, rawlen)) == -1)
+                       goto fail;
+               if (rawlen - nread != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_CLOSE:
+               if (rawlen != 4) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               if (u8[0] != AX_CLOSE_OTHER &&
+                   u8[0] != AX_CLOSEN_PARSEERROR &&
+                   u8[0] != AX_CLOSE_PROTOCOLERROR &&
+                   u8[0] != AX_CLOSE_TIMEOUTS &&
+                   u8[0] != AX_CLOSE_SHUTDOWN &&
+                   u8[0] != AX_CLOSE_BYMANAGER) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               pdu->ap_payload.ap_close.ap_reason = u8[0];
+               break;
+       case AX_PDU_TYPE_REGISTER:
+               if (rawlen < 8) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               pdu->ap_payload.ap_register.ap_timeout = *u8++;
+               pdu->ap_payload.ap_register.ap_priority = *u8++;
+               pdu->ap_payload.ap_register.ap_range_subid = *u8++;
+               u8++;
+               rawlen -= 4;
+               if ((nread = ax_pdutooid(&header,
+                   &(pdu->ap_payload.ap_register.ap_subtree),
+                   u8, rawlen)) == -1)
+                       goto fail;
+               rawlen -= nread;
+               u8 += nread;
+               if (pdu->ap_payload.ap_register.ap_range_subid) {
+                       if (rawlen != 4) {
+                               errno = EPROTO;
+                               goto fail;
+                       }
+                       pdu->ap_payload.ap_register.ap_upper_bound =
+                           ax_pdutoh32(&header, u8);
+                       rawlen -= 4;
+               }
+               if (rawlen != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_UNREGISTER:
+               if (rawlen < 8) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               u8++;
+               pdu->ap_payload.ap_unregister.ap_priority = *u8++;
+               pdu->ap_payload.ap_unregister.ap_range_subid = *u8++;
+               u8++;
+               rawlen -= 4;
+               if ((nread = ax_pdutooid(&header,
+                   &(pdu->ap_payload.ap_unregister.ap_subtree),
+                   u8, rawlen)) == -1)
+                       goto fail;
+               rawlen -= nread;
+               u8 += nread;
+               if (pdu->ap_payload.ap_unregister.ap_range_subid) {
+                       if (rawlen != 4) {
+                               errno = EPROTO;
+                               goto fail;
+                       }
+                       pdu->ap_payload.ap_unregister.ap_upper_bound =
+                           ax_pdutoh32(&header, u8);
+                       rawlen -= 4;
+               }
+               if (rawlen != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_GETBULK:
+               if (rawlen < 4) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               pdu->ap_payload.ap_getbulk.ap_nonrep =
+                   ax_pdutoh16(&header, u8);
+               u8 += 2;
+               pdu->ap_payload.ap_getbulk.ap_maxrep =
+                   ax_pdutoh16(&header, u8);
+               u8 += 2;
+               srl = &(pdu->ap_payload.ap_getbulk.ap_srl);
+               rawlen -= 4;
+               /* FALLTHROUGH */
+       case AX_PDU_TYPE_GET:
+       case AX_PDU_TYPE_GETNEXT:
+               if (pdu->ap_header.aph_type != AX_PDU_TYPE_GETBULK)
+                       srl = &(pdu->ap_payload.ap_srl);
+               while (rawlen > 0 ) {
+                       srl->ap_nsr++;
+                       sr = reallocarray(srl->ap_sr, srl->ap_nsr, sizeof(*sr));
+                       if (sr == NULL)
+                               goto fail;
+                       srl->ap_sr = sr;
+                       sr += (srl->ap_nsr - 1);
+                       if ((nread = ax_pdutooid(&header, &(sr->asr_start),
+                           u8, rawlen)) == -1)
+                               goto fail;
+                       rawlen -= nread;
+                       u8 += nread;
+                       if ((nread = ax_pdutooid(&header, &(sr->asr_stop),
+                           u8, rawlen)) == -1)
+                               goto fail;
+                       rawlen -= nread;
+                       u8 += nread;
+               }
+               break;
+       case AX_PDU_TYPE_TESTSET:
+       case AX_PDU_TYPE_INDEXALLOCATE:
+       case AX_PDU_TYPE_INDEXDEALLOCATE:
+       case AX_PDU_TYPE_NOTIFY:
+               vbl = &(pdu->ap_payload.ap_vbl);
+               while (rawlen > 0) {
+                       varbind = recallocarray(vbl->ap_varbind,
+                           vbl->ap_nvarbind, vbl->ap_nvarbind + 1,
+                           sizeof(*(vbl->ap_varbind)));
+                       if (varbind == NULL)
+                               goto fail;
+                       vbl->ap_varbind = varbind;
+                       nread = ax_pdutovarbind(&header,
+                           &(vbl->ap_varbind[vbl->ap_nvarbind]), u8, rawlen);
+                       if (nread == -1)
+                               goto fail;
+                       vbl->ap_nvarbind++;
+                       u8 += nread;
+                       rawlen -= nread;
+               }
+               break;
+       case AX_PDU_TYPE_COMMITSET:
+       case AX_PDU_TYPE_UNDOSET:
+       case AX_PDU_TYPE_CLEANUPSET:
+       case AX_PDU_TYPE_PING:
+               if (rawlen != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_ADDAGENTCAPS:
+               nread = ax_pdutooid(&header,
+                   &(pdu->ap_payload.ap_addagentcaps.ap_oid), u8, rawlen);
+               if (nread == -1)
+                       goto fail;
+               rawlen -= nread;
+               u8 += nread;
+               nread = ax_pdutoostring(&header,
+                   &(pdu->ap_payload.ap_addagentcaps.ap_descr), u8, rawlen);
+               if (nread == -1)
+                       goto fail;
+               if (rawlen - nread != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_REMOVEAGENTCAPS:
+               nread = ax_pdutooid(&header,
+                   &(pdu->ap_payload.ap_removeagentcaps.ap_oid), u8, rawlen);
+               if (nread == -1)
+                       goto fail;
+               if (rawlen - nread != 0) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               break;
+       case AX_PDU_TYPE_RESPONSE:
+               if (ax->ax_packetids != NULL) {
+                       found = 0;
+                       for (i = 0; ax->ax_packetids[i] != 0; i++) {
+                               if (ax->ax_packetids[i] ==
+                                   pdu->ap_header.aph_packetid) {
+                                       packetidx = i;
+                                       found = 1;
+                               }
+                       }
+                       if (found) {
+                               ax->ax_packetids[packetidx] =
+                                   ax->ax_packetids[i - 1];
+                               ax->ax_packetids[i - 1] = 0;
+                       } else {
+                               errno = EPROTO;
+                               goto fail;
+                       }
+               }
+               if (rawlen < 8) {
+                       errno = EPROTO;
+                       goto fail;
+               }
+               response = &(pdu->ap_payload.ap_response);
+               response->ap_uptime = ax_pdutoh32(&header, u8);
+               u8 += 4;
+               response->ap_error = ax_pdutoh16(&header, u8);
+               u8 += 2;
+               response->ap_index = ax_pdutoh16(&header, u8);
+               u8 += 2;
+               rawlen -= 8;
+               while (rawlen > 0) {
+                       varbind = recallocarray(response->ap_varbindlist,
+                           response->ap_nvarbind, response->ap_nvarbind + 1,
+                           sizeof(*(response->ap_varbindlist)));
+                       if (varbind == NULL)
+                               goto fail;
+                       response->ap_varbindlist = varbind;
+                       nread = ax_pdutovarbind(&header,
+                           &(response->ap_varbindlist[response->ap_nvarbind]),
+                           u8, rawlen);
+                       if (nread == -1)
+                               goto fail;
+                       response->ap_nvarbind++;
+                       u8 += nread;
+                       rawlen -= nread;
+               }
+               break;
+       default:
+               errno = EPROTO;
+               goto fail;
+       }
+
+       ax->ax_rblen = 0;
+
+       return pdu;
+fail:
+       ax_pdu_free(pdu);
+       return NULL;
+}
+
+static int
+ax_pdu_need(struct ax *ax, size_t need)
+{
+       uint8_t *wbuf;
+       size_t wbsize;
+
+       if (ax->ax_wbtlen + need >= ax->ax_wbsize) {
+               wbsize = (((ax->ax_wbtlen + need) / 512) + 1) * 512;
+               wbuf = recallocarray(ax->ax_wbuf, ax->ax_wbsize, wbsize, 1);
+               if (wbuf == NULL) {
+                       ax->ax_wbtlen = ax->ax_wblen;
+                       return -1;
+               }
+               ax->ax_wbsize = wbsize;
+               ax->ax_wbuf = wbuf;
+       }
+
+       return 0;
+}
+
+ssize_t
+ax_send(struct ax *ax)
+{
+       ssize_t nwrite;
+
+       if (ax->ax_wblen != ax->ax_wbtlen) {
+               errno = EALREADY;
+               return -1;
+       }
+
+       if (ax->ax_wblen == 0)
+               return 0;
+
+#if defined(AX_DEBUG) && defined(AX_DEBUG_VERBOSE)
+       {
+               size_t i;
+               char chars[4];
+               int print = 1;
+
+               fprintf(stderr, "sending packet:\n");
+               for (i = 0; i < ax->ax_wblen; i++) {
+                       fprintf(stderr, "%02hhx ", ax->ax_wbuf[i]);
+                       chars[i % 4] = ax->ax_wbuf[i];
+                       if (!isprint(ax->ax_wbuf[i]))
+                               print = 0;
+                       if (i % 4 == 3) {
+                               if (print)
+                                       fprintf(stderr, "%.4s", chars);
+                               fprintf(stderr, "\n");
+                               print = 1;
+                       }
+               }
+       }
+#endif
+
+       if ((nwrite = send(ax->ax_fd, ax->ax_wbuf, ax->ax_wblen,
+           MSG_NOSIGNAL | MSG_DONTWAIT)) == -1)
+               return -1;
+
+       memmove(ax->ax_wbuf, ax->ax_wbuf + nwrite, ax->ax_wblen - nwrite);
+       ax->ax_wblen -= nwrite;
+       ax->ax_wbtlen = ax->ax_wblen;
+
+       return ax->ax_wblen;
+}
+
+uint32_t
+ax_open(struct ax *ax, uint8_t timeout, struct ax_oid *oid,
+    struct ax_ostring *descr)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_OPEN, 0, 0, 0, 0,
+           NULL) == -1)
+               return 0;
+       ax_pdu_need(ax, 4);
+       ax->ax_wbuf[ax->ax_wbtlen++] = timeout;
+       memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 3);
+       ax->ax_wbtlen += 3;
+       if (ax_pdu_add_oid(ax, oid) == -1)
+               return 0;
+       if (ax_pdu_add_str(ax, descr) == -1)
+               return 0;
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_close(struct ax *ax, uint32_t sessionid,
+    enum ax_close_reason reason)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_CLOSE, 0, sessionid, 0, 0,
+           NULL) == -1)
+               return 0;
+
+       if (ax_pdu_need(ax, 4) == -1)
+               return 0;
+       ax->ax_wbuf[ax->ax_wbtlen++] = (uint8_t)reason;
+       memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 3);
+       ax->ax_wbtlen += 3;
+
+       return ax_pdu_queue(ax);
+}
+
+int
+ax_get(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
+    uint32_t packetid, struct ax_ostring *context, struct ax_searchrange *srl,
+    size_t nsr)
+{
+       size_t i;
+
+       if (ax_pdu_header(ax, AX_PDU_TYPE_GET, 0, sessionid, transactionid,
+           packetid, context) == -1)
+               return -1;
+
+       for (i = 0; i < nsr; i++) {
+               if (ax_pdu_add_searchrange(ax, &(srl[i])) == -1)
+                       return 0;
+       }
+
+       return ax_pdu_queue(ax);
+}
+
+int
+ax_getnext(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
+    uint32_t packetid, struct ax_ostring *context, struct ax_searchrange *srl,
+    size_t nsr)
+{
+       size_t i;
+
+       if (ax_pdu_header(ax, AX_PDU_TYPE_GETNEXT, 0, sessionid, transactionid,
+           packetid, context) == -1)
+               return -1;
+
+       for (i = 0; i < nsr; i++) {
+               if (ax_pdu_add_searchrange(ax, &(srl[i])) == -1)
+                       return 0;
+       }
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_indexallocate(struct ax *ax, uint8_t flags, uint32_t sessionid,
+    struct ax_ostring *context, struct ax_varbind *vblist, size_t nvb)
+{
+       if (flags & ~(AX_PDU_FLAG_NEW_INDEX | AX_PDU_FLAG_ANY_INDEX)) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if (ax_pdu_header(ax, AX_PDU_TYPE_INDEXALLOCATE, flags,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+
+       if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
+               return 0;
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_indexdeallocate(struct ax *ax, uint32_t sessionid,
+    struct ax_ostring *context, struct ax_varbind *vblist, size_t nvb)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_INDEXDEALLOCATE, 0,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+
+       if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
+               return 0;
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_addagentcaps(struct ax *ax, uint32_t sessionid,
+    struct ax_ostring *context, struct ax_oid *id,
+    struct ax_ostring *descr)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_ADDAGENTCAPS, 0,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+       if (ax_pdu_add_oid(ax, id) == -1)
+               return 0;
+       if (ax_pdu_add_str(ax, descr) == -1)
+               return 0;
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_removeagentcaps(struct ax *ax, uint32_t sessionid,
+    struct ax_ostring *context, struct ax_oid *id)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_REMOVEAGENTCAPS, 0,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+       if (ax_pdu_add_oid(ax, id) == -1)
+               return 0;
+
+       return ax_pdu_queue(ax);
+
+}
+
+uint32_t
+ax_register(struct ax *ax, uint8_t flags, uint32_t sessionid,
+    struct ax_ostring *context, uint8_t timeout, uint8_t priority,
+    uint8_t range_subid, struct ax_oid *subtree, uint32_t upperbound)
+{
+       if (flags & ~(AX_PDU_FLAG_INSTANCE_REGISTRATION)) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if (ax_pdu_header(ax, AX_PDU_TYPE_REGISTER, flags,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+
+       if (ax_pdu_need(ax, 4) == -1)
+               return 0;
+       ax->ax_wbuf[ax->ax_wbtlen++] = timeout;
+       ax->ax_wbuf[ax->ax_wbtlen++] = priority;
+       ax->ax_wbuf[ax->ax_wbtlen++] = range_subid;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 0;
+       if (ax_pdu_add_oid(ax, subtree) == -1)
+               return 0;
+       if (range_subid != 0) {
+               if (ax_pdu_add_uint32(ax, upperbound) == -1)
+                       return 0;
+       }
+
+       return ax_pdu_queue(ax);
+}
+
+uint32_t
+ax_unregister(struct ax *ax, uint32_t sessionid,
+    struct ax_ostring *context, uint8_t priority, uint8_t range_subid,
+    struct ax_oid *subtree, uint32_t upperbound)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_UNREGISTER, 0,
+           sessionid, 0, 0, context) == -1)
+               return 0;
+
+       if (ax_pdu_need(ax, 4) == -1)
+               return 0;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 0;
+       ax->ax_wbuf[ax->ax_wbtlen++] = priority;
+       ax->ax_wbuf[ax->ax_wbtlen++] = range_subid;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 0;
+       if (ax_pdu_add_oid(ax, subtree) == -1)
+               return 0;
+       if (range_subid != 0) {
+               if (ax_pdu_add_uint32(ax, upperbound) == -1)
+                       return 0;
+       }
+
+       return ax_pdu_queue(ax);
+}
+
+int
+ax_response(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
+    uint32_t packetid, struct ax_ostring *context, uint32_t sysuptime,
+    uint16_t error, uint16_t index, struct ax_varbind *vblist, size_t nvb)
+{
+       if (ax_pdu_header(ax, AX_PDU_TYPE_RESPONSE, 0, sessionid,
+           transactionid, packetid, context) == -1)
+               return -1;
+
+       if (ax_pdu_add_uint32(ax, sysuptime) == -1 ||
+           ax_pdu_add_uint16(ax, error) == -1 ||
+           ax_pdu_add_uint16(ax, index) == -1)
+               return -1;
+
+       if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
+               return -1;
+       if (ax_pdu_queue(ax) == 0)
+               return -1;
+       return 0;
+}
+
+void
+ax_pdu_free(struct ax_pdu *pdu)
+{
+       size_t i;
+       struct ax_pdu_response *response;
+       struct ax_pdu_varbindlist *vblist;
+
+       if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT)
+               free(pdu->ap_context.aos_string);
+
+       switch (pdu->ap_header.aph_type) {
+       case AX_PDU_TYPE_OPEN:
+               free(pdu->ap_payload.ap_open.ap_descr.aos_string);
+               break;
+       case AX_PDU_TYPE_GET:
+       case AX_PDU_TYPE_GETNEXT:
+       case AX_PDU_TYPE_GETBULK:
+               free(pdu->ap_payload.ap_srl.ap_sr);
+               break;
+       case AX_PDU_TYPE_TESTSET:
+       case AX_PDU_TYPE_INDEXALLOCATE:
+       case AX_PDU_TYPE_INDEXDEALLOCATE:
+               vblist = &(pdu->ap_payload.ap_vbl);
+               for (i = 0; i < vblist->ap_nvarbind; i++)
+                       ax_varbind_free(&(vblist->ap_varbind[i]));
+               free(vblist->ap_varbind);
+               break;
+       case AX_PDU_TYPE_RESPONSE:
+               response = &(pdu->ap_payload.ap_response);
+               for (i = 0; i < response->ap_nvarbind; i++)
+                       ax_varbind_free(&(response->ap_varbindlist[i]));
+               free(response->ap_varbindlist);
+               break;
+       default:
+               break;
+       }
+       free(pdu);
+}
+
+void
+ax_varbind_free(struct ax_varbind *varbind)
+{
+       switch (varbind->avb_type) {
+       case AX_DATA_TYPE_OCTETSTRING:
+       case AX_DATA_TYPE_IPADDRESS:
+       case AX_DATA_TYPE_OPAQUE:
+               free(varbind->avb_data.avb_ostring.aos_string);
+               break;
+       default:
+               break;
+       }
+}
+
+const char *
+ax_error2string(enum ax_pdu_error error)
+{
+       static char buffer[64];
+       switch (error) {
+       case AX_PDU_ERROR_NOERROR:
+               return "No error";
+       case AX_PDU_ERROR_GENERR:
+               return "Generic error";
+       case AX_PDU_ERROR_NOACCESS:
+               return "No access";
+       case AX_PDU_ERROR_WRONGTYPE:
+               return "Wrong type";
+       case AX_PDU_ERROR_WRONGLENGTH:
+               return "Wrong length";
+       case AX_PDU_ERROR_WRONGENCODING:
+               return "Wrong encoding";
+       case AX_PDU_ERROR_WRONGVALUE:
+               return "Wrong value";
+       case AX_PDU_ERROR_NOCREATION:
+               return "No creation";
+       case AX_PDU_ERROR_INCONSISTENTVALUE:
+               return "Inconsistent value";
+       case AX_PDU_ERROR_RESOURCEUNAVAILABLE:
+               return "Resource unavailable";
+       case AX_PDU_ERROR_COMMITFAILED:
+               return "Commit failed";
+       case AX_PDU_ERROR_UNDOFAILED:
+               return "Undo failed";
+       case AX_PDU_ERROR_NOTWRITABLE:
+               return "Not writable";
+       case AX_PDU_ERROR_INCONSISTENTNAME:
+               return "Inconsistent name";
+       case AX_PDU_ERROR_OPENFAILED:
+               return "Open Failed";
+       case AX_PDU_ERROR_NOTOPEN:
+               return "Not open";
+       case AX_PDU_ERROR_INDEXWRONGTYPE:
+               return "Index wrong type";
+       case AX_PDU_ERROR_INDEXALREADYALLOCATED:
+               return "Index already allocated";
+       case AX_PDU_ERROR_INDEXNONEAVAILABLE:
+               return "Index none available";
+       case AX_PDU_ERROR_INDEXNOTALLOCATED:
+               return "Index not allocated";
+       case AX_PDU_ERROR_UNSUPPORTEDCONETXT:
+               return "Unsupported context";
+       case AX_PDU_ERROR_DUPLICATEREGISTRATION:
+               return "Duplicate registration";
+       case AX_PDU_ERROR_UNKNOWNREGISTRATION:
+               return "Unkown registration";
+       case AX_PDU_ERROR_UNKNOWNAGENTCAPS:
+               return "Unknown agent capabilities";
+       case AX_PDU_ERROR_PARSEERROR:
+               return "Parse error";
+       case AX_PDU_ERROR_REQUESTDENIED:
+               return "Request denied";
+       case AX_PDU_ERROR_PROCESSINGERROR:
+               return "Processing error";
+       }
+       snprintf(buffer, sizeof(buffer), "Unknown error: %d", error);
+       return buffer;
+}
+
+const char *
+ax_pdutype2string(enum ax_pdu_type type)
+{
+       static char buffer[64];
+       switch(type) {
+       case AX_PDU_TYPE_OPEN:
+               return "agentx-Open-PDU";
+       case AX_PDU_TYPE_CLOSE:
+               return "agentx-Close-PDU";
+       case AX_PDU_TYPE_REGISTER:
+               return "agentx-Register-PDU";
+       case AX_PDU_TYPE_UNREGISTER:
+               return "agentx-Unregister-PDU";
+       case AX_PDU_TYPE_GET:
+               return "agentx-Get-PDU";
+       case AX_PDU_TYPE_GETNEXT:
+               return "agentx-GetNext-PDU";
+       case AX_PDU_TYPE_GETBULK:
+               return "agentx-GetBulk-PDU";
+       case AX_PDU_TYPE_TESTSET:
+               return "agentx-TestSet-PDU";
+       case AX_PDU_TYPE_COMMITSET:
+               return "agentx-CommitSet-PDU";
+       case AX_PDU_TYPE_UNDOSET:
+               return "agentx-UndoSet-PDU";
+       case AX_PDU_TYPE_CLEANUPSET:
+               return "agentx-CleanupSet-PDU";
+       case AX_PDU_TYPE_NOTIFY:
+               return "agentx-Notify-PDU";
+       case AX_PDU_TYPE_PING:
+               return "agentx-Ping-PDU";
+       case AX_PDU_TYPE_INDEXALLOCATE:
+               return "agentx-IndexAllocate-PDU";
+       case AX_PDU_TYPE_INDEXDEALLOCATE:
+               return "agentx-IndexDeallocate-PDU";
+       case AX_PDU_TYPE_ADDAGENTCAPS:
+               return "agentx-AddAgentCaps-PDU";
+       case AX_PDU_TYPE_REMOVEAGENTCAPS:
+               return "agentx-RemoveAgentCaps-PDU";
+       case AX_PDU_TYPE_RESPONSE:
+               return "agentx-Response-PDU";
+       }
+       snprintf(buffer, sizeof(buffer), "Unknown type: %d", type);
+       return buffer;
+}
+
+const char *
+ax_closereason2string(enum ax_close_reason reason)
+{
+       static char buffer[64];
+
+       switch (reason) {
+       case AX_CLOSE_OTHER:
+               return "Undefined reason";
+       case AX_CLOSEN_PARSEERROR:
+               return "Too many AgentX parse errors from peer";
+       case AX_CLOSE_PROTOCOLERROR:
+               return "Too many AgentX protocol errors from peer";
+       case AX_CLOSE_TIMEOUTS:
+               return "Too many timeouts waiting for peer";
+       case AX_CLOSE_SHUTDOWN:
+               return "shutting down";
+       case AX_CLOSE_BYMANAGER:
+               return "Manager shuts down";
+       }
+       snprintf(buffer, sizeof(buffer), "Unknown reason: %d", reason);
+       return buffer;
+}
+
+const char *
+ax_oid2string(struct ax_oid *oid)
+{
+       return ax_oidrange2string(oid, 0, 0);
+}
+
+const char *
+ax_oidrange2string(struct ax_oid *oid, uint8_t range_subid,
+    uint32_t upperbound)
+{
+       static char buf[1024];
+       char *p;
+       size_t i, rest;
+       int ret;
+
+       rest = sizeof(buf);
+       p = buf;
+       for (i = 0; i < oid->aoi_idlen; i++) {
+               if (range_subid != 0 && range_subid - 1 == (uint8_t)i)
+                       ret = snprintf(p, rest, ".[%u-%u]", oid->aoi_id[i],
+                           upperbound);
+               else
+                       ret = snprintf(p, rest, ".%u", oid->aoi_id[i]);
+               if ((size_t) ret >= rest) {
+                       snprintf(buf, sizeof(buf), "Couldn't parse oid");
+                       return buf;
+               }
+               p += ret;
+               rest -= (size_t) ret;
+       }
+       return buf;
+}
+
+const char *
+ax_varbind2string(struct ax_varbind *vb)
+{
+       static char buf[1024];
+       char tmpbuf[1024];
+       size_t i, bufleft;
+       int ishex = 0;
+       char *p;
+       int ret;
+
+       switch (vb->avb_type) {
+       case AX_DATA_TYPE_INTEGER:
+               snprintf(buf, sizeof(buf), "%s: (int)%d",
+                   ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_int32);
+               break;
+       case AX_DATA_TYPE_OCTETSTRING:
+               for (i = 0;
+                   i < vb->avb_data.avb_ostring.aos_slen && !ishex; i++) {
+                       if (!isprint(vb->avb_data.avb_ostring.aos_string[i]))
+                               ishex = 1;
+               }
+               if (ishex) {
+                       p = tmpbuf;
+                       bufleft = sizeof(tmpbuf);
+                       for (i = 0;
+                           i < vb->avb_data.avb_ostring.aos_slen; i++) {
+                               ret = snprintf(p, bufleft, " %02hhX",
+                                   vb->avb_data.avb_ostring.aos_string[i]);
+                               if (ret >= (int) bufleft) {
+                                       p = strrchr(p, ' ');
+                                       strlcpy(p, "...", 4);
+                                       break;
+                               }
+                               p += 3;
+                               bufleft -= 3;
+                       }
+                       ret = snprintf(buf, sizeof(buf), "%s: (hex-string)%s",
+                           ax_oid2string(&(vb->avb_oid)), tmpbuf);
+                       if (ret >= (int) sizeof(buf)) {
+                               p  = strrchr(buf, ' ');
+                               strlcpy(p, "...", 4);
+                       }
+               } else {
+                       ret = snprintf(buf, sizeof(buf), "%s: (string)",
+                           ax_oid2string(&(vb->avb_oid)));
+                       if (ret >= (int) sizeof(buf)) {
+                               snprintf(buf, sizeof(buf), "<too large OID>: "
+                                   "(string)<too large string>");
+                               break;
+                       }
+                       p = buf + ret;
+                       bufleft = (int) sizeof(buf) - ret;
+                       if (snprintf(p, bufleft, "%.*s",
+                           vb->avb_data.avb_ostring.aos_slen,
+                           vb->avb_data.avb_ostring.aos_string) >=
+                           (int) bufleft) {
+                               p = buf + sizeof(buf) - 4;
+                               strlcpy(p, "...", 4);
+                       }
+               }
+               break;
+       case AX_DATA_TYPE_NULL:
+               snprintf(buf, sizeof(buf), "%s: <null>",
+                   ax_oid2string(&(vb->avb_oid)));
+               break;
+       case AX_DATA_TYPE_OID:
+               strlcpy(tmpbuf,
+                   ax_oid2string(&(vb->avb_data.avb_oid)), sizeof(tmpbuf));
+               snprintf(buf, sizeof(buf), "%s: (oid)%s",
+                   ax_oid2string(&(vb->avb_oid)), tmpbuf);
+               break;
+       case AX_DATA_TYPE_IPADDRESS:
+               if (vb->avb_data.avb_ostring.aos_slen != 4) {
+                       snprintf(buf, sizeof(buf), "%s: (ipaddress)<invalid>",
+                           ax_oid2string(&(vb->avb_oid)));
+                       break;
+               }
+               if (inet_ntop(PF_INET, vb->avb_data.avb_ostring.aos_string,
+                   tmpbuf, sizeof(tmpbuf)) == NULL) {
+                       snprintf(buf, sizeof(buf), "%s: (ipaddress)"
+                           "<unparseable>: %s",
+                           ax_oid2string(&(vb->avb_oid)),
+                           strerror(errno));
+                       break;
+               }
+               snprintf(buf, sizeof(buf), "%s: (ipaddress)%s",
+                   ax_oid2string(&(vb->avb_oid)), tmpbuf);
+               break;
+       case AX_DATA_TYPE_COUNTER32:
+               snprintf(buf, sizeof(buf), "%s: (counter32)%u",
+                   ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
+               break;
+       case AX_DATA_TYPE_GAUGE32:
+               snprintf(buf, sizeof(buf), "%s: (gauge32)%u",
+                   ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
+               break;
+       case AX_DATA_TYPE_TIMETICKS:
+               snprintf(buf, sizeof(buf), "%s: (timeticks)%u",
+                   ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
+               break;
+       case AX_DATA_TYPE_OPAQUE:
+               p = tmpbuf;
+               bufleft = sizeof(tmpbuf);
+               for (i = 0;
+                   i < vb->avb_data.avb_ostring.aos_slen; i++) {
+                       ret = snprintf(p, bufleft, " %02hhX",
+                           vb->avb_data.avb_ostring.aos_string[i]);
+                       if (ret >= (int) bufleft) {
+                               p = strrchr(p, ' ');
+                               strlcpy(p, "...", 4);
+                               break;
+                       }
+                       p += 3;
+                       bufleft -= 3;
+               }
+               ret = snprintf(buf, sizeof(buf), "%s: (opaque)%s",
+                   ax_oid2string(&(vb->avb_oid)), tmpbuf);
+               if (ret >= (int) sizeof(buf)) {
+                       p  = strrchr(buf, ' ');
+                       strlcpy(p, "...", 4);
+               }
+               break;
+       case AX_DATA_TYPE_COUNTER64:
+               snprintf(buf, sizeof(buf), "%s: (counter64)%"PRIu64,
+                   ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint64);
+               break;
+       case AX_DATA_TYPE_NOSUCHOBJECT:
+               snprintf(buf, sizeof(buf), "%s: <noSuchObject>",
+                   ax_oid2string(&(vb->avb_oid)));
+               break;
+       case AX_DATA_TYPE_NOSUCHINSTANCE:
+               snprintf(buf, sizeof(buf), "%s: <noSuchInstance>",
+                   ax_oid2string(&(vb->avb_oid)));
+               break;
+       case AX_DATA_TYPE_ENDOFMIBVIEW:
+               snprintf(buf, sizeof(buf), "%s: <endOfMibView>",
+                   ax_oid2string(&(vb->avb_oid)));
+               break;
+       }
+       return buf;
+}
+
+int
+ax_oid_cmp(struct ax_oid *o1, struct ax_oid *o2)
+{
+       size_t i, min;
+
+       min = o1->aoi_idlen < o2->aoi_idlen ? o1->aoi_idlen : o2->aoi_idlen;
+       for (i = 0; i < min; i++) {
+               if (o1->aoi_id[i] < o2->aoi_id[i])
+                       return -1;
+               if (o1->aoi_id[i] > o2->aoi_id[i])
+                       return 1;
+       }
+       /* o1 is parent of o2 */
+       if (o1->aoi_idlen < o2->aoi_idlen)
+               return -2;
+       /* o1 is child of o2 */
+       if (o1->aoi_idlen > o2->aoi_idlen)
+               return 2;
+       return 0;
+}
+
+int
+ax_oid_add(struct ax_oid *oid, uint32_t value)
+{
+       if (oid->aoi_idlen == AX_OID_MAX_LEN)
+               return -1;
+       oid->aoi_id[oid->aoi_idlen++] = value;
+       return 0;
+}
+
+static uint32_t
+ax_pdu_queue(struct ax *ax)
+{
+       struct ax_pdu_header header;
+       uint32_t packetid, plength;
+       size_t wbtlen = ax->ax_wbtlen;
+
+       header.aph_flags = ax->ax_byteorder == AX_BYTE_ORDER_BE ?
+           AX_PDU_FLAG_NETWORK_BYTE_ORDER : 0;
+       packetid = ax_pdutoh32(&header, &(ax->ax_wbuf[ax->ax_wblen + 12]));
+       plength = (ax->ax_wbtlen - ax->ax_wblen) - AX_PDU_HEADER;
+       ax->ax_wbtlen = ax->ax_wblen + 16;
+       (void)ax_pdu_add_uint32(ax, plength);
+
+       ax->ax_wblen = ax->ax_wbtlen = wbtlen;
+
+       return packetid;
+}
+
+static int
+ax_pdu_header(struct ax *ax, enum ax_pdu_type type, uint8_t flags,
+    uint32_t sessionid, uint32_t transactionid, uint32_t packetid,
+    struct ax_ostring *context)
+{
+       if (ax->ax_wblen != ax->ax_wbtlen) {
+               errno = EALREADY;
+               return -1;
+       }
+
+       if (ax_pdu_need(ax, 4) == -1)
+               return -1;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 1;
+       ax->ax_wbuf[ax->ax_wbtlen++] = (uint8_t) type;
+       if (context != NULL)
+               flags |= AX_PDU_FLAG_NON_DEFAULT_CONTEXT;
+       if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
+               flags |= AX_PDU_FLAG_NETWORK_BYTE_ORDER;
+       ax->ax_wbuf[ax->ax_wbtlen++] = flags;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 0;
+       if (packetid == 0)
+               packetid = ax_packetid(ax);
+       if (ax_pdu_add_uint32(ax, sessionid) == -1 ||
+           ax_pdu_add_uint32(ax, transactionid) == -1 ||
+           ax_pdu_add_uint32(ax, packetid) == -1 ||
+           ax_pdu_need(ax, 4) == -1)
+               return -1;
+       ax->ax_wbtlen += 4;
+       if (context != NULL) {
+               if (ax_pdu_add_str(ax, context) == -1)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static uint32_t
+ax_packetid(struct ax *ax)
+{
+       uint32_t packetid, *packetids;
+       size_t npackets = 0, i;
+       int found;
+
+       if (ax->ax_packetids != NULL) {
+               for (npackets = 0; ax->ax_packetids[npackets] != 0; npackets++)
+                       continue;
+       }
+       if (ax->ax_packetidsize == 0 || npackets == ax->ax_packetidsize - 1) {
+               packetids = recallocarray(ax->ax_packetids, ax->ax_packetidsize,
+                   ax->ax_packetidsize + 25, sizeof(*packetids));
+               if (packetids == NULL)
+                       return 0;
+               ax->ax_packetidsize += 25;
+               ax->ax_packetids = packetids;
+       }
+       do {
+               found = 0;
+               packetid = arc4random();
+               for (i = 0; ax->ax_packetids[i] != 0; i++) {
+                       if (ax->ax_packetids[i] == packetid) {
+                               found = 1;
+                               break;
+                       }
+               }
+       } while (packetid == 0 || found);
+       ax->ax_packetids[npackets] = packetid;
+
+       return packetid;
+}
+
+static int
+ax_pdu_add_uint16(struct ax *ax, uint16_t value)
+{
+       if (ax_pdu_need(ax, sizeof(value)) == -1)
+               return -1;
+
+       if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
+               value = htobe16(value);
+       else
+               value = htole16(value);
+       memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
+       ax->ax_wbtlen += sizeof(value);
+       return 0;
+}
+
+static int
+ax_pdu_add_uint32(struct ax *ax, uint32_t value)
+{
+       if (ax_pdu_need(ax, sizeof(value)) == -1)
+               return -1;
+
+       if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
+               value = htobe32(value);
+       else
+               value = htole32(value);
+       memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
+       ax->ax_wbtlen += sizeof(value);
+       return 0;
+}
+
+static int
+ax_pdu_add_uint64(struct ax *ax, uint64_t value)
+{
+       if (ax_pdu_need(ax, sizeof(value)) == -1)
+               return -1;
+
+       if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
+               value = htobe64(value);
+       else
+               value = htole64(value);
+       memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
+       ax->ax_wbtlen += sizeof(value);
+       return 0;
+}
+
+
+static int
+ax_pdu_add_oid(struct ax *ax, struct ax_oid *oid)
+{
+       static struct ax_oid nulloid = {0};
+       uint8_t prefix = 0, n_subid, i = 0;
+
+       n_subid = oid->aoi_idlen;
+
+       if (oid == NULL)
+               oid = &nulloid;
+
+       if (oid->aoi_idlen > 4 &&
+           oid->aoi_id[0] == 1 && oid->aoi_id[1] == 3 &&
+           oid->aoi_id[2] == 6 && oid->aoi_id[3] == 1 &&
+           oid->aoi_id[4] <= UINT8_MAX) {
+               prefix = oid->aoi_id[4];
+               i = 5;
+       }
+
+       if (ax_pdu_need(ax, 4) == -1)
+               return -1;
+       ax->ax_wbuf[ax->ax_wbtlen++] = n_subid - i;
+       ax->ax_wbuf[ax->ax_wbtlen++] = prefix;
+       ax->ax_wbuf[ax->ax_wbtlen++] = oid->aoi_include;
+       ax->ax_wbuf[ax->ax_wbtlen++] = 0;
+
+       for (; i < n_subid; i++) {
+               if (ax_pdu_add_uint32(ax, oid->aoi_id[i]) == -1)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int
+ax_pdu_add_str(struct ax *ax, struct ax_ostring *str)
+{
+       size_t length, zeroes;
+
+       if (ax_pdu_add_uint32(ax, str->aos_slen) == -1)
+               return -1;
+
+       if ((zeroes = (4 - (str->aos_slen % 4))) == 4)
+               zeroes = 0;
+       length = str->aos_slen + zeroes;
+       if (ax_pdu_need(ax, length) == -1)
+               return -1;
+
+       memcpy(&(ax->ax_wbuf[ax->ax_wbtlen]), str->aos_string, str->aos_slen);
+       ax->ax_wbtlen += str->aos_slen;
+       memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, zeroes);
+       ax->ax_wbtlen += zeroes;
+       return 0;
+}
+
+static int
+ax_pdu_add_varbindlist(struct ax *ax,
+    struct ax_varbind *vblist, size_t nvb)
+{
+       size_t i;
+       uint16_t temp;
+
+       for (i = 0; i < nvb; i++) {
+               temp = (uint16_t) vblist[i].avb_type;
+               if (ax_pdu_add_uint16(ax, temp) == -1 ||
+                   ax_pdu_need(ax, 2))
+                       return -1;
+               memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 2);
+               ax->ax_wbtlen += 2;
+               if (ax_pdu_add_oid(ax, &(vblist[i].avb_oid)) == -1)
+                       return -1;
+               switch (vblist[i].avb_type) {
+               case AX_DATA_TYPE_INTEGER:
+                       if (ax_pdu_add_uint32(ax,
+                           vblist[i].avb_data.avb_int32) == -1)
+                               return -1;
+                       break;
+               case AX_DATA_TYPE_COUNTER32:
+               case AX_DATA_TYPE_GAUGE32:
+               case AX_DATA_TYPE_TIMETICKS:
+                       if (ax_pdu_add_uint32(ax,
+                           vblist[i].avb_data.avb_uint32) == -1)
+                               return -1;
+                       break;
+               case AX_DATA_TYPE_COUNTER64:
+                       if (ax_pdu_add_uint64(ax,
+                           vblist[i].avb_data.avb_uint64) == -1)
+                               return -1;
+                       break;
+               case AX_DATA_TYPE_OCTETSTRING:
+               case AX_DATA_TYPE_IPADDRESS:
+               case AX_DATA_TYPE_OPAQUE:
+                       if (ax_pdu_add_str(ax,
+                           &(vblist[i].avb_data.avb_ostring)) == -1)
+                               return -1;
+                       break;
+               case AX_DATA_TYPE_OID:
+                       if (ax_pdu_add_oid(ax,
+                           &(vblist[i].avb_data.avb_oid)) == -1)
+                               return -1;
+                       break;
+               case AX_DATA_TYPE_NULL:
+               case AX_DATA_TYPE_NOSUCHOBJECT:
+               case AX_DATA_TYPE_NOSUCHINSTANCE:
+               case AX_DATA_TYPE_ENDOFMIBVIEW:
+                       break;
+               default:
+                       errno = EINVAL;
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+static int
+ax_pdu_add_searchrange(struct ax *ax, struct ax_searchrange *sr)
+{
+       if (ax_pdu_add_oid(ax, &(sr->asr_start)) == -1 ||
+           ax_pdu_add_oid(ax, &(sr->asr_stop)) == -1)
+               return -1;
+       return 0;
+}
+
+static uint16_t
+ax_pdutoh16(struct ax_pdu_header *header, uint8_t *buf)
+{
+       uint16_t value;
+
+       memcpy(&value, buf, sizeof(value));
+       if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
+               return be16toh(value);
+       return le16toh(value);
+}
+
+static uint32_t
+ax_pdutoh32(struct ax_pdu_header *header, uint8_t *buf)
+{
+       uint32_t value;
+
+       memcpy(&value, buf, sizeof(value));
+       if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
+               return be32toh(value);
+       return le32toh(value);
+}
+
+static uint64_t
+ax_pdutoh64(struct ax_pdu_header *header, uint8_t *buf)
+{
+       uint64_t value;
+
+       memcpy(&value, buf, sizeof(value));
+       if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
+               return be64toh(value);
+       return le64toh(value);
+}
+
+static ssize_t
+ax_pdutooid(struct ax_pdu_header *header, struct ax_oid *oid,
+    uint8_t *buf, size_t rawlen)
+{
+       size_t i = 0;
+       ssize_t nread;
+
+       if (rawlen < 4)
+               goto fail;
+       rawlen -= 4;
+       nread = 4;
+       oid->aoi_idlen = *buf++;
+       if (rawlen < (oid->aoi_idlen * 4))
+               goto fail;
+       nread += oid->aoi_idlen * 4;
+       if (*buf != 0) {
+               oid->aoi_id[0] = 1;
+               oid->aoi_id[1] = 3;
+               oid->aoi_id[2] = 6;
+               oid->aoi_id[3] = 1;
+               oid->aoi_id[4] = *buf;
+               oid->aoi_idlen += 5;
+               i = 5;
+       }
+       buf++;
+       oid->aoi_include = *buf;
+       for (buf += 2; i < oid->aoi_idlen; i++, buf += 4)
+               oid->aoi_id[i] = ax_pdutoh32(header, buf);
+
+       return nread;
+
+fail:
+       errno = EPROTO;
+       return -1;
+}
+
+static ssize_t
+ax_pdutoostring(struct ax_pdu_header *header,
+    struct ax_ostring *ostring, uint8_t *buf, size_t rawlen)
+{
+       ssize_t nread;
+
+       if (rawlen < 4)
+               goto fail;
+
+       ostring->aos_slen = ax_pdutoh32(header, buf);
+       rawlen -= 4;
+       buf += 4;
+       if (ostring->aos_slen > rawlen)
+               goto fail;
+       if ((ostring->aos_string = malloc(ostring->aos_slen + 1)) == NULL)
+               return -1;
+       memcpy(ostring->aos_string, buf, ostring->aos_slen);
+       ostring->aos_string[ostring->aos_slen] = '\0';
+
+       nread = 4 + ostring->aos_slen;
+       if (ostring->aos_slen % 4 != 0)
+               nread += 4 - (ostring->aos_slen % 4);
+
+       return nread;
+
+fail:
+       errno = EPROTO;
+       return -1;
+}
+
+static ssize_t
+ax_pdutovarbind(struct ax_pdu_header *header,
+    struct ax_varbind *varbind, uint8_t *buf, size_t rawlen)
+{
+       ssize_t nread, rread = 4;
+
+       if (rawlen == 0)
+               return 0;
+
+       if (rawlen < 8)
+               goto fail;
+       varbind->avb_type = ax_pdutoh16(header, buf);
+
+       buf += 4;
+       rawlen -= 4;
+       nread = ax_pdutooid(header, &(varbind->avb_oid), buf, rawlen);
+       if (nread == -1)
+               return -1;
+       rread += nread;
+       buf += nread;
+       rawlen -= nread;
+
+       switch(varbind->avb_type) {
+       case AX_DATA_TYPE_INTEGER:
+               if (rawlen < 4)
+                       goto fail;
+               varbind->avb_data.avb_int32 = ax_pdutoh32(header, buf);
+               return rread + 4;
+       case AX_DATA_TYPE_COUNTER32:
+       case AX_DATA_TYPE_GAUGE32:
+       case AX_DATA_TYPE_TIMETICKS:
+               if (rawlen < 4)
+                       goto fail;
+               varbind->avb_data.avb_uint32 = ax_pdutoh32(header, buf);
+               return rread + 4;
+       case AX_DATA_TYPE_COUNTER64:
+               if (rawlen < 8)
+                       goto fail;
+               varbind->avb_data.avb_uint64 = ax_pdutoh64(header, buf);
+               return rread + 8;
+       case AX_DATA_TYPE_OCTETSTRING:
+       case AX_DATA_TYPE_IPADDRESS:
+       case AX_DATA_TYPE_OPAQUE:
+               nread = ax_pdutoostring(header,
+                   &(varbind->avb_data.avb_ostring), buf, rawlen);
+               if (nread == -1)
+                       return -1;
+               return nread + rread;
+       case AX_DATA_TYPE_OID:
+               nread = ax_pdutooid(header, &(varbind->avb_data.avb_oid),
+                   buf, rawlen);
+               if (nread == -1)
+                       return -1;
+               return nread + rread;
+       case AX_DATA_TYPE_NULL:
+       case AX_DATA_TYPE_NOSUCHOBJECT:
+       case AX_DATA_TYPE_NOSUCHINSTANCE:
+       case AX_DATA_TYPE_ENDOFMIBVIEW:
+               return rread;
+       }
+
+fail:
+       errno = EPROTO;
+       return -1;
+}
diff --git a/usr.sbin/snmpd/ax.h b/usr.sbin/snmpd/ax.h
new file mode 100644 (file)
index 0000000..3b2ea93
--- /dev/null
@@ -0,0 +1,265 @@
+/*     $OpenBSD: ax.h,v 1.1 2022/08/23 08:56:20 martijn Exp $ */
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@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 <stdint.h>
+
+#define AX_PDU_FLAG_INSTANCE_REGISTRATION (1 << 0)
+#define AX_PDU_FLAG_NEW_INDEX (1 << 1)
+#define AX_PDU_FLAG_ANY_INDEX (1 << 2)
+#define AX_PDU_FLAG_NON_DEFAULT_CONTEXT (1 << 3)
+#define AX_PDU_FLAG_NETWORK_BYTE_ORDER (1 << 4)
+
+#define AX_PRIORITY_DEFAULT 127
+
+enum ax_byte_order {
+       AX_BYTE_ORDER_BE,
+       AX_BYTE_ORDER_LE
+};
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define AX_BYTE_ORDER_NATIVE AX_BYTE_ORDER_BE
+#else
+#define AX_BYTE_ORDER_NATIVE AX_BYTE_ORDER_LE
+#endif
+
+enum ax_pdu_type {
+       AX_PDU_TYPE_OPEN                = 1,
+       AX_PDU_TYPE_CLOSE               = 2,
+       AX_PDU_TYPE_REGISTER            = 3,
+       AX_PDU_TYPE_UNREGISTER          = 4,
+       AX_PDU_TYPE_GET                 = 5,
+       AX_PDU_TYPE_GETNEXT             = 6,
+       AX_PDU_TYPE_GETBULK             = 7,
+       AX_PDU_TYPE_TESTSET             = 8,
+       AX_PDU_TYPE_COMMITSET           = 9,
+       AX_PDU_TYPE_UNDOSET             = 10,
+       AX_PDU_TYPE_CLEANUPSET          = 11,
+       AX_PDU_TYPE_NOTIFY              = 12,
+       AX_PDU_TYPE_PING                = 13,
+       AX_PDU_TYPE_INDEXALLOCATE       = 14,
+       AX_PDU_TYPE_INDEXDEALLOCATE     = 15,
+       AX_PDU_TYPE_ADDAGENTCAPS        = 16,
+       AX_PDU_TYPE_REMOVEAGENTCAPS     = 17,
+       AX_PDU_TYPE_RESPONSE            = 18
+};
+
+enum ax_pdu_error {
+       AX_PDU_ERROR_NOERROR                    = 0,
+       AX_PDU_ERROR_GENERR                     = 5,
+       AX_PDU_ERROR_NOACCESS                   = 6,
+       AX_PDU_ERROR_WRONGTYPE                  = 7,
+       AX_PDU_ERROR_WRONGLENGTH                = 8,
+       AX_PDU_ERROR_WRONGENCODING              = 9,
+       AX_PDU_ERROR_WRONGVALUE                 = 10,
+       AX_PDU_ERROR_NOCREATION                 = 11,
+       AX_PDU_ERROR_INCONSISTENTVALUE          = 12,
+       AX_PDU_ERROR_RESOURCEUNAVAILABLE        = 13,
+       AX_PDU_ERROR_COMMITFAILED               = 14,
+       AX_PDU_ERROR_UNDOFAILED                 = 15,
+       AX_PDU_ERROR_NOTWRITABLE                = 17,
+       AX_PDU_ERROR_INCONSISTENTNAME           = 18,
+       AX_PDU_ERROR_OPENFAILED                 = 256,
+       AX_PDU_ERROR_NOTOPEN                    = 257,
+       AX_PDU_ERROR_INDEXWRONGTYPE             = 258,
+       AX_PDU_ERROR_INDEXALREADYALLOCATED      = 259,
+       AX_PDU_ERROR_INDEXNONEAVAILABLE         = 260,
+       AX_PDU_ERROR_INDEXNOTALLOCATED          = 261,
+       AX_PDU_ERROR_UNSUPPORTEDCONETXT         = 262,
+       AX_PDU_ERROR_DUPLICATEREGISTRATION      = 263,
+       AX_PDU_ERROR_UNKNOWNREGISTRATION        = 264,
+       AX_PDU_ERROR_UNKNOWNAGENTCAPS           = 265,
+       AX_PDU_ERROR_PARSEERROR                 = 266,
+       AX_PDU_ERROR_REQUESTDENIED              = 267,
+       AX_PDU_ERROR_PROCESSINGERROR            = 268
+};
+
+enum ax_data_type {
+       AX_DATA_TYPE_INTEGER            = 2,
+       AX_DATA_TYPE_OCTETSTRING        = 4,
+       AX_DATA_TYPE_NULL               = 5,
+       AX_DATA_TYPE_OID                = 6,
+       AX_DATA_TYPE_IPADDRESS          = 64,
+       AX_DATA_TYPE_COUNTER32          = 65,
+       AX_DATA_TYPE_GAUGE32            = 66,
+       AX_DATA_TYPE_TIMETICKS          = 67,
+       AX_DATA_TYPE_OPAQUE             = 68,
+       AX_DATA_TYPE_COUNTER64          = 70,
+       AX_DATA_TYPE_NOSUCHOBJECT       = 128,
+       AX_DATA_TYPE_NOSUCHINSTANCE     = 129,
+       AX_DATA_TYPE_ENDOFMIBVIEW       = 130
+};
+
+enum ax_close_reason {
+       AX_CLOSE_OTHER                  = 1,
+       AX_CLOSEN_PARSEERROR            = 2,
+       AX_CLOSE_PROTOCOLERROR          = 3,
+       AX_CLOSE_TIMEOUTS               = 4,
+       AX_CLOSE_SHUTDOWN               = 5,
+       AX_CLOSE_BYMANAGER              = 6
+};
+
+struct ax {
+       int ax_fd;
+       enum ax_byte_order ax_byteorder;
+       uint8_t *ax_rbuf;
+       size_t ax_rblen;
+       size_t ax_rbsize;
+       uint8_t *ax_wbuf;
+       size_t ax_wblen;
+       size_t ax_wbtlen;
+       size_t ax_wbsize;
+       uint32_t *ax_packetids;
+       size_t ax_packetidsize;
+};
+
+#ifndef AX_PRIMITIVE
+#define AX_PRIMITIVE
+
+#define AX_OID_MAX_LEN 128
+
+struct ax_oid {
+       uint8_t aoi_include;
+       uint32_t aoi_id[AX_OID_MAX_LEN];
+       size_t aoi_idlen;
+};
+
+struct ax_ostring {
+       unsigned char *aos_string;
+       uint32_t aos_slen;
+};
+#endif
+
+struct ax_searchrange {
+       struct ax_oid asr_start;
+       struct ax_oid asr_stop;
+};
+
+struct ax_pdu_header {
+       uint8_t aph_version;
+       uint8_t aph_type;
+       uint8_t aph_flags;
+       uint8_t aph_reserved;
+       uint32_t aph_sessionid;
+       uint32_t aph_transactionid;
+       uint32_t aph_packetid;
+       uint32_t aph_plength;
+};
+
+struct ax_varbind {
+       enum ax_data_type avb_type;
+       struct ax_oid avb_oid;
+       union ax_data {
+               int32_t avb_int32;
+               uint32_t avb_uint32;
+               uint64_t avb_uint64;
+               struct ax_ostring avb_ostring;
+               struct ax_oid avb_oid;
+       } avb_data;
+};
+
+struct ax_pdu {
+       struct ax_pdu_header ap_header;
+       struct ax_ostring ap_context;
+       union {
+               struct ax_pdu_open {
+                       uint8_t ap_timeout;
+                       struct ax_oid ap_oid;
+                       struct ax_ostring ap_descr;
+               } ap_open;
+               struct ax_pdu_close {
+                       enum ax_close_reason ap_reason;
+               } ap_close;
+               struct ax_pdu_register {
+                       uint8_t ap_timeout;
+                       uint8_t ap_priority;
+                       uint8_t ap_range_subid;
+                       struct ax_oid ap_subtree;
+                       uint32_t ap_upper_bound;
+               } ap_register;
+               struct ax_pdu_unregister {
+                       uint8_t ap_priority;
+                       uint8_t ap_range_subid;
+                       struct ax_oid ap_subtree;
+                       uint32_t ap_upper_bound;
+               } ap_unregister;
+               struct ax_pdu_searchrangelist {
+                       size_t ap_nsr;
+                       struct ax_searchrange *ap_sr;
+               } ap_srl;
+               struct ax_pdu_getbulk {
+                       uint16_t ap_nonrep;
+                       uint16_t ap_maxrep;
+                       struct ax_pdu_searchrangelist ap_srl;
+               } ap_getbulk;
+               struct ax_pdu_varbindlist {
+                       struct ax_varbind *ap_varbind;
+                       size_t ap_nvarbind;
+               } ap_vbl;
+               struct ax_pdu_addagentcaps {
+                       struct ax_oid ap_oid;
+                       struct ax_ostring ap_descr;
+               } ap_addagentcaps;
+               struct ax_pdu_removeagentcaps {
+                       struct ax_oid ap_oid;
+               } ap_removeagentcaps;
+               struct ax_pdu_response {
+                       uint32_t ap_uptime;
+                       enum ax_pdu_error ap_error;
+                       uint16_t ap_index;
+                       struct ax_varbind *ap_varbindlist;
+                       size_t ap_nvarbind;
+               } ap_response;
+       } ap_payload;
+};
+
+struct ax *ax_new(int);
+void ax_free(struct ax *);
+struct ax_pdu *ax_recv(struct ax *);
+ssize_t ax_send(struct ax *);
+uint32_t ax_open(struct ax *, uint8_t, struct ax_oid *,
+    struct ax_ostring *);
+uint32_t ax_close(struct ax *, uint32_t, enum ax_close_reason);
+int ax_get(struct ax *, uint32_t, uint32_t, uint32_t, struct ax_ostring *,
+    struct ax_searchrange *, size_t);
+int ax_getnext(struct ax *, uint32_t, uint32_t, uint32_t, struct ax_ostring *,
+    struct ax_searchrange *, size_t);
+uint32_t ax_indexallocate(struct ax *, uint8_t, uint32_t,
+    struct ax_ostring *, struct ax_varbind *, size_t);
+uint32_t ax_indexdeallocate(struct ax *, uint32_t,
+    struct ax_ostring *, struct ax_varbind *, size_t);
+uint32_t ax_addagentcaps(struct ax *, uint32_t, struct ax_ostring *,
+    struct ax_oid *, struct ax_ostring *);
+uint32_t ax_removeagentcaps(struct ax *, uint32_t,
+    struct ax_ostring *, struct ax_oid *);
+uint32_t ax_register(struct ax *, uint8_t, uint32_t,
+    struct ax_ostring *, uint8_t, uint8_t, uint8_t, struct ax_oid *,
+    uint32_t);
+uint32_t ax_unregister(struct ax *, uint32_t, struct ax_ostring *,
+    uint8_t, uint8_t, struct ax_oid *, uint32_t);
+int ax_response(struct ax *, uint32_t, uint32_t, uint32_t,
+    struct ax_ostring *, uint32_t, uint16_t, uint16_t,
+    struct ax_varbind *, size_t);
+void ax_pdu_free(struct ax_pdu *);
+void ax_varbind_free(struct ax_varbind *);
+const char *ax_error2string(enum ax_pdu_error);
+const char *ax_pdutype2string(enum ax_pdu_type);
+const char *ax_oid2string(struct ax_oid *);
+const char *ax_oidrange2string(struct ax_oid *, uint8_t, uint32_t);
+const char *ax_varbind2string(struct ax_varbind *);
+const char *ax_closereason2string(enum ax_close_reason);
+int ax_oid_cmp(struct ax_oid *, struct ax_oid *);
+int ax_oid_add(struct ax_oid *, uint32_t);
index 6c26f3a..f056263 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.76 2022/06/30 11:53:07 martijn Exp $      */
+/*     $OpenBSD: parse.y,v 1.77 2022/08/23 08:56:20 martijn Exp $      */
 
 /*
  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
@@ -41,6 +41,7 @@
 #include <err.h>
 #include <errno.h>
 #include <event.h>
+#include <grp.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <stdint.h>
@@ -48,6 +49,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <netdb.h>
+#include <pwd.h>
 #include <string.h>
 #include <syslog.h>
 
@@ -111,6 +113,7 @@ typedef struct {
                struct host     *host;
                struct timeval   tv;
                struct ber_oid  *oid;
+               struct agentx_master ax;
                struct {
                        int              type;
                        void            *data;
@@ -122,10 +125,17 @@ typedef struct {
        int lineno;
 } YYSTYPE;
 
+#define axm_opts       axm_fd
+#define AXM_PATH       1 << 0
+#define AXM_OWNER      1 << 1
+#define AXM_GROUP      1 << 2
+#define AXM_MODE       1 << 3
+
 %}
 
 %token INCLUDE
 %token LISTEN ON READ WRITE NOTIFY SNMPV1 SNMPV2 SNMPV3
+%token AGENTX PATH OWNER GROUP MODE
 %token ENGINEID PEN OPENBSD IP4 IP6 MAC TEXT OCTETS AGENTID HOSTHASH
 %token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER
 %token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER
@@ -141,6 +151,7 @@ typedef struct {
 %type  <v.oid>         oid hostoid trapoid
 %type  <v.auth>        auth
 %type  <v.enc>         enc
+%type  <v.ax>          agentxopts agentxopt
 
 %%
 
@@ -203,6 +214,53 @@ yesno              :  STRING                       {
                ;
 
 main           : LISTEN ON listen_udptcp
+               | AGENTX agentxopts {
+                       struct agentx_master *master, *test;
+                       struct group *grp;
+
+                       if ((master = malloc(sizeof(*master))) == NULL) {
+                               yyerror("malloc");
+                               YYERROR;
+                       }
+                       master->axm_fd = -1;
+                       master->axm_sun.sun_len = sizeof(master->axm_sun);
+                       master->axm_sun.sun_family = AF_UNIX;
+                       if ($2.axm_opts & AXM_PATH)
+                               strlcpy(master->axm_sun.sun_path,
+                                   $2.axm_sun.sun_path,
+                                   sizeof(master->axm_sun.sun_path));
+                       else
+                               strlcpy(master->axm_sun.sun_path,
+                                   AGENTX_MASTER_PATH,
+                                   sizeof(master->axm_sun.sun_path));
+                       master->axm_owner = $2.axm_opts & AXM_OWNER ?
+                           $2.axm_owner : 0;
+                       if ($2.axm_opts & AXM_GROUP)
+                               master->axm_group = $2.axm_group;
+                       else {
+                               if ((grp = getgrnam(AGENTX_GROUP)) == NULL) {
+                                       yyerror("agentx: group %s not found",
+                                           AGENTX_GROUP);
+                                       YYERROR;
+                               }
+                               master->axm_group = grp->gr_gid;
+                       }
+                       master->axm_mode = $2.axm_opts & AXM_MODE ?
+                           $2.axm_mode : 0660;
+
+                       TAILQ_FOREACH(test, &conf->sc_agentx_masters,
+                           axm_entry) {
+                               if (strcmp(test->axm_sun.sun_path,
+                                   master->axm_sun.sun_path) == 0) {
+                                       yyerror("agentx: duplicate entry: %s",
+                                           test->axm_sun.sun_path);
+                                       YYERROR;
+                               }
+                       }
+
+                       TAILQ_INSERT_TAIL(&conf->sc_agentx_masters, master,
+                           axm_entry);
+               }
                | engineid_local {
                        if (conf->sc_engineid_len != 0) {
                                yyerror("Redefinition of engineid");
@@ -408,6 +466,112 @@ port              : /* empty */                   {
                }
                ;
 
+agentxopt      : PATH STRING                   {
+                       if ($2[0] != '/') {
+                               yyerror("agentx path: must be absolute");
+                               YYERROR;
+                       }
+                       if (strlcpy($$.axm_sun.sun_path, $2,
+                           sizeof($$.axm_sun.sun_path)) >=
+                           sizeof($$.axm_sun.sun_path)) {
+                               yyerror("agentx path: too long");
+                               YYERROR;
+                       }
+                       $$.axm_opts = AXM_PATH;
+               }
+               | OWNER NUMBER                  {
+                       if ($2 > UID_MAX) {
+                               yyerror("agentx owner: too large");
+                               YYERROR;
+                       }
+                       $$.axm_owner = $2;
+                       $$.axm_opts = AXM_OWNER;
+               }
+               | OWNER STRING                  {
+                       struct passwd *pw;
+                       const char *errstr;
+
+                       $$.axm_owner = strtonum($2, 0, UID_MAX, &errstr);
+                       if (errstr != NULL && errno == ERANGE) {
+                               yyerror("agentx owner: %s", errstr);
+                               YYERROR;
+                       }
+                       if ((pw = getpwnam($2)) == NULL) {
+                               yyerror("agentx owner: user not found");
+                               YYERROR;
+                       }
+                       $$.axm_owner = pw->pw_uid;
+                       $$.axm_opts = AXM_OWNER;
+               }
+               | GROUP NUMBER                  {
+                       if ($2 > GID_MAX) {
+                               yyerror("agentx group: too large");
+                               YYERROR;
+                       }
+                       $$.axm_group = $2;
+                       $$.axm_opts = AXM_GROUP;
+               }
+               | GROUP STRING                  {
+                       struct group *gr;
+                       const char *errstr;
+
+                       $$.axm_group = strtonum($2, 0, GID_MAX, &errstr);
+                       if (errstr != NULL && errno == ERANGE) {
+                               yyerror("agentx group: %s", errstr);
+                               YYERROR;
+                       }
+                       if ((gr = getgrnam($2)) == NULL) {
+                               yyerror("agentx group: group not found");
+                               YYERROR;
+                       }
+                       $$.axm_group = gr->gr_gid;
+                       $$.axm_opts = AXM_GROUP;
+               }
+               | MODE NUMBER                   {
+                       long mode;
+                       char *endptr, str[21];
+
+                       snprintf(str, sizeof(str), "%lld", $2);
+                       errno = 0;
+                       mode = strtol(str, &endptr, 8);
+                       if (errno != 0 || endptr[0] != '\0' ||
+                           mode <= 0 || mode > 0777) {
+                               yyerror("agentx mode: invalid");
+                               YYERROR;
+                       }
+                       $$.axm_mode = mode;
+                       $$.axm_opts = AXM_MODE;
+               }
+               ;
+
+agentxopts     : /* empty */                   {
+                       bzero(&$$, sizeof($$));
+               }
+               | agentxopts agentxopt {
+                       if ($$.axm_opts & $2.axm_opts) {
+                               yyerror("agentx: duplicate option");
+                               YYERROR;
+                       }
+                       switch ($2.axm_opts) {
+                       case AXM_PATH:
+                               strlcpy($$.axm_sun.sun_path,
+                                   $2.axm_sun.sun_path,
+                                   sizeof($$.axm_sun.sun_path));
+                               break;
+                       case AXM_OWNER:
+                               $$.axm_owner = $2.axm_owner;
+                               break;
+                       case AXM_GROUP:
+                               $$.axm_group = $2.axm_group;
+                               break;
+                       case AXM_MODE:
+                               $$.axm_mode = $2.axm_mode;
+                               break;
+                       }
+                       $$.axm_opts |= $2.axm_opts;
+               }
+               ;
+
 enginefmt      : IP4 STRING                    {
                        struct in_addr addr;
 
@@ -1024,6 +1188,7 @@ lookup(char *s)
        /* this has to be sorted always */
        static const struct keywords keywords[] = {
                { "agentid",                    AGENTID },
+               { "agentx",                     AGENTX },
                { "auth",                       AUTH },
                { "authkey",                    AUTHKEY },
                { "blocklist",                  BLOCKLIST },
@@ -1036,6 +1201,7 @@ lookup(char *s)
                { "engineid",                   ENGINEID },
                { "filter-pf-addresses",        PFADDRFILTER },
                { "filter-routes",              RTFILTER },
+               { "group",                      GROUP },
                { "handle",                     HANDLE },
                { "hosthash",                   HOSTHASH },
                { "include",                    INCLUDE },
@@ -1045,6 +1211,7 @@ lookup(char *s)
                { "listen",                     LISTEN },
                { "location",                   LOCATION },
                { "mac",                        MAC },
+               { "mode",                       MODE },
                { "name",                       NAME },
                { "none",                       NONE },
                { "notify",                     NOTIFY },
@@ -1052,6 +1219,8 @@ lookup(char *s)
                { "oid",                        OBJECTID },
                { "on",                         ON },
                { "openbsd",                    OPENBSD },
+               { "owner",                      OWNER },
+               { "path",                       PATH },
                { "pen",                        PEN },
                { "port",                       PORT },
                { "read",                       READ },
@@ -1448,6 +1617,7 @@ parse_config(const char *filename, u_int flags)
        conf->sc_flags = flags;
        conf->sc_confpath = filename;
        TAILQ_INIT(&conf->sc_addresses);
+       TAILQ_INIT(&conf->sc_agentx_masters);
        TAILQ_INIT(&conf->sc_trapreceivers);
        conf->sc_min_seclevel = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV;
 
index a415f03..7273006 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: snmpd.conf.5,v 1.60 2022/06/30 11:28:36 martijn Exp $
+.\" $OpenBSD: snmpd.conf.5,v 1.61 2022/08/23 08:56:21 martijn Exp $
 .\"
 .\" Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: June 30 2022 $
+.Dd $Mdocdate: August 23 2022 $
 .Dt SNMPD.CONF 5
 .Os
 .Sh NAME
@@ -151,6 +151,21 @@ Having
 set requires at least one
 .Ic trap handle
 statement.
+.It Ic agentx Oo Ic path Ar path Oc Oo Ic owner Ar owner Oc Oo group Ar group Oc Oo Ic mode Ar mode Oc
+Set up an agentx master socket at
+.Ar path 
+and defaults to
+.Pa /var/agentx/master .
+Socket owner, group, and permissions can be set with
+.Ar owner ,
+.Ar group ,
+and
+.Ar mode
+respectively and defaults to root, _agentx, and 660.
+Multiple
+.Ic agentx
+statements are supported.
+Only unix sockets are supported.
 .It Ic engineid Oo Ic pen Ar enterprise Oc Ar format
 Set the snmp engineid, used for instance identification and key
 generation for the
index fa4d9ae..faa26cd 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: snmpd.h,v 1.103 2022/06/30 11:28:36 martijn Exp $     */
+/*     $OpenBSD: snmpd.h,v 1.104 2022/08/23 08:56:21 martijn Exp $     */
 
 /*
  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
@@ -21,6 +21,7 @@
 #define SNMPD_H
 
 #include <sys/tree.h>
+#include <sys/un.h>
 
 #include <net/if.h>
 #include <net/if_dl.h>
@@ -53,6 +54,9 @@
 #define SNMP_PORT              "161"
 #define SNMPTRAP_PORT          "162"
 
+#define AGENTX_MASTER_PATH     "/var/agentx/master"
+#define AGENTX_GROUP           "_agentx"
+
 #define SNMPD_MAXSTRLEN                484
 #define SNMPD_MAXCOMMUNITYLEN  SNMPD_MAXSTRLEN
 #define SNMPD_MAXVARBIND       0x7fffffff
@@ -502,6 +506,19 @@ struct address {
 };
 TAILQ_HEAD(addresslist, address);
 
+struct agentx_master {
+       int                     axm_fd;
+       struct sockaddr_un      axm_sun;
+       uid_t                   axm_owner;
+       gid_t                   axm_group;
+       mode_t                  axm_mode;
+
+       struct event            axm_ev;
+
+       TAILQ_ENTRY(agentx_master) axm_entry;
+};
+TAILQ_HEAD(axmasterlist, agentx_master);
+
 #define ADDRESS_FLAG_READ      0x01
 #define ADDRESS_FLAG_WRITE     0x02
 #define ADDRESS_FLAG_NOTIFY    0x04
@@ -575,6 +592,7 @@ struct snmpd {
 
        const char              *sc_confpath;
        struct addresslist       sc_addresses;
+       struct axmasterlist      sc_agentx_masters;
        struct timeval           sc_starttime;
        u_int32_t                sc_engine_boots;
 
index 19c0f00..3fa22de 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: snmpe.c,v 1.82 2022/01/19 11:00:56 martijn Exp $      */
+/*     $OpenBSD: snmpe.c,v 1.83 2022/08/23 08:56:21 martijn Exp $      */
 
 /*
  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
@@ -33,6 +33,7 @@
 #include <errno.h>
 #include <event.h>
 #include <fcntl.h>
+#include <locale.h>
 #include <string.h>
 #include <unistd.h>
 #include <pwd.h>
@@ -74,6 +75,9 @@ snmpe(struct privsep *ps, struct privsep_proc *p)
        struct oid      *oid;
 #endif
 
+       if ((setlocale(LC_CTYPE, "en_US.UTF-8")) == NULL)
+               fatal("setlocale(LC_CTYPE, \"en_US.UTF-8\")");
+
 #ifdef DEBUG
        for (oid = NULL; (oid = smi_foreach(oid, 0)) != NULL;) {
                smi_oid2string(&oid->o_id, buf, sizeof(buf), 0);
@@ -81,6 +85,8 @@ snmpe(struct privsep *ps, struct privsep_proc *p)
        }
 #endif
 
+       appl();
+
        /* bind SNMP UDP/TCP sockets */
        TAILQ_FOREACH(h, &env->sc_addresses, entry)
                if ((h->fd = snmpe_bind(h)) == -1)