File: [local] / src / usr.sbin / snmpd / snmpe.c (download)
Revision 1.94, Tue Jan 16 13:33:13 2024 UTC (4 months, 2 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5 Changes since 1.93: +2 -2 lines
Convert to imsg_get_fd() and remove unused proc_forward_imsg().
OK martijn@
|
/* $OpenBSD: snmpe.c,v 1.94 2024/01/16 13:33:13 claudio Exp $ */
/*
* Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2017 Marco Pfatschbacher <mpf@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/time.h>
#include <sys/tree.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <ber.h>
#include <event.h>
#include <errno.h>
#include <fcntl.h>
#include <imsg.h>
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "application.h"
#include "log.h"
#include "snmpd.h"
#include "snmpe.h"
#include "mib.h"
void snmpe_init(struct privsep *, struct privsep_proc *, void *);
int snmpe_dispatch_parent(int, struct privsep_proc *, struct imsg *);
int snmpe_parse(struct snmp_message *);
void snmpe_tryparse(int, struct snmp_message *);
int snmpe_parsevarbinds(struct snmp_message *);
void snmpe_sig_handler(int sig, short, void *);
int snmpe_bind(struct address *);
void snmpe_recvmsg(int fd, short, void *);
void snmpe_readcb(int fd, short, void *);
void snmpe_writecb(int fd, short, void *);
void snmpe_acceptcb(int fd, short, void *);
void snmpe_prepare_read(struct snmp_message *, int);
int snmpe_encode(struct snmp_message *);
struct imsgev *iev_parent;
static const struct timeval snmpe_tcp_timeout = { 10, 0 }; /* 10s */
struct snmp_messages snmp_messages = RB_INITIALIZER(&snmp_messages);
static struct privsep_proc procs[] = {
{ "parent", PROC_PARENT, snmpe_dispatch_parent }
};
void
snmpe(struct privsep *ps, struct privsep_proc *p)
{
struct snmpd *env = ps->ps_env;
struct address *h;
if ((setlocale(LC_CTYPE, "en_US.UTF-8")) == NULL)
fatal("setlocale(LC_CTYPE, \"en_US.UTF-8\")");
appl();
/* bind SNMP UDP/TCP sockets */
TAILQ_FOREACH(h, &env->sc_addresses, entry)
if ((h->fd = snmpe_bind(h)) == -1)
fatal("snmpe: failed to bind SNMP socket");
proc_run(ps, p, procs, nitems(procs), snmpe_init, NULL);
}
void
snmpe_init(struct privsep *ps, struct privsep_proc *p, void *arg)
{
struct snmpd *env = ps->ps_env;
struct address *h;
usm_generate_keys();
appl_init();
/* listen for incoming SNMP UDP/TCP messages */
TAILQ_FOREACH(h, &env->sc_addresses, entry) {
if (h->type == SOCK_STREAM) {
if (listen(h->fd, 5) < 0)
fatalx("snmpe: failed to listen on socket");
event_set(&h->ev, h->fd, EV_READ, snmpe_acceptcb, h);
evtimer_set(&h->evt, snmpe_acceptcb, h);
} else {
event_set(&h->ev, h->fd, EV_READ|EV_PERSIST,
snmpe_recvmsg, h);
}
event_add(&h->ev, NULL);
}
/* no filesystem visibility */
if (unveil("/", "") == -1)
fatal("unveil /");
if (pledge("stdio recvfd inet unix", NULL) == -1)
fatal("pledge");
log_info("snmpe %s: ready",
tohexstr(env->sc_engineid, env->sc_engineid_len));
trap_init();
}
void
snmpe_shutdown(void)
{
struct address *h;
TAILQ_FOREACH(h, &snmpd_env->sc_addresses, entry) {
event_del(&h->ev);
if (h->type == SOCK_STREAM)
event_del(&h->evt);
close(h->fd);
}
appl_shutdown();
}
int
snmpe_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
{
switch (imsg->hdr.type) {
case IMSG_AX_FD:
appl_agentx_backend(imsg_get_fd(imsg));
return 0;
default:
return -1;
}
}
int
snmpe_bind(struct address *addr)
{
char buf[512];
int val, s;
if ((s = snmpd_socket_af(&addr->ss, addr->type)) == -1)
return (-1);
/*
* Socket options
*/
if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
goto bad;
if (addr->type == SOCK_STREAM) {
val = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
&val, sizeof(val)) == -1)
fatal("setsockopt SO_REUSEADDR");
} else { /* UDP */
switch (addr->ss.ss_family) {
case AF_INET:
val = 1;
if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR,
&val, sizeof(int)) == -1) {
log_warn("%s: failed to set IPv4 packet info",
__func__);
goto bad;
}
break;
case AF_INET6:
val = 1;
if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&val, sizeof(int)) == -1) {
log_warn("%s: failed to set IPv6 packet info",
__func__);
goto bad;
}
}
}
if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1)
goto bad;
if (print_host(&addr->ss, buf, sizeof(buf)) == NULL)
goto bad;
log_info("snmpe: listening on %s %s:%d",
(addr->type == SOCK_STREAM) ? "tcp" : "udp", buf, addr->port);
return (s);
bad:
close(s);
return (-1);
}
const char *
snmpe_pdutype2string(enum snmp_pdutype pdutype)
{
static char unknown[sizeof("Unknown (4294967295)")];
switch (pdutype) {
case SNMP_C_GETREQ:
return "GetRequest";
case SNMP_C_GETNEXTREQ:
return "GetNextRequest";
case SNMP_C_RESPONSE:
return "Response";
case SNMP_C_SETREQ:
return "SetRequest";
case SNMP_C_TRAP:
return "Trap";
case SNMP_C_GETBULKREQ:
return "GetBulkRequest";
case SNMP_C_INFORMREQ:
return "InformRequest";
case SNMP_C_TRAPV2:
return "SNMPv2-Trap";
case SNMP_C_REPORT:
return "Report";
}
snprintf(unknown, sizeof(unknown), "Unknown (%u)", pdutype);
return unknown;
}
int
snmpe_parse(struct snmp_message *msg)
{
struct snmpd *env = snmpd_env;
struct snmp_stats *stats = &env->sc_stats;
struct ber_element *a;
long long ver, req;
long long errval, erridx;
u_int class;
char *comn;
char *flagstr, *ctxname, *engineid;
size_t len;
struct sockaddr_storage *ss = &msg->sm_ss;
struct ber_element *root = msg->sm_req;
msg->sm_errstr = "invalid message";
do {
msg->sm_transactionid = arc4random();
} while (msg->sm_transactionid == 0 ||
RB_INSERT(snmp_messages, &snmp_messages, msg) != NULL);
if (ober_scanf_elements(root, "{ie", &ver, &a) != 0)
goto parsefail;
/* SNMP version and community */
msg->sm_version = ver;
switch (msg->sm_version) {
case SNMP_V1:
if (!(msg->sm_aflags & ADDRESS_FLAG_SNMPV1)) {
msg->sm_errstr = "SNMPv1 disabled";
goto badversion;
}
case SNMP_V2:
if (msg->sm_version == SNMP_V2 &&
!(msg->sm_aflags & ADDRESS_FLAG_SNMPV2)) {
msg->sm_errstr = "SNMPv2c disabled";
goto badversion;
}
if (ober_scanf_elements(a, "seS$", &comn, &msg->sm_pdu) != 0)
goto parsefail;
if (strlcpy(msg->sm_community, comn,
sizeof(msg->sm_community)) >= sizeof(msg->sm_community) ||
msg->sm_community[0] == '\0') {
stats->snmp_inbadcommunitynames++;
msg->sm_errstr = "invalid community name";
goto fail;
}
break;
case SNMP_V3:
if (!(msg->sm_aflags & ADDRESS_FLAG_SNMPV3)) {
msg->sm_errstr = "SNMPv3 disabled";
goto badversion;
}
if (ober_scanf_elements(a, "{iisi$}e",
&msg->sm_msgid, &msg->sm_max_msg_size, &flagstr,
&msg->sm_secmodel, &a) != 0)
goto parsefail;
msg->sm_flags = *flagstr;
if ((a = usm_decode(msg, a, &msg->sm_errstr)) == NULL)
goto parsefail;
if (MSG_SECLEVEL(msg) < env->sc_min_seclevel ||
msg->sm_secmodel != SNMP_SEC_USM) {
/* XXX currently only USM supported */
msg->sm_errstr = "unsupported security model";
stats->snmp_usmbadseclevel++;
msg->sm_usmerr = OIDVAL_usmErrSecLevel;
goto parsefail;
}
if (ober_scanf_elements(a, "{xxeS$}$",
&engineid, &msg->sm_ctxengineid_len, &ctxname, &len,
&msg->sm_pdu) != 0)
goto parsefail;
if (msg->sm_ctxengineid_len > sizeof(msg->sm_ctxengineid))
goto parsefail;
memcpy(msg->sm_ctxengineid, engineid, msg->sm_ctxengineid_len);
if (len > SNMPD_MAXCONTEXNAMELEN)
goto parsefail;
memcpy(msg->sm_ctxname, ctxname, len);
msg->sm_ctxname[len] = '\0';
break;
default:
msg->sm_errstr = "unsupported snmp version";
badversion:
stats->snmp_inbadversions++;
goto fail;
}
if (ober_scanf_elements(msg->sm_pdu, "t{e", &class, &(msg->sm_pdutype),
&a) != 0)
goto parsefail;
/* SNMP PDU context */
if (class != BER_CLASS_CONTEXT)
goto parsefail;
switch (msg->sm_pdutype) {
case SNMP_C_GETBULKREQ:
if (msg->sm_version == SNMP_V1) {
stats->snmp_inbadversions++;
msg->sm_errstr =
"invalid request for protocol version 1";
goto fail;
}
/* FALLTHROUGH */
case SNMP_C_GETREQ:
stats->snmp_ingetrequests++;
/* FALLTHROUGH */
case SNMP_C_GETNEXTREQ:
if (msg->sm_pdutype == SNMP_C_GETNEXTREQ)
stats->snmp_ingetnexts++;
if (!(msg->sm_aflags & ADDRESS_FLAG_READ)) {
msg->sm_errstr = "read requests disabled";
goto fail;
}
if (msg->sm_version != SNMP_V3 &&
strcmp(env->sc_rdcommunity, msg->sm_community) != 0 &&
strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
stats->snmp_inbadcommunitynames++;
msg->sm_errstr = "wrong read community";
goto fail;
}
break;
case SNMP_C_SETREQ:
stats->snmp_insetrequests++;
if (!(msg->sm_aflags & ADDRESS_FLAG_WRITE)) {
msg->sm_errstr = "write requests disabled";
goto fail;
}
if (msg->sm_version != SNMP_V3 &&
strcmp(env->sc_rwcommunity, msg->sm_community) != 0) {
if (strcmp(env->sc_rdcommunity, msg->sm_community) != 0)
stats->snmp_inbadcommunitynames++;
else
stats->snmp_inbadcommunityuses++;
msg->sm_errstr = "wrong write community";
goto fail;
}
break;
case SNMP_C_RESPONSE:
stats->snmp_ingetresponses++;
msg->sm_errstr = "response without request";
goto parsefail;
case SNMP_C_TRAP:
if (msg->sm_version != SNMP_V1) {
msg->sm_errstr = "trapv1 request on !SNMPv1 message";
goto parsefail;
}
case SNMP_C_TRAPV2:
if (msg->sm_pdutype == SNMP_C_TRAPV2 &&
!(msg->sm_version == SNMP_V2 ||
msg->sm_version == SNMP_V3)) {
msg->sm_errstr = "trapv2 request on !SNMPv2C or "
"!SNMPv3 message";
goto parsefail;
}
if (!(msg->sm_aflags & ADDRESS_FLAG_NOTIFY)) {
msg->sm_errstr = "notify requests disabled";
goto fail;
}
if (msg->sm_version == SNMP_V3) {
msg->sm_errstr = "SNMPv3 doesn't support traps yet";
goto fail;
}
if (strcmp(env->sc_trcommunity, msg->sm_community) != 0) {
stats->snmp_inbadcommunitynames++;
msg->sm_errstr = "wrong trap community";
goto fail;
}
stats->snmp_intraps++;
/*
* This should probably go into parsevarbinds, but that's for a
* next refactor
*/
if (traphandler_parse(msg) == -1)
goto fail;
/* Shortcircuit */
return 0;
default:
msg->sm_errstr = "invalid context";
goto parsefail;
}
/* SNMP PDU */
if (ober_scanf_elements(a, "iiie{e{}}$",
&req, &errval, &erridx, &msg->sm_pduend,
&msg->sm_varbind) != 0) {
stats->snmp_silentdrops++;
msg->sm_errstr = "invalid PDU";
goto fail;
}
for (len = 0, a = msg->sm_varbind; a != NULL; a = a->be_next, len++) {
if (ober_scanf_elements(a, "{oS$}", NULL) == -1)
goto parsefail;
}
/*
* error-status == non-repeaters
* error-index == max-repetitions
*/
if (msg->sm_pdutype == SNMP_C_GETBULKREQ &&
(errval < 0 || errval > (long long)len ||
erridx < 1 || erridx > UINT16_MAX))
goto parsefail;
msg->sm_request = req;
msg->sm_error = errval;
msg->sm_errorindex = erridx;
print_host(ss, msg->sm_host, sizeof(msg->sm_host));
if (msg->sm_version == SNMP_V3)
log_debug("%s: %s:%hd: SNMPv3 pdutype %s, flags %#x, "
"secmodel %lld, user '%s', ctx-engine %s, ctx-name '%s', "
"request %lld", __func__, msg->sm_host, msg->sm_port,
snmpe_pdutype2string(msg->sm_pdutype), msg->sm_flags,
msg->sm_secmodel, msg->sm_username,
tohexstr(msg->sm_ctxengineid, msg->sm_ctxengineid_len),
msg->sm_ctxname, msg->sm_request);
else
log_debug("%s: %s:%hd: SNMPv%d '%s' pdutype %s request %lld",
__func__, msg->sm_host, msg->sm_port, msg->sm_version + 1,
msg->sm_community, snmpe_pdutype2string(msg->sm_pdutype),
msg->sm_request);
return (0);
parsefail:
stats->snmp_inasnparseerrs++;
fail:
print_host(ss, msg->sm_host, sizeof(msg->sm_host));
log_debug("%s: %s:%hd: %s", __func__, msg->sm_host, msg->sm_port,
msg->sm_errstr);
return (-1);
}
int
snmpe_parsevarbinds(struct snmp_message *msg)
{
appl_processpdu(msg, msg->sm_ctxname, msg->sm_version, msg->sm_pdu);
return 0;
}
void
snmpe_acceptcb(int fd, short type, void *arg)
{
struct address *h = arg;
struct sockaddr_storage ss;
socklen_t len = sizeof(ss);
struct snmp_message *msg;
int afd;
event_add(&h->ev, NULL);
if ((type & EV_TIMEOUT))
return;
if ((afd = accept4(fd, (struct sockaddr *)&ss, &len,
SOCK_NONBLOCK|SOCK_CLOEXEC)) < 0) {
/* Pause accept if we are out of file descriptors */
if (errno == ENFILE || errno == EMFILE) {
struct timeval evtpause = { 1, 0 };
event_del(&h->ev);
evtimer_add(&h->evt, &evtpause);
} else if (errno != EAGAIN && errno != EINTR)
log_debug("%s: accept4", __func__);
return;
}
if ((msg = calloc(1, sizeof(*msg))) == NULL)
goto fail;
memcpy(&(msg->sm_ss), &ss, len);
msg->sm_slen = len;
msg->sm_aflags = h->flags;
msg->sm_port = h->port;
snmpe_prepare_read(msg, afd);
return;
fail:
free(msg);
close(afd);
return;
}
void
snmpe_prepare_read(struct snmp_message *msg, int fd)
{
msg->sm_sock = fd;
msg->sm_sock_tcp = 1;
event_del(&msg->sm_sockev);
event_set(&msg->sm_sockev, fd, EV_READ,
snmpe_readcb, msg);
event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
}
void
snmpe_tryparse(int fd, struct snmp_message *msg)
{
struct snmp_stats *stats = &snmpd_env->sc_stats;
ober_set_application(&msg->sm_ber, smi_application);
ober_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
msg->sm_req = ober_read_elements(&msg->sm_ber, NULL);
if (msg->sm_req == NULL) {
if (errno == ECANCELED) {
/* short read; try again */
snmpe_prepare_read(msg, fd);
return;
}
goto fail;
}
if (snmpe_parse(msg) == -1) {
if (msg->sm_usmerr && MSG_REPORT(msg)) {
usm_make_report(msg);
return;
} else
goto fail;
}
stats->snmp_inpkts++;
snmpe_dispatchmsg(msg);
return;
fail:
snmp_msgfree(msg);
close(fd);
}
void
snmpe_readcb(int fd, short type, void *arg)
{
struct snmp_message *msg = arg;
ssize_t len;
if (type == EV_TIMEOUT || msg->sm_datalen >= sizeof(msg->sm_data))
goto fail;
len = read(fd, msg->sm_data + msg->sm_datalen,
sizeof(msg->sm_data) - msg->sm_datalen);
if (len <= 0) {
if (errno != EAGAIN && errno != EINTR)
goto fail;
snmpe_prepare_read(msg, fd);
return;
}
msg->sm_datalen += (size_t)len;
snmpe_tryparse(fd, msg);
return;
fail:
snmp_msgfree(msg);
close(fd);
}
void
snmpe_writecb(int fd, short type, void *arg)
{
struct snmp_stats *stats = &snmpd_env->sc_stats;
struct snmp_message *msg = arg;
struct snmp_message *nmsg;
ssize_t len;
size_t reqlen;
struct ber *ber = &msg->sm_ber;
if (type == EV_TIMEOUT)
goto fail;
len = ber->br_wend - ber->br_wptr;
log_debug("%s: write fd %d len %zd", __func__, fd, len);
len = write(fd, ber->br_wptr, len);
if (len == -1) {
if (errno == EAGAIN || errno == EINTR)
return;
else
goto fail;
}
ber->br_wptr += len;
if (ber->br_wptr < ber->br_wend) {
event_del(&msg->sm_sockev);
event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
snmpe_writecb, msg);
event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
return;
}
stats->snmp_outpkts++;
if ((nmsg = calloc(1, sizeof(*nmsg))) == NULL)
goto fail;
memcpy(&(nmsg->sm_ss), &(msg->sm_ss), msg->sm_slen);
nmsg->sm_slen = msg->sm_slen;
nmsg->sm_aflags = msg->sm_aflags;
nmsg->sm_port = msg->sm_port;
/*
* Reuse the connection.
* In case we already read data of the next message, copy it over.
*/
reqlen = ober_calc_len(msg->sm_req);
if (msg->sm_datalen > reqlen) {
memcpy(nmsg->sm_data, msg->sm_data + reqlen,
msg->sm_datalen - reqlen);
nmsg->sm_datalen = msg->sm_datalen - reqlen;
snmp_msgfree(msg);
snmpe_tryparse(fd, nmsg);
} else {
snmp_msgfree(msg);
snmpe_prepare_read(nmsg, fd);
}
return;
fail:
close(fd);
snmp_msgfree(msg);
}
void
snmpe_recvmsg(int fd, short sig, void *arg)
{
struct address *h = arg;
struct snmp_stats *stats = &snmpd_env->sc_stats;
ssize_t len;
struct snmp_message *msg;
if ((msg = calloc(1, sizeof(*msg))) == NULL)
return;
msg->sm_aflags = h->flags;
msg->sm_sock = fd;
msg->sm_slen = sizeof(msg->sm_ss);
msg->sm_port = h->port;
if ((len = recvfromto(fd, msg->sm_data, sizeof(msg->sm_data), 0,
(struct sockaddr *)&msg->sm_ss, &msg->sm_slen,
(struct sockaddr *)&msg->sm_local_ss, &msg->sm_local_slen)) < 1) {
free(msg);
return;
}
stats->snmp_inpkts++;
msg->sm_datalen = (size_t)len;
bzero(&msg->sm_ber, sizeof(msg->sm_ber));
ober_set_application(&msg->sm_ber, smi_application);
ober_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
msg->sm_req = ober_read_elements(&msg->sm_ber, NULL);
if (msg->sm_req == NULL) {
stats->snmp_inasnparseerrs++;
snmp_msgfree(msg);
return;
}
#ifdef DEBUG
fprintf(stderr, "recv msg:\n");
smi_debug_elements(msg->sm_req);
#endif
if (snmpe_parse(msg) == -1) {
if (msg->sm_usmerr != 0 && MSG_REPORT(msg)) {
usm_make_report(msg);
return;
} else {
snmp_msgfree(msg);
return;
}
}
snmpe_dispatchmsg(msg);
}
void
snmpe_dispatchmsg(struct snmp_message *msg)
{
if (msg->sm_pdutype == SNMP_C_TRAP ||
msg->sm_pdutype == SNMP_C_TRAPV2) {
snmp_msgfree(msg);
return;
}
/* dispatched to subagent */
/* XXX Do proper error handling */
(void) snmpe_parsevarbinds(msg);
return;
/*
* Leave code here for now so it's easier to switch back in case of
* issues.
*/
/* respond directly */
msg->sm_pdutype = SNMP_C_RESPONSE;
snmpe_response(msg);
}
void
snmpe_send(struct snmp_message *msg, enum snmp_pdutype type, int32_t requestid,
int32_t error, uint32_t index, struct ber_element *varbindlist)
{
msg->sm_request = requestid;
msg->sm_pdutype = type;
msg->sm_error = error;
msg->sm_errorindex = index;
msg->sm_varbindresp = varbindlist;
snmpe_response(msg);
}
void
snmpe_response(struct snmp_message *msg)
{
struct snmp_stats *stats = &snmpd_env->sc_stats;
u_int8_t *ptr = NULL;
ssize_t len;
if (msg->sm_varbindresp == NULL && msg->sm_pduend != NULL)
msg->sm_varbindresp = ober_unlink_elements(msg->sm_pduend);
switch (msg->sm_error) {
case SNMP_ERROR_NONE:
break;
case SNMP_ERROR_TOOBIG:
stats->snmp_intoobigs++;
break;
case SNMP_ERROR_NOSUCHNAME:
stats->snmp_innosuchnames++;
break;
case SNMP_ERROR_BADVALUE:
stats->snmp_inbadvalues++;
break;
case SNMP_ERROR_READONLY:
stats->snmp_inreadonlys++;
break;
case SNMP_ERROR_GENERR:
default:
stats->snmp_ingenerrs++;
break;
}
/* Create new SNMP packet */
if (snmpe_encode(msg) < 0)
goto done;
len = ober_write_elements(&msg->sm_ber, msg->sm_resp);
if (ober_get_writebuf(&msg->sm_ber, (void *)&ptr) == -1)
goto done;
usm_finalize_digest(msg, ptr, len);
if (msg->sm_sock_tcp) {
msg->sm_ber.br_wptr = msg->sm_ber.br_wbuf;
event_del(&msg->sm_sockev);
event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
snmpe_writecb, msg);
event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
return;
} else {
len = sendtofrom(msg->sm_sock, ptr, len, 0,
(struct sockaddr *)&msg->sm_ss, msg->sm_slen,
(struct sockaddr *)&msg->sm_local_ss, msg->sm_local_slen);
if (len != -1)
stats->snmp_outpkts++;
}
done:
snmp_msgfree(msg);
}
void
snmp_msgfree(struct snmp_message *msg)
{
if (msg->sm_transactionid != 0)
RB_REMOVE(snmp_messages, &snmp_messages, msg);
event_del(&msg->sm_sockev);
ober_free(&msg->sm_ber);
if (msg->sm_req != NULL)
ober_free_elements(msg->sm_req);
if (msg->sm_resp != NULL)
ober_free_elements(msg->sm_resp);
free(msg);
}
int
snmpe_encode(struct snmp_message *msg)
{
struct ber_element *ehdr;
struct ber_element *pdu, *epdu;
msg->sm_resp = ober_add_sequence(NULL);
if ((ehdr = ober_add_integer(msg->sm_resp, msg->sm_version)) == NULL)
return -1;
if (msg->sm_version == SNMP_V3) {
char f = MSG_SECLEVEL(msg);
if ((ehdr = ober_printf_elements(ehdr, "{iixi}", msg->sm_msgid,
msg->sm_max_msg_size, &f, sizeof(f),
msg->sm_secmodel)) == NULL)
return -1;
/* XXX currently only USM supported */
if ((ehdr = usm_encode(msg, ehdr)) == NULL)
return -1;
} else {
if ((ehdr = ober_add_string(ehdr, msg->sm_community)) == NULL)
return -1;
}
pdu = epdu = ober_add_sequence(NULL);
if (msg->sm_version == SNMP_V3) {
if ((epdu = ober_printf_elements(epdu, "xs{",
snmpd_env->sc_engineid, snmpd_env->sc_engineid_len,
msg->sm_ctxname)) == NULL) {
ober_free_elements(pdu);
return -1;
}
}
if (!ober_printf_elements(epdu, "tiii{e}", BER_CLASS_CONTEXT,
msg->sm_pdutype, msg->sm_request,
msg->sm_error, msg->sm_errorindex,
msg->sm_varbindresp)) {
ober_free_elements(pdu);
return -1;
}
if (MSG_HAS_PRIV(msg))
pdu = usm_encrypt(msg, pdu);
ober_link_elements(ehdr, pdu);
#ifdef DEBUG
fprintf(stderr, "resp msg:\n");
smi_debug_elements(msg->sm_resp);
#endif
return 0;
}
int
snmp_messagecmp(struct snmp_message *m1, struct snmp_message *m2)
{
return (m1->sm_transactionid < m2->sm_transactionid ? -1 :
m1->sm_transactionid > m2->sm_transactionid);
}
RB_GENERATE(snmp_messages, snmp_message, sm_entry, snmp_messagecmp)