summaryrefslogtreecommitdiff
path: root/src/snmpclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/snmpclient.c')
-rw-r--r--src/snmpclient.c967
1 files changed, 967 insertions, 0 deletions
diff --git a/src/snmpclient.c b/src/snmpclient.c
new file mode 100644
index 0000000..5d316ad
--- /dev/null
+++ b/src/snmpclient.c
@@ -0,0 +1,967 @@
+/*
+ * Copyright (c) 2004-2005
+ * Hartmut Brandt.
+ * All rights reserved.
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ *
+ * Author: Harti Brandt <harti@freebsd.org>
+ * Kendy Kutzner
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Begemot: bsnmp/lib/snmpclient.c,v 1.31 2005/05/23 11:10:13 brandt_h Exp $
+ *
+ * Support functions for SNMP clients.
+ */
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <limits.h>
+#ifdef HAVE_ERR_H
+#include <err.h>
+#endif
+
+#include "asn1.h"
+#include "snmp.h"
+#include "snmpclient.h"
+#include "snmppriv.h"
+
+/* ---------------------------------------------------------------------------- */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+
+/* ---------------------------------------------------------------------------- */
+
+/* global context */
+struct snmp_client snmp_client;
+
+/* List of all outstanding requests */
+struct sent_pdu {
+ int reqid;
+ struct snmp_pdu *pdu;
+ struct timeval time;
+ u_int retrycount;
+ snmp_send_cb_f callback;
+ void *arg;
+ void *timeout_id;
+ LIST_ENTRY(sent_pdu) entries;
+};
+LIST_HEAD(sent_pdu_list, sent_pdu);
+
+static struct sent_pdu_list sent_pdus;
+
+/*
+ * Set the error string
+ */
+static void
+seterr(struct snmp_client *sc, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(sc->error, sizeof(sc->error), fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Initialize a client structure
+ */
+void
+snmp_client_init(struct snmp_client *c)
+{
+ memset(c, 0, sizeof(*c));
+
+ c->version = SNMP_V2c;
+ c->trans = SNMP_TRANS_UDP;
+ c->chost = NULL;
+ c->cport = NULL;
+
+ strcpy(c->read_community, "public");
+ strcpy(c->write_community, "private");
+
+ c->timeout.tv_sec = 3;
+ c->timeout.tv_usec = 0;
+ c->retries = 3;
+ c->dump_pdus = 0;
+ c->txbuflen = c->rxbuflen = 10000;
+
+ c->fd = -1;
+
+ c->max_reqid = INT32_MAX;
+ c->min_reqid = 0;
+ c->next_reqid = 0;
+}
+
+
+/*
+ * Open UDP client socket
+ */
+static int
+open_client_udp(const char *host, const char *port)
+{
+ int error;
+ char *ptr;
+ struct addrinfo hints, *res0, *res;
+
+ /* copy host- and portname */
+ if (snmp_client.chost == NULL) {
+ if ((snmp_client.chost = malloc(1 + sizeof(DEFAULT_HOST)))
+ == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+ strcpy(snmp_client.chost, DEFAULT_HOST);
+ }
+ if (host != NULL) {
+ if ((ptr = malloc(1 + strlen(host))) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+ free(snmp_client.chost);
+ snmp_client.chost = ptr;
+ strcpy(snmp_client.chost, host);
+ }
+ if (snmp_client.cport == NULL) {
+ if ((snmp_client.cport = malloc(1 + sizeof(DEFAULT_PORT)))
+ == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+ strcpy(snmp_client.cport, DEFAULT_PORT);
+ }
+ if (port != NULL) {
+ if ((ptr = malloc(1 + strlen(port))) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+ free(snmp_client.cport);
+ snmp_client.cport = ptr;
+ strcpy(snmp_client.cport, port);
+ }
+
+ /* open connection */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = 0;
+ error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0);
+ if (error != 0) {
+ seterr(&snmp_client, "%s: %s", snmp_client.chost,
+ gai_strerror(error));
+ return (-1);
+ }
+ res = res0;
+ for (;;) {
+ if ((snmp_client.fd = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) == -1) {
+ if ((res = res->ai_next) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ freeaddrinfo(res0);
+ return (-1);
+ }
+ } else if (connect(snmp_client.fd, res->ai_addr,
+ res->ai_addrlen) == -1) {
+ if ((res = res->ai_next) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ freeaddrinfo(res0);
+ return (-1);
+ }
+ } else
+ break;
+ }
+ freeaddrinfo(res0);
+ return (0);
+}
+
+/*
+ * SNMP_OPEN
+ */
+int
+snmp_open(const char *host, const char *port, const char *readcomm,
+ const char *writecomm)
+{
+ struct timeval tout;
+
+ /* still open ? */
+ if (snmp_client.fd != -1) {
+ errno = EBUSY;
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+
+ /* copy community strings */
+ if (readcomm != NULL) {
+ strncpy(snmp_client.read_community, readcomm,
+ sizeof(snmp_client.read_community));
+ snmp_client.read_community[sizeof(snmp_client.read_community) - 1] = 0;
+ }
+ if (writecomm != NULL) {
+ strncpy(snmp_client.write_community, writecomm,
+ sizeof(snmp_client.write_community));
+ snmp_client.write_community[sizeof(snmp_client.write_community) - 1] = 0;
+ }
+
+ switch (snmp_client.trans) {
+
+ case SNMP_TRANS_UDP:
+ if (open_client_udp(host, port))
+ return (-1);
+ break;
+
+ default:
+ seterr(&snmp_client, "bad transport mapping");
+ return (-1);
+ }
+ tout.tv_sec = 0;
+ tout.tv_usec = 0;
+ if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_SNDTIMEO,
+ &tout, sizeof(struct timeval)) == -1) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ (void)close(snmp_client.fd);
+ snmp_client.fd = -1;
+ if (snmp_client.local_path[0] != '\0')
+ (void)remove(snmp_client.local_path);
+ return (-1);
+ }
+
+ /* initialize list */
+ LIST_INIT(&sent_pdus);
+
+ return (0);
+}
+
+
+/*
+ * SNMP_CLOSE
+ *
+ * closes connection to snmp server
+ * - function cannot fail
+ * - clears connection
+ * - clears list of sent pdus
+ *
+ * input:
+ * void
+ * return:
+ * void
+ */
+void
+snmp_close(void)
+{
+ struct sent_pdu *p1;
+
+ if (snmp_client.fd != -1) {
+ (void)close(snmp_client.fd);
+ snmp_client.fd = -1;
+ if (snmp_client.local_path[0] != '\0')
+ (void)remove(snmp_client.local_path);
+ }
+ while(!LIST_EMPTY(&sent_pdus)){
+ p1 = LIST_FIRST(&sent_pdus);
+ if (p1->timeout_id != NULL)
+ snmp_client.timeout_stop(p1->timeout_id);
+ LIST_REMOVE(p1, entries);
+ free(p1);
+ }
+ free(snmp_client.chost);
+ free(snmp_client.cport);
+}
+
+/*
+ * initialize a snmp_pdu structure
+ */
+void
+snmp_pdu_create(struct snmp_pdu *pdu, u_int op)
+{
+ memset(pdu,0,sizeof(struct snmp_pdu));
+ if (op == SNMP_PDU_SET)
+ strncpy(pdu->community, snmp_client.write_community,
+ sizeof(pdu->community));
+ else
+ strncpy(pdu->community, snmp_client.read_community,
+ sizeof(pdu->community));
+ pdu->community[sizeof(pdu->community) - 1] = 0;
+
+
+ pdu->type = op;
+ pdu->version = snmp_client.version;
+ pdu->error_status = 0;
+ pdu->error_index = 0;
+ pdu->nbindings = 0;
+}
+
+/* add pairs of (struct asn_oid, enum snmp_syntax) to an existing pdu */
+/* added 10/04/02 by kek: check for MAX_BINDINGS */
+int
+snmp_add_binding(struct snmp_v1_pdu *pdu, ...)
+{
+ va_list ap;
+ const struct asn_oid *oid;
+ u_int ret;
+
+ va_start(ap, pdu);
+
+ ret = pdu->nbindings;
+ while ((oid = va_arg(ap, const struct asn_oid *)) != NULL) {
+ if (pdu->nbindings >= SNMP_MAX_BINDINGS){
+ va_end(ap);
+ return (-1);
+ }
+ pdu->bindings[pdu->nbindings].var = *oid;
+ pdu->bindings[pdu->nbindings].syntax =
+ va_arg(ap, enum snmp_syntax);
+ pdu->nbindings++;
+ }
+ va_end(ap);
+ return (ret);
+}
+
+
+static int32_t
+snmp_next_reqid(struct snmp_client * c)
+{
+ int32_t i;
+
+ i = c->next_reqid;
+ if (c->next_reqid >= c->max_reqid)
+ c->next_reqid = c->min_reqid;
+ else
+ c->next_reqid++;
+ return (i);
+}
+
+/*
+ * Send request and return request id.
+ */
+static int32_t
+snmp_send_packet(struct snmp_pdu * pdu)
+{
+ u_char *buf;
+ struct asn_buf b;
+ ssize_t ret;
+
+ if ((buf = malloc(snmp_client.txbuflen)) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+
+ pdu->request_id = snmp_next_reqid(&snmp_client);
+
+ b.asn_ptr = buf;
+ b.asn_len = snmp_client.txbuflen;
+ if (snmp_pdu_encode(pdu, &b)) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ free(buf);
+ return (-1);
+ }
+
+ if (snmp_client.dump_pdus)
+ snmp_pdu_dump(pdu);
+
+ if ((ret = send(snmp_client.fd, buf, b.asn_ptr - buf, 0)) == -1) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ free(buf);
+ return (-1);
+ }
+ free(buf);
+
+ return pdu->request_id;
+}
+
+/*
+ * to be called when a snmp request timed out
+ */
+static void
+snmp_timeout(void * listentry_ptr)
+{
+ struct sent_pdu *listentry = listentry_ptr;
+
+#if 0
+ warnx("snmp request %i timed out, attempt (%i/%i)",
+ listentry->reqid, listentry->retrycount, snmp_client.retries);
+#endif
+
+ listentry->retrycount++;
+ if (listentry->retrycount > snmp_client.retries) {
+ /* there is no answer at all */
+ LIST_REMOVE(listentry, entries);
+ listentry->callback(listentry->pdu, NULL, listentry->arg);
+ free(listentry);
+ } else {
+ /* try again */
+ /* new request with new request ID */
+ listentry->reqid = snmp_send_packet(listentry->pdu);
+ listentry->timeout_id =
+ snmp_client.timeout_start(&snmp_client.timeout,
+ snmp_timeout, listentry);
+ }
+}
+
+int32_t
+snmp_pdu_send(struct snmp_pdu *pdu, snmp_send_cb_f func, void *arg)
+{
+ struct sent_pdu *listentry;
+ int32_t id;
+
+ if ((listentry = malloc(sizeof(struct sent_pdu))) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+
+ /* here we really send */
+ if ((id = snmp_send_packet(pdu)) == -1) {
+ free(listentry);
+ return (-1);
+ }
+
+ /* add entry to list of sent PDUs */
+ listentry->pdu = pdu;
+ if (gettimeofday(&listentry->time, NULL) == -1)
+ warn("gettimeofday() failed");
+
+ listentry->reqid = pdu->request_id;
+ listentry->callback = func;
+ listentry->arg = arg;
+ listentry->retrycount=1;
+ listentry->timeout_id =
+ snmp_client.timeout_start(&snmp_client.timeout, snmp_timeout,
+ listentry);
+
+ LIST_INSERT_HEAD(&sent_pdus, listentry, entries);
+
+ return (id);
+}
+
+/*
+ * Receive an SNMP packet.
+ *
+ * tv controls how we wait for a packet: if tv is a NULL pointer,
+ * the receive blocks forever, if tv points to a structure with all
+ * members 0 the socket is polled, in all other cases tv specifies the
+ * maximum time to wait for a packet.
+ *
+ * Return:
+ * -1 on errors
+ * 0 on timeout
+ * +1 if packet received
+ */
+static int
+snmp_receive_packet(struct snmp_pdu *pdu, struct timeval *tv)
+{
+ int dopoll, setpoll;
+ int flags;
+ int saved_errno;
+ u_char *buf;
+ int ret;
+ struct asn_buf abuf;
+ int32_t ip;
+#ifdef bsdi
+ int optlen;
+#else
+ socklen_t optlen;
+#endif
+
+ if ((buf = malloc(snmp_client.rxbuflen)) == NULL) {
+ seterr(&snmp_client, "%s", strerror(errno));
+ return (-1);
+ }
+ dopoll = setpoll = 0;
+ flags = 0;
+ if (tv != NULL) {
+ /* poll or timeout */
+ if (tv->tv_sec != 0 || tv->tv_usec != 0) {
+ /* wait with timeout */
+ if (setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
+ tv, sizeof(*tv)) == -1) {
+ seterr(&snmp_client, "setsockopt: %s",
+ strerror(errno));
+ free(buf);
+ return (-1);
+ }
+ optlen = sizeof(*tv);
+ if (getsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
+ tv, &optlen) == -1) {
+ seterr(&snmp_client, "getsockopt: %s",
+ strerror(errno));
+ free(buf);
+ return (-1);
+ }
+ /* at this point tv_sec and tv_usec may appear
+ * as 0. This happens for timeouts lesser than
+ * the clock granularity. The kernel rounds these to
+ * 0 and this would result in a blocking receive.
+ * Instead of an else we check tv_sec and tv_usec
+ * again below and if this rounding happens,
+ * switch to a polling receive. */
+ }
+ if (tv->tv_sec == 0 && tv->tv_usec == 0) {
+ /* poll */
+ dopoll = 1;
+ if ((flags = fcntl(snmp_client.fd, F_GETFL, 0)) == -1) {
+ seterr(&snmp_client, "fcntl: %s",
+ strerror(errno));
+ free(buf);
+ return (-1);
+ }
+ if (!(flags & O_NONBLOCK)) {
+ setpoll = 1;
+ flags |= O_NONBLOCK;
+ if (fcntl(snmp_client.fd, F_SETFL, flags) == -1) {
+ seterr(&snmp_client, "fcntl: %s",
+ strerror(errno));
+ free(buf);
+ return (-1);
+ }
+ }
+ }
+ }
+ ret = recv(snmp_client.fd, buf, snmp_client.rxbuflen, 0);
+ saved_errno = errno;
+ if (tv != NULL) {
+ if (dopoll) {
+ if (setpoll) {
+ flags &= ~O_NONBLOCK;
+ (void)fcntl(snmp_client.fd, F_SETFL, flags);
+ }
+ } else {
+ tv->tv_sec = 0;
+ tv->tv_usec = 0;
+ (void)setsockopt(snmp_client.fd, SOL_SOCKET, SO_RCVTIMEO,
+ tv, sizeof(*tv));
+ }
+ }
+ if (ret == -1) {
+ free(buf);
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return (0);
+ seterr(&snmp_client, "recv: %s", strerror(saved_errno));
+ return (-1);
+ }
+ if (ret == 0) {
+ /* this happens when we have a streaming socket and the
+ * remote side has closed it */
+ free(buf);
+ seterr(&snmp_client, "recv: socket closed by peer");
+ errno = EPIPE;
+ return (-1);
+ }
+
+ abuf.asn_ptr = buf;
+ abuf.asn_len = ret;
+
+ if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) {
+ seterr(&snmp_client, "snmp_decode_pdu: failed %d", ret);
+ free(buf);
+ return (-1);
+ }
+ free(buf);
+ if (snmp_client.dump_pdus)
+ snmp_pdu_dump(pdu);
+
+ return (+1);
+}
+
+static int
+snmp_deliver_packet(struct snmp_pdu * resp)
+{
+ struct sent_pdu *listentry;
+
+ if (resp->type != SNMP_PDU_RESPONSE) {
+ warn("ignoring snmp pdu %u", resp->type);
+ return (-1);
+ }
+
+ LIST_FOREACH(listentry, &sent_pdus, entries)
+ if (listentry->reqid == resp->request_id)
+ break;
+ if (listentry == NULL)
+ return (-1);
+
+ LIST_REMOVE(listentry, entries);
+ listentry->callback(listentry->pdu, resp, listentry->arg);
+
+ snmp_client.timeout_stop(listentry->timeout_id);
+
+ free(listentry);
+ return (0);
+}
+
+int
+snmp_receive(int blocking)
+{
+ int ret;
+
+ struct timeval tv;
+ struct snmp_pdu * resp;
+
+ memset(&tv, 0, sizeof(tv));
+
+ resp = malloc(sizeof(struct snmp_pdu));
+ if (resp == NULL) {
+ seterr(&snmp_client, "no memory for returning PDU");
+ return (-1) ;
+ }
+
+ if ((ret = snmp_receive_packet(resp, blocking ? NULL : &tv)) <= 0) {
+ free(resp);
+ return (ret);
+ }
+ ret = snmp_deliver_packet(resp);
+ snmp_pdu_free(resp);
+ free(resp);
+ return (ret);
+}
+
+
+/*
+ * Check a GETNEXT response. Here we have three possible outcomes: -1 an
+ * unexpected error happened. +1 response is ok and is within the table 0
+ * response is ok, but is behind the table or error is NOSUCHNAME. The req
+ * should point to a template PDU which contains the base OIDs and the
+ * syntaxes. This is really only useful to sweep non-sparse tables.
+ */
+static int
+ok_getnext(const struct snmp_pdu * req, const struct snmp_pdu * resp)
+{
+ u_int i;
+
+ if (resp->version != req->version) {
+ warnx("SNMP GETNEXT: response has wrong version");
+ return (-1);
+ }
+
+ if (resp->error_status == SNMP_ERR_NOSUCHNAME)
+ return (0);
+
+ if (resp->error_status != SNMP_ERR_NOERROR) {
+ warnx("SNMP GETNEXT: error %d", resp->error_status);
+ return (-1);
+ }
+ if (resp->nbindings != req->nbindings) {
+ warnx("SNMP GETNEXT: bad number of bindings in response");
+ return (-1);
+ }
+ for (i = 0; i < req->nbindings; i++) {
+ if (!asn_is_suboid(&req->bindings[i].var,
+ &resp->bindings[i].var)) {
+ if (i != 0)
+ warnx("SNMP GETNEXT: inconsistent table "
+ "response");
+ return (0);
+ }
+ if (resp->version != SNMP_V1 &&
+ resp->bindings[i].syntax == SNMP_SYNTAX_ENDOFMIBVIEW)
+ return (0);
+
+ if (resp->bindings[i].syntax != req->bindings[i].syntax) {
+ warnx("SNMP GETNEXT: bad syntax in response");
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Check a GET response. Here we have three possible outcomes: -1 an
+ * unexpected error happened. +1 response is ok. 0 NOSUCHNAME The req should
+ * point to a template PDU which contains the OIDs and the syntaxes. This
+ * is only useful for SNMPv1 or single object GETS.
+ */
+static int
+ok_get(const struct snmp_pdu * req, const struct snmp_pdu * resp)
+{
+ u_int i;
+
+ if (resp->version != req->version) {
+ warnx("SNMP GET: response has wrong version");
+ return (-1);
+ }
+
+ if (resp->error_status == SNMP_ERR_NOSUCHNAME)
+ return (0);
+
+ if (resp->error_status != SNMP_ERR_NOERROR) {
+ warnx("SNMP GET: error %d", resp->error_status);
+ return (-1);
+ }
+
+ if (resp->nbindings != req->nbindings) {
+ warnx("SNMP GET: bad number of bindings in response");
+ return (-1);
+ }
+ for (i = 0; i < req->nbindings; i++) {
+ if (asn_compare_oid(&req->bindings[i].var,
+ &resp->bindings[i].var) != 0) {
+ warnx("SNMP GET: bad OID in response");
+ return (-1);
+ }
+ if (snmp_client.version != SNMP_V1 &&
+ (resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHOBJECT ||
+ resp->bindings[i].syntax == SNMP_SYNTAX_NOSUCHINSTANCE))
+ return (0);
+ if (resp->bindings[i].syntax != req->bindings[i].syntax) {
+ warnx("SNMP GET: bad syntax in response");
+ return (-1);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Check the reponse to a SET PDU. We check: - the error status must be 0 -
+ * the number of bindings must be equal in response and request - the
+ * syntaxes must be the same in response and request - the OIDs must be the
+ * same in response and request
+ */
+static int
+ok_set(const struct snmp_pdu * req, const struct snmp_pdu * resp)
+{
+ u_int i;
+
+ if (resp->version != req->version) {
+ warnx("SNMP SET: response has wrong version");
+ return (-1);
+ }
+
+ if (resp->error_status == SNMP_ERR_NOSUCHNAME) {
+ warnx("SNMP SET: error %d", resp->error_status);
+ return (0);
+ }
+ if (resp->error_status != SNMP_ERR_NOERROR) {
+ warnx("SNMP SET: error %d", resp->error_status);
+ return (-1);
+ }
+
+ if (resp->nbindings != req->nbindings) {
+ warnx("SNMP SET: bad number of bindings in response");
+ return (-1);
+ }
+ for (i = 0; i < req->nbindings; i++) {
+ if (asn_compare_oid(&req->bindings[i].var,
+ &resp->bindings[i].var) != 0) {
+ warnx("SNMP SET: wrong OID in response to SET");
+ return (-1);
+ }
+ if (resp->bindings[i].syntax != req->bindings[i].syntax) {
+ warnx("SNMP SET: bad syntax in response");
+ return (-1);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Simple checks for response PDUs against request PDUs. Return values: 1=ok,
+ * 0=nosuchname or similar, -1=failure, -2=no response at all
+ */
+int
+snmp_pdu_check(const struct snmp_pdu *req,
+ const struct snmp_pdu *resp)
+{
+ if (resp == NULL)
+ return (-2);
+
+ switch (req->type) {
+
+ case SNMP_PDU_GET:
+ return (ok_get(req, resp));
+
+ case SNMP_PDU_SET:
+ return (ok_set(req, resp));
+
+ case SNMP_PDU_GETNEXT:
+ return (ok_getnext(req, resp));
+
+ }
+ errx(1, "%s: bad pdu type %i", __func__, req->type);
+}
+
+int
+snmp_dialog(struct snmp_v1_pdu *req, struct snmp_v1_pdu *resp)
+{
+ u_int i;
+ int32_t reqid;
+ int ret;
+ struct timeval tv = snmp_client.timeout;
+ struct timeval end;
+ struct snmp_pdu pdu;
+
+ /*
+ * Make a copy of the request and replace the syntaxes by NULL
+ * if this is a GET,GETNEXT or GETBULK.
+ */
+ pdu = *req;
+ if (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GETNEXT ||
+ pdu.type == SNMP_PDU_GETBULK) {
+ for (i = 0; i < pdu.nbindings; i++)
+ pdu.bindings[i].syntax = SNMP_SYNTAX_NULL;
+ }
+
+ for (i = 0; i <= snmp_client.retries; i++) {
+ (void)gettimeofday(&end, NULL);
+ timeradd(&end, &snmp_client.timeout, &end);
+ if ((reqid = snmp_send_packet(&pdu)) == -1)
+ return (-1);
+ for (;;) {
+ (void)gettimeofday(&tv, NULL);
+ if (timercmp(&end, &tv, <=))
+ break;
+ timersub(&end, &tv, &tv);
+ if ((ret = snmp_receive_packet(resp, &tv)) == 0)
+ /* timeout */
+ break;
+
+ if (ret > 0) {
+ if (reqid == resp->request_id)
+ return (0);
+ /* not for us */
+ (void)snmp_deliver_packet(resp);
+ }
+ if (ret < 0 && errno == EPIPE)
+ /* stream closed */
+ return (-1);
+ }
+ }
+ errno = ETIMEDOUT;
+ seterr(&snmp_client, "retry count exceeded");
+ return (-1);
+}
+
+/*
+ * parse a server specification
+ *
+ * [trans::][community@][server][:port]
+ */
+int
+snmp_parse_server(struct snmp_client *sc, const char *str)
+{
+ const char *p, *s = str;
+
+ /* look for a double colon */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == ':' && p[1] == ':')
+ break;
+ }
+ if (*p != '\0') {
+ if (p > s) {
+ if (p - s == 3 && strncmp(s, "udp", 3) == 0)
+ sc->trans = SNMP_TRANS_UDP;
+ else if (p - s == 6 && strncmp(s, "stream", 6) == 0)
+ sc->trans = SNMP_TRANS_LOC_STREAM;
+ else if (p - s == 5 && strncmp(s, "dgram", 5) == 0)
+ sc->trans = SNMP_TRANS_LOC_DGRAM;
+ else {
+ seterr(sc, "unknown SNMP transport '%.*s'",
+ (int)(p - s), s);
+ return (-1);
+ }
+ }
+ s = p + 2;
+ }
+
+ /* look for a @ */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == '@')
+ break;
+ }
+
+ if (*p != '\0') {
+ if (p - s > SNMP_COMMUNITY_MAXLEN) {
+ seterr(sc, "community string too long");
+ return (-1);
+ }
+ strncpy(sc->read_community, s, p - s);
+ sc->read_community[p - s] = '\0';
+ strncpy(sc->write_community, s, p - s);
+ sc->write_community[p - s] = '\0';
+ s = p + 1;
+ }
+
+ /* look for a colon */
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ p++;
+ continue;
+ }
+ if (*p == ':')
+ break;
+ }
+
+ if (*p == ':') {
+ if (p > s) {
+ /* host:port */
+ free(sc->chost);
+ if ((sc->chost = malloc(p - s + 1)) == NULL) {
+ seterr(sc, "%s", strerror(errno));
+ return (-1);
+ }
+ strncpy(sc->chost, s, p - s);
+ sc->chost[p - s] = '\0';
+ }
+ /* port */
+ free(sc->cport);
+ if ((sc->cport = malloc(strlen(p + 1) + 1)) == NULL) {
+ seterr(sc, "%s", strerror(errno));
+ return (-1);
+ }
+ strcpy(sc->cport, p + 1);
+
+ } else if (p > s) {
+ /* host */
+ free(sc->chost);
+ if ((sc->chost = malloc(strlen(s) + 1)) == NULL) {
+ seterr(sc, "%s", strerror(errno));
+ return (-1);
+ }
+ strcpy(sc->chost, s);
+ }
+ return (0);
+}