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