summaryrefslogtreecommitdiff
path: root/usr.sbin/snmpd/snmpe.c
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2007-12-05 09:22:45 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2007-12-05 09:22:45 +0000
commit174402352855c05a4a262f9afbbd6de1dd3ed82c (patch)
tree66e421d1b6a3d063f3d25c178aef3a600d370fc1 /usr.sbin/snmpd/snmpe.c
parent67c2cb68245a11d91e5fe144633f5f970f775656 (diff)
Start working on snmpd(8) and snmpctl(8), a lightweight SNMP implementation
for OpenBSD. SNMP is a necessary evil. This is work in progress, don't expect too much from it yet. ok deraadt@
Diffstat (limited to 'usr.sbin/snmpd/snmpe.c')
-rw-r--r--usr.sbin/snmpd/snmpe.c803
1 files changed, 803 insertions, 0 deletions
diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c
new file mode 100644
index 00000000000..4d9cad4062c
--- /dev/null
+++ b/usr.sbin/snmpd/snmpe.c
@@ -0,0 +1,803 @@
+/* $OpenBSD: snmpe.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */
+
+/*
+ * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ *
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "snmpd.h"
+
+int snmpe_parse(struct sockaddr_storage *,
+ struct ber_element *, struct snmp_message *);
+unsigned long
+ snmpe_application(struct ber_element *);
+void snmpe_sig_handler(int sig, short, void *);
+void snmpe_shutdown(void);
+void snmpe_dispatch_parent(int, short, void *);
+int snmpe_socket_af(struct sockaddr_storage *, in_port_t);
+int snmpe_bind(struct address *);
+void snmpe_recvmsg(int fd, short, void *);
+
+struct snmpd *env = NULL;
+
+struct imsgbuf *ibuf_parent;
+
+void
+snmpe_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ snmpe_shutdown();
+ default:
+ fatalx("snmpe_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+snmpe(struct snmpd *x_env, int pipe_parent2snmpe[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+#ifdef DEBUG
+ struct oid *oid;
+#endif
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("snmpe: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ if (control_init() == -1)
+ fatalx("snmpe: control socket setup failed");
+
+ if ((env->sc_sock = snmpe_bind(&env->sc_address)) == -1)
+ fatalx("snmpe: failed to bind SNMP UDP socket");
+
+ if ((pw = getpwnam(SNMPD_USER)) == NULL)
+ fatal("snmpe: getpwnam");
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("snmpe: chroot");
+ if (chdir("/") == -1)
+ fatal("snmpe: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG mode
+#endif
+
+ setproctitle("snmp engine");
+ snmpd_process = PROC_SNMPE;
+
+#ifndef DEBUG
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("snmpe: cannot drop privileges");
+#endif
+
+#ifdef DEBUG
+ for (oid = NULL; (oid = mps_foreach(oid, 0)) != NULL;) {
+ char buf[BUFSIZ];
+ mps_oidstring(&oid->o_id, buf, sizeof(buf));
+ log_debug("oid %s", buf);
+ }
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, snmpe_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, snmpe_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ close(pipe_parent2snmpe[0]);
+
+ if ((ibuf_parent = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("snmpe");
+
+ imsg_init(ibuf_parent, pipe_parent2snmpe[1], snmpe_dispatch_parent);
+
+ ibuf_parent->events = EV_READ;
+ event_set(&ibuf_parent->ev, ibuf_parent->fd, ibuf_parent->events,
+ ibuf_parent->handler, ibuf_parent);
+ event_add(&ibuf_parent->ev, NULL);
+
+ TAILQ_INIT(&ctl_conns);
+
+ if (control_listen(env, ibuf_parent) == -1)
+ fatalx("snmpe: control socket listen failed");
+
+ event_set(&env->sc_ev, env->sc_sock, EV_READ|EV_PERSIST,
+ snmpe_recvmsg, env);
+ event_add(&env->sc_ev, NULL);
+
+ kr_init();
+
+ event_dispatch();
+
+ snmpe_shutdown();
+ kr_shutdown();
+
+ return (0);
+}
+
+void
+snmpe_shutdown(void)
+{
+ log_info("snmp engine exiting");
+ _exit(0);
+}
+
+void
+snmpe_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("snmpe_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("snmpe_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("snmpe_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+int
+snmpe_socket_af(struct sockaddr_storage *ss, in_port_t port)
+{
+ switch (ss->ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)ss)->sin_port = port;
+ ((struct sockaddr_in *)ss)->sin_len =
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)ss)->sin6_port = port;
+ ((struct sockaddr_in6 *)ss)->sin6_len =
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+snmpe_bind(struct address *addr)
+{
+ char buf[512];
+ int s = -1;
+
+ if (snmpe_socket_af(&addr->ss, htons(addr->port)) == -1)
+ goto bad;
+
+ if ((s = socket(addr->ss.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1)
+ goto bad;
+
+ /*
+ * Socket options
+ */
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ goto bad;
+
+ if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1)
+ goto bad;
+
+ if (log_host(&addr->ss, buf, sizeof(buf)) == NULL)
+ goto bad;
+
+ log_info("snmpe_bind: binding to address %s:%d", buf, addr->port);
+
+ return (s);
+
+ bad:
+ if (s != -1)
+ close(s);
+ return (-1);
+}
+
+#ifdef DEBUG
+static void
+snmpe_debug_elements(struct ber_element *root)
+{
+ static int indent = 0;
+ long long v;
+ int d;
+ char *buf;
+ size_t len;
+ u_int i;
+ int constructed;
+ struct ber_oid o;
+ char str[BUFSIZ];
+
+ /* calculate lengths */
+ ber_calc_len(root);
+
+ switch (root->be_encoding) {
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ constructed = root->be_encoding;
+ break;
+ default:
+ constructed = 0;
+ break;
+ }
+
+ fprintf(stderr, "%*slen %lu ", indent, "", root->be_len);
+ switch (root->be_class) {
+ case BER_CLASS_UNIVERSAL:
+ fprintf(stderr, "class: universal(%u) type: ", root->be_class);
+ switch (root->be_type) {
+ case BER_TYPE_EOC:
+ fprintf(stderr, "end-of-content");
+ break;
+ case BER_TYPE_BOOLEAN:
+ fprintf(stderr, "boolean");
+ break;
+ case BER_TYPE_INTEGER:
+ fprintf(stderr, "integer");
+ break;
+ case BER_TYPE_BITSTRING:
+ fprintf(stderr, "bit-string");
+ break;
+ case BER_TYPE_OCTETSTRING:
+ fprintf(stderr, "octet-string");
+ break;
+ case BER_TYPE_NULL:
+ fprintf(stderr, "null");
+ break;
+ case BER_TYPE_OBJECT:
+ fprintf(stderr, "object");
+ break;
+ case BER_TYPE_ENUMERATED:
+ fprintf(stderr, "enumerated");
+ break;
+ case BER_TYPE_SEQUENCE:
+ fprintf(stderr, "sequence");
+ break;
+ case BER_TYPE_SET:
+ fprintf(stderr, "set");
+ break;
+ }
+ break;
+ case BER_CLASS_APPLICATION:
+ fprintf(stderr, "class: application(%u) type: ",
+ root->be_class);
+ switch (root->be_type) {
+ case SNMP_T_IPADDR:
+ fprintf(stderr, "ipaddr");
+ break;
+ case SNMP_T_COUNTER32:
+ fprintf(stderr, "counter32");
+ break;
+ case SNMP_T_GAUGE32:
+ fprintf(stderr, "gauge32");
+ break;
+ case SNMP_T_TIMETICKS:
+ fprintf(stderr, "timeticks");
+ break;
+ case SNMP_T_OPAQUE:
+ fprintf(stderr, "opaque");
+ break;
+ case SNMP_T_COUNTER64:
+ fprintf(stderr, "counter64");
+ break;
+ }
+ break;
+ case BER_CLASS_CONTEXT:
+ fprintf(stderr, "class: context(%u) type: ",
+ root->be_class);
+ switch (root->be_type) {
+ case SNMP_T_GETREQ:
+ fprintf(stderr, "getreq");
+ break;
+ case SNMP_T_GETNEXTREQ:
+ fprintf(stderr, "nextreq");
+ break;
+ case SNMP_T_GETRESP:
+ fprintf(stderr, "getresp");
+ break;
+ case SNMP_T_SETREQ:
+ fprintf(stderr, "setreq");
+ break;
+ case SNMP_T_TRAP:
+ fprintf(stderr, "trap");
+ break;
+ case SNMP_T_GETBULKREQ:
+ fprintf(stderr, "getbulkreq");
+ break;
+ case SNMP_T_INFORMREQ:
+ fprintf(stderr, "informreq");
+ break;
+ case SNMP_T_TRAPV2:
+ fprintf(stderr, "trapv2");
+ break;
+ case SNMP_T_REPORT:
+ fprintf(stderr, "report");
+ break;
+ }
+ break;
+ case BER_CLASS_PRIVATE:
+ fprintf(stderr, "class: private(%u) type: ", root->be_class);
+ break;
+ default:
+ fprintf(stderr, "class: <INVALID>(%u) type: ", root->be_class);
+ break;
+ }
+ fprintf(stderr, "(%lu) encoding %lu ",
+ root->be_type, root->be_encoding);
+
+ if (constructed)
+ root->be_encoding = constructed;
+
+ switch (root->be_encoding) {
+ case BER_TYPE_BOOLEAN:
+ if (ber_get_boolean(root, &d) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "%s(%d)\n", d ? "true" : "false", d);
+ break;
+ case BER_TYPE_INTEGER:
+ case BER_TYPE_ENUMERATED:
+ if (ber_get_integer(root, &v) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "value %lld\n", v);
+ break;
+ case BER_TYPE_BITSTRING:
+ if (ber_get_bitstring(root, (void *)&buf, &len) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "hexdump ");
+ for (i = 0; i < len; i++)
+ fprintf(stderr, "%02x", buf[i]);
+ fprintf(stderr, "\n");
+ break;
+ case BER_TYPE_OBJECT:
+ if (ber_get_oid(root, &o) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "oid %s",
+ mps_oidstring(&o, str, sizeof(str)));
+ fprintf(stderr, "\n");
+ break;
+ case BER_TYPE_OCTETSTRING:
+ if (ber_get_string(root, &buf) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ if (root->be_class == BER_CLASS_APPLICATION &&
+ root->be_type == SNMP_T_IPADDR) {
+ fprintf(stderr, "addr %s\n",
+ inet_ntoa(*(struct in_addr *)buf));
+ } else
+ fprintf(stderr, "string \"%s\"\n", buf);
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ case BER_TYPE_EOC:
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ default:
+ fprintf(stderr, "\n");
+ break;
+ }
+
+ if (constructed && root->be_sub) {
+ indent += 2;
+ snmpe_debug_elements(root->be_sub);
+ indent -= 2;
+ }
+ if (root->be_next)
+ snmpe_debug_elements(root->be_next);
+}
+#endif
+
+unsigned long
+snmpe_application(struct ber_element *elm)
+{
+ if (elm->be_class != BER_CLASS_APPLICATION)
+ return (BER_TYPE_OCTETSTRING);
+
+ switch (elm->be_type) {
+ case SNMP_T_IPADDR:
+ return (BER_TYPE_OCTETSTRING);
+ case SNMP_T_COUNTER32:
+ case SNMP_T_GAUGE32:
+ case SNMP_T_TIMETICKS:
+ case SNMP_T_OPAQUE:
+ case SNMP_T_COUNTER64:
+ return (BER_TYPE_INTEGER);
+ default:
+ break;
+ }
+ return (BER_TYPE_OCTETSTRING);
+}
+
+int
+snmpe_parse(struct sockaddr_storage *ss,
+ struct ber_element *root, struct snmp_message *msg)
+{
+ struct snmp_stats *stats = &env->sc_stats;
+ struct ber_element *a, *b, *c, *d, *e, *f, *next, *last;
+ const char *errstr = "invalid message";
+ long long ver;
+ unsigned long type, req, errval, erridx;
+ int class, state, i = 0, j = 0;
+ char *comn, buf[BUFSIZ], host[MAXHOSTNAMELEN];
+ struct ber_oid o;
+ size_t len;
+
+ bzero(msg, sizeof(*msg));
+
+ if (ber_scanf_elements(root, "e{ieset{e",
+ &msg->sm_header, &ver, &msg->sm_headerend, &comn,
+ &msg->sm_pdu, &class, &type, &a) != 0)
+ goto fail;
+
+ /* SNMP version and community */
+ switch (ver) {
+ case SNMP_V1:
+ case SNMP_V2:
+ msg->sm_version = ver;
+ break;
+ case SNMP_V3:
+ default:
+ stats->snmp_inbadversions++;
+ log_debug("bad snmp version");
+ return (-1);
+ }
+
+ /* SNMP PDU context */
+ if (class != BER_CLASS_CONTEXT)
+ goto fail;
+ switch (type) {
+ case SNMP_T_GETBULKREQ:
+ if (msg->sm_version == SNMP_V1) {
+ stats->snmp_inbadversions++;
+ log_debug("invalid request for protocol version 1");
+ return (-1);
+ }
+ /* FALLTHROUGH */
+ case SNMP_T_GETREQ:
+ stats->snmp_ingetrequests++;
+ /* FALLTHROUGH */
+ case SNMP_T_GETNEXTREQ:
+ if (type == SNMP_T_GETNEXTREQ)
+ stats->snmp_ingetnexts++;
+ if (strcmp(env->sc_rdcommunity, comn) != 0 &&
+ strcmp(env->sc_rwcommunity, comn) != 0) {
+ stats->snmp_inbadcommunitynames++;
+ log_debug("wrong read community");
+ return (-1);
+ }
+ msg->sm_context = type;
+ break;
+ case SNMP_T_SETREQ:
+ stats->snmp_insetrequests++;
+ if (strcmp(env->sc_rwcommunity, comn) != 0) {
+ if (strcmp(env->sc_rdcommunity, comn) != 0)
+ stats->snmp_inbadcommunitynames++;
+ else
+ stats->snmp_inbadcommunityuses++;
+ log_debug("wrong write community");
+ return (-1);
+ }
+ msg->sm_context = type;
+ break;
+ case SNMP_T_GETRESP:
+ errstr = "response without request";
+ stats->snmp_ingetresponses++;
+ goto fail;
+ case SNMP_T_TRAP:
+ case SNMP_T_TRAPV2:
+ if (strcmp(env->sc_trcommunity, comn) != 0) {
+ stats->snmp_inbadcommunitynames++;
+ log_debug("wrong trap community");
+ return (-1);
+ }
+ errstr = "received trap";
+ stats->snmp_intraps++;
+ goto fail;
+ default:
+ errstr = "invalid context";
+ goto fail;
+ }
+
+ if (strlcpy(msg->sm_community, comn, sizeof(msg->sm_community)) >=
+ sizeof(msg->sm_community)) {
+ stats->snmp_inbadcommunitynames++;
+ log_debug("community name too long");
+ return (-1);
+ }
+
+ /* SNMP PDU */
+ if (ber_scanf_elements(a, "iiie{et",
+ &req, &errval, &erridx, &msg->sm_pduend,
+ &msg->sm_varbind, &class, &type) != 0) {
+ stats->snmp_silentdrops++;
+ log_debug("invalid PDU");
+ return (-1);
+ }
+ if (class != BER_CLASS_UNIVERSAL && type != BER_TYPE_SEQUENCE) {
+ stats->snmp_silentdrops++;
+ log_debug("invalid varbind");
+ return (-1);
+ }
+
+ msg->sm_request = req;
+ msg->sm_error = errval;
+ msg->sm_errorindex = erridx;
+
+ log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d request %d",
+ log_host(ss, host, sizeof(host)),
+ msg->sm_version + 1, msg->sm_community, msg->sm_context,
+ msg->sm_request);
+
+ errstr = "invalid varbind element";
+ for (i = 1, a = msg->sm_varbind, last = NULL;
+ a != NULL; a = a->be_next, i++) {
+ next = a->be_next;
+
+ if (a->be_class != BER_CLASS_UNIVERSAL &&
+ a->be_type != BER_TYPE_SEQUENCE)
+ goto varfail;
+ if ((b = a->be_sub) == NULL)
+ continue;
+ for (state = 0; state < 2 && b != NULL; b = b->be_next) {
+ switch (state++) {
+ case 0:
+ if (ber_get_oid(b, &o) != 0)
+ goto varfail;
+ if (o.bo_n < BER_MIN_OID_LEN ||
+ o.bo_n > BER_MAX_OID_LEN)
+ goto varfail;
+ log_debug("snmpe_parse: %s: oid %s", host,
+ mps_oidstring(&o, buf, sizeof(buf)));
+ break;
+ case 1:
+ c = d = NULL;
+ switch (msg->sm_context) {
+ case SNMP_T_GETNEXTREQ:
+ c = ber_add_sequence(NULL);
+ if ((d = mps_getnextreq(c, &o)) != NULL)
+ break;
+ ber_free_elements(c);
+ c = NULL;
+ msg->sm_error = SNMP_ERROR_NOSUCHNAME;
+ msg->sm_errorindex = i;
+ break; /* ignore error */
+ case SNMP_T_GETREQ:
+ c = ber_add_sequence(NULL);
+ if ((d = mps_getreq(c, &o)) != NULL)
+ break;
+ msg->sm_error = SNMP_ERROR_NOSUCHNAME;
+ ber_free_elements(c);
+ goto varfail;
+ case SNMP_T_SETREQ:
+ if (mps_setreq(b, &o) == 0)
+ break;
+ msg->sm_error = SNMP_ERROR_READONLY;
+ goto varfail;
+ case SNMP_T_GETBULKREQ:
+ j = msg->sm_maxrepetitions;
+ msg->sm_errorindex = 0;
+ msg->sm_error = SNMP_ERROR_NOSUCHNAME;
+ for (d = NULL, len = 0; j > 0; j--) {
+ e = ber_add_sequence(NULL);
+ if (c == NULL)
+ c = e;
+ f = mps_getnextreq(e, &o);
+ if (f == NULL) {
+ ber_free_elements(e);
+ if (d == NULL)
+ goto varfail;
+ break;
+ }
+ len += ber_calc_len(e);
+ if (len > SNMPD_MAXVARBINDLEN) {
+ ber_free_elements(e);
+ break;
+ }
+ if (d != NULL)
+ ber_link_elements(d, e);
+ d = e;
+ }
+ msg->sm_error = 0;
+ break;
+ default:
+ goto varfail;
+ }
+ if (c == NULL)
+ break;
+ if (last == NULL)
+ msg->sm_varbindresp = c;
+ else
+ ber_replace_elements(last, c);
+ a = c;
+ break;
+ }
+ }
+ if (state < 2) {
+ log_debug("snmpe_parse: state %d", state);
+ goto varfail;
+ }
+ last = a;
+ }
+
+ return (0);
+ varfail:
+ log_debug("snmpe_parse: %s: %s, error index %d", host, errstr, i);
+ if (msg->sm_error == 0)
+ msg->sm_error = SNMP_ERROR_GENERR;
+ msg->sm_errorindex = i;
+ return (0);
+ fail:
+ stats->snmp_inasnparseerrs++;
+ log_debug("snmpe_parse: %s: %s", host, errstr);
+ return (-1);
+}
+
+void
+snmpe_recvmsg(int fd, short sig, void *arg)
+{
+ struct snmp_stats *stats = &env->sc_stats;
+ struct sockaddr_storage ss;
+ u_int8_t buf[READ_BUF_SIZE], *ptr;
+ socklen_t slen;
+ ssize_t len;
+ struct ber ber;
+ struct ber_element *req = NULL, *resp = NULL;
+ struct snmp_message msg;
+
+ slen = sizeof(ss);
+ if ((len = recvfrom(fd, buf, sizeof(buf), 0,
+ (struct sockaddr *)&ss, &slen)) < 1)
+ return;
+
+ stats->snmp_inpkts++;
+
+ ber.fd = -1;
+ ber_set_application(&ber, snmpe_application);
+
+ ber_set_readbuf(&ber, buf, len);
+ req = ber_read_elements(&ber, NULL);
+
+ if (req == NULL) {
+ stats->snmp_inasnparseerrs++;
+ goto done;
+ }
+
+#ifdef DEBUG
+ snmpe_debug_elements(req);
+#endif
+
+ if (snmpe_parse(&ss, req, &msg) == -1)
+ goto done;
+
+ if (msg.sm_varbindresp == NULL)
+ msg.sm_varbindresp = ber_unlink_elements(msg.sm_pduend);
+
+ switch (msg.sm_error) {
+ 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 */
+ resp = ber_add_sequence(NULL);
+ ber_printf_elements(resp, "is{tiii{e}}.",
+ msg.sm_version, msg.sm_community,
+ BER_CLASS_CONTEXT, SNMP_T_GETRESP,
+ msg.sm_request, msg.sm_error, msg.sm_errorindex,
+ msg.sm_varbindresp);
+
+#ifdef DEBUG
+ snmpe_debug_elements(resp);
+#endif
+
+ ber_write_elements(&ber, resp);
+ if ((len = ber_get_writebuf(&ber, (void *)&ptr)) == -1)
+ goto done;
+
+ len = sendto(fd, ptr, len, 0, (struct sockaddr *)&ss, slen);
+ if (len != -1)
+ stats->snmp_outpkts++;
+
+ done:
+ if (req != NULL)
+ ber_free_elements(req);
+ if (resp != NULL)
+ ber_free_elements(resp);
+}