diff options
Diffstat (limited to 'src/snmpclient.c')
-rw-r--r-- | src/snmpclient.c | 967 |
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); +} |