diff options
author | Martijn van Duren <martijn@cvs.openbsd.org> | 2022-08-23 08:56:22 +0000 |
---|---|---|
committer | Martijn van Duren <martijn@cvs.openbsd.org> | 2022-08-23 08:56:22 +0000 |
commit | d22bfb695a028538e4f01a769177d2a1586e62c5 (patch) | |
tree | 5d2770194179e5d22e07ecea1d57250a06a6112f /usr.sbin | |
parent | b1f93ce47bb6913e0760cc10338efd1d18776f2c (diff) |
(Re)add support for agentx in snmpd
Current omissions in protocol support are notifications,
index (de)allocation, and agent capabilities.
Help testing sthen@
Feedback/tweaks/OK jmatthew@
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/snmpd/Makefile | 3 | ||||
-rw-r--r-- | usr.sbin/snmpd/application.c | 13 | ||||
-rw-r--r-- | usr.sbin/snmpd/application.h | 8 | ||||
-rw-r--r-- | usr.sbin/snmpd/application_agentx.c | 879 | ||||
-rw-r--r-- | usr.sbin/snmpd/ax.c | 1549 | ||||
-rw-r--r-- | usr.sbin/snmpd/ax.h | 265 | ||||
-rw-r--r-- | usr.sbin/snmpd/parse.y | 172 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf.5 | 19 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.h | 20 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpe.c | 8 |
10 files changed, 2928 insertions, 8 deletions
diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile index f49829c0221..c112d9212f0 100644 --- a/usr.sbin/snmpd/Makefile +++ b/usr.sbin/snmpd/Makefile @@ -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 diff --git a/usr.sbin/snmpd/application.c b/usr.sbin/snmpd/application.c index 0fb8a456255..335123f63e4 100644 --- a/usr.sbin/snmpd/application.c +++ b/usr.sbin/snmpd/application.c @@ -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> @@ -146,10 +146,17 @@ 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); diff --git a/usr.sbin/snmpd/application.h b/usr.sbin/snmpd/application.h index fd5b8146335..7f22af85fed 100644 --- a/usr.sbin/snmpd/application.h +++ b/usr.sbin/snmpd/application.h @@ -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 index 00000000000..5ac8b2d4852 --- /dev/null +++ b/usr.sbin/snmpd/application_agentx.c @@ -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 index 00000000000..63add683efe --- /dev/null +++ b/usr.sbin/snmpd/ax.c @@ -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 index 00000000000..3b2ea93ba99 --- /dev/null +++ b/usr.sbin/snmpd/ax.h @@ -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); diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y index 6c26f3a0f4e..f0562637e1a 100644 --- a/usr.sbin/snmpd/parse.y +++ b/usr.sbin/snmpd/parse.y @@ -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; diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 index a415f0391d3..72730069f2f 100644 --- a/usr.sbin/snmpd/snmpd.conf.5 +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -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 diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index fa4d9aef015..faa26cd3f3a 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -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; diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c index 19c0f009941..3fa22de026f 100644 --- a/usr.sbin/snmpd/snmpe.c +++ b/usr.sbin/snmpd/snmpe.c @@ -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) |