File: [local] / src / usr.sbin / snmpd / application_agentx.c (download)
Revision 1.16, Tue Feb 6 12:44:27 2024 UTC (4 months ago) by martijn
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD Changes since 1.15: +3 -2 lines
Replace most smi_oid2string() calls with the new mib_oid2string().
smi_oid2string() is still called from trap handle context to not break
any existing scripts.
OK tb@
|
/* $OpenBSD: application_agentx.c,v 1.16 2024/02/06 12:44:27 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/tree.h>
#include <sys/un.h>
#include <ber.h>
#include <errno.h>
#include <event.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "application.h"
#include "ax.h"
#include "log.h"
#include "mib.h"
#include "smi.h"
#include "snmp.h"
#include "snmpd.h"
#define AGENTX_DEFAULTTIMEOUT 5
struct appl_agentx_connection {
uint32_t conn_id;
/*
* A backend has several overruling properties:
* - If it exits, snmpd crashes
* - All registrations are priority 1
* - All registrations own the subtree.
*/
int conn_backend;
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 *, enum appl_close_reason);
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_addagentcaps(struct appl_agentx_session *, struct ax_pdu *);
void appl_agentx_removeagentcaps(struct appl_agentx_session *, struct ax_pdu *);
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, APPL_CLOSE_REASONSHUTDOWN);
}
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;
}
conn->conn_backend = 0;
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(%"PRIu32"): new connection", conn->conn_id);
return;
fail:
close(fd);
free(conn);
}
void
appl_agentx_backend(int fd)
{
struct appl_agentx_connection *conn;
if ((conn = malloc(sizeof(*conn))) == NULL)
fatal(NULL);
conn->conn_backend = 1;
TAILQ_INIT(&(conn->conn_sessions));
if ((conn->conn_ax = ax_new(fd)) == NULL)
fatal("ax_new");
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);
}
void
appl_agentx_free(struct appl_agentx_connection *conn,
enum appl_close_reason reason)
{
struct appl_agentx_session *session;
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),
reason);
}
event_del(&(conn->conn_rev));
event_del(&(conn->conn_wev));
RB_REMOVE(appl_agentx_conns, &appl_agentx_conns, conn);
if (conn->conn_ax != NULL)
(void)ax_send(conn->conn_ax);
ax_free(conn->conn_ax);
if (conn->conn_backend)
fatalx("AgentX(%"PRIu32"): disappeared unexpected",
conn->conn_id);
free(conn);
}
void
appl_agentx_recv(int fd, short event, void *cookie)
{
struct appl_agentx_connection *conn = cookie;
struct appl_agentx_session *session = NULL;
struct ax_pdu *pdu;
enum appl_error error;
char name[100];
snprintf(name, sizeof(name), "AgentX(%"PRIu32")", conn->conn_id);
if ((pdu = ax_recv(conn->conn_ax)) == NULL) {
if (errno == EAGAIN)
return;
log_warn("%s", name);
/*
* 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, errno == EPROTO ?
APPL_CLOSE_REASONPROTOCOLERROR : APPL_CLOSE_REASONOTHER);
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("%s: Session %"PRIu32" not found for request",
name, pdu->ap_header.aph_sessionid);
error = APPL_ERROR_NOTOPEN;
goto fail;
}
strlcpy(name, session->sess_backend.ab_name, sizeof(name));
/*
* 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.
*/
}
if (pdu->ap_header.aph_flags & AX_PDU_FLAG_INSTANCE_REGISTRATION) {
if (pdu->ap_header.aph_type != AX_PDU_TYPE_REGISTER) {
log_warnx("%s: %s: Invalid INSTANCE_REGISTRATION flag",
name, ax_pdutype2string(pdu->ap_header.aph_flags));
error = APPL_ERROR_PARSEERROR;
goto fail;
}
}
if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NEW_INDEX) {
if (pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE) {
log_warnx("%s: %s: Invalid NEW_INDEX flag", name,
ax_pdutype2string(pdu->ap_header.aph_flags));
error = APPL_ERROR_PARSEERROR;
goto fail;
}
}
if (pdu->ap_header.aph_flags & AX_PDU_FLAG_ANY_INDEX) {
if (pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE) {
log_warnx("%s: %s: Invalid ANY_INDEX flag", name,
ax_pdutype2string(pdu->ap_header.aph_flags));
error = APPL_ERROR_PARSEERROR;
goto fail;
}
}
if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT) {
if (pdu->ap_header.aph_type != AX_PDU_TYPE_REGISTER &&
pdu->ap_header.aph_type != AX_PDU_TYPE_UNREGISTER &&
pdu->ap_header.aph_type != AX_PDU_TYPE_ADDAGENTCAPS &&
pdu->ap_header.aph_type != AX_PDU_TYPE_REMOVEAGENTCAPS &&
pdu->ap_header.aph_type != AX_PDU_TYPE_GET &&
pdu->ap_header.aph_type != AX_PDU_TYPE_GETNEXT &&
pdu->ap_header.aph_type != AX_PDU_TYPE_GETBULK &&
pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE &&
pdu->ap_header.aph_type != AX_PDU_TYPE_NOTIFY &&
pdu->ap_header.aph_type != AX_PDU_TYPE_TESTSET &&
pdu->ap_header.aph_type != AX_PDU_TYPE_PING) {
log_warnx("%s: %s: Invalid NON_DEFAULT_CONTEXT flag",
name, ax_pdutype2string(pdu->ap_header.aph_flags));
error = APPL_ERROR_PARSEERROR;
goto fail;
}
if (appl_context(pdu->ap_context.aos_string, 0) == NULL) {
log_warnx("%s: %s: Unsupported context",
name, ax_pdutype2string(pdu->ap_header.aph_flags));
error = APPL_ERROR_UNSUPPORTEDCONTEXT;
goto fail;
}
}
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:
log_warnx("%s: %s: Not an adminsitrative message", name,
ax_pdutype2string(pdu->ap_header.aph_type));
error = APPL_ERROR_PARSEERROR;
goto fail;
case AX_PDU_TYPE_NOTIFY:
log_warnx("%s: %s: not supported", name,
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, smi_getticks(),
APPL_ERROR_NOERROR, 0, NULL, 0);
event_add(&(conn->conn_wev), NULL);
break;
case AX_PDU_TYPE_INDEXALLOCATE:
case AX_PDU_TYPE_INDEXDEALLOCATE:
log_warnx("%s: %s: not supported", name,
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, smi_getticks(),
APPL_ERROR_PROCESSINGERROR, 1,
pdu->ap_payload.ap_vbl.ap_varbind,
pdu->ap_payload.ap_vbl.ap_nvarbind);
event_add(&(conn->conn_wev), NULL);
break;
case AX_PDU_TYPE_ADDAGENTCAPS:
appl_agentx_addagentcaps(session, pdu);
break;
case AX_PDU_TYPE_REMOVEAGENTCAPS:
appl_agentx_removeagentcaps(session, pdu);
break;
case AX_PDU_TYPE_RESPONSE:
appl_agentx_response(session, pdu);
break;
}
ax_pdu_free(pdu);
return;
fail:
ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
pdu->ap_header.aph_transactionid,
pdu->ap_header.aph_packetid, smi_getticks(),
error, 0, NULL, 0);
event_add(&(conn->conn_wev), NULL);
ax_pdu_free(pdu);
if (session == NULL || error != APPL_ERROR_PARSEERROR)
return;
appl_agentx_forceclose(&(session->sess_backend),
APPL_CLOSE_REASONPARSEERROR);
if (TAILQ_EMPTY(&(conn->conn_sessions)))
appl_agentx_free(conn, APPL_CLOSE_REASONOTHER);
}
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];
enum appl_error error = APPL_ERROR_NOERROR;
if ((session = malloc(sizeof(*session))) == NULL) {
log_warn(NULL);
error = APPL_ERROR_OPENFAILED;
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);
error = APPL_ERROR_PARSEERROR;
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);
error = APPL_ERROR_PARSEERROR;
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);
error = APPL_ERROR_PARSEERROR;
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);
error = APPL_ERROR_OPENFAILED;
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(%"PRIu32"): asprintf: Open Failed",
conn->conn_id);
error = APPL_ERROR_OPENFAILED;
goto fail;
}
session->sess_backend.ab_cookie = session;
session->sess_backend.ab_retries = 0;
session->sess_backend.ab_fn = &appl_agentx_functions;
session->sess_backend.ab_range = 1;
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);
mib_oid2string(&oid, oidbuf, sizeof(oidbuf), snmpd_env->sc_oidfmt);
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,
smi_getticks(), APPL_ERROR_NOERROR, 0, NULL, 0);
event_add(&(conn->conn_wev), NULL);
return;
fail:
ax_response(conn->conn_ax, 0, pdu->ap_header.aph_transactionid,
pdu->ap_header.aph_packetid, 0, error, 0, NULL, 0);
event_add(&(conn->conn_wev), NULL);
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];
enum appl_error error = APPL_ERROR_NOERROR;
strlcpy(name, session->sess_backend.ab_name, sizeof(name));
if (pdu->ap_payload.ap_close.ap_reason == AX_CLOSE_BYMANAGER) {
log_warnx("%s: Invalid close reason", name);
error = APPL_ERROR_PARSEERROR;
} else {
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,
smi_getticks(), error, 0, NULL, 0);
event_add(&(conn->conn_wev), NULL);
if (error == APPL_ERROR_NOERROR)
return;
appl_agentx_forceclose(&(session->sess_backend),
APPL_CLOSE_REASONPARSEERROR);
if (TAILQ_EMPTY(&(conn->conn_sessions)))
appl_agentx_free(conn, APPL_CLOSE_REASONOTHER);
}
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);
event_add(&(session->sess_conn->conn_wev), NULL);
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;
int subtree = 0;
timeout = pdu->ap_payload.ap_register.ap_timeout;
timeout = timeout != 0 ? timeout : session->sess_timeout != 0 ?
session->sess_timeout : AGENTX_DEFAULTTIMEOUT;
timeout *= 100;
if (session->sess_conn->conn_backend) {
pdu->ap_payload.ap_register.ap_priority = 1;
subtree = 1;
}
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,
subtree, 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,
smi_getticks(), error, 0, NULL, 0);
event_add(&(session->sess_conn->conn_wev), NULL);
}
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,
smi_getticks(), error, 0, NULL, 0);
event_add(&(session->sess_conn->conn_wev), NULL);
}
#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;
if (session->sess_conn->conn_ax == NULL)
return;
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
event_add(&(session->sess_conn->conn_wev), NULL);
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;
if (session->sess_conn->conn_ax == NULL)
return;
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_end.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
event_add(&(session->sess_conn->conn_wev), NULL);
free(srl);
if (context != NULL)
free(context->aos_string);
}
void
appl_agentx_addagentcaps(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_addagentcaps.ap_oid),
&oid) == NULL) {
log_warnx("%s: Failed to add agent capabilities: oid too small",
session->sess_backend.ab_name);
error = APPL_ERROR_PARSEERROR;
goto fail;
}
error = appl_addagentcaps(pdu->ap_context.aos_string, &oid,
pdu->ap_payload.ap_addagentcaps.ap_descr.aos_string,
&(session->sess_backend));
fail:
ax_response(session->sess_conn->conn_ax, session->sess_id,
pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
smi_getticks(), error, 0, NULL, 0);
event_add(&(session->sess_conn->conn_wev), NULL);
}
void
appl_agentx_removeagentcaps(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_addagentcaps.ap_oid),
&oid) == NULL) {
log_warnx("%s: Failed to remove agent capabilities: "
"oid too small", session->sess_backend.ab_name);
error = APPL_ERROR_PARSEERROR;
goto fail;
}
error = appl_removeagentcaps(pdu->ap_context.aos_string, &oid,
&(session->sess_backend));
fail:
ax_response(session->sess_conn->conn_ax, session->sess_id,
pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
smi_getticks(), error, 0, NULL, 0);
event_add(&(session->sess_conn->conn_wev), NULL);
}
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, APPL_CLOSE_REASONOTHER);
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);