diff options
-rw-r--r-- | usr.sbin/snmpd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/snmpd/agentx.c | 969 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.c | 3 | ||||
-rw-r--r-- | usr.sbin/snmpd/control.c | 261 | ||||
-rw-r--r-- | usr.sbin/snmpd/parse.y | 22 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmp.h | 280 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf.5 | 15 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.h | 20 | ||||
-rw-r--r-- | usr.sbin/snmpd/trap.c | 265 |
9 files changed, 1650 insertions, 189 deletions
diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile index 0dd6ed369af..753ab01ae1e 100644 --- a/usr.sbin/snmpd/Makefile +++ b/usr.sbin/snmpd/Makefile @@ -1,10 +1,10 @@ -# $OpenBSD: Makefile,v 1.11 2014/01/18 05:54:52 martynas Exp $ +# $OpenBSD: Makefile,v 1.12 2014/04/14 12:55:10 blambert Exp $ PROG= snmpd MAN= snmpd.8 snmpd.conf.5 SRCS= parse.y ber.c log.c control.c snmpe.c \ mps.c trap.c mib.c smi.c kroute.c snmpd.c timer.c \ - pf.c proc.c usm.c + pf.c proc.c usm.c agentx.c LDADD= -levent -lutil -lkvm -lcrypto DPADD= ${LIBEVENT} ${LIBUTIL} diff --git a/usr.sbin/snmpd/agentx.c b/usr.sbin/snmpd/agentx.c new file mode 100644 index 00000000000..55a5c18f075 --- /dev/null +++ b/usr.sbin/snmpd/agentx.c @@ -0,0 +1,969 @@ +/* $OpenBSD: agentx.c,v 1.1 2014/04/14 12:55:10 blambert Exp $ */ +/* + * Copyright (c) 2013,2014 Bret Stephen Lambert <blambert@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/types.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/uio.h> +#include <sys/un.h> + +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "snmp.h" +#include "ber.h" + +int snmp_agentx_octetstring(struct agentx_pdu *, char *, int); +int snmp_agentx_buffercheck(struct agentx_pdu *, size_t); +int snmp_agentx_oid(struct agentx_pdu *, struct snmp_oid *); +int snmp_agentx_buffer_consume(struct agentx_pdu *, u_int); +int snmp_agentx_int(struct agentx_pdu *, uint32_t *); +int snmp_agentx_int64(struct agentx_pdu *, uint64_t *); +int snmp_agentx_do_read_raw(struct agentx_pdu *, void *, int, int); +void snmp_agentx_update_ids(struct agentx_handle *, struct agentx_pdu *); +struct agentx_pdu * + agentx_find_inflight(struct agentx_handle *, uint32_t, uint32_t); + +#ifdef DEBUG +static void snmp_agentx_dump_hdr(struct agentx_hdr *); +#endif + +#define PDU_BUFLEN 256 + +/* snmpTrapOid.0 */ +struct snmp_oid trapoid_0 = { + .o_id = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }, + .o_n = 11 +}; + +/* + * AgentX handle allocation and management routines. + */ + +struct agentx_handle * +snmp_agentx_alloc(int s) +{ + struct agentx_handle *h; + + if ((h = calloc(1, sizeof(*h))) == NULL) + return (NULL); + + h->fd = s; + h->timeout = AGENTX_DEFAULT_TIMEOUT; + + TAILQ_INIT(&h->w); + TAILQ_INIT(&h->inflight); + + return (h); +} + +/* + * Synchronous open of unix socket path. + */ +struct agentx_handle * +snmp_agentx_open(const char *path, char *descr, struct snmp_oid *oid) +{ + struct sockaddr_un sun; + struct agentx_handle *h; + int s; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + return (NULL); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) + goto fail; + + if ((h = snmp_agentx_fdopen(s, descr, oid)) == NULL) + goto fail; + + return (h); + fail: + close(s); + return (NULL); +} + +/* + * Synchronous AgentX open operation over previously-opened socket. + */ +struct agentx_handle * +snmp_agentx_fdopen(int s, char *descr, struct snmp_oid *oid) +{ + struct agentx_handle *h; + struct agentx_pdu *pdu = NULL; + + if ((h = snmp_agentx_alloc(s)) == NULL) + return (NULL); + + if ((pdu = snmp_agentx_open_pdu(h, descr, oid)) == NULL || + (pdu = snmp_agentx_request(h, pdu)) == NULL || + snmp_agentx_open_response(h, pdu) == -1) { + if (pdu) + snmp_agentx_pdu_free(pdu); + snmp_agentx_free(h); + return (NULL); + } + + return (h); +} + +/* + * Synchronous close of agentx handle. + */ +int +snmp_agentx_close(struct agentx_handle *h, uint8_t reason) +{ + struct agentx_pdu *pdu; + int error = 0; + + if ((pdu = snmp_agentx_close_pdu(h, reason)) == NULL) + return (-1); + if ((pdu = snmp_agentx_request(h, pdu)) == NULL) + return (-1); + if (snmp_agentx_response(h, pdu) == -1) + error = -1; + + snmp_agentx_pdu_free(pdu); + + return (error); +} + +void +snmp_agentx_free(struct agentx_handle *h) +{ + struct agentx_pdu *pdu; + + if (h->fd != -1) + close(h->fd); + + while ((pdu = TAILQ_FIRST(&h->w))) { + TAILQ_REMOVE(&h->w, pdu, entry); + snmp_agentx_pdu_free(pdu); + } + while ((pdu = TAILQ_FIRST(&h->inflight))) { + TAILQ_REMOVE(&h->w, pdu, entry); + snmp_agentx_pdu_free(pdu); + } + if (h->r) + snmp_agentx_pdu_free(h->r); + + free(h); +} + +/* + * AgentX pdu allocation routines. + */ + +/* + * Allocate an AgentX PDU. + */ +struct agentx_pdu * +snmp_agentx_pdu_alloc(void) +{ + struct agentx_pdu *pdu; + + if ((pdu = calloc(1, sizeof(*pdu))) == NULL) + return (NULL); + if ((pdu->buffer = calloc(PDU_BUFLEN, sizeof(uint8_t))) == NULL) { + free(pdu); + return (NULL); + } + + pdu->buflen = PDU_BUFLEN; + + bzero(pdu->buffer, pdu->buflen); + pdu->ptr = pdu->buffer + sizeof(struct agentx_hdr); + pdu->ioptr = pdu->buffer; + pdu->hdr = (struct agentx_hdr *)pdu->buffer; + pdu->hdr->version = AGENTX_VERSION; + pdu->hdr->flags = AGENTX_LOCAL_BYTE_ORDER_FLAG; + pdu->hdr->reserved = 0; + pdu->hdr->length = 0; + pdu->datalen = sizeof(struct agentx_hdr); + + return (pdu); +} + +/* + * Read the response PDU for a generic operation. + */ +int +snmp_agentx_response(struct agentx_handle *h, struct agentx_pdu *pdu) +{ + struct agentx_response_data resp; + + if (snmp_agentx_read_raw(pdu, &resp, sizeof(resp)) == -1) + return (-1); + + if (!snmp_agentx_byteorder_native(pdu->hdr)) { + resp.error = snmp_agentx_int16_byteswap(resp.error); + resp.index = snmp_agentx_int16_byteswap(resp.index); + } + + h->error = resp.error; + if (resp.error != AGENTX_ERR_NONE) + return (-1); + + return (0); +} + +/* + * Read the response PDU for an open operation. + */ +int +snmp_agentx_open_response(struct agentx_handle *h, struct agentx_pdu *pdu) +{ + + if (snmp_agentx_response(h, pdu) == -1) + return (-1); + + h->sessionid = pdu->hdr->sessionid; + return (0); +} + +void +snmp_agentx_pdu_free(struct agentx_pdu *pdu) +{ + free(pdu->buffer); + if (pdu->request) + free(pdu->request); + free(pdu); +} + +/* + * Set the callback function to be called when a complete PDU is received. + */ +void +snmp_agentx_set_callback(struct agentx_handle *h, + void (*cb)(struct agentx_handle *, struct agentx_pdu *, void *), void *arg) +{ + h->cb = cb; + h->cb_arg = arg; +} + +int +snmp_agentx_buffer_consume(struct agentx_pdu *b, u_int len) +{ + int padding; + + padding = ((len + 3) & ~0x03) - len; + + if (b->datalen < (len + padding)) + return (-1); + + b->datalen -= len + padding; + b->ptr += len + padding; + + return (0); +} + +/* + * Send an AgentX PDU. Flushes any already-enqueued PDUs. + */ +int +snmp_agentx_send(struct agentx_handle *h, struct agentx_pdu *pdu) +{ + ssize_t n; + + /* set the appropriate IDs in the protocol header */ + if (pdu != NULL && + (pdu->datalen == pdu->hdr->length + sizeof(struct agentx_hdr))) { + pdu->hdr->sessionid = h->sessionid; + + if (pdu->hdr->type != AGENTX_RESPONSE) { + ++h->transactid; + ++h->packetid; + } + + pdu->hdr->transactid = h->transactid; + pdu->hdr->packetid = h->packetid; + TAILQ_INSERT_TAIL(&h->w, pdu, entry); + } + + again: + if ((pdu = TAILQ_FIRST(&h->w)) == NULL) + return (0); + + if ((n = send(h->fd, pdu->ioptr, pdu->datalen, 0)) == -1) + return (-1); + + pdu->ioptr += n; + pdu->datalen -= n; + + if (pdu->datalen > 0) { + errno = EAGAIN; + return (-1); + } + +#ifdef DEBUG + snmp_agentx_dump_hdr(pdu->hdr); +#endif + + TAILQ_REMOVE(&h->w, pdu, entry); + TAILQ_INSERT_TAIL(&h->inflight, pdu, entry); + + goto again; +} + +/* + * Attempt to read a single AgentX PDU. + */ +struct agentx_pdu * +snmp_agentx_recv(struct agentx_handle *h) +{ + struct agentx_pdu *pdu, *match; + ssize_t n; + + h->error = AGENTX_ERR_NONE; + + if (h->r == NULL) { + if ((h->r = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + h->r->datalen = 0; /* XXX -- force this for receive buffers */ + } + pdu = h->r; + + if (snmp_agentx_buffercheck(pdu, sizeof(struct agentx_hdr)) == -1) + return (NULL); + + /* read header */ + if (pdu->datalen < sizeof(struct agentx_hdr)) { + n = recv(h->fd, pdu->ioptr, sizeof(struct agentx_hdr), 0); + + if (n == 0 || n == -1) + return (NULL); + + pdu->datalen += n; + pdu->ioptr += n; + + if (pdu->datalen < sizeof(struct agentx_hdr)) { + errno = EAGAIN; + return (NULL); + } + + if (snmp_agentx_buffercheck(pdu, pdu->hdr->length) == -1) + return (NULL); + } + + /* read body */ + n = recv(h->fd, pdu->ioptr, pdu->hdr->length, 0); + + if (n == 0 || n == -1) + return (NULL); + + pdu->datalen += n; + pdu->ioptr += n; + + if (pdu->datalen < pdu->hdr->length + sizeof(struct agentx_hdr)) { + errno = EAGAIN; + return (NULL); + } +#ifdef DEBUG + snmp_agentx_dump_hdr(pdu->hdr); +#endif + /* If this is an open on a new connection, fix it up */ + if (pdu->hdr->type == AGENTX_OPEN && h->sessionid == 0) { + pdu->hdr->sessionid = 0; /* ignored, per RFC */ + h->transactid = pdu->hdr->transactid; + h->packetid = pdu->hdr->packetid; + } + + if (pdu->hdr->version != AGENTX_VERSION) { + h->error = AGENTX_ERR_PARSE_ERROR; + goto fail; + } + + if (pdu->hdr->type == AGENTX_RESPONSE) { + + match = agentx_find_inflight(h, pdu->hdr->transactid, + pdu->hdr->packetid); + if (match == NULL) { + errno = ESRCH; /* XXX */ + goto fail; + } + + TAILQ_REMOVE(&h->inflight, match, entry); + pdu->request = match; + h->r = NULL; + + if (h->cb) + h->cb(h, pdu, h->cb_arg); + + } else { + if (pdu->hdr->sessionid != h->sessionid) { + h->error = AGENTX_ERR_NOT_OPEN; + goto fail; + } + + if (pdu->hdr->flags & AGENTX_NON_DEFAULT_CONTEXT) { + h->error = AGENTX_ERR_UNSUPPORTED_CONTEXT; + goto fail; + } + + snmp_agentx_update_ids(h, pdu); /* XXX */ + + if (pdu->datalen != pdu->hdr->length + sizeof(*pdu->hdr)) { + h->error = AGENTX_ERR_PARSE_ERROR; + goto fail; + } + } + + h->r = NULL; + return (pdu); + fail: + snmp_agentx_pdu_free(pdu); + h->r = NULL; + return (NULL); +} + +/* + * Synchonous request and receipt of response. + */ +struct agentx_pdu * +snmp_agentx_request(struct agentx_handle *h, struct agentx_pdu *pdu) +{ + + if (snmp_agentx_send(h, pdu) == -1) { + if (errno != EAGAIN) + return (NULL); + } + while (snmp_agentx_send(h, NULL) == -1) { + if (errno != EAGAIN) + return (NULL); + } + while ((pdu = snmp_agentx_recv(h)) == NULL) { + if (errno != EAGAIN) + return (NULL); + } + h->error = AGENTX_ERR_NONE; + + return (pdu); +} + +struct agentx_pdu * +agentx_find_inflight(struct agentx_handle *h, uint32_t tid, uint32_t pid) +{ + struct agentx_pdu *pdu; + + TAILQ_FOREACH(pdu, &h->inflight, entry) + if (pdu->hdr->transactid == tid && pdu->hdr->packetid == pid) + break; + return (pdu); +} + +int +snmp_agentx_buffercheck(struct agentx_pdu *pdu, size_t len) +{ + uint8_t *newptr; + int newlen; + + if (pdu->buflen - pdu->datalen >= len) + return (0); + + newlen = pdu->buflen; + while (newlen - pdu->datalen < len) + newlen *= 2; + + if ((newptr = realloc(pdu->buffer, newlen)) == NULL) + return (-1); + + pdu->buflen = newlen; + pdu->ioptr = &newptr[pdu->ioptr - pdu->buffer]; + pdu->buffer = newptr; + pdu->hdr = (struct agentx_hdr *)pdu->buffer; + pdu->ptr = &pdu->buffer[pdu->datalen]; + + return (0); +} + +/* + * Utility routines for initializing common AgentX PDUs. + */ + +struct agentx_pdu * +snmp_agentx_open_pdu(struct agentx_handle *h, char *descr, + struct snmp_oid *oid) +{ + struct agentx_open_timeout to; + struct snmp_oid nulloid; + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + + pdu->hdr->type = AGENTX_OPEN; + + if (oid == NULL) { + bzero(&nulloid, sizeof(nulloid)); + oid = &nulloid; + } + + bzero(&to, sizeof(to)); + to.timeout = AGENTX_DEFAULT_TIMEOUT; + + if (snmp_agentx_raw(pdu, &to, sizeof(to)) == -1 || + snmp_agentx_oid(pdu, oid) == -1 || + snmp_agentx_octetstring(pdu, descr, strlen(descr)) == -1) + goto fail; + + return (pdu); + fail: + snmp_agentx_pdu_free(pdu); + return (NULL); +} + +struct agentx_pdu * +snmp_agentx_close_pdu(struct agentx_handle *h, uint8_t reason) +{ + struct agentx_close_request_data req; + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + pdu->hdr->type = AGENTX_CLOSE; + + bzero(&req, sizeof(req)); + req.reason = reason; + + if (snmp_agentx_raw(pdu, &req, sizeof(req)) == -1) { + snmp_agentx_pdu_free(pdu); + return (NULL); + } + + return (pdu); +} + +struct agentx_pdu * +snmp_agentx_notify_pdu(struct snmp_oid *oid) +{ + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + pdu->hdr->type = AGENTX_NOTIFY; + + if (snmp_agentx_varbind(pdu, &trapoid_0, + AGENTX_OBJECT_IDENTIFIER, oid, sizeof(*oid)) == -1) { + snmp_agentx_pdu_free(pdu); + return (NULL); + } + + return (pdu); +} + +struct agentx_pdu * +snmp_agentx_response_pdu(int uptime, int error, int idx) +{ + struct agentx_response_data resp; + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + pdu->hdr->type = AGENTX_RESPONSE; + + resp.sysuptime = uptime; + resp.error = error; + resp.index = idx; + + if (snmp_agentx_raw(pdu, &resp, sizeof(resp)) == -1) { + snmp_agentx_pdu_free(pdu); + return (NULL); + } + + return (pdu); +} + +struct agentx_pdu * +snmp_agentx_ping_pdu(void) +{ + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + pdu->hdr->version = AGENTX_VERSION; + pdu->hdr->type = AGENTX_PING; + + return (pdu); +} + +struct agentx_pdu * +snmp_agentx_register_pdu(struct snmp_oid *oid, int timeout, int range_index, + int range_bound) +{ + struct agentx_register_hdr rhdr; + struct agentx_pdu *pdu; + + if ((pdu = snmp_agentx_pdu_alloc()) == NULL) + return (NULL); + + pdu->hdr->version = AGENTX_VERSION; + pdu->hdr->type = AGENTX_REGISTER; + + rhdr.timeout = timeout; + rhdr.priority = AGENTX_REGISTER_PRIO_DEFAULT; + rhdr.subrange = range_index; + rhdr.reserved = 0; + + if (snmp_agentx_raw(pdu, &rhdr, sizeof(rhdr)) == -1 || + snmp_agentx_oid(pdu, oid) == -1 || + (range_index && snmp_agentx_int(pdu, &range_bound) == -1)) { + snmp_agentx_pdu_free(pdu); + return (NULL); + } + + return (pdu); +} + +/* + * AgentX PDU write routines. + */ + +int +snmp_agentx_raw(struct agentx_pdu *pdu, void *data, int len) +{ + + if (snmp_agentx_buffercheck(pdu, len) == -1) + return (-1); + + memcpy(pdu->ptr, data, len); + + pdu->hdr->length += len; + pdu->ptr += len; + pdu->datalen += len; + + return (0); +} + +int +snmp_agentx_int(struct agentx_pdu *pdu, uint32_t *i) +{ + return (snmp_agentx_raw(pdu, i, sizeof(*i))); +} + +int +snmp_agentx_int64(struct agentx_pdu *pdu, uint64_t *i) +{ + return (snmp_agentx_raw(pdu, i, sizeof(*i))); +} + +int +snmp_agentx_octetstring(struct agentx_pdu *pdu, char *str, int len) +{ + static uint8_t pad[4] = { 0, 0, 0, 0 }; + int padding; + uint32_t l; + + padding = ((len + 3) & ~0x03) - len; + + l = len; + if (snmp_agentx_int(pdu, &len) == -1 || + snmp_agentx_raw(pdu, str, len) == -1 || + snmp_agentx_raw(pdu, pad, padding) == -1) + return (-1); + + return (0); +} + +int +snmp_agentx_oid(struct agentx_pdu *pdu, struct snmp_oid *oid) +{ + struct agentx_oid_hdr ohdr; + u_int i, prefix; + + i = prefix = 0; + + if (oid->o_id[0] == 1 && oid->o_id[1] == 3 && + oid->o_id[2] == 6 && oid->o_id[3] == 1 && + oid->o_id[4] < 256) { + prefix = oid->o_id[4]; + i = 5; + } + + if (prefix) + ohdr.n_subid = oid->o_n - 5; + else + ohdr.n_subid = oid->o_n; + ohdr.prefix = prefix; + ohdr.include = 0; + ohdr.reserved = 0; + + if (snmp_agentx_raw(pdu, &ohdr, sizeof(ohdr)) == -1) + return (-1); + + for (; i < oid->o_n; i++) + if (snmp_agentx_int(pdu, &oid->o_id[i]) == -1) + return (-1); + + return (0); +} + +int +snmp_agentx_varbind(struct agentx_pdu *pdu, struct snmp_oid *oid, int type, + void *data, int len) +{ + struct agentx_varbind_hdr vbhdr; + + vbhdr.type = type; + vbhdr.reserved = 0; + if (snmp_agentx_raw(pdu, &vbhdr, sizeof(vbhdr)) == -1) + return (-1); + + if (snmp_agentx_oid(pdu, oid) == -1) + return (-1); + + switch (type) { + + case AGENTX_NO_SUCH_OBJECT: + case AGENTX_NO_SUCH_INSTANCE: + case AGENTX_END_OF_MIB_VIEW: + case AGENTX_NULL: + /* no data follows the OID */ + return (0); + + case AGENTX_IP_ADDRESS: + case AGENTX_OPAQUE: + case AGENTX_OCTET_STRING: + return (snmp_agentx_octetstring(pdu, data, len)); + + case AGENTX_OBJECT_IDENTIFIER: + return (snmp_agentx_oid(pdu, (struct snmp_oid *)data)); + + case AGENTX_INTEGER: + case AGENTX_COUNTER32: + case AGENTX_GAUGE32: + case AGENTX_TIME_TICKS: + return (snmp_agentx_int(pdu, (uint32_t *)data)); + + case AGENTX_COUNTER64: + return (snmp_agentx_int64(pdu, (uint64_t *)data)); + + default: + return (-1); + } + /* NOTREACHED */ +} + +/* + * AgentX PDU read routines. + */ + +int +snmp_agentx_read_vbhdr(struct agentx_pdu *pdu, + struct agentx_varbind_hdr *vbhdr) +{ + if (snmp_agentx_read_raw(pdu, vbhdr, sizeof(*vbhdr)) == -1) + return (-1); + if (!snmp_agentx_byteorder_native(pdu->hdr)) + vbhdr->type = snmp_agentx_int16_byteswap(vbhdr->type); + return (0); +} + +int +snmp_agentx_copy_raw(struct agentx_pdu *pdu, void *v, int len) +{ + return (snmp_agentx_do_read_raw(pdu, v, len, 0)); +} + +int +snmp_agentx_read_raw(struct agentx_pdu *pdu, void *v, int len) +{ + return (snmp_agentx_do_read_raw(pdu, v, len, 1)); +} + +int +snmp_agentx_do_read_raw(struct agentx_pdu *pdu, void *v, int len, int consume) +{ + void *ptr = pdu->ptr; + + if (consume) + if (snmp_agentx_buffer_consume(pdu, len) == -1) + return (-1); + + memcpy(v, ptr, len); + + return (0); +} + +int +snmp_agentx_read_int(struct agentx_pdu *pdu, uint32_t *i) +{ + if (snmp_agentx_read_raw(pdu, i, sizeof(*i)) == -1) + return (-1); + if (!snmp_agentx_byteorder_native(pdu->hdr)) + *i = snmp_agentx_int_byteswap(*i); + return (0); +} + +int +snmp_agentx_read_int64(struct agentx_pdu *pdu, uint64_t *i) +{ + if (snmp_agentx_read_raw(pdu, i, sizeof(*i)) == -1) + return (-1); + if (!snmp_agentx_byteorder_native(pdu->hdr)) + *i = snmp_agentx_int64_byteswap(*i); + return (0); +} + +int +snmp_agentx_read_oid(struct agentx_pdu *pdu, struct snmp_oid *oid) +{ + struct agentx_oid_hdr ohdr; + int i = 0; + + if (snmp_agentx_read_raw(pdu, &ohdr, sizeof(ohdr)) == -1) + return (-1); + + bzero(oid, sizeof(*oid)); + + if (ohdr.prefix != 0) { + oid->o_id[0] = 1; + oid->o_id[1] = 3; + oid->o_id[2] = 6; + oid->o_id[3] = 1; + oid->o_id[4] = ohdr.prefix; + i = 5; + } + + while (ohdr.n_subid--) + if (snmp_agentx_read_int(pdu, &oid->o_id[i++]) == -1) + return (-1); + + oid->o_n = i; + + return (0); +} + +char * +snmp_agentx_read_octetstr(struct agentx_pdu *pdu, int *len) +{ + char *str; + uint32_t l; + + if (snmp_agentx_read_int(pdu, &l) == -1) + return (NULL); + + if ((str = malloc(l)) == NULL) + return (NULL); + + if (snmp_agentx_read_raw(pdu, str, l) == -1) { + free(str); + return (NULL); + } + *len = l; + + return (str); +} + +/* + * Synchronous AgentX calls. + */ + +int +snmp_agentx_ping(struct agentx_handle *h) +{ + struct agentx_pdu *pdu; + int error = 0; + + if ((pdu = snmp_agentx_ping_pdu()) == NULL || + (pdu = snmp_agentx_request(h, pdu)) == NULL) + return (-1); + + if (snmp_agentx_response(h, pdu) == -1) + error = -1; + snmp_agentx_pdu_free(pdu); + + return (error); +} + +/* + * Internal utility functions. + */ + +void +snmp_agentx_update_ids(struct agentx_handle *h, struct agentx_pdu *pdu) +{ + /* XXX -- update to reflect the new queueing semantics */ + h->transactid = pdu->hdr->transactid; + h->packetid = pdu->hdr->packetid; +} + +char * +snmp_agentx_type2name(int type) +{ + static char *names[] = { + "AGENTX_OPEN", + "AGENTX_CLOSE", + "AGENTX_REGISTER", + "AGENTX_UNREGISTER", + "AGENTX_GET", + "AGENTX_GET_NEXT", + "AGENTX_GET_BULK", + "AGENTX_TEST_SET", + "AGENTX_COMMIT_SET", + "AGENTX_UNDO_SET", + "AGENTX_CLEANUP_SET", + "AGENTX_NOTIFY", + "AGENTX_PING", + "AGENTX_INDEX_ALLOCATE", + "AGENTX_INDEX_DEALLOCATE", + "AGENTX_ADD_AGENT_CAPS", + "AGENTX_REMOVE_AGENT_CAPS", + "AGENTX_RESPONSE" + }; + + if (type > 18) + return ("unknown"); + + return (names[type - 1]); +} + +#ifdef DEBUG +static void +snmp_agentx_dump_hdr(struct agentx_hdr *hdr) +{ + if (hdr == NULL) { + printf("NULL\n"); + return; + } + + printf("Version: %i\n", hdr->version); + printf("Type: %s\n", snmp_agentx_type2name(hdr->type)); + printf("Flags: %i\n", hdr->flags); + printf("Reserved: %i\n", hdr->reserved); + printf("Session ID: %i\n", hdr->sessionid); + printf("Transaction ID: %i\n", hdr->transactid); + printf("Packet ID: %i\n", hdr->packetid); + printf("Data Length: %i\n", hdr->length); + + if (hdr->type == AGENTX_RESPONSE) { + struct agentx_response *r = (struct agentx_response *)hdr; + + printf("SysUptime: %i\n", r->data.sysuptime); + printf("Error: %i\n", r->data.error); + printf("Index: %i\n", r->data.index); + } +} +#endif diff --git a/usr.sbin/snmpd/ber.c b/usr.sbin/snmpd/ber.c index 82831393922..9ae498292ef 100644 --- a/usr.sbin/snmpd/ber.c +++ b/usr.sbin/snmpd/ber.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ber.c,v 1.25 2013/10/01 12:41:47 reyk Exp $ */ +/* $OpenBSD: ber.c,v 1.26 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2007, 2012 Reyk Floeter <reyk@openbsd.org> @@ -651,6 +651,7 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...) goto fail; ret++; break; + case 'd': case 'i': i = va_arg(ap, long long *); if (ber_get_integer(ber, i) == -1) diff --git a/usr.sbin/snmpd/control.c b/usr.sbin/snmpd/control.c index 1b0d736cede..d5bfada60cc 100644 --- a/usr.sbin/snmpd/control.c +++ b/usr.sbin/snmpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.21 2013/11/26 12:02:22 henning Exp $ */ +/* $OpenBSD: control.c,v 1.22 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org> @@ -40,12 +40,14 @@ struct ctl_connlist ctl_conns; +static int agentx_sessionid = 1; + void control_accept(int, short, void *); -struct ctl_conn - *control_connbyfd(int); -void control_close(int, struct control_sock *); +void control_close(struct ctl_conn *); void control_dispatch_imsg(int, short, void *); +void control_dispatch_agentx(int, short, void *); void control_imsg_forward(struct imsg *); +void control_event_add(struct ctl_conn *, int, int, struct timeval *); int control_init(struct privsep *ps, struct control_sock *cs) @@ -78,7 +80,7 @@ control_init(struct privsep *ps, struct control_sock *cs) return (-1); } - if (cs->cs_restricted) { + if (cs->cs_restricted || cs->cs_agentx) { old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; } else { @@ -178,9 +180,20 @@ control_accept(int listenfd, short event, void *arg) } imsg_init(&c->iev.ibuf, connfd); - c->iev.handler = control_dispatch_imsg; + if (cs->cs_agentx) { + c->data = snmp_agentx_alloc(c->iev.ibuf.fd); + if (c->data == NULL) { + free(c); + log_warn("%s: agentx", __func__); + return; + } + c->flags |= CTL_CONN_LOCKED; + c->iev.handler = control_dispatch_agentx; + } else + c->iev.handler = control_dispatch_imsg; c->iev.events = EV_READ; - c->iev.data = cs; + c->iev.data = c; + c->cs = cs; event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, c->iev.handler, c->iev.data); event_add(&c->iev.ev, NULL); @@ -188,27 +201,10 @@ control_accept(int listenfd, short event, void *arg) TAILQ_INSERT_TAIL(&ctl_conns, c, entry); } -struct ctl_conn * -control_connbyfd(int fd) -{ - struct ctl_conn *c; - - for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd; - c = TAILQ_NEXT(c, entry)) - ; /* nothing */ - - return (c); -} - void -control_close(int fd, struct control_sock *cs) +control_close(struct ctl_conn *c) { - struct ctl_conn *c; - - if ((c = control_connbyfd(fd)) == NULL) { - log_warn("%s: fd %d: not found", __func__, fd); - return; - } + struct control_sock *cs = c->cs; msgbuf_clear(&c->iev.ibuf.w); TAILQ_REMOVE(&ctl_conns, c, entry); @@ -229,27 +225,22 @@ control_close(int fd, struct control_sock *cs) void control_dispatch_imsg(int fd, short event, void *arg) { - struct control_sock *cs = arg; + struct ctl_conn *c = arg; + struct control_sock *cs = c->cs; struct snmpd *env = cs->cs_env; - struct ctl_conn *c; struct imsg imsg; int n, v, i; - if ((c = control_connbyfd(fd)) == NULL) { - log_warn("%s: fd %d: not found", __func__, fd); - return; - } - switch (event) { case EV_READ: if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) { - control_close(fd, cs); + control_close(c); return; } break; case EV_WRITE: if (msgbuf_write(&c->iev.ibuf.w) < 0 && errno != EAGAIN) { - control_close(fd, cs); + control_close(c); return; } break; @@ -259,7 +250,7 @@ control_dispatch_imsg(int fd, short event, void *arg) for (;;) { if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { - control_close(fd, cs); + control_close(c); return; } @@ -268,7 +259,7 @@ control_dispatch_imsg(int fd, short event, void *arg) if (cs->cs_restricted || (c->flags & CTL_CONN_LOCKED)) { switch (imsg.hdr.type) { - case IMSG_SNMP_TRAP: + case IMSG_SNMP_AGENTX: case IMSG_SNMP_ELEMENT: case IMSG_SNMP_END: case IMSG_SNMP_LOCK: @@ -277,7 +268,7 @@ control_dispatch_imsg(int fd, short event, void *arg) log_debug("control_dispatch_imsg: " "client requested restricted command"); imsg_free(&imsg); - control_close(fd, cs); + control_close(c); return; } } @@ -296,20 +287,39 @@ control_dispatch_imsg(int fd, short event, void *arg) } c->flags |= CTL_CONN_NOTIFY; break; + case IMSG_SNMP_LOCK: /* enable restricted control mode */ c->flags |= CTL_CONN_LOCKED; break; - case IMSG_SNMP_TRAP: - if (trap_imsg(&c->iev, imsg.hdr.pid) == -1) { + + case IMSG_SNMP_AGENTX: + + /* rendezvous with the client */ + imsg_compose_event(&c->iev, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + if (imsg_flush(&c->iev.ibuf) == -1) { log_debug("control_dispatch_imsg: " - "received invalid trap (pid %d)", - imsg.hdr.pid); + "could not rendezvous with client"); imsg_free(&imsg); - control_close(fd, cs); + control_close(c); return; } + + /* enable AgentX socket */ + c->data = snmp_agentx_alloc(c->iev.ibuf.fd); + if (c->data == NULL) { + log_debug("control_dispatch_imsg: " + "could not allocate restricted socket"); + imsg_free(&imsg); + control_close(c); + return; + } + /* disable IMSG notifications */ + c->flags &= ~CTL_CONN_NOTIFY; + c->flags |= CTL_CONN_LOCKED; + c->iev.handler = control_dispatch_agentx; break; + case IMSG_CTL_VERBOSE: IMSG_SIZE_CHECK(&imsg, &v); @@ -336,6 +346,163 @@ control_dispatch_imsg(int fd, short event, void *arg) imsg_event_add(&c->iev); } +/* ARGSUSED */ +void +control_dispatch_agentx(int fd, short event, void *arg) +{ + struct ctl_conn *c = arg; + struct agentx_handle *h = c->data; + struct agentx_pdu *pdu; + struct timeval tv; + struct agentx_open_timeout to; + struct ber_oid oid; + struct agentx_close_request_data clhdr; + int closing = 0; + int evflags = 0; + int timer = 0; + int error = AGENTX_ERR_NONE; + int idx = 0, vcpylen, dlen, uptime; + char *descr, *varcpy; + + varcpy = descr = NULL; + if (h->timeout != 0) + tv.tv_sec = h->timeout; + else + tv.tv_sec = AGENTX_DEFAULT_TIMEOUT; + tv.tv_usec = 0; + + if (event & EV_TIMEOUT) { + log_info("subagent session '%i' timed out after %i seconds", + h->sessionid, h->timeout); + goto teardown; + } + + if (event & EV_WRITE) { + if (snmp_agentx_send(h, NULL) == -1) { + if (errno != EAGAIN) + goto teardown; + + /* short write */ + evflags |= EV_WRITE; + timer = 1; + } + } + + if (event & EV_READ) { + if ((pdu = snmp_agentx_recv(h)) == NULL) { + if (h->error) { + error = h->error; + goto respond; + } + if (errno != EAGAIN) + goto teardown; + + /* short read */ + timer = 1; + goto done; + } + + switch (pdu->hdr->type) { + case AGENTX_OPEN: + if (snmp_agentx_read_raw(pdu, &to, sizeof(to)) == -1 || + snmp_agentx_read_oid(pdu, + (struct snmp_oid *)&oid) == -1 || + (descr = + snmp_agentx_read_octetstr(pdu, &dlen)) == NULL) { + error = AGENTX_ERR_PARSE_ERROR; + break; + } + + log_info("opening AgentX socket for '%.*s'", + dlen, descr); + + h->sessionid = pdu->hdr->sessionid = + agentx_sessionid++; + if (to.timeout != 0) + h->timeout = to.timeout; + else + h->timeout = AGENTX_DEFAULT_TIMEOUT; + break; + + case AGENTX_CLOSE: + if (snmp_agentx_read_raw(pdu, + &clhdr, sizeof(clhdr)) == -1) { + error = AGENTX_ERR_PARSE_ERROR; + break; + } + closing = 1; + break; + + case AGENTX_NOTIFY: + error = trap_agentx(h, pdu, &idx, &varcpy, &vcpylen); + break; + + case AGENTX_PING: + /* no processing, just an empty response */ + break; + + /* unimplemented */ + case AGENTX_ADD_AGENT_CAPS: + case AGENTX_REMOVE_AGENT_CAPS: + case AGENTX_RESPONSE: + case AGENTX_REGISTER: + case AGENTX_UNREGISTER: + case AGENTX_GET: + case AGENTX_GET_NEXT: + case AGENTX_GET_BULK: + case AGENTX_TEST_SET: + case AGENTX_COMMIT_SET: + case AGENTX_UNDO_SET: + case AGENTX_CLEANUP_SET: + case AGENTX_INDEX_ALLOCATE: + case AGENTX_INDEX_DEALLOCATE: + error = AGENTX_ERR_REQUEST_DENIED; + break; + + /* NB: by RFC, this should precede all other checks. */ + default: + log_info("unknown AgentX type '%i'", pdu->hdr->type); + error = AGENTX_ERR_PARSE_ERROR; + break; + } + respond: + if (pdu) + snmp_agentx_pdu_free(pdu); + + uptime = smi_getticks(); + if ((pdu = snmp_agentx_response_pdu(uptime, error, idx)) == NULL) { + log_debug("bad response generation: %s", + snmp_agentx_type2name(pdu->hdr->type)); + if (varcpy) + free(varcpy); + control_event_add(c, fd, EV_WRITE, NULL); /* XXX -- EV_WRITE? */ + return; + } + + if (varcpy) { + snmp_agentx_raw(pdu, varcpy, vcpylen); /* XXX */ + free(varcpy); + } + snmp_agentx_send(h, pdu); + + /* Request processed, now write out response */ + evflags |= EV_WRITE; + } + + if (closing) + goto teardown; + done: + control_event_add(c, fd, evflags, timer ? &tv : NULL); + return; + + teardown: + log_debug("subagent session '%i' destroyed", h->sessionid); + snmp_agentx_free(h); + if (varcpy) + free(varcpy); + control_close(c); +} + void control_imsg_forward(struct imsg *imsg) { @@ -347,3 +514,11 @@ control_imsg_forward(struct imsg *imsg) 0, imsg->hdr.pid, -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); } + +void +control_event_add(struct ctl_conn *c, int fd, int wflag, struct timeval *tv) +{ + event_del(&c->iev.ev); + event_set(&c->iev.ev, fd, EV_READ|wflag, control_dispatch_agentx, c); + event_add(&c->iev.ev, tv); +} diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y index 69b45cd0bf6..db25bb8c037 100644 --- a/usr.sbin/snmpd/parse.y +++ b/usr.sbin/snmpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.29 2014/01/22 00:21:17 henning Exp $ */ +/* $OpenBSD: parse.y,v 1.30 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -51,6 +51,11 @@ #include "snmpd.h" #include "mib.h" +enum socktype { + SOCK_TYPE_RESTRICTED = 1, + SOCK_TYPE_AGENTX = 2 +}; + TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; @@ -120,11 +125,11 @@ typedef struct { %token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER %token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER %token SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR DISABLED -%token SOCKET RESTRICTED +%token SOCKET RESTRICTED AGENTX %token <v.string> STRING %token <v.number> NUMBER %type <v.string> hostcmn -%type <v.number> optwrite yesno seclevel restricted +%type <v.number> optwrite yesno seclevel socktype %type <v.data> objtype %type <v.oid> oid hostoid %type <v.auth> auth @@ -266,7 +271,7 @@ main : LISTEN ON STRING { } user = NULL; } - | SOCKET STRING restricted { + | SOCKET STRING socktype { if ($3) { struct control_sock *rcsock; @@ -276,7 +281,10 @@ main : LISTEN ON STRING { YYERROR; } rcsock->cs_name = $2; - rcsock->cs_restricted = 1; + if ($3 == SOCK_TYPE_RESTRICTED) + rcsock->cs_restricted = 1; + else if ($3 == SOCK_TYPE_AGENTX) + rcsock->cs_agentx = 1; TAILQ_INSERT_TAIL(&conf->sc_ps.ps_rcsocks, rcsock, cs_entry); } else { @@ -475,7 +483,8 @@ enc : STRING { } ; -restricted : RESTRICTED { $$ = 1; } +socktype : RESTRICTED { $$ = SOCK_TYPE_RESTRICTED; } + | AGENTX { $$ = SOCK_TYPE_AGENTX; } | /* nothing */ { $$ = 0; } ; @@ -513,6 +522,7 @@ lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { + { "agentx", AGENTX }, { "auth", AUTH }, { "authkey", AUTHKEY }, { "community", COMMUNITY }, diff --git a/usr.sbin/snmpd/snmp.h b/usr.sbin/snmpd/snmp.h index eaf5f01a585..4d03441f5a0 100644 --- a/usr.sbin/snmpd/snmp.h +++ b/usr.sbin/snmpd/snmp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmp.h,v 1.10 2012/09/17 16:43:59 reyk Exp $ */ +/* $OpenBSD: snmp.h,v 1.11 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -19,12 +19,17 @@ #ifndef SNMP_HEADER #define SNMP_HEADER +#include <sys/types.h> +#include <machine/endian.h> + /* * SNMP IMSG interface */ -#define SNMP_MAX_OID_LEN 128 /* max size of the OID _string_ */ +#define SNMP_MAX_OID_STRLEN 128 /* max size of the OID _string_ */ #define SNMP_SOCKET "/var/run/snmpd.sock" +#define AGENTX_SOCKET "/var/run/agentx.sock" +#define SNMP_RESTRICTED_SOCKET "/var/run/snmpd.rsock" enum snmp_type { SNMP_IPADDR = 0, @@ -45,10 +50,11 @@ enum snmp_type { }; enum snmp_imsg_ctl { - IMSG_SNMP_TRAP = 1000, /* something that works everywhere */ + IMSG_SNMP_DUMMY = 1000, /* something that works everywhere */ IMSG_SNMP_ELEMENT, IMSG_SNMP_END, - IMSG_SNMP_LOCK /* enable restricted mode */ + IMSG_SNMP_LOCK, /* enable restricted mode */ + IMSG_SNMP_AGENTX }; struct snmp_imsg_hdr { @@ -60,7 +66,7 @@ struct snmp_imsg_hdr { }; struct snmp_imsg { - char snmp_oid[SNMP_MAX_OID_LEN]; + char snmp_oid[SNMP_MAX_OID_STRLEN]; u_int8_t snmp_type; u_int16_t snmp_len; }; @@ -150,4 +156,268 @@ enum snmp_security_model { #define SNMP_MAX_TIMEWINDOW 150 /* RFC3414 */ +#define SNMP_MIN_OID_LEN 2 /* OBJECT */ +#define SNMP_MAX_OID_LEN 32 /* OBJECT */ + +struct snmp_oid { + u_int32_t o_id[SNMP_MAX_OID_LEN + 1]; + size_t o_n; +}; + +/* AgentX protocol, as outlined in RFC 2741 */ + +/* version */ +#define AGENTX_VERSION 1 + +/* type */ +#define AGENTX_OPEN 1 +#define AGENTX_CLOSE 2 +#define AGENTX_REGISTER 3 +#define AGENTX_UNREGISTER 4 +#define AGENTX_GET 5 +#define AGENTX_GET_NEXT 6 +#define AGENTX_GET_BULK 7 +#define AGENTX_TEST_SET 8 +#define AGENTX_COMMIT_SET 9 +#define AGENTX_UNDO_SET 10 +#define AGENTX_CLEANUP_SET 11 +#define AGENTX_NOTIFY 12 +#define AGENTX_PING 13 +#define AGENTX_INDEX_ALLOCATE 14 +#define AGENTX_INDEX_DEALLOCATE 15 +#define AGENTX_ADD_AGENT_CAPS 16 +#define AGENTX_REMOVE_AGENT_CAPS 17 +#define AGENTX_RESPONSE 18 + +/* error return codes */ +#define AGENTX_ERR_NONE 0 +#define AGENTX_ERR_OPEN_FAILED 256 +#define AGENTX_ERR_NOT_OPEN 257 +#define AGENTX_ERR_INDEX_WRONG_TYPE 258 +#define AGENTX_ERR_INDEX_ALREADY_ALLOCATED 259 +#define AGENTX_ERR_INDEX_NONE_AVAILABLE 260 +#define AGENTX_ERR_INDEX_NOT_ALLOCATED 261 +#define AGENTX_ERR_UNSUPPORTED_CONTEXT 262 +#define AGENTX_ERR_DUPLICATE_REGISTRATION 263 +#define AGENTX_ERR_UNKNOWN_REGISTRATION 264 +#define AGENTX_ERR_UNKNOWN_AGENT_CAPS 265 +#define AGENTX_ERR_PARSE_ERROR 266 +#define AGENTX_ERR_REQUEST_DENIED 267 +#define AGENTX_ERR_PROCESSING_ERROR 268 + +/* flags */ +#define AGENTX_INSTANCE_REGISTRATION 0x01 +#define AGENTX_NEW_INDEX 0x02 +#define AGENTX_ANY_INDEX 0x04 +#define AGENTX_NON_DEFAULT_CONTEXT 0x08 +#define AGENTX_NETWORK_BYTE_ORDER 0x10 +#define AGENTX_FLAGS_MASK 0x1f + +/* encoded data types */ +#define AGENTX_INTEGER 2 +#define AGENTX_OCTET_STRING 4 +#define AGENTX_NULL 5 +#define AGENTX_OBJECT_IDENTIFIER 6 +#define AGENTX_IP_ADDRESS 64 +#define AGENTX_COUNTER32 65 +#define AGENTX_GAUGE32 66 +#define AGENTX_TIME_TICKS 67 +#define AGENTX_OPAQUE 68 +#define AGENTX_COUNTER64 70 +#define AGENTX_NO_SUCH_OBJECT 128 +#define AGENTX_NO_SUCH_INSTANCE 129 +#define AGENTX_END_OF_MIB_VIEW 130 + +/* for registered MIB overlap */ +#define AGENTX_REGISTER_PRIO_DEFAULT 127 + +/* reasons for request of close */ +#define AGENTX_CLOSE_OTHER 1 +#define AGENTX_CLOSE_PARSE_ERROR 2 +#define AGENTX_CLOSE_PROTOCOL_ERROR 3 +#define AGENTX_CLOSE_TIMEOUTS 4 +#define AGENTX_CLOSE_SHUTDOWN 5 +#define AGENTX_CLOSE_BY_MANAGER 6 + +#define AGENTX_DEFAULT_TIMEOUT 3 + +#define MIN_OID_LEN 2 /* OBJECT */ +#define MAX_OID_LEN 32 /* OBJECT */ + +/* + * Protocol header prefixed to all messages + */ +struct agentx_hdr { + uint8_t version; + uint8_t type; + uint8_t flags; + uint8_t reserved; + uint32_t sessionid; /* chosen by agent */ + uint32_t transactid; /* chosen by subagent */ + uint32_t packetid; /* per-request id */ + uint32_t length; +} __packed; + +/* + * Prefixed to a series of 4-byte values indicating the OID + */ +struct agentx_oid_hdr { + uint8_t n_subid; /* # of oid elements (named in RFC) */ + uint8_t prefix; /* if not 0, OID is 1.3.6.1.<prefix> */ + uint8_t include; /* ??? */ + uint8_t reserved; /* always 0 */ +} __packed; + +struct agentx_response_data { + uint32_t sysuptime; /* uptime of SNMP context */ + uint16_t error; /* status of request */ + uint16_t index; /* index of failed variable binding */ +} __packed; + +struct agentx_open_timeout { + uint8_t timeout; + uint8_t reserved[3]; +} __packed; + +struct agentx_register_hdr { + uint8_t timeout; + uint8_t priority; + uint8_t subrange; + uint8_t reserved; +} __packed; + +struct agentx_null_oid { + uint8_t padding[4]; +} __packed; + +#define AGENTX_NULL_OID { 0, 0, 0, 0 } + +struct agentx_varbind_hdr { + uint16_t type; + uint16_t reserved; +} __packed; + +struct agentx_response { + struct agentx_hdr hdr; + struct agentx_response_data data; +} __packed; + +struct agentx_close_request_data { + uint8_t reason; + uint8_t padding[3]; +} __packed; + +struct agentx_close_request { + struct agentx_hdr hdr; + struct agentx_close_request_data data; +} __packed; + +struct agentx_pdu { + uint8_t *buffer; + uint8_t *ptr; + uint8_t *ioptr; + size_t buflen; + size_t datalen; + struct agentx_hdr *hdr; + + struct agentx_pdu *request; /* request this is a response to */ + TAILQ_ENTRY(agentx_pdu) entry; +}; +TAILQ_HEAD(agentx_pdulist, agentx_pdu); + +struct agentx_handle { + int fd; + uint32_t sessionid; + uint32_t transactid; + uint32_t packetid; + int timeout; /* in seconds */ + int error; + int erridx; + + struct agentx_pdulist w; + struct agentx_pdulist inflight; + + struct agentx_pdu *r; + + void (*cb)(struct agentx_handle *, struct agentx_pdu *, void *); + void *cb_arg; +}; + +struct agentx_handle * + snmp_agentx_alloc(int); +struct agentx_handle * + snmp_agentx_open(const char *, char *, struct snmp_oid *); +struct agentx_handle * + snmp_agentx_fdopen(int, char *, struct snmp_oid *); +int snmp_agentx_response(struct agentx_handle *, struct agentx_pdu *); +int snmp_agentx_open_response(struct agentx_handle *, struct agentx_pdu *); +struct agentx_pdu * + snmp_agentx_open_pdu(struct agentx_handle *, char *descr, + struct snmp_oid *); +void snmp_agentx_set_callback(struct agentx_handle *, + void (*)(struct agentx_handle *, struct agentx_pdu *, void *), + void *); +struct agentx_pdu * + snmp_agentx_close_pdu(struct agentx_handle *, uint8_t); +int snmp_agentx_close(struct agentx_handle *, uint8_t); +void snmp_agentx_free(struct agentx_handle *); +int snmp_agentx_ping(struct agentx_handle *); +struct agentx_pdu * + snmp_agentx_ping_pdu(void); +struct agentx_pdu * + snmp_agentx_notify_pdu(struct snmp_oid *); +struct agentx_pdu * + snmp_agentx_request(struct agentx_handle *, struct agentx_pdu *); +int snmp_agentx_varbind(struct agentx_pdu *, struct snmp_oid *, int, + void *, int); +int snmp_agentx_send(struct agentx_handle *, struct agentx_pdu *); +struct agentx_pdu * + snmp_agentx_recv(struct agentx_handle *); +struct agentx_pdu * + snmp_agentx_response_pdu(int, int, int); +struct agentx_pdu * + snmp_agentx_register_pdu(struct snmp_oid *, int, int, int); +char *snmp_agentx_read_octetstr(struct agentx_pdu *, int *); +int snmp_agentx_read_oid(struct agentx_pdu *, struct snmp_oid *); +int snmp_agentx_read_raw(struct agentx_pdu *, void *, int); +int snmp_agentx_copy_raw(struct agentx_pdu *, void *, int); +char *snmp_agentx_type2name(int); +int snmp_agentx_read_int(struct agentx_pdu *, uint32_t *); +int snmp_agentx_read_int64(struct agentx_pdu *, uint64_t *); +int snmp_agentx_raw(struct agentx_pdu *, void *, int); +int snmp_agentx_read_vbhdr(struct agentx_pdu *, struct + agentx_varbind_hdr *); +struct agentx_pdu *snmp_agentx_pdu_alloc(void); +void snmp_agentx_pdu_free(struct agentx_pdu *); + +#if BYTE_ORDER == BIG_ENDIAN + +static __inline int +snmp_agentx_byteorder_native(struct agentx_hdr *h) +{ + return ((h->flags & AGENTX_NETWORK_BYTE_ORDER) != 0); +} + +#define AGENTX_LOCAL_BYTE_ORDER_FLAG AGENTX_NETWORK_BYTE_ORDER +#define snmp_agentx_int_byteswap(_i) htole32(_i) +#define snmp_agentx_int16_byteswap(_i) htole16(_i) +#define snmp_agentx_int64_byteswap(_i) htole64(_i) + +#elif BYTE_ORDER == LITTLE_ENDIAN + +static __inline int +snmp_agentx_byteorder_native(struct agentx_hdr *h) +{ + return ((h->flags & AGENTX_NETWORK_BYTE_ORDER) == 0); +} + +#define AGENTX_LOCAL_BYTE_ORDER_FLAG 0 +#define snmp_agentx_int_byteswap(_i) htobe32(_i) +#define snmp_agentx_int16_byteswap(_i) htobe16(_i) +#define snmp_agentx_int64_byteswap(_i) htobe64(_i) + +#else +#error "Unknown host byte order" +#endif + #endif /* SNMP_HEADER */ diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 index d84d9caf4b2..c1cec135a4d 100644 --- a/usr.sbin/snmpd/snmpd.conf.5 +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: snmpd.conf.5,v 1.28 2013/10/17 09:14:01 blambert Exp $ +.\" $OpenBSD: snmpd.conf.5,v 1.29 2014/04/14 12:55:10 blambert Exp $ .\" .\" Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: October 17 2013 $ +.Dd $Mdocdate: April 14 2014 $ .Dt SNMPD.CONF 5 .Os .Sh NAME @@ -136,18 +136,23 @@ If the chosen value is different from will accept only SNMPv3 requests since older versions neither support authentication nor encryption. .Pp -.It Ic socket Qo Ar path Qc Op Ic restricted +.It Ic socket Qo Ar path Qc Op Ic restricted | Ic agentx Create a control socket at .Ar path . If .Ic restricted -is specified a restricted control socket will be created. +is specified, a restricted control socket will be created. +If +.Ic agentx +is specified, a socket which speaks the AgentX protocol will be created. Multiple .Ic restricted +and +.Ic agentx sockets may be created. By default .Pa /var/run/snmpd.sock -is used and no restricted socket is created. +is created and no other sockets are created. .Pp .It Ic system contact Ar string Specify the name or description of the system contact, typically a diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index 95244c48e09..a07f28e3a66 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.h,v 1.49 2014/02/14 10:38:09 florian Exp $ */ +/* $OpenBSD: snmpd.h,v 1.50 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -100,6 +100,7 @@ struct control_sock { struct event cs_evt; int cs_fd; int cs_restricted; + int cs_agentx; void *cs_env; TAILQ_ENTRY(control_sock) cs_entry; @@ -162,7 +163,8 @@ struct ctl_conn { #define CTL_CONN_NOTIFY 0x01 #define CTL_CONN_LOCKED 0x02 /* restricted mode */ struct imsgev iev; - + void *data; + struct control_sock *cs; }; TAILQ_HEAD(ctl_connlist, ctl_conn); extern struct ctl_connlist ctl_conns; @@ -336,7 +338,14 @@ struct pfr_buffer { #define MSG_REPORT(m) (((m)->sm_flags & SNMP_MSGFLAG_REPORT) != 0) struct snmp_message { + struct sockaddr_storage sm_ss; + socklen_t sm_slen; + char sm_host[MAXHOSTNAMELEN]; + + struct ber sm_ber; + struct ber_element *sm_req; struct ber_element *sm_resp; + u_int8_t sm_data[READ_BUF_SIZE]; size_t sm_datalen; @@ -366,6 +375,7 @@ struct snmp_message { long long sm_request; + const char *sm_errstr; long long sm_error; #define sm_nonrepeaters sm_error long long sm_errorindex; @@ -547,6 +557,8 @@ void snmpe_shutdown(struct privsep *, struct privsep_proc *); /* trap.c */ void trap_init(void); int trap_imsg(struct imsgev *, pid_t); +int trap_agentx(struct agentx_handle *, struct agentx_pdu *, + int *, char **, int *); int trap_send(struct ber_oid *, struct ber_element *); /* mps.c */ @@ -554,6 +566,8 @@ struct ber_element * mps_getreq(struct ber_element *, struct ber_oid *, u_int); struct ber_element * mps_getnextreq(struct ber_element *, struct ber_oid *); +struct ber_element * + mps_getbulkreq(struct ber_element *, struct ber_oid *, int); int mps_setreq(struct ber_element *, struct ber_oid *); int mps_set(struct ber_oid *, void *, long long); int mps_getstr(struct oid *, struct ber_oid *, @@ -650,4 +664,6 @@ int proc_composev_imsg(struct privsep *, enum privsep_procid, u_int16_t, int, const struct iovec *, int); int proc_forward_imsg(struct privsep *, struct imsg *, enum privsep_procid); +void proc_flush_imsg(struct privsep *, enum privsep_procid); + #endif /* _SNMPD_H */ diff --git a/usr.sbin/snmpd/trap.c b/usr.sbin/snmpd/trap.c index 5c3ca65eecd..5375418a415 100644 --- a/usr.sbin/snmpd/trap.c +++ b/usr.sbin/snmpd/trap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: trap.c,v 1.21 2013/10/19 14:18:39 blambert Exp $ */ +/* $OpenBSD: trap.c,v 1.22 2014/04/14 12:55:10 blambert Exp $ */ /* * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org> @@ -55,146 +55,162 @@ trap_init(void) } int -trap_imsg(struct imsgev *iev, pid_t pid) +trap_agentx(struct agentx_handle *h, struct agentx_pdu *pdu, int *idx, + char **varcpy, int *vcpylen) { - struct imsgbuf *ibuf; - struct imsg imsg; - int ret = -1, n, x = 0, state = 0; - int done = 0; - struct snmp_imsg *sm; - u_int32_t d; - u_int64_t l; - u_int8_t *c; - char ostr[SNMP_MAX_OID_LEN]; - struct ber_element *ber = NULL, *varbind = NULL, *a; - size_t len = 0; - struct ber_oid o; - - ibuf = &iev->ibuf; - while (!done) { - while (!done) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - goto done; - if (n == 0) - break; - - switch (imsg.hdr.type) { - case IMSG_SNMP_ELEMENT: - if (imsg.hdr.len < (IMSG_HEADER_SIZE + - sizeof(struct snmp_imsg))) - goto imsgdone; - - sm = (struct snmp_imsg *)imsg.data; - - if (!state++) { - /* First element must be the trap OID */ - if (sm->snmp_type != SNMP_NULL) - goto imsgdone; - ber_string2oid(sm->snmp_oid, &o); - break; - } + struct agentx_varbind_hdr vbhdr; + struct ber_oid o, oid; + struct ber_oid uptime = OID(MIB_sysUpTime); + struct ber_oid trapoid = OID(MIB_snmpTrapOID); + u_int32_t d; + u_int64_t l; + char *str; + int slen; + struct ber_element *a, *ber, *varbind; + int x = 0, state = 0; + int ret = AGENTX_ERR_NONE; + int seensysuptime, seentrapoid; + size_t len = 0; + pid_t pid = -1; + char *v = NULL; + + *varcpy = NULL; + ber = varbind = NULL; + seensysuptime = seentrapoid = 0; + + if (pdu->hdr->flags & AGENTX_NON_DEFAULT_CONTEXT) { + ret = AGENTX_ERR_UNSUPPORTED_CONTEXT; + goto done; + } + + if ((v = malloc(pdu->hdr->length)) == NULL || + snmp_agentx_copy_raw(pdu, v, pdu->hdr->length) == -1) { + ret = AGENTX_ERR_PROCESSING_ERROR; + goto done; + } - ber = ber_add_sequence(ber); - if (varbind == NULL) - varbind = ber; - a = ber_add_oidstring(ber, sm->snmp_oid); - - switch (sm->snmp_type) { - case SNMP_OBJECT: - if (sm->snmp_len > sizeof(ostr) - 1) - goto imsgdone; - bzero(&ostr, sizeof(ostr)); - bcopy(sm + 1, &ostr, sm->snmp_len); - a = ber_add_oidstring(a, ostr); - break; - case SNMP_BITSTRING: - if (sm->snmp_len < 1) - goto imsgdone; - /* FALLTHROUGH */ - case SNMP_OCTETSTRING: - case SNMP_IPADDR: - if (sm->snmp_len >= SNMPD_MAXSTRLEN) - goto imsgdone; - c = (u_int8_t *)(sm + 1); - if (sm->snmp_type == SNMP_BITSTRING) - a = ber_add_bitstring(a, c, - sm->snmp_len); - else - a = ber_add_nstring(a, c, - sm->snmp_len); - break; - case SNMP_NULL: - a = ber_add_null(a); - break; - case SNMP_INTEGER32: - case SNMP_COUNTER32: - case SNMP_GAUGE32: - case SNMP_TIMETICKS: - case SNMP_OPAQUE: - case SNMP_UINTEGER32: - if (sm->snmp_len != sizeof(d)) - goto imsgdone; - bcopy(sm + 1, &d, sm->snmp_len); - a = ber_add_integer(a, d); - break; - case SNMP_COUNTER64: - if (sm->snmp_len != sizeof(l)) - goto imsgdone; - bcopy(sm + 1, &l, sm->snmp_len); - a = ber_add_integer(a, l); - break; - default: - log_debug("trap_imsg: illegal type %d", - sm->snmp_type); - imsg_free(&imsg); - goto imsgdone; + while (pdu->datalen > sizeof(struct agentx_hdr)) { + x++; + + if (snmp_agentx_read_vbhdr(pdu, &vbhdr) == -1 || + snmp_agentx_read_oid(pdu, (struct snmp_oid *)&oid) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; + } + if (state < 2) { + if (state == 0 && ber_oid_cmp(&oid, &uptime) == 0) { + if (snmp_agentx_read_int(pdu, &d) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; } - switch (sm->snmp_type) { - case SNMP_INTEGER32: - case SNMP_BITSTRING: - case SNMP_OCTETSTRING: - case SNMP_NULL: - case SNMP_OBJECT: - /* universal types */ - break; - default: - /* application-specific types */ - ber_set_header(a, BER_CLASS_APPLICATION, - sm->snmp_type); - break; + state = 1; + continue; + } else if (ber_oid_cmp(&oid, &trapoid) == 0) { + if (snmp_agentx_read_oid(pdu, + (struct snmp_oid *)&o) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; } - x++; - break; - case IMSG_SNMP_END: - done = 1; - break; - default: - log_debug("trap_imsg: illegal imsg %d", - imsg.hdr.type); - goto imsgdone; + state = 2; + continue; + } else { + ret = AGENTX_ERR_PROCESSING_ERROR; + goto done; } - imsg_free(&imsg); } - if (done) + + ber = ber_add_sequence(ber); + if (varbind == NULL) + varbind = ber; + + a = ber_add_oid(ber, &oid); + + switch (vbhdr.type) { + case AGENTX_NO_SUCH_OBJECT: + case AGENTX_NO_SUCH_INSTANCE: + case AGENTX_END_OF_MIB_VIEW: + case AGENTX_NULL: + a = ber_add_null(a); break; - if ((n = imsg_read(ibuf)) == -1) - goto done; - if (n == 0) + + case AGENTX_IP_ADDRESS: + case AGENTX_OPAQUE: + case AGENTX_OCTET_STRING: + str = snmp_agentx_read_octetstr(pdu, &slen); + if (str == NULL) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; + } + a = ber_add_nstring(a, str, slen); + break; + + case AGENTX_OBJECT_IDENTIFIER: + if (snmp_agentx_read_oid(pdu, + (struct snmp_oid *)&oid) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; + } + a = ber_add_oid(a, &oid); + break; + + case AGENTX_INTEGER: + case AGENTX_COUNTER32: + case AGENTX_GAUGE32: + case AGENTX_TIME_TICKS: + if (snmp_agentx_read_int(pdu, &d) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; + } + a = ber_add_integer(a, d); + break; + + case AGENTX_COUNTER64: + if (snmp_agentx_read_int64(pdu, &l) == -1) { + ret = AGENTX_ERR_PARSE_ERROR; + goto done; + } + a = ber_add_integer(a, l); + break; + + default: + log_debug("unknown data type '%i'", vbhdr.type); + ret = AGENTX_ERR_PARSE_ERROR; goto done; + } + + /* AgentX types correspond to BER types */ + switch (vbhdr.type) { + case BER_TYPE_INTEGER: + case BER_TYPE_BITSTRING: + case BER_TYPE_OCTETSTRING: + case BER_TYPE_NULL: + case BER_TYPE_OBJECT: + /* universal types */ + break; + default: + /* application-specific types */ + ber_set_header(a, BER_CLASS_APPLICATION, vbhdr.type); + break; + } } if (varbind != NULL) len = ber_calc_len(varbind); - log_debug("trap_imsg: from pid %u len %d elements %d", + log_debug("trap_agentx: from pid %u len %d elements %d", pid, len, x); + trap_send(&o, varbind); - return (0); - imsgdone: - imsg_free(&imsg); + *varcpy = v; + *vcpylen = pdu->hdr->length; + + return (AGENTX_ERR_NONE); done: if (varbind != NULL) ber_free_elements(varbind); + if (v) + free(v); + *idx = x; return (ret); } @@ -210,7 +226,7 @@ trap_send(struct ber_oid *oid, struct ber_element *elm) u_int8_t *ptr; struct ber_oid uptime = OID(MIB_sysUpTime); struct ber_oid trapoid = OID(MIB_snmpTrapOID); - char ostr[SNMP_MAX_OID_LEN]; + char ostr[SNMP_MAX_OID_STRLEN]; struct oid oa, ob; if (TAILQ_EMPTY(&env->sc_trapreceivers)) @@ -270,7 +286,6 @@ trap_send(struct ber_oid *oid, struct ber_element *elm) #ifdef DEBUG smi_debug_elements(root); #endif - len = ber_write_elements(&ber, root); if (ber_get_writebuf(&ber, (void *)&ptr) > 0 && sendto(s, ptr, len, 0, (struct sockaddr *)&tr->ss, |