diff options
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/snmpctl/Makefile | 16 | ||||
-rw-r--r-- | usr.sbin/snmpctl/parser.c | 155 | ||||
-rw-r--r-- | usr.sbin/snmpctl/parser.h | 30 | ||||
-rw-r--r-- | usr.sbin/snmpctl/snmpctl.8 | 56 | ||||
-rw-r--r-- | usr.sbin/snmpctl/snmpctl.c | 191 | ||||
-rw-r--r-- | usr.sbin/snmpd/Makefile | 17 | ||||
-rw-r--r-- | usr.sbin/snmpd/README | 121 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.3 | 233 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.c | 1163 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.h | 123 | ||||
-rw-r--r-- | usr.sbin/snmpd/buffer.c | 244 | ||||
-rw-r--r-- | usr.sbin/snmpd/control.c | 270 | ||||
-rw-r--r-- | usr.sbin/snmpd/imsg.c | 233 | ||||
-rw-r--r-- | usr.sbin/snmpd/kroute.c | 1093 | ||||
-rw-r--r-- | usr.sbin/snmpd/log.c | 182 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.c | 836 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.h | 209 | ||||
-rw-r--r-- | usr.sbin/snmpd/mps.c | 447 | ||||
-rw-r--r-- | usr.sbin/snmpd/parse.y | 936 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmp.h | 86 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.8 | 93 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.c | 295 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf | 9 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf.5 | 83 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.h | 407 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpe.c | 803 |
26 files changed, 8331 insertions, 0 deletions
diff --git a/usr.sbin/snmpctl/Makefile b/usr.sbin/snmpctl/Makefile new file mode 100644 index 00000000000..dedb04dd6e7 --- /dev/null +++ b/usr.sbin/snmpctl/Makefile @@ -0,0 +1,16 @@ +# $Id: Makefile,v 1.1 2007/12/05 09:22:44 reyk Exp $ + +.PATH: ${.CURDIR}/../snmpd + +PROG= snmpctl +SRCS= buffer.c imsg.c log.c snmpctl.c parser.c + +MAN= snmpctl.8 + +CFLAGS+= -Wall -I${.CURDIR} -I${.CURDIR}/../snmpd +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded + +.include <bsd.prog.mk> diff --git a/usr.sbin/snmpctl/parser.c b/usr.sbin/snmpctl/parser.c new file mode 100644 index 00000000000..1f9c0140c6e --- /dev/null +++ b/usr.sbin/snmpctl/parser.c @@ -0,0 +1,155 @@ +/* $Id: parser.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/queue.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <event.h> + +#include "snmpd.h" +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_show[]; + +static const struct token t_main[] = { + {KEYWORD, "monitor", MONITOR, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static struct parse_result res; + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +const struct token * +match_token(const char *word, const struct token table[]) +{ + u_int i, match; + const struct token *t = NULL; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " <cr>\n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/snmpctl/parser.h b/usr.sbin/snmpctl/parser.h new file mode 100644 index 00000000000..41202685b9d --- /dev/null +++ b/usr.sbin/snmpctl/parser.h @@ -0,0 +1,30 @@ +/* $OpenBSD: parser.h,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. + */ + +enum actions { + NONE, + MONITOR +}; + +struct parse_result { + enum actions action; +}; + +struct parse_result *parse(int, char *[]); +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); diff --git a/usr.sbin/snmpctl/snmpctl.8 b/usr.sbin/snmpctl/snmpctl.8 new file mode 100644 index 00000000000..c83747ffe1c --- /dev/null +++ b/usr.sbin/snmpctl/snmpctl.8 @@ -0,0 +1,56 @@ +.\" $OpenBSD: snmpctl.8,v 1.1 2007/12/05 09:22:44 reyk Exp $ +.\" +.\" Copyright (c) 2006 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. +.\" +.Dd $Mdocdate: December 5 2007 $ +.Dt SNMPCTL 8 +.Os +.Sh NAME +.Nm snmpctl +.Nd control the SNMP daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr snmpd 8 +daemon. +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm something +Do something +.El +.Sh FILES +.Bl -tag -width "/var/run/snmpd.sockXX" -compact +.It /var/run/snmpd.sock +Unix-domain socket used for communication with +.Xr snmpd 8 . +.El +.Sh SEE ALSO +.Xr snmpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.3 . +.Sh AUTHORS +The +.Nm +program was written by +.An Reyk Floeter Aq reyk@vantronix.net . diff --git a/usr.sbin/snmpctl/snmpctl.c b/usr.sbin/snmpctl/snmpctl.c new file mode 100644 index 00000000000..c625645223f --- /dev/null +++ b/usr.sbin/snmpctl/snmpctl.c @@ -0,0 +1,191 @@ +/* $Id: snmpctl.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 Henning Brauer <henning@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/queue.h> +#include <sys/un.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "snmpd.h" +#include "parser.h" + +__dead void usage(void); + +struct imsgname { + int type; + char *name; + void (*func)(struct imsg *); +}; + +struct imsgname *monitor_lookup(u_int8_t); +void monitor_host_status(struct imsg *); +void monitor_id(struct imsg *); +int monitor(struct imsg *); + +struct imsgname imsgs[] = { + { 0, NULL, NULL } +}; +struct imsgname imsgunknown = { + -1, "<unknown>", NULL +}; + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname); + exit(1); +} + +/* dummy function so that hoststatectl does not need libevent */ +void +imsg_event_add(struct imsgbuf *i) +{ + /* nothing */ +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n; + + /* parse options */ + if ((res = parse(argc - 1, argv + 1)) == NULL) + exit(1); + + /* connect to snmpd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, SNMPD_SOCKET, sizeof(sun.sun_path)); + reconnect: + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + /* Keep retrying if running in monitor mode */ + if (res->action == MONITOR && + (errno == ENOENT || errno == ECONNREFUSED)) { + usleep(100); + goto reconnect; + } + err(1, "connect: %s", SNMPD_SOCKET); + } + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock, NULL); + done = 0; + + /* process user request */ + switch (res->action) { + case NONE: + usage(); + /* not reached */ + case MONITOR: + imsg_compose(ibuf, IMSG_CTL_NOTIFY, 0, 0, -1, NULL, 0); + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case MONITOR: + done = monitor(&imsg); + break; + case NONE: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +struct imsgname * +monitor_lookup(u_int8_t type) +{ + int i; + + for (i = 0; imsgs[i].name != NULL; i++) + if (imsgs[i].type == type) + return (&imsgs[i]); + return (&imsgunknown); +} + +int +monitor(struct imsg *imsg) +{ + time_t now; + int done = 0; + struct imsgname *imn; + + now = time(NULL); + + imn = monitor_lookup(imsg->hdr.type); + printf("%s: imsg type %u len %u peerid %u pid %d\n", imn->name, + imsg->hdr.type, imsg->hdr.len, imsg->hdr.peerid, imsg->hdr.pid); + printf("\ttimestamp: %u, %s", now, ctime(&now)); + if (imn->type == -1) + done = 1; + if (imn->func != NULL) + (*imn->func)(imsg); + + return (done); +} diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile new file mode 100644 index 00000000000..2ac483ded9b --- /dev/null +++ b/usr.sbin/snmpd/Makefile @@ -0,0 +1,17 @@ +# $OpenBSD: Makefile,v 1.1 2007/12/05 09:22:44 reyk Exp $ + +PROG= snmpd +MAN= snmpd.8 snmpd.conf.5 ber.3 +SRCS= parse.y ber.c log.c control.c buffer.c imsg.c snmpe.c \ + mps.c mib.c kroute.c snmpd.c + +LDADD= -levent +DPADD= ${LIBEVENT} +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare -Wbounded +CLEANFILES+= y.tab.h + +.include <bsd.prog.mk> diff --git a/usr.sbin/snmpd/README b/usr.sbin/snmpd/README new file mode 100644 index 00000000000..8025c02be2b --- /dev/null +++ b/usr.sbin/snmpd/README @@ -0,0 +1,121 @@ +# $OpenBSD: README,v 1.1 2007/12/05 09:22:44 reyk Exp $ + +SNMP daemon +----------- + +(Scary Network Management Protocol Daemon) + + This is the very experimental implementation of a simple SNMP daemon. + Even if SNMP is not our favorite protocol, it is a very important + key requirement in many corporate networks; sync to reality. And + we don't want to rely on horrid SNMP implementations in this case. + +DESIGN + + The main amazingly new thing is that this daemon will use the imsg + privsep approach using several different processes ("engines") to + separate privileges when handling the requests. + + It uses a new ASN.1/BER implementation to parse and create the SNMP + messages. It is a simple implementation which does what we need. + + I may change the layout a to look a bit more like RFC 2271. But + the proposed design in RFC 2271 is a bad joke; it uses too many + subsystems with the requirement to communicate with ASN.1/BER + messages. + + The following picture is based on section 3.1 of RFC 2271 describing + the proposed design of SNMPv3 entities and the current mapping in + snmpd: + + +-------------------------------------------------------------------+ + | SNMP entity (snmpd(8)) | + | snmpd(8) | + | +-------------------------------------------------------------+ | + | | SNMP engine (identified by snmpEngineID) | | + | | snmpe.c | | + | | +------------+ +------------+ +-----------+-+-----------+ | | + | | | | | | | |\ | | | + | | | Dispatcher | | Message | | Security | | Access | | | + | | | | | Processing | | Subsystem | | Control | | | + | | | IMSG, | | Subsystem | | | | Subsystem | | | + | | | event(3) | | mps.c | | TODO \| | | | + | | +------------+ +------------+ +-----------+-+-----------+ | | + | | || || | | | + | +--------||-----------||---|----------------------------------+ | + | || || | | + | || || | --> application | + | || || | (unpriv, functions calls) | + | || || | | + | || || ------> application | + | || || | (priv, IMSGs to snmpd.c) | + | || || | | + . ||--------------------> external application . + . || || | (IMSGs via /var/run/snmpd.sock) . + . || || | . + . +--------||-----------||---|----------------------------------+ . + | | Application(s) | | + | | snmpd.c, snmpctl(8), possible: ospfd, bgpd, relayd, ... | | + | | +-------------+ +--------------+ +--------------+ | | + | | | Command | | Notification | | Proxy | | | + | | | Generator | | Receiver | | Forwarder | | | + | | +-------------+ +--------------+ +--------------+ | | + | | | | + | | +-------------+ +--------------+ +--------------+ | | + | | | Command | | Notification | | Other | | | + | | | Responder | | Originator | | | | | + | | +-------------+ +--------------+ +--------------+ | | + | | | | + | +-------------------------------------------------------------+ | + | | + +-------------------------------------------------------------------+ + +FUZZ + + There is at least one SNMP fuzzer. Of course, it will not crash snmpd. + http://www.hackingciscoexposed.com/tools/snmp-fuzzer-0.1.1.tar.gz + +TODO + + snmpd: + - Implement the advanced SNMPv2 features like INFORM + - Implement USM (User-based Security Model) + - Implement some MIBs (SNMPv2/3 standard MIBs) + - MIB-2 (mostly done) + - IF-MIB (mostly done) + - IP-MIB + - TCP-MIB + - UDP-MIB + - BRIDGE-MIB + - OPENBSD-MIB (for sensors, etc.) + - ... + - Review review review + - Separate mps and snmpe? + - add trap receiver/logger + - manpages + + - There is a SNMP over SSH draft, interesting reading: + http://tools.ietf.org/html/draft-ietf-isms-secshell-09 + http://tools.ietf.org/wg/isms/ + + snmpctl: + - Add commands to query the SNMP mibtree via imsg + - Add commands to query/set remote SNMP daemons (instead snmpwalk etc.) + - Internal status, lalala, ... + - manpage + +NOT TO DO + + - Do not implement all the zillions of MIBs from ucdavis/net-snmp + - Do not provide a complex API for plugins, modules, and pimp-my-SNMP + - ... + +THANKS + + To .vantronix for supporting this work - it is time to get a + nice(r) SNMP implementation for OpenBSD. + + Thanks to claudio@ and mbalmer@ for starting the implementation + of ASN.1/BER encoding. + +reyk diff --git a/usr.sbin/snmpd/ber.3 b/usr.sbin/snmpd/ber.3 new file mode 100644 index 00000000000..3f1e66f15c3 --- /dev/null +++ b/usr.sbin/snmpd/ber.3 @@ -0,0 +1,233 @@ +.\" $OpenBSD: ber.3,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. +.\" +.Dd $Mdocdate: December 5 2007 $ +.Dt BER 3 +.Os +.Sh NAME +.Nm ber_get_element , +.Nm ber_set_header , +.Nm ber_link_elements , +.Nm ber_unlink_elements , +.Nm ber_replace_elements , +.Nm ber_add_sequence , +.Nm ber_add_set , +.Nm ber_add_integer , +.Nm ber_get_integer , +.Nm ber_add_boolean , +.Nm ber_get_boolean , +.Nm ber_add_string , +.Nm ber_get_string , +.Nm ber_add_nstring , +.Nm ber_add_bitstring , +.Nm ber_get_nstring , +.Nm ber_get_bitstring , +.Nm ber_add_null , +.Nm ber_get_null , +.Nm ber_add_eoc , +.Nm ber_get_eoc , +.Nm ber_add_oid , +.Nm ber_add_oidstring , +.Nm ber_get_oid , +.Nm ber_oid2ber , +.Nm ber_string2oid , +.Nm ber_printf_elements , +.Nm ber_scanf_elements , +.Nm ber_get_writebuf , +.Nm ber_write_elements , +.Nm ber_set_readbuf , +.Nm ber_read_elements , +.Nm ber_free_elements , +.Nm ber_calc_len , +.Nm ber_set_application , +.Nd Parse ASN.1 with Basic Encoding Rules. +.Sh SYNOPSIS +.Fd #include <ber.h> +.Ft "struct ber_element *" +.Fn "ber_get_element" "unsigned long encoding" +.Ft "void" +.Fn "ber_set_header" "struct ber_element *elm" "int class" "unsigned long type" +.Ft "void" +.Fn "ber_link_elements" "struct ber_element *prev" "struct ber_element *elm" +.Ft "struct ber_element *" +.Fn "ber_unlink_elements" "struct ber_element *prev" +.Ft "void" +.Fn "ber_replace_elements" "struct ber_element *prev" "struct ber_element *elm" +.Ft "struct ber_element *" +.Fn "ber_add_sequence" "struct ber_element *prev" +.Ft "struct ber_element *" +.Fn "ber_add_set" "struct ber_element *prev" +.Ft "struct ber_element *" +.Fn "ber_add_integer" "struct ber_element *prev" "long long val" +.Ft "int" +.Fn "ber_get_integer" "struct ber_element *root" "long long *val" +.Ft "struct ber_element *" +.Fn "ber_add_boolean" "struct ber_element *prev" "int bool" +.Ft "int" +.Fn "ber_get_boolean" "struct ber_element *root" "int *bool" +.Ft "struct ber_element *" +.Fn "ber_add_string" "struct ber_element *prev" "char *string" +.Ft "struct ber_element *" +.Fn "ber_add_nstring" "struct ber_element *prev" "char *string" "size_t size" +.Ft "int" +.Fn "ber_get_string" "struct ber_element *root" "char **charbuf" +.Ft "int" +.Fn "ber_get_nstring" "struct ber_element *root" "void **buf" "size_t *size" +.Ft "struct ber_element *" +.Fn "ber_add_bitstring" "struct ber_element *prev" "void *buf" "size_t size" +.Ft "int" +.Fn "ber_get_bitstring" "struct ber_element *root" "void **buf" "size_t *size" +.Ft "struct ber_element *" +.Fn "ber_add_null" "struct ber_element *prev" +.Ft "int" +.Fn "ber_get_null" "struct ber_element *root" +.Ft "struct ber_element *" +.Fn "ber_add_eoc" "struct ber_element *prev" +.Ft "int" +.Fn "ber_get_eoc" "struct ber_element *root" +.Ft "struct ber_element *" +.Fn "ber_add_oid" "struct ber_element *prev" "struct ber_oid *oid" +.Ft "struct ber_element *" +.Fn "ber_add_oidstring" "struct ber_element *prev" "const char *string" +.Ft "int" +.Fn "ber_get_oid" "struct ber_element *root" "struct ber_oid *oid" +.Ft "size_t" +.Fn "ber_oid2ber" "struct ber_oid *oid" "u_int8_t *buf" "size_t size" +.Ft "int" +.Fn "ber_string2oid" "const char *string" "struct ber_oid *oid" +.Ft "struct ber_element *" +.Fn "ber_printf_elements" "struct ber_element *prev" "char *format" "..." +.Ft "int" +.Fn "ber_scanf_elements" "struct ber_element *root" "char *format" "..." +.Ft "ssize_t" +.Fn "ber_get_writebuf" "struct ber *ber" "void **buf" +.Ft "int" +.Fn "ber_write_elements" "struct ber *ber" "struct ber_element *root" +.Ft "void" +.Fn "ber_set_readbuf" "struct ber *ber" "void *buf" "size_t len" +.Ft "struct" +.Fn "ber_element *ber_read_elements" "struct ber *ber" "struct ber_element *root" +.Ft "void" +.Fn "ber_free_elements" "struct ber_element *root" +.Ft "size_t" +.Fn "ber_calc_len" "struct ber_element *root" +.Ft "void" +.Fn "ber_set_application" "struct ber *ber" "unsigned long (*cb)(struct ber_element *)" +.Sh DESCRIPTION +The +.Nm ber +API provides a mechanism to read and write ASN.1 streams and buffers +using the +.Ic Basic Encoding Rules . +.Sh BER ELEMENTS +.Fn ber_get_element , +.Fn ber_set_header , +.Fn ber_link_elements , +.Fn ber_unlink_elements , +.Fn ber_replace_elements , +.Fn ber_calc_len +.Pp + +.Sh BER TYPES +.Fn ber_add_sequence , +.Fn ber_add_set , +.Fn ber_add_integer , +.Fn ber_get_integer , +.Fn ber_add_boolean , +.Fn ber_get_boolean , +.Fn ber_add_string , +.Fn ber_get_string , +.Fn ber_add_nstring , +.Fn ber_add_bitstring , +.Fn ber_get_nstring , +.Fn ber_get_bitstring , +.Fn ber_add_null , +.Fn ber_get_null , +.Fn ber_add_eoc , +.Fn ber_get_eoc + +.Sh OBJECT IDS +Object Identifiers are commonly used in ASN.1-bases protocols. +These functions provide an interface to parse OIDs. +For internal representation of OIDs, the following structure +.Ft struct ber_oid +is beeing used: +.Bd -literal +#define BER_MIN_OID_LEN 2 +#define BER_MAX_OID_LEN 128 + +struct ber_oid { + u_int32_t bo_id[BER_MAX_OID_LEN + 1]; + size_t bo_n; +}; +.Ed +.Pp +.Fn ber_add_oid , +.Fn ber_add_oidstring , +.Fn ber_get_oid , +.Fn ber_oid2ber , +.Fn ber_string2oid +.Pp + +.Sh FORMAT STRINGS +.Fn ber_printf_elements , +.Fn ber_scanf_elements +.Pp + +.Sh I/O OPERATIONS +.Fn ber_get_writebuf , +.Fn ber_write_elements , +.Fn ber_set_readbuf , +.Fn ber_read_elements , +.Fn ber_free_elements , +.Fn ber_set_application +.Pp + +.Sh RETURN VALUES +Upon successful completion +.Fn ber_get_integer , +.Fn ber_get_boolean , +.Fn ber_get_string , +.Fn ber_get_nstring , +.Fn ber_get_bitstring , +.Fn ber_get_null , +.Fn ber_get_eoc , +.Fn ber_get_oid , +.Fn ber_string2oid , +.Fn ber_scanf_elements , +and +.Fn ber_write_elements +return 0. +Otherwise, \-1 is returned and the global variable errno is +set to indicate the error. +.Sh SEE ALSO +.Xr socket 2 +.Sh HISTORY +The +.Nm ber +manpage first appeared in +.Ox 4.3 . +.Sh AUTHORS +The +.Nm ber +library was written by +.An Claudio Jeker Aq claudio@openbsd.org , +.An Marc Balmer Aq marc@openbsd.org +and +.An Reyk Floeter Aq reyk@openbsd.org . +.Sh BUGS +The code is buggy and incomplete. +This manpage is a stub. diff --git a/usr.sbin/snmpd/ber.c b/usr.sbin/snmpd/ber.c new file mode 100644 index 00000000000..09429075c84 --- /dev/null +++ b/usr.sbin/snmpd/ber.c @@ -0,0 +1,1163 @@ +/* $OpenBSD: ber.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@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/param.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <err.h> /* XXX for debug output */ +#include <stdio.h> /* XXX for debug output */ +#include <strings.h> +#include <unistd.h> +#include <stdarg.h> + +#include "ber.h" + + +#define BER_TYPE_CONSTRUCTED 0x20 /* otherwise primitive */ +#define BER_TYPE_SINGLE_MAX 30 +#define BER_TAG_MASK 0x1f +#define BER_TAG_MORE 0x80 /* more subsequent octets */ +#define BER_TAG_TYPE_MASK 0x7f +#define BER_CLASS_SHIFT 6 + +static int ber_dump_element(struct ber *ber, struct ber_element *root); +static void ber_dump_header(struct ber *ber, struct ber_element *root); +static void ber_putc(struct ber *ber, u_char c); +static void ber_write(struct ber *ber, void *buf, size_t len); +static ssize_t get_id(struct ber *b, unsigned long *tag, int *class, + int *cstruct); +static ssize_t get_len(struct ber *b, ssize_t *len); +static ssize_t ber_read_element(struct ber *ber, struct ber_element *elm); +static ssize_t ber_readbuf(struct ber *b, void *buf, size_t nbytes); +static ssize_t ber_getc(struct ber *b, u_char *c); +static ssize_t ber_read(struct ber *ber, void *buf, size_t len); + +#ifdef DEBUG +#define DPRINTF(x...) printf(x) +#else +#define DPRINTF(x...) do { } while (0) +#endif + +struct ber_element * +ber_get_element(unsigned long encoding) +{ + struct ber_element *elm; + + if ((elm = calloc(1, sizeof(*elm))) == NULL) + return NULL; + + elm->be_encoding = encoding; + ber_set_header(elm, BER_CLASS_UNIVERSAL, BER_TYPE_DEFAULT); + + return elm; +} + +void +ber_set_header(struct ber_element *elm, int class, unsigned long type) +{ + elm->be_class = class & BER_CLASS_MASK; + if (type == BER_TYPE_DEFAULT) + type = elm->be_encoding; + elm->be_type = type; +} + +void +ber_link_elements(struct ber_element *prev, struct ber_element *elm) +{ + if (prev != NULL) { + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub == NULL) + prev->be_sub = elm; + else + prev->be_next = elm; + } +} + +struct ber_element * +ber_unlink_elements(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub == NULL) { + elm = prev->be_sub; + prev->be_sub = NULL; + } else { + elm = prev->be_next; + prev->be_next = NULL; + } + + return (elm); +} + +void +ber_replace_elements(struct ber_element *prev, struct ber_element *new) +{ + struct ber_element *ber, *next; + + ber = ber_unlink_elements(prev); + next = ber_unlink_elements(ber); + ber_link_elements(new, next); + ber_link_elements(prev, new); + + /* cleanup old element */ + ber_free_elements(ber); +} + +struct ber_element * +ber_add_sequence(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SEQUENCE)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_set(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SET)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_integer(struct ber_element *prev, long long val) +{ + struct ber_element *elm; + u_int i, len = 0; + u_char cur, last = 0; + + if ((elm = ber_get_element(BER_TYPE_INTEGER)) == NULL) + return NULL; + + elm->be_numeric = val; + + for (i = 0; i < sizeof(long long); i++) { + cur = val & 0xff; + if (cur != 0 && cur != 0xff) + len = i; + if ((cur == 0 && last & 0x80) || + (cur == 0xff && (last & 0x80) == 0)) + len = i; + val >>= 8; + last = cur; + } + elm->be_len = len + 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_integer(struct ber_element *elm, long long *n) +{ + if (elm->be_encoding != BER_TYPE_INTEGER) + return -1; + + *n = elm->be_numeric; + return 0; +} + +struct ber_element * +ber_add_boolean(struct ber_element *prev, int bool) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_BOOLEAN)) == NULL) + return NULL; + + elm->be_numeric = bool ? 0xff : 0; + elm->be_len = 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_boolean(struct ber_element *elm, int *b) +{ + if (elm->be_encoding != BER_TYPE_BOOLEAN) + return -1; + + *b = !(elm->be_numeric == 0); + return 0; +} + +struct ber_element * +ber_add_string(struct ber_element *prev, char *string) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) + return NULL; + + elm->be_val = string; + elm->be_len = strlen(string); /* terminating '\0' not included */ + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_nstring(struct ber_element *prev, char *string, size_t len) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) + return NULL; + + elm->be_val = string; + elm->be_len = len; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_string(struct ber_element *elm, char **s) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *s = elm->be_val; + return 0; +} + +int +ber_get_nstring(struct ber_element *elm, void **p, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *p = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_bitstring(struct ber_element *prev, void *v, size_t len) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_BITSTRING)) == NULL) + return NULL; + + elm->be_val = v; + elm->be_len = len; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_bitstring(struct ber_element *elm, void **v, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_BITSTRING) + return -1; + + *v = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_null(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_NULL)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_null(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_NULL) + return -1; + + return 0; +} + +struct ber_element * +ber_add_eoc(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_EOC)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_eoc(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_EOC) + return -1; + + return 0; +} + +size_t +ber_oid2ber(struct ber_oid *o, u_int8_t *buf, size_t len) +{ + u_int32_t v; + u_int i, j = 0, k; + + if (o->bo_n < BER_MIN_OID_LEN || o->bo_n > BER_MAX_OID_LEN || + o->bo_id[0] > 2 || o->bo_id[1] > 40) + return (0); + + v = (o->bo_id[0] * 40) + o->bo_id[1]; + for (i = 2, j = 0; i <= o->bo_n; v = o->bo_id[i], i++) { + for (k = 28; k >= 7; k -= 7) { + if (v > (u_int)(1 << k)) { + if (len) + buf[j] = v >> k | BER_TAG_MORE; + j++; + } + } + if (len) + buf[j] = v & BER_TAG_TYPE_MASK; + j++; + } + + return (j); +} + +int +ber_string2oid(const char *oidstr, struct ber_oid *o) +{ + size_t len; + char *sp, *p, str[BUFSIZ]; + const char *errstr; + + if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str)) + return (-1); + len = strlen(str); + bzero(o, sizeof(*o)); + + /* Parse OID strings in the common forms n.n.n, n_n_n_n, or n-n-n */ + for (p = sp = str; p != NULL; sp = p) { + if ((p = strpbrk(p, "._-")) != NULL) + *p++ = '\0'; + o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr); + if (errstr || o->bo_n > BER_MAX_OID_LEN) + return (-1); + } + + return (0); +} + +struct ber_element * +ber_add_oid(struct ber_element *prev, struct ber_oid *o) +{ + struct ber_element *elm; + u_int8_t *buf; + size_t len; + + if ((elm = ber_get_element(BER_TYPE_OBJECT)) == NULL) + return (NULL); + + if ((len = ber_oid2ber(o, NULL, 0)) == 0) + goto fail; + + if ((buf = calloc(1, len)) == NULL) + goto fail; + + elm->be_val = buf; + elm->be_len = len; + elm->be_free = 1; + + if (ber_oid2ber(o, buf, len) != len) + goto fail; + + ber_link_elements(prev, elm); + + return (elm); + + fail: + ber_free_elements(elm); + return (NULL); +} + +struct ber_element * +ber_add_oidstring(struct ber_element *prev, const char *oidstr) +{ + struct ber_oid o; + + if (ber_string2oid(oidstr, &o) == -1) + return (NULL); + + return (ber_add_oid(prev, &o)); +} + +int +ber_get_oid(struct ber_element *elm, struct ber_oid *o) +{ + u_int8_t *buf; + size_t len, i = 0, j = 0; + + if (elm->be_encoding != BER_TYPE_OBJECT) + return (-1); + + buf = elm->be_val; + len = elm->be_len; + + if (!buf[i]) + return (-1); + + bzero(o, sizeof(*o)); + o->bo_id[j++] = buf[i] / 40; + o->bo_id[j++] = buf[i++] % 40; + for (; i < len && j < BER_MAX_OID_LEN; i++) { + o->bo_id[j] = (o->bo_id[j] << 7) + (buf[i] & ~0x80); + if (buf[i] & 0x80) + continue; + j++; + } + o->bo_n = j; + + return (0); +} + +struct ber_element * +ber_printf_elements(struct ber_element *ber, char *fmt, ...) +{ + va_list ap; + int d, class; + size_t len; + unsigned long type; + long long i; + char *s; + void *p; + struct ber_oid *o; + struct ber_element *sub = ber, *e; + + va_start(ap, fmt); + while (*fmt) { + switch (*fmt++) { + case 'B': + p = va_arg(ap, void *); + len = va_arg(ap, size_t); + ber = ber_add_bitstring(ber, p, len); + break; + case 'b': + d = va_arg(ap, int); + ber = ber_add_boolean(ber, d); + break; + case 'e': + e = va_arg(ap, struct ber_element *); + ber_link_elements(ber, e); + break; + case 'i': + i = va_arg(ap, long long); + ber = ber_add_integer(ber, i); + break; + case 'O': + o = va_arg(ap, struct ber_oid *); + ber = ber_add_oid(ber, o); + break; + case 'o': + s = va_arg(ap, char *); + ber = ber_add_oidstring(ber, s); + break; + case 's': + s = va_arg(ap, char *); + ber = ber_add_string(ber, s); + break; + case 't': + class = va_arg(ap, int); + type = va_arg(ap, unsigned long); + ber_set_header(ber, class, type); + break; + case 'x': + s = va_arg(ap, char *); + len = va_arg(ap, size_t); + ber = ber_add_nstring(ber, s, len); + break; + case '0': + ber = ber_add_null(ber); + break; + case '{': + ber = sub = ber_add_sequence(ber); + break; + case '(': + ber = sub = ber_add_set(ber); + break; + case '}': + case ')': + ber = sub; + break; + case '.': + ber = ber_add_eoc(ber); + break; + default: + break; + } + } + va_end(ap); + + return (ber); +} + +int +ber_scanf_elements(struct ber_element *ber, char *fmt, ...) +{ +#define _MAX_SEQ 128 + va_list ap; + int *d, level = -1; + unsigned long *t; + long long *i; + void **ptr; + size_t *len, ret = 0, n = strlen(fmt); + char **s; + struct ber_oid *o; + struct ber_element *parent[_MAX_SEQ], **e; + + bzero(parent, sizeof(struct ber_element *) * _MAX_SEQ); + + va_start(ap, fmt); + while (*fmt) { + switch (*fmt++) { + case 'B': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_bitstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case 'b': + d = va_arg(ap, int *); + if (ber_get_boolean(ber, d) == -1) + goto fail; + ret++; + break; + case 'e': + e = va_arg(ap, struct ber_element **); + *e = ber; + ret++; + continue; + case 'i': + i = va_arg(ap, long long *); + if (ber_get_integer(ber, i) == -1) + goto fail; + ret++; + break; + case 'o': + o = va_arg(ap, struct ber_oid *); + if (ber_get_oid(ber, o) == -1) + goto fail; + ret++; + break; + case 's': + s = va_arg(ap, char **); + if (ber_get_string(ber, s) == -1) + goto fail; + ret++; + break; + case 't': + d = va_arg(ap, int *); + t = va_arg(ap, unsigned long *); + *d = ber->be_class; + *t = ber->be_type; + ret++; + continue; + case 'x': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_nstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case '0': + if (ber->be_encoding != BER_TYPE_NULL) + goto fail; + ret++; + break; + case '.': + if (ber->be_encoding != BER_TYPE_EOC) + goto fail; + ret++; + break; + case '{': + case '(': + if (ber->be_sub == NULL || level >= _MAX_SEQ) + goto fail; + parent[++level] = ber; + ber = ber->be_sub; + ret++; + continue; + case '}': + case ')': + if (parent[level] == NULL) + goto fail; + ber = parent[level--]; + ret++; + continue; + default: + goto fail; + } + + if (ber->be_next == NULL) + continue; + ber = ber->be_next; + } + va_end(ap); + return (ret == n ? 0 : -1); + + fail: + va_end(ap); + return (-1); + +} + +/* + * write ber elements to the socket + * + * params: + * ber holds the socket + * root fully populated element tree + * + * returns: + * 0 on success + * + * returns: + * 0 on success + * -1 on failure and sets errno + */ +int +ber_write_elements(struct ber *ber, struct ber_element *root) +{ + size_t len; + + /* calculate length because only the definite form is required */ + len = ber_calc_len(root); + DPRINTF("write ber element of %zd bytes length\n", len); + + if (ber->br_wbuf != NULL && ber->br_wbuf + len > ber->br_wend) { + free(ber->br_wbuf); + ber->br_wbuf = NULL; + } + if (ber->br_wbuf == NULL) { + if ((ber->br_wbuf = malloc(len)) == NULL) + return -1; + ber->br_wend = ber->br_wbuf + len; + } + + /* reset write pointer */ + ber->br_wptr = ber->br_wbuf; + + if (ber_dump_element(ber, root) == -1) + return -1; + + /* XXX this should be moved to a different function */ + if (ber->fd != -1) + return write(ber->fd, ber->br_wbuf, len); + + return (len); +} + +/* + * read ber elements from the socket + * + * params: + * ber holds the socket and lot more + * root if NULL, build up an element tree from what we receive on + * the wire. If not null, use the specified encoding for the + * elements received. + * + * returns: + * !=NULL, elements read and store in the ber_element tree + * NULL, type mismatch or read error + */ +struct ber_element * +ber_read_elements(struct ber *ber, struct ber_element *root) +{ + if (root == NULL) { + if ((root = ber_get_element(0)) == NULL) + return NULL; + } + + DPRINTF("read ber elements, root %p\n", root); + + if (ber_read_element(ber, root) == -1) + return NULL; + + return root; +} + +void +ber_free_elements(struct ber_element *root) +{ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + ber_free_elements(root->be_sub); + if (root->be_next) + ber_free_elements(root->be_next); + if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING || + root->be_encoding == BER_TYPE_BITSTRING || + root->be_encoding == BER_TYPE_OBJECT)) + free(root->be_val); + free(root); +} + +/* + * internal functions + */ + +size_t +ber_calc_len(struct ber_element *root) +{ + unsigned long t; + size_t s; + size_t size = 2; /* minimum 1 byte head and 1 byte size */ + + /* calculate the real lenght of a sequence or set */ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + root->be_len = ber_calc_len(root->be_sub); + + /* fix header length for extended types */ + if (root->be_type > BER_TYPE_SINGLE_MAX) + for (t = root->be_type; t > 0; t >>= 7) + size++; + if (root->be_len >= BER_TAG_MORE) + for (s = root->be_len; s > 0; s >>= 8) + size++; + + /* calculate the length of the following elements */ + if (root->be_next) + size += ber_calc_len(root->be_next); + + /* This is an empty element, do not use a minimal size */ + if (root->be_type == BER_TYPE_EOC && root->be_len == 0) + return (0); + + return (root->be_len + size); +} + +static int +ber_dump_element(struct ber *ber, struct ber_element *root) +{ + unsigned long long l; + int i, pos; + uint8_t u; + + ber_dump_header(ber, root); + + switch (root->be_encoding) { + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + pos = (root->be_numeric > 0); + l = (unsigned long long)root->be_numeric; + for (i = root->be_len; i > 0; i--) { + u = (l >> ((i - 1) * 8)) & 0xff; + ber_putc(ber, u); + } + break; + case BER_TYPE_BITSTRING: + return -1; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + ber_write(ber, root->be_val, root->be_len); + break; + case BER_TYPE_NULL: /* no payload */ + case BER_TYPE_EOC: + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (root->be_sub && ber_dump_element(ber, root->be_sub) == -1) + return -1; + break; + } + + if (root->be_next == NULL) + return 0; + return ber_dump_element(ber, root->be_next); +} + +static void +ber_dump_header(struct ber *ber, struct ber_element *root) +{ + u_char id = 0, t, buf[8]; + unsigned long type; + size_t size; + + /* class universal, type encoding depending on type value */ + /* length encoding */ + if (root->be_type <= BER_TYPE_SINGLE_MAX) { + id = root->be_type | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + } else { + id = BER_TAG_MASK | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + + for (t = 0, type = root->be_type; type > 0; type >>= 7) + buf[t++] = type & ~BER_TAG_MORE; + + while (t-- > 0) { + if (t > 0) + buf[t] |= BER_TAG_MORE; + ber_putc(ber, buf[t]); + } + } + + if (root->be_len < BER_TAG_MORE) { + /* short form */ + ber_putc(ber, root->be_len); + } else { + for (t = 0, size = root->be_len; size > 0; size >>= 8) + buf[t++] = size & 0xff; + + ber_putc(ber, t | BER_TAG_MORE); + + while (t > 0) + ber_putc(ber, buf[--t]); + } +} + +static void +ber_putc(struct ber *ber, u_char c) +{ + if (ber->br_wptr + 1 <= ber->br_wend) + *ber->br_wptr = c; + ber->br_wptr++; +} + +static void +ber_write(struct ber *ber, void *buf, size_t len) +{ + if (ber->br_wptr + len <= ber->br_wend) + bcopy(buf, ber->br_wptr, len); + ber->br_wptr += len; +} + +/* + * extract a BER encoded tag. There are two types, a short and long form. + */ +static ssize_t +get_id(struct ber *b, unsigned long *tag, int *class, int *cstruct) +{ + u_char u; + size_t i = 0; + unsigned long t = 0; + + if (ber_getc(b, &u) == -1) + return -1; + + *class = (u >> BER_CLASS_SHIFT) & BER_CLASS_MASK; + *cstruct = (u & BER_TYPE_CONSTRUCTED) == BER_TYPE_CONSTRUCTED; + + if ((u & BER_TAG_MASK) != BER_TAG_MASK) { + *tag = u & BER_TAG_MASK; + return 1; + } + + do { + if (ber_getc(b, &u) == -1) + return -1; + t = (t << 7) | (u & ~BER_TAG_MORE); + i++; + } while (u & BER_TAG_MORE); + + if (i > sizeof(unsigned long)) { + errno = ERANGE; + return -1; + } + + *tag = t; + return i + 1; +} + +/* + * extract lenght of a ber object -- if length is unknown a length of -1 is + * returned. + */ +static ssize_t +get_len(struct ber *b, ssize_t *len) +{ + u_char u, n; + ssize_t s, r; + + if (ber_getc(b, &u) == -1) + return -1; + if ((u & BER_TAG_MORE) == 0) { + /* short form */ + *len = u; + return 1; + } + + n = u & ~BER_TAG_MORE; + if (sizeof(ssize_t) < n) { + errno = ERANGE; + return -1; + } + r = n + 1; + + for (s = 0; n > 0; n--) { + if (ber_getc(b, &u) == -1) + return -1; + s = (s << 8) | u; + } + + if (s < 0) { + /* overflow */ + errno = ERANGE; + return -1; + } + + if (s == 0) + s = -1; + + *len = s; + return r; +} + +static ssize_t +ber_read_element(struct ber *ber, struct ber_element *elm) +{ + long long val = 0; + struct ber_element *next; + unsigned long type; + int i, class, cstruct; + ssize_t len, r, totlen = 0; + u_char c; + + if ((r = get_id(ber, &type, &class, &cstruct)) == -1) + return -1; + DPRINTF("ber read got class %d type %lu, %s\n", + class, type, cstruct ? "constructive" : "primitive"); + totlen += r; + if ((r = get_len(ber, &len)) == -1) + return -1; + DPRINTF("ber read element size %zd\n", len); + totlen += r + len; + + elm->be_type = type; + elm->be_len = len; + elm->be_class = class; + + if (elm->be_encoding == 0) { + /* try to figure out the encoding via class, type and cstruct */ + if (cstruct) + elm->be_encoding = BER_TYPE_SEQUENCE; + else if (class == BER_CLASS_UNIVERSAL) + elm->be_encoding = type; + else if (ber->br_application != NULL) { + /* + * Ask the application to map the encoding to a + * universal type. For example, a SMI IpAddress + * type is defined as 4 byte OCTET STRING. + */ + elm->be_encoding = (*ber->br_application)(elm); + } else + /* last resort option */ + elm->be_encoding = BER_TYPE_NULL; + } + + switch (elm->be_encoding) { + case BER_TYPE_EOC: /* End-Of-Content */ + break; + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + if (len > (u_int)sizeof(long long)) + return -1; + for (i = 0; i < len; i++) { + if (ber_getc(ber, &c) != 1) + return -1; + val <<= 8; + val |= c; + } + + /* sign extend if MSB is set */ + if (val >> ((i - 1) * 8) & 0x80) + val |= ULLONG_MAX << (i * 8); + elm->be_numeric = val; + break; + case BER_TYPE_BITSTRING: + elm->be_val = malloc(len); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + break; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + elm->be_val = malloc(len + 1); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + ((u_char *)elm->be_val)[len] = '\0'; + break; + case BER_TYPE_NULL: /* no payload */ + if (len != 0) + return -1; + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (elm->be_sub == NULL) { + if ((elm->be_sub = ber_get_element(0)) == NULL) + return -1; + } + next = elm->be_sub; + do { + r = ber_read_element(ber, next); + if (r == -1) + return -1; + if (next->be_next == NULL) { + if ((next->be_next = ber_get_element(0)) == + NULL) + return -1; + } + next = next->be_next; + len -= r; + } while (len > 0); + break; + } + return totlen; +} + +static ssize_t +ber_readbuf(struct ber *b, void *buf, size_t nbytes) +{ + size_t sz; + size_t len; + + if (b->br_rbuf == NULL) + return -1; + + sz = b->br_rend - b->br_rptr; + len = MIN(nbytes, sz); + + bcopy(b->br_rptr, buf, len); + b->br_rptr += len; + + return (len); +} + +void +ber_set_readbuf(struct ber *b, void *buf, size_t len) +{ + b->br_rbuf = b->br_rptr = buf; + b->br_rend = (u_int8_t *)buf + len; +} + +ssize_t +ber_get_writebuf(struct ber *b, void **buf) +{ + if (b->br_wbuf == NULL) + return -1; + *buf = b->br_wbuf; + return (b->br_wend - b->br_wbuf); +} + +void +ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *)) +{ + b->br_application = cb; +} + +static ssize_t +ber_getc(struct ber *b, u_char *c) +{ + ssize_t r; + /* + * XXX calling read here is wrong in many ways. The most obivous one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + if (b->fd == -1) + r = ber_readbuf(b, c, 1); + else + r = read(b->fd, c, 1); + if (r == -1) + warn("ber_getc"); + return r; +} + +static ssize_t +ber_read(struct ber *ber, void *buf, unsigned long len) +{ + u_char *b = buf; + ssize_t r, remain = len; + + /* + * XXX calling read here is wrong in many ways. The most obivous one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + + while (remain > 0) { + if (ber->fd == -1) + r = ber_readbuf(ber, b, remain); + else + r = read(ber->fd, b, remain); + if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (r == 0) + return (b - (u_char *)buf); + b += r; + remain -= r; + } + return (b - (u_char *)buf); +} diff --git a/usr.sbin/snmpd/ber.h b/usr.sbin/snmpd/ber.h new file mode 100644 index 00000000000..726515720e5 --- /dev/null +++ b/usr.sbin/snmpd/ber.h @@ -0,0 +1,123 @@ +/* $OpenBSD: ber.h,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2006, 2007 Claudio Jeker <claudio@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. + */ + +struct ber_element { + struct ber_element *be_next; + unsigned long be_type; + unsigned long be_encoding; + size_t be_len; + int be_free; + u_int8_t be_class; + union { + struct ber_element *bv_sub; + void *bv_val; + long long bv_numeric; + } be_union; +#define be_sub be_union.bv_sub +#define be_val be_union.bv_val +#define be_numeric be_union.bv_numeric +}; + +struct ber { + int fd; + u_char *br_wbuf; + u_char *br_wptr; + u_char *br_wend; + u_char *br_rbuf; + u_char *br_rptr; + u_char *br_rend; + + unsigned long (*br_application)(struct ber_element *); +}; + +/* well-known ber_element types */ +#define BER_TYPE_DEFAULT ((unsigned long)-1) +#define BER_TYPE_EOC 0 +#define BER_TYPE_BOOLEAN 1 +#define BER_TYPE_INTEGER 2 +#define BER_TYPE_BITSTRING 3 +#define BER_TYPE_OCTETSTRING 4 +#define BER_TYPE_NULL 5 +#define BER_TYPE_OBJECT 6 +#define BER_TYPE_ENUMERATED 10 +#define BER_TYPE_SEQUENCE 16 +#define BER_TYPE_SET 17 + +/* ber classes */ +#define BER_CLASS_UNIVERSAL 0x0 +#define BER_CLASS_UNIV BER_CLASS_UNIVERSAL +#define BER_CLASS_APPLICATION 0x1 +#define BER_CLASS_APP BER_CLASS_APPLICATION +#define BER_CLASS_CONTEXT 0x2 +#define BER_CLASS_PRIVATE 0x3 +#define BER_CLASS_MASK 0x3 + +/* common definitions */ +#define BER_MIN_OID_LEN 2 /* OBJECT */ +#define BER_MAX_OID_LEN 128 /* OBJECT */ + +struct ber_oid { + u_int32_t bo_id[BER_MAX_OID_LEN + 1]; + size_t bo_n; +}; + +__BEGIN_DECLS +struct ber_element *ber_get_element(unsigned long); +void ber_set_header(struct ber_element *, int, + unsigned long); +void ber_link_elements(struct ber_element *, + struct ber_element *); +struct ber_element *ber_unlink_elements(struct ber_element *); +void ber_replace_elements(struct ber_element *, + struct ber_element *); +struct ber_element *ber_add_sequence(struct ber_element *); +struct ber_element *ber_add_set(struct ber_element *); +struct ber_element *ber_add_integer(struct ber_element *, long long); +int ber_get_integer(struct ber_element *, long long *); +struct ber_element *ber_add_boolean(struct ber_element *, int); +int ber_get_boolean(struct ber_element *, int *); +struct ber_element *ber_add_string(struct ber_element *, char *); +struct ber_element *ber_add_nstring(struct ber_element *, char *, size_t); +int ber_get_string(struct ber_element *, char **); +int ber_get_nstring(struct ber_element *, void **, + size_t *); +struct ber_element *ber_add_bitstring(struct ber_element *, void *, + size_t); +int ber_get_bitstring(struct ber_element *, void **, + size_t *); +struct ber_element *ber_add_null(struct ber_element *); +int ber_get_null(struct ber_element *); +struct ber_element *ber_add_eoc(struct ber_element *); +int ber_get_eoc(struct ber_element *); +struct ber_element *ber_add_oid(struct ber_element *, struct ber_oid *); +struct ber_element *ber_add_oidstring(struct ber_element *, const char *); +int ber_get_oid(struct ber_element *, struct ber_oid *); +size_t ber_oid2ber(struct ber_oid *, u_int8_t *, size_t); +int ber_string2oid(const char *, struct ber_oid *); +struct ber_element *ber_printf_elements(struct ber_element *, char *, ...); +int ber_scanf_elements(struct ber_element *, char *, ...); +ssize_t ber_get_writebuf(struct ber *, void **); +int ber_write_elements(struct ber *, struct ber_element *); +void ber_set_readbuf(struct ber *, void *, size_t); +struct ber_element *ber_read_elements(struct ber *, struct ber_element *); +void ber_free_elements(struct ber_element *); +size_t ber_calc_len(struct ber_element *); +void ber_set_application(struct ber *, + unsigned long (*)(struct ber_element *)); +__END_DECLS diff --git a/usr.sbin/snmpd/buffer.c b/usr.sbin/snmpd/buffer.c new file mode 100644 index 00000000000..c0fc184df46 --- /dev/null +++ b/usr.sbin/snmpd/buffer.c @@ -0,0 +1,244 @@ +/* $OpenBSD: buffer.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <sys/tree.h> + +#include <net/if.h> + +#include <errno.h> +#include <event.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "snmpd.h" + +int buf_realloc(struct buf *, size_t); +void buf_enqueue(struct msgbuf *, struct buf *); +void buf_dequeue(struct msgbuf *, struct buf *); + +struct buf * +buf_open(size_t len) +{ + struct buf *buf; + + if ((buf = calloc(1, sizeof(struct buf))) == NULL) + return (NULL); + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return (NULL); + } + buf->size = buf->max = len; + buf->fd = -1; + + return (buf); +} + +struct buf * +buf_dynamic(size_t len, size_t max) +{ + struct buf *buf; + + if (max < len) + return (NULL); + + if ((buf = buf_open(len)) == NULL) + return (NULL); + + if (max > 0) + buf->max = max; + + return (buf); +} + +int +buf_realloc(struct buf *buf, size_t len) +{ + u_char *b; + + /* on static buffers max is eq size and so the following fails */ + if (buf->wpos + len > buf->max) { + errno = ENOMEM; + return (-1); + } + + b = realloc(buf->buf, buf->wpos + len); + if (b == NULL) + return (-1); + buf->buf = b; + buf->size = buf->wpos + len; + + return (0); +} + +int +buf_add(struct buf *buf, void *data, size_t len) +{ + if (buf->wpos + len > buf->size) + if (buf_realloc(buf, len) == -1) + return (-1); + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return (0); +} + +void * +buf_reserve(struct buf *buf, size_t len) +{ + void *b; + + if (buf->wpos + len > buf->size) + if (buf_realloc(buf, len) == -1) + return (NULL); + + b = buf->buf + buf->wpos; + buf->wpos += len; + return (b); +} + +int +buf_close(struct msgbuf *msgbuf, struct buf *buf) +{ + buf_enqueue(msgbuf, buf); + return (1); +} + +void +buf_free(struct buf *buf) +{ + free(buf->buf); + free(buf); +} + +void +msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void +msgbuf_clear(struct msgbuf *msgbuf) +{ + struct buf *buf; + + while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) + buf_dequeue(msgbuf, buf); +} + +int +msgbuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct buf *buf, *next; + int i = 0; + ssize_t n; + struct msghdr msg; + struct cmsghdr *cmsg; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + bzero(&iov, sizeof(iov)); + bzero(&msg, sizeof(msg)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->size - buf->rpos; + i++; + if (buf->fd != -1) + break; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)cmsgbuf; + msg.msg_controllen = CMSG_LEN(sizeof(int)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = buf->fd; + } + + if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { + if (errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) /* try later */ + return (0); + else + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (-2); + } + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)cmsgbuf; + msg.msg_controllen = CMSG_LEN(sizeof(int)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = buf->fd; + } + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->size) { + n -= buf->size - buf->rpos; + buf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } + + return (0); +} + +void +buf_enqueue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); + msgbuf->queued++; +} + +void +buf_dequeue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_REMOVE(&msgbuf->bufs, buf, entry); + + if (buf->fd != -1) + close(buf->fd); + + msgbuf->queued--; + buf_free(buf); +} diff --git a/usr.sbin/snmpd/control.c b/usr.sbin/snmpd/control.c new file mode 100644 index 00000000000..24023e798c5 --- /dev/null +++ b/usr.sbin/snmpd/control.c @@ -0,0 +1,270 @@ +/* $OpenBSD: control.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/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 <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> + +#include "snmpd.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_connlist ctl_conns; + +struct ctl_conn *control_connbyfd(int); +void control_close(int); + +struct imsgbuf *ibuf_parent = NULL; + +int +control_init(void) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("control_init: socket"); + return (-1); + } + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, SNMPD_SOCKET, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + log_warn("control_init: %s name too long", SNMPD_SOCKET); + close(fd); + return (-1); + } + + if (unlink(SNMPD_SOCKET) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", SNMPD_SOCKET); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("control_init: bind: %s", SNMPD_SOCKET); + close(fd); + (void)umask(old_umask); + return (-1); + } + (void)umask(old_umask); + + if (chmod(SNMPD_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(SNMPD_SOCKET); + return (-1); + } + + session_socket_blockmode(fd, BM_NONBLOCK); + control_state.fd = fd; + + return (0); +} + +int +control_listen(struct snmpd *env, struct imsgbuf *parent) +{ + ibuf_parent = parent; + + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("control_listen: listen"); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST, + control_accept, env); + event_add(&control_state.ev, NULL); + + return (0); +} + +void +control_cleanup(void) +{ + (void)unlink(SNMPD_SOCKET); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + struct snmpd *env = arg; + + len = sizeof(sun); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sun, &len)) == -1) { + if (errno != EWOULDBLOCK && errno != EINTR) + log_warn("control_accept"); + return; + } + + session_socket_blockmode(connfd, BM_NONBLOCK); + + if ((c = malloc(sizeof(struct ctl_conn))) == NULL) { + log_warn("control_accept"); + return; + } + + imsg_init(&c->ibuf, connfd, control_dispatch_imsg); + c->ibuf.events = EV_READ; + event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events, + c->ibuf.handler, env); + event_add(&c->ibuf.ev, NULL); + + 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->ibuf.fd != fd; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) + log_warn("control_close: fd %d: not found", fd); + + msgbuf_clear(&c->ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->ibuf.ev); + close(c->ibuf.fd); + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *arg) +{ + struct ctl_conn *c; + struct imsg imsg; + int n; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_dispatch_imsg: fd %d: not found", fd); + return; + } + + switch (event) { + case EV_READ: + if ((n = imsg_read(&c->ibuf)) == -1 || n == 0) { + control_close(fd); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&c->ibuf.w) < 0) { + control_close(fd); + return; + } + imsg_event_add(&c->ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(&c->ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_NOTIFY: + if (c->flags & CTL_CONN_NOTIFY) { + log_debug("control_dispatch_imsg: " + "client requested notify more than once"); + imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, + NULL, 0); + break; + } + c->flags |= CTL_CONN_NOTIFY; + break; + default: + log_debug("control_dispatch_imsg: " + "error handling imsg %d", imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->ibuf); +} + +void +control_imsg_forward(struct imsg *imsg) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) + if (c->flags & CTL_CONN_NOTIFY) + imsg_compose(&c->ibuf, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); +} + +void +session_socket_blockmode(int fd, enum blockmodes bm) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fcntl F_GETFL"); + + if (bm == BM_NONBLOCK) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} diff --git a/usr.sbin/snmpd/imsg.c b/usr.sbin/snmpd/imsg.c new file mode 100644 index 00000000000..83bb7262568 --- /dev/null +++ b/usr.sbin/snmpd/imsg.c @@ -0,0 +1,233 @@ +/* $OpenBSD: imsg.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/queue.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <sys/tree.h> + +#include <net/if.h> + +#include <errno.h> +#include <event.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "snmpd.h" + +void +imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *)) +{ + msgbuf_init(&ibuf->w); + bzero(&ibuf->r, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); + ibuf->handler = handler; + TAILQ_INIT(&ibuf->fds); +} + +ssize_t +imsg_read(struct imsgbuf *ibuf) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + char cmsgbuf[CMSG_SPACE(sizeof(int) * 16)]; + struct iovec iov; + ssize_t n; + int fd; + struct imsg_fd *ifd; + + bzero(&msg, sizeof(msg)); + + iov.iov_base = ibuf->r.buf + ibuf->r.wpos; + iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { + if (errno != EINTR && errno != EAGAIN) { + log_warn("imsg_read: pipe read error"); + return (-1); + } + return (-2); + } + + ibuf->r.wpos += n; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + fd = (*(int *)CMSG_DATA(cmsg)); + if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) + fatal("imsg_read calloc"); + ifd->fd = fd; + TAILQ_INSERT_TAIL(&ibuf->fds, ifd, entry); + } else + log_warn("imsg_read: got unexpected ctl data level %d " + "type %d", cmsg->cmsg_level, cmsg->cmsg_type); + } + + return (n); +} + +ssize_t +imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +{ + size_t av, left, datalen; + + av = ibuf->r.wpos; + + if (IMSG_HEADER_SIZE > av) + return (0); + + memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); + if (imsg->hdr.len < IMSG_HEADER_SIZE || + imsg->hdr.len > MAX_IMSGSIZE) { + log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u", + imsg->hdr.len, imsg->hdr.type); + return (-1); + } + if (imsg->hdr.len > av) + return (0); + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; + if ((imsg->data = malloc(datalen)) == NULL) { + log_warn("imsg_get"); + return (-1); + } + memcpy(imsg->data, ibuf->r.rptr, datalen); + + if (imsg->hdr.len < av) { + left = av - imsg->hdr.len; + memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); + ibuf->r.wpos = left; + } else + ibuf->r.wpos = 0; + + return (datalen + IMSG_HEADER_SIZE); +} + +int +imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, + pid_t pid, int fd, void *data, u_int16_t datalen) +{ + struct buf *wbuf; + int n; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + if (imsg_add(wbuf, data, datalen) == -1) + return (-1); + + wbuf->fd = fd; + + if ((n = imsg_close(ibuf, wbuf)) < 0) + return (-1); + + return (n); +} + +/* ARGSUSED */ +struct buf * +imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, + pid_t pid, u_int16_t datalen) +{ + struct buf *wbuf; + struct imsg_hdr hdr; + + datalen += IMSG_HEADER_SIZE; + if (datalen > MAX_IMSGSIZE) { + log_warnx("imsg_create: len %u > MAX_IMSGSIZE; " + "type %u peerid %lu", datalen + IMSG_HEADER_SIZE, + type, peerid); + return (NULL); + } + + hdr.type = type; + hdr.peerid = peerid; + if ((hdr.pid = pid) == 0) + hdr.pid = ibuf->pid; + if ((wbuf = buf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { + log_warn("imsg_create: buf_open"); + return (NULL); + } + if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) + return (NULL); + + return (wbuf); +} + +int +imsg_add(struct buf *msg, void *data, u_int16_t datalen) +{ + if (datalen) + if (buf_add(msg, data, datalen) == -1) { + log_warnx("imsg_add: buf_add error"); + buf_free(msg); + return (-1); + } + return (datalen); +} + +int +imsg_close(struct imsgbuf *ibuf, struct buf *msg) +{ + int n; + struct imsg_hdr *hdr; + + hdr = (struct imsg_hdr *)msg->buf; + hdr->len = (u_int16_t)msg->wpos; + if ((n = buf_close(&ibuf->w, msg)) < 0) { + log_warnx("imsg_close: buf_close error"); + buf_free(msg); + return (-1); + } + imsg_event_add(ibuf); + + return (n); +} + +void +imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} + +int +imsg_get_fd(struct imsgbuf *ibuf) +{ + int fd; + struct imsg_fd *ifd; + + if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) + return (-1); + + fd = ifd->fd; + TAILQ_REMOVE(&ibuf->fds, ifd, entry); + free(ifd); + + return (fd); +} diff --git a/usr.sbin/snmpd/kroute.c b/usr.sbin/snmpd/kroute.c new file mode 100644 index 00000000000..40d9709398a --- /dev/null +++ b/usr.sbin/snmpd/kroute.c @@ -0,0 +1,1093 @@ +/* $OpenBSD: kroute.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/tree.h> +#include <sys/uio.h> +#include <sys/ioctl.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <event.h> + +#include "snmpd.h" + +struct { + u_int32_t ks_rtseq; + pid_t ks_pid; + int ks_fd; + int ks_ifd; + struct event ks_ev; + u_short ks_nkif; + u_long ks_iflastchange; +} kr_state; + +struct kroute_node { + RB_ENTRY(kroute_node) entry; + struct kroute r; + struct kroute_node *next; +}; + +struct kif_node { + RB_ENTRY(kif_node) entry; + TAILQ_HEAD(, kif_addr) addrs; + struct kif k; +}; + +int kroute_compare(struct kroute_node *, struct kroute_node *); +struct kroute_node *kroute_find(in_addr_t, u_int8_t); +struct kroute_node *kroute_match(in_addr_t); +struct kroute_node *kroute_matchgw(struct kroute_node *, struct in_addr); +int kroute_insert(struct kroute_node *); +int kroute_remove(struct kroute_node *); +void kroute_clear(void); + +int kif_init(void); +int kif_compare(struct kif_node *, struct kif_node *); +struct kif_node *kif_find(u_short); +struct kif_node *kif_insert(u_short); +int kif_remove(struct kif_node *); +void kif_clear(void); +struct kif *kif_update(u_short, int, struct if_data *, + struct sockaddr_dl *); +int kif_validate(u_short); + +u_int16_t rtlabel_name2id(const char *); +const char *rtlabel_id2name(u_int16_t); +void rtlabel_unref(u_int16_t); + +int protect_lo(void); +u_int8_t prefixlen_classful(in_addr_t); +u_int8_t mask2prefixlen(in_addr_t); +in_addr_t prefixlen2mask(u_int8_t); +void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); +void if_change(u_short, int, struct if_data *); +void if_newaddr(u_short, struct sockaddr_in *, struct sockaddr_in *, + struct sockaddr_in *); +void if_announce(void *); + +int send_rtmsg(int, int, struct kroute *); +void dispatch_rtmsg(int, short, void *); +int fetchifs(u_short); +int fetchtable(void); + +RB_HEAD(kroute_tree, kroute_node) krt; +RB_PROTOTYPE(kroute_tree, kroute_node, entry, kroute_compare) +RB_GENERATE(kroute_tree, kroute_node, entry, kroute_compare) + +RB_HEAD(kif_tree, kif_node) kit; +RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare) +RB_GENERATE(kif_tree, kif_node, entry, kif_compare) + +int +kif_init(void) +{ + RB_INIT(&kit); + + if (fetchifs(0) == -1) + return (-1); + + return (0); +} + +int +kr_init(void) +{ + int opt = 0, rcvbuf, default_rcvbuf; + socklen_t optlen; + + if (kif_init() == -1) + return (-1); + + if ((kr_state.ks_ifd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_warn("kr_init: ioctl socket"); + return (-1); + } + + if ((kr_state.ks_fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) { + log_warn("kr_init: route socket"); + return (-1); + } + + /* not interested in my own messages */ + if (setsockopt(kr_state.ks_fd, SOL_SOCKET, SO_USELOOPBACK, + &opt, sizeof(opt)) == -1) + log_warn("kr_init: setsockopt"); /* not fatal */ + + /* grow receive buffer, don't wanna miss messages */ + optlen = sizeof(default_rcvbuf); + if (getsockopt(kr_state.ks_fd, SOL_SOCKET, SO_RCVBUF, + &default_rcvbuf, &optlen) == -1) + log_warn("kr_init getsockopt SOL_SOCKET SO_RCVBUF"); + else + for (rcvbuf = MAX_RTSOCK_BUF; + rcvbuf > default_rcvbuf && + setsockopt(kr_state.ks_fd, SOL_SOCKET, SO_RCVBUF, + &rcvbuf, sizeof(rcvbuf)) == -1 && errno == ENOBUFS; + rcvbuf /= 2) + ; /* nothing */ + + kr_state.ks_pid = getpid(); + kr_state.ks_rtseq = 1; + + RB_INIT(&krt); + + if (fetchtable() == -1) + return (-1); + + if (protect_lo() == -1) + return (-1); + + event_set(&kr_state.ks_ev, kr_state.ks_fd, EV_READ | EV_PERSIST, + dispatch_rtmsg, NULL); + event_add(&kr_state.ks_ev, NULL); + + return (0); +} + +void +kr_shutdown(void) +{ + kroute_clear(); + kif_clear(); +} + +u_int +kr_ifnumber(void) +{ + return (kr_state.ks_nkif); +} + +u_long +kr_iflastchange(void) +{ + return (kr_state.ks_iflastchange); +} + +int +kr_updateif(u_int if_index) +{ + struct kif_node *kn; + + if ((kn = kif_find(if_index)) != NULL) + kif_remove(kn); + + /* Do not update the interface address list */ + return (fetchifs(if_index)); +} + +/* rb-tree compare */ +int +kroute_compare(struct kroute_node *a, struct kroute_node *b) +{ + if (ntohl(a->r.prefix.s_addr) < ntohl(b->r.prefix.s_addr)) + return (-1); + if (ntohl(a->r.prefix.s_addr) > ntohl(b->r.prefix.s_addr)) + return (1); + if (a->r.prefixlen < b->r.prefixlen) + return (-1); + if (a->r.prefixlen > b->r.prefixlen) + return (1); + return (0); +} + +int +kif_compare(struct kif_node *a, struct kif_node *b) +{ + return (a->k.if_index - b->k.if_index); +} + +/* tree management */ +struct kroute_node * +kroute_find(in_addr_t prefix, u_int8_t prefixlen) +{ + struct kroute_node s; + + s.r.prefix.s_addr = prefix; + s.r.prefixlen = prefixlen; + + return (RB_FIND(kroute_tree, &krt, &s)); +} + +struct kroute_node * +kroute_matchgw(struct kroute_node *kr, struct in_addr nh) +{ + in_addr_t nexthop; + + nexthop = nh.s_addr; + + while (kr) { + if (kr->r.nexthop.s_addr == nexthop) + return (kr); + kr = kr->next; + } + + return (NULL); +} + + +int +kroute_insert(struct kroute_node *kr) +{ + struct kroute_node *krm; + + if ((krm = RB_INSERT(kroute_tree, &krt, kr)) != NULL) { + /* + * Multipath route, add at end of list and clone the + * ospfd inserted flag. + */ + kr->r.flags |= krm->r.flags & F_OSPFD_INSERTED; + while (krm->next != NULL) + krm = krm->next; + krm->next = kr; + kr->next = NULL; /* to be sure */ + } else + krm = kr; + + if (!(kr->r.flags & F_KERNEL)) { + /* don't validate or redistribute ospf route */ + kr->r.flags &= ~F_DOWN; + return (0); + } + + if (kif_validate(kr->r.if_index)) + kr->r.flags &= ~F_DOWN; + else + kr->r.flags |= F_DOWN; + + return (0); +} + +int +kroute_remove(struct kroute_node *kr) +{ + struct kroute_node *krm; + + if ((krm = RB_FIND(kroute_tree, &krt, kr)) == NULL) { + log_warnx("kroute_remove failed to find %s/%u", + inet_ntoa(kr->r.prefix), kr->r.prefixlen); + return (-1); + } + + if (krm == kr) { + /* head element */ + if (RB_REMOVE(kroute_tree, &krt, kr) == NULL) { + log_warnx("kroute_remove failed for %s/%u", + inet_ntoa(kr->r.prefix), kr->r.prefixlen); + return (-1); + } + if (kr->next != NULL) { + if (RB_INSERT(kroute_tree, &krt, kr->next) != NULL) { + log_warnx("kroute_remove failed to add %s/%u", + inet_ntoa(kr->r.prefix), kr->r.prefixlen); + return (-1); + } + } + } else { + /* somewhere in the list */ + while (krm->next != kr && krm->next != NULL) + krm = krm->next; + if (krm->next == NULL) { + log_warnx("kroute_remove multipath list corrupted " + "for %s/%u", inet_ntoa(kr->r.prefix), + kr->r.prefixlen); + return (-1); + } + krm->next = kr->next; + } + + rtlabel_unref(kr->r.rtlabel); + + free(kr); + return (0); +} + +void +kroute_clear(void) +{ + struct kroute_node *kr; + + while ((kr = RB_MIN(kroute_tree, &krt)) != NULL) + kroute_remove(kr); +} + +struct kif_node * +kif_find(u_short if_index) +{ + struct kif_node s; + + bzero(&s, sizeof(s)); + s.k.if_index = if_index; + + return (RB_FIND(kif_tree, &kit, &s)); +} + +struct kif * +kr_getif(u_short if_index) +{ + struct kif_node *kn; + + if (if_index == 0) + kn = RB_ROOT(&kit); + else + kn = kif_find(if_index); + if (kn == NULL) + return (NULL); + + return (&kn->k); +} + +struct kif * +kr_getnextif(u_short if_index) +{ + struct kif_node *kn; + + if (if_index == 0) { + kn = RB_ROOT(&kit); + return (&kn->k); + } + + if ((kn = kif_find(if_index)) == NULL) + return (NULL); + kn = RB_NEXT(kif_tree, &kit, kn); + if (kn == NULL) + return (NULL); + + return (&kn->k); +} + +struct kif_node * +kif_insert(u_short if_index) +{ + struct kif_node *kif; + + if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) + return (NULL); + + kif->k.if_index = if_index; + TAILQ_INIT(&kif->addrs); + + if (RB_INSERT(kif_tree, &kit, kif) != NULL) + fatalx("kif_insert: RB_INSERT"); + + kr_state.ks_nkif++; + kr_state.ks_iflastchange = mps_getticks(); + + return (kif); +} + +int +kif_remove(struct kif_node *kif) +{ + struct kif_addr *ka; + + if (RB_REMOVE(kif_tree, &kit, kif) == NULL) { + log_warnx("RB_REMOVE(kif_tree, &kit, kif)"); + return (-1); + } + + while ((ka = TAILQ_FIRST(&kif->addrs)) != NULL) { + TAILQ_REMOVE(&kif->addrs, ka, entry); + free(ka); + } + free(kif); + + kr_state.ks_nkif--; + kr_state.ks_iflastchange = mps_getticks(); + + return (0); +} + +void +kif_clear(void) +{ + struct kif_node *kif; + + while ((kif = RB_MIN(kif_tree, &kit)) != NULL) + kif_remove(kif); + kr_state.ks_nkif = 0; + kr_state.ks_iflastchange = mps_getticks(); +} + +struct kif * +kif_update(u_short if_index, int flags, struct if_data *ifd, + struct sockaddr_dl *sdl) +{ + struct kif_node *kif; + struct ether_addr *ea; + struct ifreq ifr; + + if ((kif = kif_find(if_index)) == NULL) + if ((kif = kif_insert(if_index)) == NULL) + return (NULL); + + kif->k.if_flags = flags; + bcopy(ifd, &kif->k.if_data, sizeof(struct if_data)); + kif->k.if_ticks = mps_getticks(); + + if (sdl && sdl->sdl_family == AF_LINK) { + if (sdl->sdl_nlen >= sizeof(kif->k.if_name)) + memcpy(kif->k.if_name, sdl->sdl_data, + sizeof(kif->k.if_name) - 1); + else if (sdl->sdl_nlen > 0) + memcpy(kif->k.if_name, sdl->sdl_data, + sdl->sdl_nlen); + /* string already terminated via calloc() */ + + if ((ea = (struct ether_addr *)LLADDR(sdl)) != NULL) + bcopy(&ea->ether_addr_octet, kif->k.if_lladdr, + ETHER_ADDR_LEN); + } + + bzero(&ifr, sizeof(ifr)); + strlcpy(ifr.ifr_name, kif->k.if_name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (caddr_t)&kif->k.if_descr; + if (ioctl(kr_state.ks_ifd, SIOCGIFDESCR, &ifr) == -1) + bzero(&kif->k.if_descr, sizeof(kif->k.if_descr)); + + return (&kif->k); +} + +int +kif_validate(u_short if_index) +{ + struct kif_node *kif; + + if ((kif = kif_find(if_index)) == NULL) { + log_warnx("interface with index %u not found", if_index); + return (1); + } + + return (kif->k.if_nhreachable); +} + +struct kroute_node * +kroute_match(in_addr_t key) +{ + int i; + struct kroute_node *kr; + + /* we will never match the default route */ + for (i = 32; i > 0; i--) + if ((kr = kroute_find(key & prefixlen2mask(i), i)) != NULL) + return (kr); + + /* if we don't have a match yet, try to find a default route */ + if ((kr = kroute_find(0, 0)) != NULL) + return (kr); + + return (NULL); +} + +/* misc */ +int +protect_lo(void) +{ + struct kroute_node *kr; + + /* special protection for 127/8 */ + if ((kr = calloc(1, sizeof(struct kroute_node))) == NULL) { + log_warn("protect_lo"); + return (-1); + } + kr->r.prefix.s_addr = htonl(INADDR_LOOPBACK); + kr->r.prefixlen = 8; + kr->r.flags = F_KERNEL|F_CONNECTED; + + if (RB_INSERT(kroute_tree, &krt, kr) != NULL) + free(kr); /* kernel route already there, no problem */ + + return (0); +} + +u_int8_t +prefixlen_classful(in_addr_t ina) +{ + /* it hurt to write this. */ + + if (ina >= 0xf0000000U) /* class E */ + return (32); + else if (ina >= 0xe0000000U) /* class D */ + return (4); + else if (ina >= 0xc0000000U) /* class C */ + return (24); + else if (ina >= 0x80000000U) /* class B */ + return (16); + else /* class A */ + return (8); +} + +u_int8_t +mask2prefixlen(in_addr_t ina) +{ + if (ina == 0) + return (0); + else + return (33 - ffs(ntohl(ina))); +} + +in_addr_t +prefixlen2mask(u_int8_t prefixlen) +{ + if (prefixlen == 0) + return (0); + + return (htonl(0xffffffff << (32 - prefixlen))); +} + +#define ROUNDUP(a, size) \ + (((a) & ((size) - 1)) ? (1 + ((a) | ((size) - 1))) : (a)) + +void +get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) +{ + int i; + + for (i = 0; i < RTAX_MAX; i++) { + if (addrs & (1 << i)) { + rti_info[i] = sa; + sa = (struct sockaddr *)((char *)(sa) + + ROUNDUP(sa->sa_len, sizeof(long))); + } else + rti_info[i] = NULL; + + } +} + +void +if_change(u_short if_index, int flags, struct if_data *ifd) +{ + struct kroute_node *kr, *tkr; + struct kif *kif; + u_int8_t reachable; + + if ((kif = kif_update(if_index, flags, ifd, NULL)) == NULL) { + log_warn("if_change: kif_update(%u)", if_index); + return; + } + + reachable = (kif->if_flags & IFF_UP) && + (LINK_STATE_IS_UP(kif->if_link_state) || + (kif->if_link_state == LINK_STATE_UNKNOWN && + kif->if_type != IFT_CARP)); + + if (reachable == kif->if_nhreachable) + return; /* nothing changed wrt nexthop validity */ + + kif->if_nhreachable = reachable; + +#ifdef notyet + /* notify ospfe about interface link state */ + main_imsg_compose_ospfe(IMSG_IFINFO, 0, kif, sizeof(struct kif)); +#endif + + /* update redistribute list */ + RB_FOREACH(kr, kroute_tree, &krt) { + for (tkr = kr; tkr != NULL; tkr = tkr->next) { + if (tkr->r.if_index == if_index) { + if (reachable) + tkr->r.flags &= ~F_DOWN; + else + tkr->r.flags |= F_DOWN; + } + } + } +} + +void +if_newaddr(u_short if_index, struct sockaddr_in *ifa, struct sockaddr_in *mask, + struct sockaddr_in *brd) +{ + struct kif_node *kif; + struct kif_addr *ka; + + if (ifa == NULL || ifa->sin_family != AF_INET) + return; + if ((kif = kif_find(if_index)) == NULL) { + log_warnx("if_newaddr: corresponding if %i not found", if_index); + return; + } + if ((ka = calloc(1, sizeof(struct kif_addr))) == NULL) + fatal("if_newaddr"); + ka->addr = ifa->sin_addr; + if (mask) + ka->mask = mask->sin_addr; + else + ka->mask.s_addr = INADDR_NONE; + if (brd) + ka->dstbrd = brd->sin_addr; + else + ka->dstbrd.s_addr = INADDR_NONE; + + TAILQ_INSERT_TAIL(&kif->addrs, ka, entry); +} + +void +if_announce(void *msg) +{ + struct if_announcemsghdr *ifan; + struct kif_node *kif; + + ifan = msg; + + switch (ifan->ifan_what) { + case IFAN_ARRIVAL: + kif = kif_insert(ifan->ifan_index); + strlcpy(kif->k.if_name, ifan->ifan_name, + sizeof(kif->k.if_name)); + break; + case IFAN_DEPARTURE: + kif = kif_find(ifan->ifan_index); + kif_remove(kif); + break; + } +} + +int +fetchtable(void) +{ + size_t len; + int mib[7]; + char *buf, *next, *lim; + struct rt_msghdr *rtm; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + struct sockaddr_in *sa_in; + struct sockaddr_rtlabel *label; + struct kroute_node *kr; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + mib[6] = 0; /* rtableid */ + + if (sysctl(mib, 7, NULL, &len, NULL, 0) == -1) { + log_warn("sysctl"); + return (-1); + } + if ((buf = malloc(len)) == NULL) { + log_warn("fetchtable"); + return (-1); + } + if (sysctl(mib, 7, buf, &len, NULL, 0) == -1) { + log_warn("sysctl"); + free(buf); + return (-1); + } + + lim = buf + len; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + sa = (struct sockaddr *)(rtm + 1); + get_rtaddrs(rtm->rtm_addrs, sa, rti_info); + + if ((sa = rti_info[RTAX_DST]) == NULL) + continue; + + if (rtm->rtm_flags & RTF_LLINFO) /* arp cache */ + continue; + + if ((kr = calloc(1, sizeof(struct kroute_node))) == NULL) { + log_warn("fetchtable"); + free(buf); + return (-1); + } + + kr->r.flags = F_KERNEL; + + switch (sa->sa_family) { + case AF_INET: + kr->r.prefix.s_addr = + ((struct sockaddr_in *)sa)->sin_addr.s_addr; + sa_in = (struct sockaddr_in *)rti_info[RTAX_NETMASK]; + if (rtm->rtm_flags & RTF_STATIC) + kr->r.flags |= F_STATIC; + if (rtm->rtm_flags & RTF_DYNAMIC) + kr->r.flags |= F_DYNAMIC; + if (rtm->rtm_flags & RTF_PROTO1) + kr->r.flags |= F_BGPD_INSERTED; + if (sa_in != NULL) { + if (sa_in->sin_len == 0) + break; + kr->r.prefixlen = + mask2prefixlen(sa_in->sin_addr.s_addr); + } else if (rtm->rtm_flags & RTF_HOST) + kr->r.prefixlen = 32; + else + kr->r.prefixlen = + prefixlen_classful(kr->r.prefix.s_addr); + break; + default: + free(kr); + continue; + } + + kr->r.if_index = rtm->rtm_index; + if ((sa = rti_info[RTAX_GATEWAY]) != NULL) + switch (sa->sa_family) { + case AF_INET: + kr->r.nexthop.s_addr = + ((struct sockaddr_in *)sa)->sin_addr.s_addr; + break; + case AF_LINK: + kr->r.flags |= F_CONNECTED; + break; + } + + if ((label = (struct sockaddr_rtlabel *) + rti_info[RTAX_LABEL]) != NULL) { + kr->r.rtlabel = + rtlabel_name2id(label->sr_label); + } + kroute_insert(kr); + } + + free(buf); + return (0); +} + +int +fetchifs(u_short if_index) +{ + size_t len; + int mib[6]; + char *buf, *next, *lim; + struct rt_msghdr *rtm; + struct if_msghdr ifm; + struct ifa_msghdr *ifam; + struct kif *kif = NULL; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = 0; /* wildcard address family */ + mib[4] = NET_RT_IFLIST; + mib[5] = if_index; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) { + log_warn("sysctl"); + return (-1); + } + if ((buf = malloc(len)) == NULL) { + log_warn("fetchif"); + return (-1); + } + if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) { + log_warn("sysctl"); + free(buf); + return (-1); + } + + lim = buf + len; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + switch (rtm->rtm_type) { + case RTM_IFINFO: + bcopy(rtm, &ifm, sizeof ifm); + sa = (struct sockaddr *)(next + sizeof(ifm)); + get_rtaddrs(ifm.ifm_addrs, sa, rti_info); + + if ((kif = kif_update(ifm.ifm_index, + ifm.ifm_flags, &ifm.ifm_data, + (struct sockaddr_dl *)rti_info[RTAX_IFP])) == NULL) + fatal("fetchifs"); + + kif->if_nhreachable = (kif->if_flags & IFF_UP) && + (LINK_STATE_IS_UP(ifm.ifm_data.ifi_link_state) || + (ifm.ifm_data.ifi_link_state == + LINK_STATE_UNKNOWN && + ifm.ifm_data.ifi_type != IFT_CARP)); + break; + case RTM_NEWADDR: + ifam = (struct ifa_msghdr *)rtm; + if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | + RTA_BRD)) == 0) + break; + sa = (struct sockaddr *)(ifam + 1); + get_rtaddrs(ifam->ifam_addrs, sa, rti_info); + + if_newaddr(ifam->ifam_index, + (struct sockaddr_in *)rti_info[RTAX_IFA], + (struct sockaddr_in *)rti_info[RTAX_NETMASK], + (struct sockaddr_in *)rti_info[RTAX_BRD]); + break; + } + } + free(buf); + return (0); +} + +/* ARGSUSED */ +void +dispatch_rtmsg(int fd, short event, void *arg) +{ + char buf[RT_BUF_SIZE]; + ssize_t n; + char *next, *lim; + struct rt_msghdr *rtm; + struct if_msghdr ifm; + struct ifa_msghdr *ifam; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + struct sockaddr_in *sa_in; + struct sockaddr_rtlabel *label; + struct kroute_node *kr, *okr; + struct in_addr prefix, nexthop; + u_int8_t prefixlen; + int flags, mpath; + u_short if_index = 0; + + if ((n = read(fd, &buf, sizeof(buf))) == -1) { + log_warn("dispatch_rtmsg: read error"); + return; + } + + if (n == 0) { + log_warnx("routing socket closed"); + return; + } + + lim = buf + n; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + + prefix.s_addr = 0; + prefixlen = 0; + flags = F_KERNEL; + nexthop.s_addr = 0; + mpath = 0; + + if (rtm->rtm_type == RTM_ADD || rtm->rtm_type == RTM_CHANGE || + rtm->rtm_type == RTM_DELETE) { + sa = (struct sockaddr *)(rtm + 1); + get_rtaddrs(rtm->rtm_addrs, sa, rti_info); + + if (rtm->rtm_tableid != 0) + continue; + + if (rtm->rtm_pid == kr_state.ks_pid) /* caused by us */ + continue; + + if (rtm->rtm_errno) /* failed attempts... */ + continue; + + if (rtm->rtm_flags & RTF_LLINFO) /* arp cache */ + continue; + +#ifdef RTF_MPATH + if (rtm->rtm_flags & RTF_MPATH) + mpath = 1; +#endif + switch (sa->sa_family) { + case AF_INET: + prefix.s_addr = + ((struct sockaddr_in *)sa)->sin_addr.s_addr; + sa_in = (struct sockaddr_in *) + rti_info[RTAX_NETMASK]; + if (sa_in != NULL) { + if (sa_in->sin_len != 0) + prefixlen = mask2prefixlen( + sa_in->sin_addr.s_addr); + } else if (rtm->rtm_flags & RTF_HOST) + prefixlen = 32; + else + prefixlen = + prefixlen_classful(prefix.s_addr); + if (rtm->rtm_flags & RTF_STATIC) + flags |= F_STATIC; + if (rtm->rtm_flags & RTF_DYNAMIC) + flags |= F_DYNAMIC; + if (rtm->rtm_flags & RTF_PROTO1) + flags |= F_BGPD_INSERTED; + break; + default: + continue; + } + + if_index = rtm->rtm_index; + if ((sa = rti_info[RTAX_GATEWAY]) != NULL) { + switch (sa->sa_family) { + case AF_INET: + nexthop.s_addr = ((struct + sockaddr_in *)sa)->sin_addr.s_addr; + break; + case AF_LINK: + flags |= F_CONNECTED; + break; + } + } + } + + switch (rtm->rtm_type) { + case RTM_ADD: + case RTM_CHANGE: + if (nexthop.s_addr == 0 && !(flags & F_CONNECTED)) { + log_warnx("dispatch_rtmsg no nexthop for %s/%u", + inet_ntoa(prefix), prefixlen); + continue; + } + + if ((okr = kroute_find(prefix.s_addr, prefixlen)) != + NULL) { + /* just add new multipath routes */ + if (mpath && rtm->rtm_type == RTM_ADD) + goto add; + /* get the correct route */ + kr = okr; + if (mpath && (kr = kroute_matchgw(okr, + nexthop)) == NULL) { + log_warnx("dispatch_rtmsg mpath route" + " not found"); + /* add routes we missed out earlier */ + goto add; + } + + kr->r.nexthop.s_addr = nexthop.s_addr; + kr->r.flags = flags; + kr->r.if_index = if_index; + kr->r.ticks = mps_getticks(); + + rtlabel_unref(kr->r.rtlabel); + kr->r.rtlabel = 0; + if ((label = (struct sockaddr_rtlabel *) + rti_info[RTAX_LABEL]) != NULL) { + kr->r.rtlabel = + rtlabel_name2id(label->sr_label); + } + + if (kif_validate(kr->r.if_index)) + kr->r.flags &= ~F_DOWN; + else + kr->r.flags |= F_DOWN; + } else { +add: + if ((kr = calloc(1, + sizeof(struct kroute_node))) == NULL) { + log_warn("dispatch_rtmsg"); + return; + } + kr->r.prefix.s_addr = prefix.s_addr; + kr->r.prefixlen = prefixlen; + kr->r.nexthop.s_addr = nexthop.s_addr; + kr->r.flags = flags; + kr->r.if_index = if_index; + kr->r.ticks = mps_getticks(); + + if ((label = (struct sockaddr_rtlabel *) + rti_info[RTAX_LABEL]) != NULL) { + kr->r.rtlabel = + rtlabel_name2id(label->sr_label); + } + + kroute_insert(kr); + } + break; + case RTM_DELETE: + if ((kr = kroute_find(prefix.s_addr, prefixlen)) == + NULL) + continue; + if (!(kr->r.flags & F_KERNEL)) + continue; + /* get the correct route */ + okr = kr; + if (mpath && + (kr = kroute_matchgw(kr, nexthop)) == NULL) { + log_warnx("dispatch_rtmsg mpath route" + " not found"); + return; + } +#ifdef notyet + /* + * last route is getting removed request the + * ospf route from the RDE to insert instead + */ + if (okr == kr && kr->next == NULL && + kr->r.flags & F_OSPFD_INSERTED) + main_imsg_compose_rde(IMSG_KROUTE_GET, 0, + &kr->r, sizeof(struct kroute)); +#endif + if (kroute_remove(kr) == -1) + return; + break; + case RTM_IFINFO: + memcpy(&ifm, next, sizeof(ifm)); + if_change(ifm.ifm_index, ifm.ifm_flags, + &ifm.ifm_data); + break; + case RTM_NEWADDR: + ifam = (struct ifa_msghdr *)rtm; + if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | + RTA_BRD)) == 0) + break; + sa = (struct sockaddr *)(ifam + 1); + get_rtaddrs(ifam->ifam_addrs, sa, rti_info); + + if_newaddr(ifam->ifam_index, + (struct sockaddr_in *)rti_info[RTAX_IFA], + (struct sockaddr_in *)rti_info[RTAX_NETMASK], + (struct sockaddr_in *)rti_info[RTAX_BRD]); + break; + case RTM_IFANNOUNCE: + if_announce(next); + break; + default: + /* ignore for now */ + break; + } + } +} + +u_int16_t +rtlabel_name2id(const char *name) +{ + return (0); +} + +const char +*rtlabel_id2name(u_int16_t id) +{ + return (""); +} + +void +rtlabel_unref(u_int16_t id) +{ + /* not used */ +} diff --git a/usr.sbin/snmpd/log.c b/usr.sbin/snmpd/log.c new file mode 100644 index 00000000000..f1d773c35f0 --- /dev/null +++ b/usr.sbin/snmpd/log.c @@ -0,0 +1,182 @@ +/* $OpenBSD: log.c,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 MIND, 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/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <net/if.h> + +#include <arpa/inet.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <event.h> + +#include <openssl/ssl.h> + +#include "snmpd.h" + +int debug; + +void vlog(int, const char *, va_list); +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} + +const char * +log_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + int af = ss->ss_family; + void *ptr; + + bzero(buf, len); + if (af == AF_INET) + ptr = &((struct sockaddr_in *)ss)->sin_addr; + else + ptr = &((struct sockaddr_in6 *)ss)->sin6_addr; + return (inet_ntop(af, ptr, buf, len)); +} diff --git a/usr.sbin/snmpd/mib.c b/usr.sbin/snmpd/mib.c new file mode 100644 index 00000000000..630c1c6e797 --- /dev/null +++ b/usr.sbin/snmpd/mib.c @@ -0,0 +1,836 @@ +/* $OpenBSD: mib.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 <sys/utsname.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_types.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" +#include "mib.h" + +extern struct snmpd *env; + +/* + * Defined in SNMPv2-MIB.txt (RFC 3418) + */ + +int mib_getsys(struct oid *, struct ber_oid *, struct ber_element **); +int mib_getsnmp(struct oid *, struct ber_oid *, struct ber_element **); +int mib_sysor(struct oid *, struct ber_oid *, struct ber_element **); +int mib_setsnmp(struct oid *, struct ber_oid *, struct ber_element **); + +/* base MIB tree */ +static struct oid base_mib[] = { + { MIB(ISO), "iso" }, + { MIB(ORG), "org" }, + { MIB(DOD), "dod" }, + { MIB(INTERNET), "internet" }, + { MIB(DIRECTORY), "directory" }, + { MIB(MGMT), "mgmt" }, + { MIB(MIB_2), "mib-2", OID_MIB }, + { MIB(SYSTEM), "system" }, + { MIB(SYSDESCR), "sysDescr", OID_RD, mib_getsys }, + { MIB(SYSOID), "sysOID", OID_RD, mib_getsys }, + { MIB(SYSUPTIME), "sysUpTime", OID_RD, mib_getsys }, + { MIB(SYSCONTACT), "sysContact", OID_RW, mib_getsys, mps_setstr }, + { MIB(SYSNAME), "sysName", OID_RW, mib_getsys, mps_setstr }, + { MIB(SYSLOCATION), "sysLocation", OID_RW, mib_getsys, mps_setstr }, + { MIB(SYSSERVICES), "sysServices", OID_RS, mib_getsys }, + { MIB(SYSORLASTCHANGE), "sysORLastChange", OID_RD, mps_getts }, + { MIB(SYSORTABLE), "sysORTable" }, + { MIB(SYSORENTRY), "sysOREntry" }, + { MIB(SYSORINDEX), "sysORIndex", OID_TRD, mib_sysor }, + { MIB(SYSORID), "sysORID", OID_TRD, mib_sysor }, + { MIB(SYSORDESCR), "sysORDescr", OID_TRD, mib_sysor }, + { MIB(SYSORUPTIME), "sysORUptime", OID_TRD, mib_sysor }, + { MIB(TRANSMISSION), "transmission" }, + { MIB(SNMP), "snmp", OID_MIB }, + { MIB(SNMPINPKTS), "snmpInPkts", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTPKTS), "snmpOutPkts", OID_RD, mib_getsnmp }, + { MIB(SNMPINBADVERSIONS), "snmpInBadVersions", OID_RD, mib_getsnmp }, + { MIB(SNMPINBADCOMNNAMES), "snmpInBadCommunityNames", OID_RD, mib_getsnmp }, + { MIB(SNMPINBADCOMNUSES), "snmpInBadCommunityUses", OID_RD, mib_getsnmp }, + { MIB(SNMPINASNPARSEERRS), "snmpInASNParseErrs", OID_RD, mib_getsnmp }, + { MIB(SNMPINTOOBIGS), "snmpInTooBigs", OID_RD, mib_getsnmp }, + { MIB(SNMPINNOSUCHNAMES), "snmpInNoSuchNames", OID_RD, mib_getsnmp }, + { MIB(SNMPINBADVALUES), "snmpInBadValues", OID_RD, mib_getsnmp }, + { MIB(SNMPINREADONLYS), "snmpInReadOnlys", OID_RD, mib_getsnmp }, + { MIB(SNMPINGENERRS), "snmpInGenErrs", OID_RD, mib_getsnmp }, + { MIB(SNMPINTOTALREQVARS), "snmpInTotalReqVars", OID_RD, mib_getsnmp }, + { MIB(SNMPINTOTALSETVARS), "snmpInTotalSetVars", OID_RD, mib_getsnmp }, + { MIB(SNMPINGETREQUESTS), "snmpInGetRequests", OID_RD, mib_getsnmp }, + { MIB(SNMPINGETNEXTS), "snmpInGetNexts", OID_RD, mib_getsnmp }, + { MIB(SNMPINSETREQUESTS), "snmpInSetRequests", OID_RD, mib_getsnmp }, + { MIB(SNMPINGETRESPONSES), "snmpInGetResponses", OID_RD, mib_getsnmp }, + { MIB(SNMPINTRAPS), "snmpInTraps", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTTOOBIGS), "snmpOutTooBigs", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTNOSUCHNAMES), "snmpOutNoSuchNames", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTBADVALUES), "snmpOutBadValues", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTGENERRS), "snmpOutGenErrs", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTGETREQUESTS), "snmpOutGetRequests", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTGETNEXTS), "snmpOutGetNexts", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTSETREQUESTS), "snmpOutSetRequests", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTGETRESPONSES), "snmpOutGetResponses", OID_RD, mib_getsnmp }, + { MIB(SNMPOUTTRAPS), "snmpOutTraps", OID_RD, mib_getsnmp }, + { MIB(SNMPENAUTHTRAPS), "snmpEnableAuthenTraps", OID_RW, mib_getsnmp, mib_setsnmp }, + { MIB(SNMPSILENTDROPS), "snmpSilentDrops", OID_RD, mib_getsnmp }, + { MIB(SNMPPROXYDROPS), "snmpProxyDrops", OID_RD, mib_getsnmp }, + { MIB(EXPERIMENTAL), "experimental" }, + { MIB(PRIVATE), "private" }, + { MIB(ENTERPRISES), "enterprises" }, + { MIB(SECURITY), "security" }, + { MIB(SNMPV2), "snmpV2" }, + { MIB(SNMPDOMAINS), "snmpDomains" }, + { MIB(SNMPPROXIES), "snmpProxies" }, + { MIB(SNMPMODULES), "snmpModules" }, + { MIB(SNMPMIB), "snmpMIB" }, + { MIB(SNMPMIBOBJECTS), "snmpMIBObjects" }, + { MIB(SNMPTRAP), "snmpTrap" }, + { MIB(SNMPTRAPOID), "snmpTrapOID" }, + { MIB(SNMPTRAPENTERPRISE), "snmpTrapEnterprise" }, + { MIB(SNMPTRAPS), "snmpTraps" }, + { MIB(COLDSTART), "coldStart" }, + { MIB(WARMSTART), "warmStart" }, + { MIB(LINKDOWN), "linkDown" }, + { MIB(LINKUP), "linkUp" }, + { MIB(AUTHFAILURE), "authenticationFailure" }, + { MIB(EGPNEIGHBORLOSS), "egpNeighborLoss" } +}; + +int +mib_getsys(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_oid sysoid = OID(MIB_SYSOID_DEFAULT); + char *s = oid->o_data; + struct ber_oid *so = oid->o_data; + struct utsname u; + long long ticks; + + if (uname(&u) == -1) + return (-1); + + switch (oid->o_oid[OIDIDX_SYSTEM]) { + case 1: + if (s == NULL) { + if (asprintf(&s, "%s %s %s %s %s", + u.sysname, u.nodename, u.release, + u.version, u.machine) == -1) + return (-1); + oid->o_data = s; + oid->o_val = strlen(s); + } + *elm = ber_add_string(*elm, s); + break; + case 2: + if (so == NULL) + so = &sysoid; + mps_oidlen(so); + *elm = ber_add_oid(*elm, so); + break; + case 3: + ticks = mps_getticks(); + *elm = ber_add_integer(*elm, ticks); + ber_set_header(*elm, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + break; + case 4: + if (s == NULL) { + if (asprintf(&s, "root@%s", u.nodename) == -1) + return (-1); + oid->o_data = s; + oid->o_val = strlen(s); + } + *elm = ber_add_string(*elm, s); + break; + case 5: + if (s == NULL) { + if ((s = strdup(u.nodename)) == NULL) + return (-1); + oid->o_data = s; + oid->o_val = strlen(s); + } + *elm = ber_add_string(*elm, s); + break; + case 6: + if (s == NULL) + s = ""; + *elm = ber_add_string(*elm, s); + break; + case 7: + *elm = ber_add_integer(*elm, oid->o_val); + break; + default: + return (-1); + } + return (0); +} + +int +mib_sysor(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + u_int32_t idx = 1, nmib = 0; + struct oid *next, *miboid; + char buf[SNMPD_MAXSTRLEN], *ptr; + + /* Count MIB root OIDs in the tree */ + for (next = NULL; + (next = mps_foreach(next, OID_MIB)) != NULL; nmib++); + + /* Get and verify the current row index */ + idx = o->bo_id[OIDIDX_ORENTRY]; + if (idx > nmib) + return (1); + + /* Find the MIB root element for this Id */ + for (next = miboid = NULL, nmib = 1; + (next = mps_foreach(next, OID_MIB)) != NULL; nmib++) { + if (nmib == idx) + miboid = next; + } + if (miboid == NULL) + return (-1); + + /* Tables need to prepend the OID on their own */ + ber = ber_add_oid(ber, o); + + switch (o->bo_id[OIDIDX_OR]) { + case 1: + ber = ber_add_integer(ber, idx); + break; + case 2: + ber = ber_add_oid(ber, &miboid->o_id); + break; + case 3: + /* + * This should be a description of the MIB. + * But we use the symbolic OID string for now, it may + * help to display names of internal OIDs. + */ + mps_oidstring(&miboid->o_id, buf, sizeof(buf)); + if ((ptr = strdup(buf)) == NULL) { + ber = ber_add_string(ber, miboid->o_name); + } else { + ber = ber_add_string(ber, ptr); + ber->be_free = 1; + } + break; + case 4: + /* + * We do not support dynamic loading of MIB at runtime, + * the sysORUpTime value of 0 will indicate "loaded at + * startup". + */ + ber = ber_add_integer(ber, 0); + ber_set_header(ber, + BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + break; + default: + return (-1); + } + + return (0); +} + +int +mib_getsnmp(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct snmp_stats *stats = &env->sc_stats; + long long i; + struct statsmap { + u_int8_t m_id; + u_int32_t *m_ptr; + } mapping[] = { + { 1, &stats->snmp_inpkts }, + { 2, &stats->snmp_outpkts }, + { 3, &stats->snmp_inbadversions }, + { 4, &stats->snmp_inbadcommunitynames }, + { 5, &stats->snmp_inbadcommunityuses }, + { 6, &stats->snmp_inasnparseerrs }, + { 8, &stats->snmp_intoobigs }, + { 9, &stats->snmp_innosuchnames }, + { 10, &stats->snmp_inbadvalues }, + { 11, &stats->snmp_inreadonlys }, + { 12, &stats->snmp_ingenerrs }, + { 13, &stats->snmp_intotalreqvars }, + { 14, &stats->snmp_intotalsetvars }, + { 15, &stats->snmp_ingetrequests }, + { 16, &stats->snmp_ingetnexts }, + { 17, &stats->snmp_insetrequests }, + { 18, &stats->snmp_ingetresponses }, + { 19, &stats->snmp_intraps }, + { 20, &stats->snmp_outtoobigs }, + { 21, &stats->snmp_outnosuchnames }, + { 22, &stats->snmp_outbadvalues }, + { 24, &stats->snmp_outgenerrs }, + { 25, &stats->snmp_outgetrequests }, + { 26, &stats->snmp_outgetnexts }, + { 27, &stats->snmp_outsetrequests }, + { 28, &stats->snmp_outgetresponses }, + { 29, &stats->snmp_outtraps }, + { 31, &stats->snmp_silentdrops }, + { 32, &stats->snmp_proxydrops } + }; + + switch (oid->o_oid[OIDIDX_SNMP]) { + case 30: + i = stats->snmp_enableauthentraps == 1 ? 1 : 2; + *elm = ber_add_integer(*elm, i); + break; + default: + for (i = 0; + (u_int)i < (sizeof(mapping) / sizeof(mapping[0])); i++) { + if (oid->o_oid[OIDIDX_SNMP] == mapping[i].m_id) { + *elm = ber_add_integer(*elm, *mapping[i].m_ptr); + ber_set_header(*elm, + BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + return (0); + } + } + return (-1); + } + + return (0); +} + +int +mib_setsnmp(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct snmp_stats *stats = &env->sc_stats; + long long i; + + if (ber_get_integer(*elm, &i) == -1) + return (-1); + + stats->snmp_enableauthentraps = i == 1 ? 1 : 0; + + return (0); +} + +/* + * Defined in IF-MIB.txt (RFCs 1229, 1573, 2233, 2863) + */ + +int mib_ifnumber(struct oid *, struct ber_oid *, struct ber_element **); +struct kif + *mib_ifget(u_int); +int mib_iftable(struct oid *, struct ber_oid *, struct ber_element **); +int mib_ifxtable(struct oid *, struct ber_oid *, struct ber_element **); +int mib_ifstacklast(struct oid *, struct ber_oid *, struct ber_element **); +int mib_ifrcvtable(struct oid *, struct ber_oid *, struct ber_element **); + +static u_int8_t ether_zeroaddr[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static struct ber_oid zerodotzero = { { 0, 0 }, 2 }; + +static struct oid if_mib[] = { + { MIB(IFMIB), "ifMIB", OID_MIB }, + { MIB(IFMIBOBJECTS), "ifMIBObjects" }, + { MIB(IFXTABLE), "ifXTable" }, + { MIB(IFXENTRY), "ifXEntry" }, + { MIB(IFNAME), "ifName", OID_TRD, mib_ifxtable }, + { MIB(IFINMASTPKTS), "ifInMulticastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFINBASTPKTS), "ifInBroadcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFOUTMASTPKTS), "ifOutMulticastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFOUTBASTPKTS), "ifOurBroadcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCINOCTETS), "ifHCInOctets", OID_TRD, mib_ifxtable }, + { MIB(IFHCINUCASTPKTS), "ifHCInUcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCINMCASTPKTS), "ifHCInMulticastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCINBCASTPKTS), "ifHCInBroadcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCOUTOCTETS), "ifHCOutOctets", OID_TRD, mib_ifxtable }, + { MIB(IFHCOUTUCASTPKTS), "ifHCOutUcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCOUTMCASTPKTS), "ifHCOutMulticastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFHCOUTBCASTPKTS), "ifHCOutBroadcastPkts", OID_TRD, mib_ifxtable }, + { MIB(IFLINKUPDORNTRAPENABLE), "ifLinkUpDownTrapEnable", OID_TRD, mib_ifxtable }, + { MIB(IFHIGHSPEED), "ifHighSpeed", OID_TRD, mib_ifxtable }, + { MIB(IFPROMISCMODE), "ifPromiscuousMode", OID_TRD, mib_ifxtable }, + { MIB(IFCONNECTORPRESENT), "ifConnectorPresent", OID_TRD, mib_ifxtable }, + { MIB(IFALIAS), "ifAlias", OID_TRD, mib_ifxtable }, + { MIB(IFCNTDISCONTINUITYTIME), "ifCounterDiscontinuityTime", OID_TRD, mib_ifxtable }, + { MIB(IFSTACKTABLE), "ifStackTable" }, + { MIB(IFSTACKENTRY), "ifStackEntry" }, + { MIB(IFRCVTABLE), "ifRcvAddressTable" }, + { MIB(IFRCVENTRY), "ifRcvAddressEntry" }, + { MIB(IFRCVSTATUS), "ifRcvAddressStatus", OID_TRD, mib_ifrcvtable }, + { MIB(IFRCVTYPE), "ifRcvAddressType", OID_TRD, mib_ifrcvtable }, + { MIB(IFSTACKLASTCHANGE), "ifStackLastChange", OID_RD, mib_ifstacklast }, + { MIB(INTERFACES), "interfaces" }, + { MIB(IFNUMBER), "ifNumber", OID_RD, mib_ifnumber }, + { MIB(IFTABLE), "ifTable" }, + { MIB(IFENTRY), "ifEntry" }, + { MIB(IFINDEX), "ifIndex", OID_TRD, mib_iftable }, + { MIB(IFDESCR), "ifDescr", OID_TRD, mib_iftable }, + { MIB(IFTYPE), "ifDescr", OID_TRD, mib_iftable }, + { MIB(IFMTU), "ifMtu", OID_TRD, mib_iftable }, + { MIB(IFSPEED), "ifSpeed", OID_TRD, mib_iftable }, + { MIB(IFPHYSADDR), "ifPhysAddress", OID_TRD, mib_iftable }, + { MIB(IFADMINSTATUS), "ifAdminStatus", OID_TRD, mib_iftable }, + { MIB(IFOPERSTATUS), "ifOperStatus", OID_TRD, mib_iftable }, + { MIB(IFLASTCHANGE), "ifLastChange", OID_TRD, mib_iftable }, + { MIB(IFINOCTETS), "ifInOctets", OID_TRD, mib_iftable }, + { MIB(IFINUCASTPKTS), "ifInUcastPkts", OID_TRD, mib_iftable }, + { MIB(IFINNUCASTPKTS), "ifInNUcastPkts", OID_TRD, mib_iftable }, + { MIB(IFINDISCARDS), "ifInDiscards", OID_TRD, mib_iftable }, + { MIB(IFINERRORS), "ifInErrors", OID_TRD, mib_iftable }, + { MIB(IFINUNKNOWNERRORS), "ifInUnknownErrors", OID_TRD, mib_iftable }, + { MIB(IFOUTOCTETS), "ifOutOctets", OID_TRD, mib_iftable }, + { MIB(IFOUTUCASTPKTS), "ifOutUcastPkts", OID_TRD, mib_iftable }, + { MIB(IFOUTNUCASTPKTS), "ifOutNUcastPkts", OID_TRD, mib_iftable }, + { MIB(IFOUTDISCARDS), "ifOutDiscards", OID_TRD, mib_iftable }, + { MIB(IFOUTERRORS), "ifOutErrors", OID_TRD, mib_iftable }, + { MIB(IFOUTQLEN), "ifOutQLen", OID_TRD, mib_iftable }, + { MIB(IFSPECIFIC), "ifSpecific", OID_TRD, mib_iftable } +}; + +int +mib_ifnumber(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + *elm = ber_add_integer(*elm, kr_ifnumber()); + return (0); +} + +struct kif * +mib_ifget(u_int idx) +{ + struct kif *kif; + + if ((kif = kr_getif(idx)) == NULL) { + /* + * It may happen that a interface with a specific index + * does not exist or has been removed. Jump to the next + * available interface index. + */ + for (kif = kr_getif(0); kif != NULL; + kif = kr_getnextif(kif->if_index)) + if (kif->if_index > idx) + break; + if (kif == NULL) + return (NULL); + } + + /* Update interface information */ + kr_updateif(kif->if_index); + if ((kif = kr_getif(kif->if_index)) == NULL) { + log_debug("mib_ifxtable: interface disappeared?"); + return (NULL); + } + + return (kif); +} + +int +mib_iftable(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + u_int32_t idx = 0; + struct kif *kif; + long long i; + size_t len; + int ifq; + int mib[5]; + + /* Get and verify the current row index */ + idx = o->bo_id[OIDIDX_IFENTRY]; + if ((kif = mib_ifget(idx)) == NULL) + return (1); + + /* Tables need to prepend the OID on their own */ + o->bo_id[OIDIDX_IFENTRY] = kif->if_index; + ber = ber_add_oid(ber, o); + + switch (o->bo_id[OIDIDX_IF]) { + case 1: + ber = ber_add_integer(ber, kif->if_index); + break; + case 2: + /* + * The ifDescr should contain a vendor, product, etc. + * but we just use the interface name (like ifName). + * The interface name includes the driver name on OpenBSD. + */ + ber = ber_add_string(ber, kif->if_name); + break; + case 3: + if (kif->if_type >= 0xf0) { + /* + * It does not make sense to announce the private + * interface types for CARP, ENC, PFSYNC, etc. + */ + ber = ber_add_integer(ber, IFT_OTHER); + } else + ber = ber_add_integer(ber, kif->if_type); + break; + case 4: + ber = ber_add_integer(ber, kif->if_mtu); + break; + case 5: + ber = ber_add_integer(ber, kif->if_baudrate); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_GAUGE32); + break; + case 6: + if (bcmp(kif->if_lladdr, ether_zeroaddr, + sizeof(kif->if_lladdr)) == 0) { + ber = ber_add_string(ber, ""); + } else { + ber = ber_add_nstring(ber, kif->if_lladdr, + sizeof(kif->if_lladdr)); + } + break; + case 7: + /* ifAdminStatus up(1), down(2), testing(3) */ + i = (kif->if_flags & IFF_UP) ? 1 : 2; + ber = ber_add_integer(ber, i); + break; + case 8: + /* ifOperStatus */ + if ((kif->if_flags & IFF_UP) == 0) { + i = 2; /* down(2) */ + } else if (LINK_STATE_IS_UP(kif->if_link_state)) { + i = 1; /* up(1) */ + } else if (kif->if_link_state == LINK_STATE_DOWN) { + i = 7; /* lowerLayerDown(7) or dormant(5)? */ + } else + i = 4; /* unknown(4) */ + ber = ber_add_integer(ber, i); + break; + case 9: + ber = ber_add_integer(ber, kif->if_ticks); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + break; + case 10: + ber = ber_add_integer(ber, (u_int32_t)kif->if_ibytes); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 11: + ber = ber_add_integer(ber, (u_int32_t)kif->if_ipackets); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 12: + ber = ber_add_integer(ber, (u_int32_t)kif->if_imcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 13: + ber = ber_add_integer(ber, (u_int32_t)kif->if_iqdrops); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 14: + ber = ber_add_integer(ber, (u_int32_t)kif->if_ierrors); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 15: + ber = ber_add_integer(ber, 0); /* unknown errors? */ + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 16: + ber = ber_add_integer(ber, (u_int32_t)kif->if_obytes); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 17: + ber = ber_add_integer(ber, (u_int32_t)kif->if_opackets); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 18: + ber = ber_add_integer(ber, (u_int32_t)kif->if_omcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 19: + mib[0] = CTL_NET; + mib[1] = AF_INET; + mib[2] = IPPROTO_IP; + mib[3] = IPCTL_IFQUEUE; + mib[4] = IFQCTL_DROPS; + len = sizeof(ifq); + if (sysctl(mib, 5, &ifq, &len, 0, 0) == -1) { + log_info("mib_iftable: %s: invalid ifq: %s", + kif->if_name, strerror(errno)); + return (-1); + } + ber = ber_add_integer(ber, kif->if_noproto + ifq); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 20: + ber = ber_add_integer(ber, (u_int32_t)kif->if_oerrors); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 21: + mib[0] = CTL_NET; + mib[1] = AF_INET; + mib[2] = IPPROTO_IP; + mib[3] = IPCTL_IFQUEUE; + mib[4] = IFQCTL_LEN; + len = sizeof(ifq); + if (sysctl(mib, 5, &ifq, &len, 0, 0) == -1) { + log_info("mib_iftable: %s: invalid ifq: %s", + kif->if_name, strerror(errno)); + return (-1); + } + ber = ber_add_integer(ber, ifq); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_GAUGE32); + break; + case 22: + ber = ber_add_oid(ber, &zerodotzero); + break; + default: + return (-1); + } + + return (0); +} + +int +mib_ifxtable(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + u_int32_t idx = 0; + struct kif *kif; + int i = 0; + + /* Get and verify the current row index */ + idx = o->bo_id[OIDIDX_IFXENTRY]; + if ((kif = mib_ifget(idx)) == NULL) + return (1); + + /* Tables need to prepend the OID on their own */ + o->bo_id[OIDIDX_IFXENTRY] = kif->if_index; + ber = ber_add_oid(ber, o); + + switch (o->bo_id[OIDIDX_IFX]) { + case 1: + ber = ber_add_string(ber, kif->if_name); + break; + case 2: + ber = ber_add_integer(ber, (u_int32_t)kif->if_imcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 3: + ber = ber_add_integer(ber, 0); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 4: + ber = ber_add_integer(ber, (u_int32_t)kif->if_omcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 5: + ber = ber_add_integer(ber, 0); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER32); + break; + case 6: + ber = ber_add_integer(ber, (u_int64_t)kif->if_ibytes); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 7: + ber = ber_add_integer(ber, (u_int64_t)kif->if_ipackets); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 8: + ber = ber_add_integer(ber, (u_int64_t)kif->if_imcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 9: + ber = ber_add_integer(ber, 0); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 10: + ber = ber_add_integer(ber, (u_int64_t)kif->if_obytes); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 11: + ber = ber_add_integer(ber, (u_int64_t)kif->if_opackets); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 12: + ber = ber_add_integer(ber, (u_int64_t)kif->if_omcasts); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 13: + ber = ber_add_integer(ber, 0); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_COUNTER64); + break; + case 14: + ber = ber_add_integer(ber, 0); /* enabled(1), disabled(2) */ + break; + case 15: + i = kif->if_baudrate >= 1000000 ? + kif->if_baudrate / 1000000 : 0; + ber = ber_add_integer(ber, i); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_GAUGE32); + break; + case 16: + /* ifPromiscuousMode: true(1), false(2) */ + i = kif->if_flags & IFF_PROMISC ? 1 : 2; + ber = ber_add_integer(ber, i); + break; + case 17: + /* ifConnectorPresent: false(2), true(1) */ + i = kif->if_type == IFT_ETHER ? 1 : 2; + ber = ber_add_integer(ber, i); + break; + case 18: + ber = ber_add_string(ber, kif->if_descr); + break; + case 19: + ber = ber_add_integer(ber, 0); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + break; + default: + return (-1); + } + + return (0); +} + +int +mib_ifstacklast(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + ber = ber_add_integer(ber, kr_iflastchange()); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + return (0); +} + +int +mib_ifrcvtable(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + u_int32_t idx = 0; + struct kif *kif; + u_int i = 0; + + /* Get and verify the current row index */ + idx = o->bo_id[OIDIDX_IFRCVENTRY]; + if ((kif = mib_ifget(idx)) == NULL) + return (1); + + /* + * The lladdr of the interface will be encoded in the returned OID + * ifRcvAddressX.ifindex.6.x.x.x.x.x.x = val + * Thanks to the virtual cloner interfaces, it is an easy 1:1 + * mapping in OpenBSD; only one lladdr (MAC) address per interface. + */ + + /* first set the base OID and caluculate the length */ + idx = 0; + o->bo_id[OIDIDX_IFRCVENTRY + idx++] = kif->if_index; + o->bo_id[OIDIDX_IFRCVENTRY + idx] = 0; + mps_oidlen(o); + + /* extend the OID with the lladdr length and octets */ + o->bo_id[OIDIDX_IFRCVENTRY + idx++] = sizeof(kif->if_lladdr); + o->bo_n++; + for (i = 0; i < sizeof(kif->if_lladdr); i++, o->bo_n++) + o->bo_id[OIDIDX_IFRCVENTRY + idx++] = kif->if_lladdr[i]; + + /* write OID */ + ber = ber_add_oid(ber, o); + + switch (o->bo_id[OIDIDX_IFRCV]) { + case 2: + /* ifRcvAddressStatus: RowStatus active(1), notInService(2) */ + i = kif->if_flags & IFF_UP ? 1 : 2; + ber = ber_add_integer(ber, i); + break; + case 3: + /* ifRcvAddressType: other(1), volatile(2), nonVolatile(3) */ + ber = ber_add_integer(ber, 1); + break; + default: + return (-1); + } + + return (0); +} + +/* + * PRIVATE ENTERPRISE NUMBERS from + * http://www.iana.org/assignments/enterprise-numbers + * + * This is not the complete list of private enterprise numbers, it only + * includes some well-known companies and especially network companies + * that are very common in the datacenters around the world. It would + * be an overkill to include ~30.000 entries for all the organizations + * from the official list. + */ +static struct oid enterprise_mib[] = { + { MIB(IBM), "ibm" }, + { MIB(CMU), "cmu" }, + { MIB(UNIX), "unix" }, + { MIB(CISCO), "ciscoSystems" }, + { MIB(HP), "hp" }, + { MIB(MIT), "mit" }, + { MIB(NORTEL), "nortelNetworks" }, + { MIB(SUN), "sun" }, + { MIB(3COM), "3com" }, + { MIB(SYNOPTICS), "synOptics" }, + { MIB(ENTERASYS), "enterasys" }, + { MIB(SGI), "sgi" }, + { MIB(APPLE), "apple" }, + { MIB(ATT), "att" }, + { MIB(NOKIA), "nokia" }, + { MIB(CERN), "cern" }, + { MIB(FSC), "fsc" }, + { MIB(COMPAQ), "compaq" }, + { MIB(DELL), "dell" }, + { MIB(ALTEON), "alteon" }, + { MIB(EXTREME), "extremeNetworks" }, + { MIB(FOUNDRY), "foundryNetworks" }, + { MIB(HUAWAI), "huawaiTechnology" }, + { MIB(UCDAVIS), "ucDavis" }, + { MIB(CHECKPOINT), "checkPoint" }, + { MIB(JUNIPER), "juniper" }, + { MIB(FORCE10), "force10Networks" }, + { MIB(ALCATELLUCENT), "alcatelLucent" }, + { MIB(SNOM), "snom" }, + { MIB(GOOGLE), "google" }, + { MIB(F5), "f5Networks" }, + { MIB(SFLOW), "sFlow" }, + { MIB(MSYS), "microSystems" }, + { MIB(VANTRONIX), "vantronix" }, + { MIB(OPENBSD), "openBSD" } +}; + +void +mib_init(void) +{ + /* SNMPv2-MIB */ + mps_mibtree(base_mib, sizeof(base_mib) / sizeof(base_mib[0])); + + /* IF-MIB */ + mps_mibtree(if_mib, sizeof(if_mib) / sizeof(if_mib[0])); + + /* some http://www.iana.org/assignments/enterprise-numbers */ + mps_mibtree(enterprise_mib, + sizeof(enterprise_mib) / sizeof(enterprise_mib[0])); +} diff --git a/usr.sbin/snmpd/mib.h b/usr.sbin/snmpd/mib.h new file mode 100644 index 00000000000..5dd8ee6b32f --- /dev/null +++ b/usr.sbin/snmpd/mib.h @@ -0,0 +1,209 @@ +/* $OpenBSD: mib.h,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. + */ + +#ifndef _SNMPD_MIB_H +#define _SNMPD_MIB_H + +/* From the SNMPv2-SMI MIB */ +#define MIB_ISO 1 +#define MIB_ORG MIB_ISO, 3 +#define MIB_DOD MIB_ORG, 6 +#define MIB_INTERNET MIB_DOD, 1 +#define MIB_DIRECTORY MIB_INTERNET, 1 +#define MIB_MGMT MIB_INTERNET, 2 +#define MIB_MIB_2 MIB_MGMT, 1 +#define MIB_SYSTEM MIB_MIB_2, 1 +#define OIDIDX_SYSTEM 7 +#define MIB_SYSDESCR MIB_SYSTEM, 1 +#define MIB_SYSOID MIB_SYSTEM, 2 +#define MIB_SYSUPTIME MIB_SYSTEM, 3 +#define MIB_SYSCONTACT MIB_SYSTEM, 4 +#define MIB_SYSNAME MIB_SYSTEM, 5 +#define MIB_SYSLOCATION MIB_SYSTEM, 6 +#define MIB_SYSSERVICES MIB_SYSTEM, 7 +#define MIB_SYSORLASTCHANGE MIB_SYSTEM, 8 +#define MIB_SYSORTABLE MIB_SYSTEM, 9 +#define MIB_SYSORENTRY MIB_SYSORTABLE, 1 +#define OIDIDX_OR 9 +#define OIDIDX_ORENTRY 10 +#define MIB_SYSORINDEX MIB_SYSORENTRY, 1 +#define MIB_SYSORID MIB_SYSORENTRY, 2 +#define MIB_SYSORDESCR MIB_SYSORENTRY, 3 +#define MIB_SYSORUPTIME MIB_SYSORENTRY, 4 +#define MIB_TRANSMISSION MIB_MIB_2, 10 +#define MIB_SNMP MIB_MIB_2, 11 +#define OIDIDX_SNMP 7 +#define MIB_SNMPINPKTS MIB_SNMP, 1 +#define MIB_SNMPOUTPKTS MIB_SNMP, 2 +#define MIB_SNMPINBADVERSIONS MIB_SNMP, 3 +#define MIB_SNMPINBADCOMNNAMES MIB_SNMP, 4 +#define MIB_SNMPINBADCOMNUSES MIB_SNMP, 5 +#define MIB_SNMPINASNPARSEERRS MIB_SNMP, 6 +#define MIB_SNMPINTOOBIGS MIB_SNMP, 8 +#define MIB_SNMPINNOSUCHNAMES MIB_SNMP, 9 +#define MIB_SNMPINBADVALUES MIB_SNMP, 10 +#define MIB_SNMPINREADONLYS MIB_SNMP, 11 +#define MIB_SNMPINGENERRS MIB_SNMP, 12 +#define MIB_SNMPINTOTALREQVARS MIB_SNMP, 13 +#define MIB_SNMPINTOTALSETVARS MIB_SNMP, 14 +#define MIB_SNMPINGETREQUESTS MIB_SNMP, 15 +#define MIB_SNMPINGETNEXTS MIB_SNMP, 16 +#define MIB_SNMPINSETREQUESTS MIB_SNMP, 17 +#define MIB_SNMPINGETRESPONSES MIB_SNMP, 18 +#define MIB_SNMPINTRAPS MIB_SNMP, 19 +#define MIB_SNMPOUTTOOBIGS MIB_SNMP, 20 +#define MIB_SNMPOUTNOSUCHNAMES MIB_SNMP, 21 +#define MIB_SNMPOUTBADVALUES MIB_SNMP, 22 +#define MIB_SNMPOUTGENERRS MIB_SNMP, 24 +#define MIB_SNMPOUTGETREQUESTS MIB_SNMP, 25 +#define MIB_SNMPOUTGETNEXTS MIB_SNMP, 26 +#define MIB_SNMPOUTSETREQUESTS MIB_SNMP, 27 +#define MIB_SNMPOUTGETRESPONSES MIB_SNMP, 28 +#define MIB_SNMPOUTTRAPS MIB_SNMP, 29 +#define MIB_SNMPENAUTHTRAPS MIB_SNMP, 30 +#define MIB_SNMPSILENTDROPS MIB_SNMP, 31 +#define MIB_SNMPPROXYDROPS MIB_SNMP, 32 +#define MIB_EXPERIMENTAL MIB_INTERNET, 3 +#define MIB_PRIVATE MIB_INTERNET, 4 +#define MIB_ENTERPRISES MIB_PRIVATE, 1 +#define MIB_SECURITY MIB_INTERNET, 5 +#define MIB_SNMPV2 MIB_INTERNET, 6 +#define MIB_SNMPDOMAINS MIB_SNMPV2, 1 +#define MIB_SNMPPROXIES MIB_SNMPV2, 2 +#define MIB_SNMPMODULES MIB_SNMPV2, 3 +#define MIB_SNMPMIB MIB_SNMPMODULES, 1 +#define MIB_SNMPMIBOBJECTS MIB_SNMPMIB, 1 +#define MIB_SNMPTRAP MIB_SNMPMIBOBJECTS, 4 +#define MIB_SNMPTRAPOID MIB_SNMPTRAP, 1 +#define MIB_SNMPTRAPENTERPRISE MIB_SNMPTRAP, 3 +#define MIB_SNMPTRAPS MIB_SNMPMIBOBJECTS, 5 +#define MIB_COLDSTART MIB_SNMPTRAPS, 1 +#define MIB_WARMSTART MIB_SNMPTRAPS, 2 +#define MIB_LINKDOWN MIB_SNMPTRAPS, 3 +#define MIB_LINKUP MIB_SNMPTRAPS, 4 +#define MIB_AUTHFAILURE MIB_SNMPTRAPS, 5 +#define MIB_EGPNEIGHBORLOSS MIB_SNMPTRAPS, 6 + +/* IF-MIB */ +#define MIB_IFMIB MIB_MIB_2, 31 +#define MIB_IFMIBOBJECTS MIB_IFMIB, 1 +#define MIB_IFXTABLE MIB_IFMIBOBJECTS, 1 +#define MIB_IFXENTRY MIB_IFXTABLE, 1 +#define OIDIDX_IFX 10 +#define OIDIDX_IFXENTRY 11 +#define MIB_IFNAME MIB_IFXENTRY, 1 +#define MIB_IFINMASTPKTS MIB_IFXENTRY, 2 +#define MIB_IFINBASTPKTS MIB_IFXENTRY, 3 +#define MIB_IFOUTMASTPKTS MIB_IFXENTRY, 4 +#define MIB_IFOUTBASTPKTS MIB_IFXENTRY, 5 +#define MIB_IFHCINOCTETS MIB_IFXENTRY, 6 +#define MIB_IFHCINUCASTPKTS MIB_IFXENTRY, 7 +#define MIB_IFHCINMCASTPKTS MIB_IFXENTRY, 8 +#define MIB_IFHCINBCASTPKTS MIB_IFXENTRY, 9 +#define MIB_IFHCOUTOCTETS MIB_IFXENTRY, 10 +#define MIB_IFHCOUTUCASTPKTS MIB_IFXENTRY, 11 +#define MIB_IFHCOUTMCASTPKTS MIB_IFXENTRY, 12 +#define MIB_IFHCOUTBCASTPKTS MIB_IFXENTRY, 13 +#define MIB_IFLINKUPDORNTRAPENABLE MIB_IFXENTRY, 14 +#define MIB_IFHIGHSPEED MIB_IFXENTRY, 15 +#define MIB_IFPROMISCMODE MIB_IFXENTRY, 16 +#define MIB_IFCONNECTORPRESENT MIB_IFXENTRY, 17 +#define MIB_IFALIAS MIB_IFXENTRY, 18 +#define MIB_IFCNTDISCONTINUITYTIME MIB_IFXENTRY, 19 +#define MIB_IFSTACKTABLE MIB_IFMIBOBJECTS, 2 +#define MIB_IFSTACKENTRY MIB_IFSTACKTABLE, 1 +#define OIDIDX_IFSTACK 10 +#define OIDIDX_IFSTACKENTRY 11 +#define MIB_IFSTACKSTATUS MIB_IFSTACKENTRY, 3 +#define MIB_IFRCVTABLE MIB_IFMIBOBJECTS, 4 +#define MIB_IFRCVENTRY MIB_IFRCVTABLE, 1 +#define OIDIDX_IFRCV 10 +#define OIDIDX_IFRCVENTRY 11 +#define MIB_IFRCVSTATUS MIB_IFRCVENTRY, 2 +#define MIB_IFRCVTYPE MIB_IFRCVENTRY, 3 +#define MIB_IFSTACKLASTCHANGE MIB_IFMIBOBJECTS, 6 +#define MIB_INTERFACES MIB_MIB_2, 2 +#define MIB_IFNUMBER MIB_INTERFACES, 1 +#define MIB_IFTABLE MIB_INTERFACES, 2 +#define MIB_IFENTRY MIB_IFTABLE, 1 +#define OIDIDX_IF 9 +#define OIDIDX_IFENTRY 10 +#define MIB_IFINDEX MIB_IFENTRY, 1 +#define MIB_IFDESCR MIB_IFENTRY, 2 +#define MIB_IFTYPE MIB_IFENTRY, 3 +#define MIB_IFMTU MIB_IFENTRY, 4 +#define MIB_IFSPEED MIB_IFENTRY, 5 +#define MIB_IFPHYSADDR MIB_IFENTRY, 6 +#define MIB_IFADMINSTATUS MIB_IFENTRY, 7 +#define MIB_IFOPERSTATUS MIB_IFENTRY, 8 +#define MIB_IFLASTCHANGE MIB_IFENTRY, 9 +#define MIB_IFINOCTETS MIB_IFENTRY, 10 +#define MIB_IFINUCASTPKTS MIB_IFENTRY, 11 +#define MIB_IFINNUCASTPKTS MIB_IFENTRY, 12 +#define MIB_IFINDISCARDS MIB_IFENTRY, 13 +#define MIB_IFINERRORS MIB_IFENTRY, 14 +#define MIB_IFINUNKNOWNERRORS MIB_IFENTRY, 15 +#define MIB_IFOUTOCTETS MIB_IFENTRY, 16 +#define MIB_IFOUTUCASTPKTS MIB_IFENTRY, 17 +#define MIB_IFOUTNUCASTPKTS MIB_IFENTRY, 18 +#define MIB_IFOUTDISCARDS MIB_IFENTRY, 19 +#define MIB_IFOUTERRORS MIB_IFENTRY, 20 +#define MIB_IFOUTQLEN MIB_IFENTRY, 21 +#define MIB_IFSPECIFIC MIB_IFENTRY, 22 + +/* Some enterprise-sepcific OIDs */ +#define MIB_IBM MIB_ENTERPRISES, 2 +#define MIB_CMU MIB_ENTERPRISES, 3 +#define MIB_UNIX MIB_ENTERPRISES, 4 +#define MIB_CISCO MIB_ENTERPRISES, 9 +#define MIB_HP MIB_ENTERPRISES, 11 +#define MIB_MIT MIB_ENTERPRISES, 20 +#define MIB_NORTEL MIB_ENTERPRISES, 35 +#define MIB_SUN MIB_ENTERPRISES, 42 +#define MIB_3COM MIB_ENTERPRISES, 43 +#define MIB_SYNOPTICS MIB_ENTERPRISES, 45 +#define MIB_ENTERASYS MIB_ENTERPRISES, 52 +#define MIB_SGI MIB_ENTERPRISES, 59 +#define MIB_APPLE MIB_ENTERPRISES, 63 +#define MIB_ATT MIB_ENTERPRISES, 74 +#define MIB_NOKIA MIB_ENTERPRISES, 94 +#define MIB_CERN MIB_ENTERPRISES, 96 +#define MIB_FSC MIB_ENTERPRISES, 231 +#define MIB_COMPAQ MIB_ENTERPRISES, 232 +#define MIB_DELL MIB_ENTERPRISES, 674 +#define MIB_ALTEON MIB_ENTERPRISES, 1872 +#define MIB_EXTREME MIB_ENTERPRISES, 1916 +#define MIB_FOUNDRY MIB_ENTERPRISES, 1991 +#define MIB_HUAWAI MIB_ENTERPRISES, 2011 +#define MIB_UCDAVIS MIB_ENTERPRISES, 2021 +#define MIB_CHECKPOINT MIB_ENTERPRISES, 2620 +#define MIB_JUNIPER MIB_ENTERPRISES, 2636 +#define MIB_FORCE10 MIB_ENTERPRISES, 6027 +#define MIB_ALCATELLUCENT MIB_ENTERPRISES, 7483 +#define MIB_SNOM MIB_ENTERPRISES, 7526 +#define MIB_GOOGLE MIB_ENTERPRISES, 11129 +#define MIB_F5 MIB_ENTERPRISES, 12276 +#define MIB_SFLOW MIB_ENTERPRISES, 14706 +#define MIB_MSYS MIB_ENTERPRISES, 18623 +#define MIB_VANTRONIX MIB_ENTERPRISES, 26766 +#define MIB_OPENBSD MIB_VANTRONIX, 42 +#define MIB_SYSOID_DEFAULT MIB_OPENBSD, 2, 1 + +void mib_init(void); + +#endif /* _SNMPD_MIB_H */ diff --git a/usr.sbin/snmpd/mps.c b/usr.sbin/snmpd/mps.c new file mode 100644 index 00000000000..8a7c90ef887 --- /dev/null +++ b/usr.sbin/snmpd/mps.c @@ -0,0 +1,447 @@ +/* $OpenBSD: mps.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 <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_arp.h> +#include <net/if_media.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/if_ether.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" +#include "mib.h" + +RB_HEAD(oidtree, oid); +RB_PROTOTYPE(oidtree, oid, o_element, mps_oid_cmp); + +extern struct snmpd *env; +struct oidtree mps_oidtree; + +struct ber_oid * + mps_table(struct oid *, struct ber_oid *, struct ber_oid *); + +u_long +mps_getticks(void) +{ + struct timeval now, run; + u_long ticks; + + gettimeofday(&now, NULL); + if (timercmp(&now, &env->sc_starttime, <=)) + return (0); + timersub(&now, &env->sc_starttime, &run); + ticks = run.tv_sec * 100; + if (run.tv_usec) + ticks += run.tv_usec / 10000; + + return (ticks); +} + +int +mps_getstr(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + char *s = oid->o_data; + + if (s == NULL) + return (-1); + *elm = ber_add_string(*elm, s); + return (0); +} + +int +mps_setstr(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + char *s; + + if ((oid->o_flags & OID_WR) == 0) + return (-1); + + if (ber->be_class != BER_CLASS_UNIVERSAL || + ber->be_type != BER_TYPE_OCTETSTRING) + return (-1); + if (ber_get_string(ber, &s) == -1) + return (-1); + if ((oid->o_data = (void *)strdup(s)) == NULL) + return (-1); + oid->o_val = strlen((char *)oid->o_data); + + return (0); +} + +int +mps_getint(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + *elm = ber_add_integer(*elm, oid->o_val); + return (0); +} + +int +mps_setint(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + long long i; + + if (ber_get_integer(*elm, &i) == -1) + return (-1); + oid->o_val = i; + + return (0); +} + +int +mps_getts(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + *elm = ber_add_integer(*elm, oid->o_val); + ber_set_header(*elm, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS); + return (0); +} + +void +mps_oidlen(struct ber_oid *o) +{ + size_t i; + for (i = 0; i < BER_MAX_OID_LEN && o->bo_id[i] != 0; i++); + o->bo_n = i; +} + +char * +mps_oidstring(struct ber_oid *o, char *buf, size_t len) +{ + char str[256]; + struct oid *value, key; + size_t i, lookup = 1; + + bzero(buf, len); + bcopy(o, &key.o_id, sizeof(struct ber_oid)); + key.o_flags |= OID_TABLE; /* do not match wildcards */ + + if (env->sc_flags & SNMPD_F_NONAMES) + lookup = 0; + + for (i = 0; i < o->bo_n; i++) { + key.o_oidlen = i + 1; + if (lookup && + (value = RB_FIND(oidtree, &mps_oidtree, &key)) != NULL) + snprintf(str, sizeof(str), "%s", value->o_name); + else + snprintf(str, sizeof(str), "%d", key.o_oid[i]); + strlcat(buf, str, len); + if (i < (o->bo_n - 1)) + strlcat(buf, ".", len); + } + + return (buf); +} + +struct ber_element * +mps_getreq(struct ber_element *root, struct ber_oid *o) +{ + struct ber_element *elm = root; + struct oid key, *value; + + mps_oidlen(o); + if (o->bo_n > BER_MAX_OID_LEN) + return (NULL); + bcopy(o, &key.o_id, sizeof(struct ber_oid)); + value = RB_FIND(oidtree, &mps_oidtree, &key); + if (value == NULL) + return (NULL); + if (OID_NOTSET(value)) + return (NULL); + if ((value->o_flags & OID_TABLE) == 0) + elm = ber_add_oid(elm, &value->o_id); + if (value->o_get == NULL) + elm = ber_add_null(elm); + else + if (value->o_get(value, o, &elm) != 0) + return (NULL); + + return (elm); +} + +int +mps_setreq(struct ber_element *ber, struct ber_oid *o) +{ + struct oid key, *value; + + mps_oidlen(o); + if (o->bo_n > BER_MAX_OID_LEN) + return (-1); + bcopy(o, &key.o_id, sizeof(struct ber_oid)); + value = RB_FIND(oidtree, &mps_oidtree, &key); + if (value == NULL) + return (-1); + if ((value->o_flags & OID_WR) == 0 || + value->o_set == NULL) + return (-1); + return (value->o_set(value, o, &ber)); +} + +struct ber_element * +mps_getnextreq(struct ber_element *root, struct ber_oid *o) +{ + struct oid *next = NULL; + struct ber_element *ber = root; + struct oid key, *value; + int ret; + struct ber_oid no; + + mps_oidlen(o); + if (o->bo_n > BER_MAX_OID_LEN) + return (NULL); + bcopy(o, &key.o_id, sizeof(struct ber_oid)); + value = RB_FIND(oidtree, &mps_oidtree, &key); + if (value == NULL) + return (NULL); + if (value->o_flags & OID_TABLE) { + /* Get the next table row for this column */ + if (mps_table(value, o, &no) == NULL) + return (NULL); + bcopy(&no, o, sizeof(*o)); + ret = value->o_get(value, o, &ber); + switch (ret) { + case 0: + return (ber); + case -1: + return (NULL); + case 1: /* end-of-rows */ + break; + } + } + for (next = value; next != NULL;) { + next = RB_NEXT(oidtree, &mps_oidtree, next); + if (next == NULL) + break; + if (!OID_NOTSET(next) && next->o_get != NULL) + break; + } + if (next == NULL || next->o_get == NULL) + return (NULL); + + if (next->o_flags & OID_TABLE) { + /* Get the next table row for this column */ + if (mps_table(next, o, &no) == NULL) + return (NULL); + bcopy(&no, o, sizeof(*o)); + if ((ret = next->o_get(next, o, &ber)) != 0) + return (NULL); + } else { + bcopy(&next->o_id, o, sizeof(*o)); + ber = ber_add_oid(ber, &next->o_id); + if ((ret = next->o_get(next, o, &ber)) != 0) + return (NULL); + } + + return (ber); +} + +int +mps_set(struct ber_oid *o, void *p, long long len) +{ + struct oid key, *value; + + mps_oidlen(o); + if (o->bo_n > BER_MAX_OID_LEN) + return (-1); + bcopy(o, &key.o_id, sizeof(struct ber_oid)); + value = RB_FIND(oidtree, &mps_oidtree, &key); + if (value == NULL) + return (-1); + if (value->o_data != NULL) + free(value->o_data); + value->o_data = p; + value->o_val = len; + + return (0); +} + +struct ber_oid * +mps_table(struct oid *oid, struct ber_oid *o, struct ber_oid *no) +{ + u_int32_t col, idx = 1; + size_t id, subid; + struct oid a, b; + + /* + * This function is beeing used to iterate through elements + * in a SMI "table". It is called by the mps_getnext() handler. + * For example, if the input is sysORIndex, it will return + * sysORIndex.1. If the inpurt is sysORIndex.2, it will return + * sysORIndex.3 etc.. The MIB code has to verify the index, + * see mib_sysor() as an example. + */ + + id = oid->o_oidlen - 1; + subid = oid->o_oidlen; + bcopy(&oid->o_id, no, sizeof(*no)); + + if (o->bo_n >= oid->o_oidlen) { + /* + * Compare the requested and the matched OID to see + * if we have to iterate to the next element. + */ + bzero(&a, sizeof(a)); + bcopy(o, &a.o_id, sizeof(struct ber_oid)); + bzero(&b, sizeof(b)); + bcopy(&oid->o_id, &b.o_id, sizeof(struct ber_oid)); + b.o_oidlen--; + b.o_flags |= OID_TABLE; + if (mps_oid_cmp(&a, &b) == 0) { + col = oid->o_oid[id]; + if (col > o->bo_id[id]) + idx = 1; + else + idx = o->bo_id[subid] + 1; + o->bo_id[subid] = idx; + o->bo_id[id] = col; + bcopy(o, no, sizeof(*no)); + } + } + + /* The root element ends with a 0, iterate to the first element */ + if (!no->bo_id[subid]) + no->bo_id[subid]++; + + mps_oidlen(no); + + return (no); +} + +void +mps_delete(struct oid *oid) +{ + struct oid key, *value; + + bcopy(&oid->o_id, &key.o_id, sizeof(struct ber_oid)); + if ((value = RB_FIND(oidtree, &mps_oidtree, &key)) != NULL && + value == oid) + RB_REMOVE(oidtree, &mps_oidtree, value); + + if (oid->o_data != NULL) + free(oid->o_data); + if (oid->o_flags & OID_DYNAMIC) { + free(oid->o_name); + free(oid); + } +} + +void +mps_insert(struct oid *oid) +{ + struct oid key, *value; + + if ((oid->o_flags & OID_TABLE) && oid->o_get == NULL) + fatalx("mps_insert: invalid MIB table"); + + bcopy(&oid->o_id, &key.o_id, sizeof(struct ber_oid)); + value = RB_FIND(oidtree, &mps_oidtree, &key); + if (value != NULL) + mps_delete(value); + + RB_INSERT(oidtree, &mps_oidtree, oid); +} + +void +mps_mibtree(struct oid *oids, size_t n) +{ + struct oid *oid; + size_t i; + + for (i = 0; i < n; i++) { + oid = &oids[i]; + mps_oidlen(&oid->o_id); + if ((oid->o_flags & OID_TABLE) && oid->o_get == NULL) + fatalx("mps_mibtree: invalid MIB table"); + RB_INSERT(oidtree, &mps_oidtree, oid); + } +} + +int +mps_init(void) +{ + RB_INIT(&mps_oidtree); + mib_init(); + return (0); +} + +struct oid * +mps_foreach(struct oid *oid, u_int flags) +{ + /* + * Traverse the tree of MIBs with the option to check + * for specific OID flags. + */ + if (oid == NULL) { + oid = RB_MIN(oidtree, &mps_oidtree); + if (oid == NULL) + return (NULL); + if (flags == 0 || (oid->o_flags & flags)) + return (oid); + } + for (;;) { + oid = RB_NEXT(oidtree, &mps_oidtree, oid); + if (oid == NULL) + break; + if (flags == 0 || (oid->o_flags & flags)) + return (oid); + } + + return (oid); +} + +long +mps_oid_cmp(struct oid *a, struct oid *b) +{ + size_t i; + + for (i = 0; i < MIN(a->o_oidlen, b->o_oidlen); i++) + if (a->o_oid[i] != b->o_oid[i]) + return (a->o_oid[i] - b->o_oid[i]); + + /* + * Return success if the matched object is a table + * (it will match any sub-elements) + */ + if ((b->o_flags & OID_TABLE) && + (a->o_flags & OID_TABLE) == 0) + return (0); + + return (a->o_oidlen - b->o_oidlen); +} + +RB_GENERATE(oidtree, oid, o_element, mps_oid_cmp); diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y new file mode 100644 index 00000000000..d7ac346d6bd --- /dev/null +++ b/usr.sbin/snmpd/parse.y @@ -0,0 +1,936 @@ +/* $OpenBSD: parse.y,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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/stat.h> +#include <sys/queue.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <net/if.h> + +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <ctype.h> +#include <unistd.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <limits.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdio.h> +#include <netdb.h> +#include <string.h> + +#include "snmpd.h" +#include "mib.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct snmpd *conf = NULL; +static int errors = 0; + +struct address *host_v4(const char *); +struct address *host_v6(const char *); +int host_dns(const char *, struct addresslist *, + int, in_port_t, const char *); +int host(const char *, struct addresslist *, + int, in_port_t, const char *); + +typedef struct { + union { + int64_t number; + char *string; + struct host *host; + struct timeval tv; + struct ber_oid *oid; + struct { + int type; + void *data; + long long value; + } data; + } v; + int lineno; +} YYSTYPE; + +%} + +%token INCLUDE +%token LISTEN ON +%token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES +%token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP +%token ERROR +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> optwrite +%type <v.data> objtype +%type <v.oid> oid + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar varset '\n' + | grammar main '\n' + | grammar system '\n' + | grammar mib '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +main : LISTEN ON STRING { + struct addresslist al; + struct address *h; + + TAILQ_INIT(&al); + if (host($3, &al, 1, SNMPD_PORT, NULL) <= 0) { + yyerror("invalid ip address: %s", $3); + free($3); + YYERROR; + } + free($3); + h = TAILQ_FIRST(&al); + bcopy(&h->ss, &conf->sc_address.ss, sizeof(*h)); + conf->sc_address.port = h->port; + } + | READONLY COMMUNITY STRING { + if (strlcpy(conf->sc_rdcommunity, $3, + sizeof(conf->sc_rdcommunity)) >= + sizeof(conf->sc_rdcommunity)) { + yyerror("r/o community name too long"); + free($3); + YYERROR; + } + free($3); + } + | READWRITE COMMUNITY STRING { + if (strlcpy(conf->sc_rwcommunity, $3, + sizeof(conf->sc_rwcommunity)) >= + sizeof(conf->sc_rwcommunity)) { + yyerror("r/w community name too long"); + free($3); + YYERROR; + } + free($3); + } + | TRAP COMMUNITY STRING { + if (strlcpy(conf->sc_trcommunity, $3, + sizeof(conf->sc_trcommunity)) >= + sizeof(conf->sc_trcommunity)) { + yyerror("r/w community name too long"); + free($3); + YYERROR; + } + free($3); + } + ; + +system : SYSTEM sysmib + ; + +sysmib : CONTACT STRING { + struct ber_oid o = OID(MIB_SYSCONTACT); + mps_set(&o, $2, strlen($2)); + } + | DESCR STRING { + struct ber_oid o = OID(MIB_SYSDESCR); + mps_set(&o, $2, strlen($2)); + } + | LOCATION STRING { + struct ber_oid o = OID(MIB_SYSLOCATION); + mps_set(&o, $2, strlen($2)); + } + | NAME STRING { + struct ber_oid o = OID(MIB_SYSNAME); + mps_set(&o, $2, strlen($2)); + } + | OBJECTID oid { + struct ber_oid o = OID(MIB_SYSOID); + mps_set(&o, $2, sizeof(struct ber_oid)); + } + | SERVICES NUMBER { + struct ber_oid o = OID(MIB_SYSSERVICES); + mps_set(&o, NULL, $2); + } + ; + +mib : OBJECTID oid NAME STRING optwrite objtype { + struct oid *oid; + if ((oid = (struct oid *) + calloc(1, sizeof(*oid))) == NULL) { + yyerror("calloc"); + free($2); + free($6.data); + YYERROR; + } + + mps_oidlen($2); + bcopy($2, &oid->o_id, sizeof(struct ber_oid)); + free($2); + oid->o_name = $4; + oid->o_data = $6.data; + oid->o_val = $6.value; + switch ($6.type) { + case 1: + oid->o_get = mps_getint; + oid->o_set = mps_setint; + break; + case 2: + oid->o_get = mps_getstr; + oid->o_set = mps_setstr; + break; + } + oid->o_flags = OID_RD|OID_DYNAMIC; + if ($5) + oid->o_flags |= OID_WR; + + mps_insert(oid); + } + ; + +objtype : INTEGER NUMBER { + $$.type = 1; + $$.data = NULL; + $$.value = $2; + } + | OCTETSTRING STRING { + $$.type = 2; + $$.data = $2; + $$.value = strlen($2); + } + ; + +optwrite : READONLY { $$ = 0; } + | READWRITE { $$ = 1; } + ; + +oid : STRING { + struct ber_oid *sysoid; + if ((sysoid = + calloc(1, sizeof(*sysoid))) == NULL) { + yyerror("calloc"); + free($1); + YYERROR; + } + if (ber_string2oid($1, sysoid) == -1) { + yyerror("invalid OID: %s", $1); + free(sysoid); + free($1); + YYERROR; + } + free($1); + $$ = sysoid; + } + ; + +comma : ',' + | /* empty */ + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "community", COMMUNITY }, + { "contact", CONTACT }, + { "description", DESCR }, + { "include", INCLUDE }, + { "integer", INTEGER }, + { "listen", LISTEN }, + { "location", LOCATION }, + { "name", NAME }, + { "oid", OBJECTID }, + { "on", ON }, + { "read-only", READONLY }, + { "read-write", READWRITE }, + { "services", SERVICES }, + { "string", OCTETSTRING }, + { "system", SYSTEM }, + { "trap", TRAP } + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing quoted string"); + if (popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(file->stream); + } while (c == '\t' || c == ' '); + ungetc(c, file->stream); + c = ' '; + } + + while (c == EOF) { + if (popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') + continue; + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IRWXG | S_IRWXO)) { + log_warnx("%s: group/world readable/writeable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL || + (nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) { + prev->errors += file->errors; + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (0); + } + return (EOF); +} + +struct snmpd * +parse_config(const char *filename, u_int flags) +{ + struct sym *sym, *next; + + if ((conf = calloc(1, sizeof(*conf))) == NULL) { + log_warn("cannot allocate memory"); + return (NULL); + } + + conf->sc_flags = flags; + conf->sc_confpath = filename; + conf->sc_address.ss.ss_family = AF_INET; + conf->sc_address.port = htons(SNMPD_PORT); + strlcpy(conf->sc_rdcommunity, "public", SNMPD_MAXCOMMUNITYLEN); + strlcpy(conf->sc_rwcommunity, "private", SNMPD_MAXCOMMUNITYLEN); + strlcpy(conf->sc_trcommunity, "public", SNMPD_MAXCOMMUNITYLEN); + + if ((file = pushfile(filename, 0)) == NULL) { + free(conf); + return (NULL); + } + setservent(1); + + yyparse(); + errors = file->errors; + popfile(); + + endservent(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((conf->sc_flags & SNMPD_F_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + free(conf); + return (NULL); + } + + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + (void)strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +struct address * +host_v4(const char *s) +{ + struct in_addr ina; + struct sockaddr_in *sain; + struct address *h; + + bzero(&ina, sizeof(ina)); + if (inet_pton(AF_INET, s, &ina) != 1) + return (NULL); + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_family = AF_INET; + sain->sin_addr.s_addr = ina.s_addr; + + return (h); +} + +struct address * +host_v6(const char *s) +{ + struct in6_addr ina6; + struct sockaddr_in6 *sin6; + struct address *h; + + bzero(&ina6, sizeof(ina6)); + if (inet_pton(AF_INET6, s, &ina6) != 1) + return (NULL); + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6)); + + return (h); +} + +int +host_dns(const char *s, struct addresslist *al, int max, + in_port_t port, const char *ifname) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct address *h; + + bzero(&hints, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ + error = getaddrinfo(s, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("host_dns: could not parse \"%s\": %s", s, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res && cnt < max; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + + h->port = port; + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) + log_warnx("host_dns: interface name truncated"); + return (-1); + } + h->ss.ss_family = res->ai_family; + if (res->ai_family == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + } + + TAILQ_INSERT_HEAD(al, h, entry); + cnt++; + } + if (cnt == max && res) { + log_warnx("host_dns: %s resolves to more than %d hosts", + s, max); + } + freeaddrinfo(res0); + return (cnt); +} + +int +host(const char *s, struct addresslist *al, int max, + in_port_t port, const char *ifname) +{ + struct address *h; + + h = host_v4(s); + + /* IPv6 address? */ + if (h == NULL) + h = host_v6(s); + + if (h != NULL) { + h->port = port; + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) { + log_warnx("host: interface name truncated"); + return (-1); + } + } + + TAILQ_INSERT_HEAD(al, h, entry); + return (1); + } + + return (host_dns(s, al, max, port, ifname)); +} diff --git a/usr.sbin/snmpd/snmp.h b/usr.sbin/snmpd/snmp.h new file mode 100644 index 00000000000..5bb21f7e199 --- /dev/null +++ b/usr.sbin/snmpd/snmp.h @@ -0,0 +1,86 @@ +/* $OpenBSD: snmp.h,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. + */ + +#ifndef SNMP_HEADER +#define SNMP_HEADER + +enum snmp_version { + SNMP_V1 = 0, + SNMP_V2 = 1, + SNMP_V3 = 2 +}; + +enum snmp_context { + SNMP_T_GETREQ = 0, + SNMP_T_GETNEXTREQ = 1, + SNMP_T_GETRESP = 2, + SNMP_T_SETREQ = 3, + SNMP_T_TRAP = 4, + + /* SNMPv2 */ + SNMP_T_GETBULKREQ = 5, + SNMP_T_INFORMREQ = 6, + SNMP_T_TRAPV2 = 7, + SNMP_T_REPORT = 8 +}; + +enum snmp_application { + SNMP_T_IPADDR = 0, + SNMP_T_COUNTER32 = 1, + SNMP_T_GAUGE32 = 2, + SNMP_T_UNSIGNED32 = 2, + SNMP_T_TIMETICKS = 3, + SNMP_T_OPAQUE = 4, + SNMP_T_COUNTER64 = 6, +}; + +enum snmp_generic_trap { + SNMP_TRAP_COLDSTART = 0, + SNMP_TRAP_WARMSTART = 1, + SNMP_TRAP_LINKDOWN = 2, + SNMP_TRAP_LINKUP = 3, + SNMP_TRAP_AUTHFAILURE = 4, + SNMP_TRAP_EGPNEIGHLOSS = 5, + SNMP_TRAP_ENTERPRISE = 6 +}; + +enum snmp_error { + SNMP_ERROR_NONE = 0, + SNMP_ERROR_TOOBIG = 1, + SNMP_ERROR_NOSUCHNAME = 2, + SNMP_ERROR_BADVALUE = 3, + SNMP_ERROR_READONLY = 4, + SNMP_ERROR_GENERR = 5, + + /* SNMPv2 */ + SNMP_ERROR_NOACCESS = 6, + SNMP_ERROR_WRONGTYPE = 7, + SNMP_ERROR_WRONGLENGTH = 8, + SNMP_ERROR_WRONGENC = 9, + SNMP_ERROR_WRONGVALUE = 10, + SNMP_ERROR_NOCREATION = 11, + SNMP_ERROR_INCONVALUE = 12, + SNMP_ERROR_RESUNAVAIL = 13, /* EGAIN */ + SNMP_ERROR_COMMITFAILED = 14, + SNMP_ERROR_UNDOFAILED = 15, + SNMP_ERROR_AUTHERROR = 16, + SNMP_ERROR_NOTWRITABLE = 17, + SNMP_ERROR_INCONNAME = 18 +}; + +#endif /* SNMP_HEADER */ diff --git a/usr.sbin/snmpd/snmpd.8 b/usr.sbin/snmpd/snmpd.8 new file mode 100644 index 00000000000..c0e5e022146 --- /dev/null +++ b/usr.sbin/snmpd/snmpd.8 @@ -0,0 +1,93 @@ +.\" $OpenBSD: snmpd.8,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. +.\" +.Dd $Mdocdate: December 5 2007 $ +.Dt SNMPD 8 +.Os +.Sh NAME +.Nm snmpd +.Nd Simple Network Management Protocol Daemon +.Sh SYNOPSIS +.Nm snmpd +.Op Fl dv +.Oo Xo +.Fl D Ar macro Ns = Ns Ar value Oc +.Xc +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is a daemon which implement the SNMP protocol. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize and log to +.Em stderr . +.It Fl f Ar file +Use +.Ar file +as the configuration file, instead of the default +.Pa /etc/snmpd.conf . +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/etc/snmpd.confXXX" -compact +.It Pa /etc/snmpd.conf +default +.Nm +configuration file +.El +.Sh SEE ALSO +.Xr snmpd.conf 5 , +.Rs +.%R RFC 1157 +.%T A Simple Network Management Protocol (SNMP) +.%D May 1990 +.Re +.Rs +.%R http://www.ibr.cs.tu-bs.de/projects/snmpv3/ +.%T SNMP Version 3 (SNMPv3) +.%D March 2002 +.Re +.Rs +.%R RFC 3410 +.%T Introduction and Applicability Statements for Internet Standard Management Framework +.%D December 2002 +.Re +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 4.3 . +.Sh AUTHORS +The +.Nm +program was written by +.An Reyk Floeter Aq reyk@vantronix.net . +.Sh CAVEATS +The +.Nm +does not fully work yet. diff --git a/usr.sbin/snmpd/snmpd.c b/usr.sbin/snmpd/snmpd.c new file mode 100644 index 00000000000..292c9d9ae42 --- /dev/null +++ b/usr.sbin/snmpd/snmpd.c @@ -0,0 +1,295 @@ +/* $OpenBSD: snmpd.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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <sys/tree.h> + +#include <net/if.h> + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <signal.h> +#include <unistd.h> +#include <pwd.h> + +#include "snmpd.h" + +__dead void usage(void); + +void snmpd_sig_handler(int, short, void *); +void snmpd_shutdown(struct snmpd *); +void snmpd_dispatch_snmpe(int, short, void *); +int check_child(pid_t, const char *); + +struct snmpd *snmpd_env; + +int pipe_parent2snmpe[2]; +struct imsgbuf *ibuf_snmpe; +pid_t snmpe_pid = 0; + +void +snmpd_sig_handler(int sig, short event, void *arg) +{ + struct snmpd *env = arg; + int die = 0; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + if (check_child(snmpe_pid, "snmp engine")) { + snmpe_pid = 0; + die = 1; + } + if (die) + snmpd_shutdown(env); + break; + case SIGHUP: + /* reconfigure */ + break; + default: + fatalx("unexpected signal"); + } +} + +/* __dead is for lint */ +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "%s [-dnNv] [-D macro=value] [-f file]\n", __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int c; + struct snmpd *env; + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + int debug = 0; + u_int flags = 0; + int noaction = 0; + const char *conffile = CONF_FILE; + + mps_init(); + + log_init(1); /* log to stderr until daemonized */ + + while ((c = getopt(argc, argv, "dD:nNf:v")) != -1) { + switch (c) { + case 'd': + debug = 1; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'n': + noaction++; + break; + case 'N': + flags |= SNMPD_F_NONAMES; + break; + case 'f': + conffile = optarg; + break; + case 'v': + flags |= SNMPD_F_VERBOSE; + break; + default: + usage(); + } + } + + if ((env = parse_config(conffile, flags)) == NULL) + exit(1); + snmpd_env = env; + + if (noaction) { + fprintf(stderr, "configuration ok\n"); + exit(0); + } + + if (geteuid()) + errx(1, "need root privileges"); + + if (getpwnam(SNMPD_USER) == NULL) + errx(1, "unknown user %s", SNMPD_USER); + + log_init(debug); + + if (!debug) { + if (daemon(1, 0) == -1) + err(1, "failed to daemonize"); + } + + gettimeofday(&env->sc_starttime, NULL); + + log_info("startup"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, + pipe_parent2snmpe) == -1) + fatal("socketpair"); + + session_socket_blockmode(pipe_parent2snmpe[0], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2snmpe[1], BM_NONBLOCK); + + snmpe_pid = snmpe(env, pipe_parent2snmpe); + setproctitle("parent"); + + event_init(); + + signal_set(&ev_sigint, SIGINT, snmpd_sig_handler, env); + signal_set(&ev_sigterm, SIGTERM, snmpd_sig_handler, env); + signal_set(&ev_sigchld, SIGCHLD, snmpd_sig_handler, env); + signal_set(&ev_sighup, SIGHUP, snmpd_sig_handler, env); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + close(pipe_parent2snmpe[1]); + + if ((ibuf_snmpe = calloc(1, sizeof(struct imsgbuf))) == NULL) + fatal(NULL); + + imsg_init(ibuf_snmpe, pipe_parent2snmpe[0], snmpd_dispatch_snmpe); + + ibuf_snmpe->events = EV_READ; + event_set(&ibuf_snmpe->ev, ibuf_snmpe->fd, ibuf_snmpe->events, + ibuf_snmpe->handler, ibuf_snmpe); + event_add(&ibuf_snmpe->ev, NULL); + + event_dispatch(); + + return (0); +} + +void +snmpd_shutdown(struct snmpd *env) +{ + pid_t pid; + + if (snmpe_pid) + kill(snmpe_pid, SIGTERM); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + control_cleanup(); + log_info("terminating"); + exit(0); +} + +int +check_child(pid_t pid, const char *pname) +{ + int status; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("check_child: lost child: %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + log_warnx("check_child: lost child: %s terminated; " + "signal %d", pname, WTERMSIG(status)); + return (1); + } + } + + return (0); +} + +void +imsg_event_add(struct imsgbuf *ibuf) +{ + ibuf->events = EV_READ; + if (ibuf->w.queued) + ibuf->events |= EV_WRITE; + + event_del(&ibuf->ev); + event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf); + event_add(&ibuf->ev, NULL); +} + +void +snmpd_dispatch_snmpe(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("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("snmpd_dispatch_relay: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("snmpd_dispatch_relay: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} diff --git a/usr.sbin/snmpd/snmpd.conf b/usr.sbin/snmpd/snmpd.conf new file mode 100644 index 00000000000..5173b98dc2d --- /dev/null +++ b/usr.sbin/snmpd/snmpd.conf @@ -0,0 +1,9 @@ +listen on 127.0.0.1 + +system oid 1.3.6.1.4.1.26766.42.2.1.42 +system services 74 + +oid 1.3.6.1.4.1.26766.42.3.1 name humppa read-only string "foobar" +oid 1.3.6.1.4.1.26766.42.3.2 name jenka read-only string "jenka" +oid 1.3.6.1.4.1.26766.42.3.3 name polka read-write string "polka" +oid 1.3.6.1.4.1.26766.42.3.4 name walzer read-write integer 1 diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 new file mode 100644 index 00000000000..5bb650dc022 --- /dev/null +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -0,0 +1,83 @@ +.\" $OpenBSD: snmpd.conf.5,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. +.\" +.Dd $Mdocdate: December 5 2007 $ +.Dt SNMPD.CONF 5 +.Os +.Sh NAME +.Nm snmpd.conf +.Nd configuration file for the SNMP daemon +.Sh DESCRIPTION +.Nm +is the configuration file for the +.Xr snmpd 8 +daemon. +.Sh SECTIONS +The +.Nm +file is divided into n main sections. +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global runtime settings for +.Xr snmpd 8 . +.El +.Pp +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/snmpd.conf.local" +.Ed +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, and may contain letters, digits +and underscores. +Macro names may not be reserved words (for example, +.Ic foo , +.Ic bar , +or +.Ic baz ) . +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent +var="blah" +foo $var +.Ed +.Sh FILES +.Bl -tag -width "/etc/snmpd.conf" -compact +.It Pa /etc/snmpd.conf +Default location of the configuration file. +.El +.Sh SEE ALSO +.Xr snmpd 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 4.3 . +.Sh AUTHORS +The +.Xr snmpd 8 +program was written by +.An Reyk Floeter Aq reyk@vantronix.net . diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h new file mode 100644 index 00000000000..b8570b3ddaa --- /dev/null +++ b/usr.sbin/snmpd/snmpd.h @@ -0,0 +1,407 @@ +/* $OpenBSD: snmpd.h,v 1.1 2007/12/05 09:22:44 reyk Exp $ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef _SNMPD_H +#define _SNMPD_H + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <net/route.h> + +#include <ber.h> +#include <snmp.h> + +/* + * common definitions for snmpd + */ + +#define CONF_FILE "/etc/snmpd.conf" +#define SNMPD_SOCKET "/var/run/snmpd.sock" +#define SNMPD_USER "_snmpd" +#define SNMPD_PORT 161 +#define SNMPD_TRAPPORT 162 + +#define SNMPD_MAXSTRLEN 484 +#define SNMPD_MAXCOMMUNITYLEN SNMPD_MAXSTRLEN +#define SNMPD_MAXVARBINDLEN 1210 + +#define SMALL_READ_BUF_SIZE 1024 +#define READ_BUF_SIZE 65535 +#define RT_BUF_SIZE 16384 +#define MAX_RTSOCK_BUF (128 * 1024) + +struct address { + struct sockaddr_storage ss; + in_port_t port; + char ifname[IFNAMSIZ]; + TAILQ_ENTRY(address) entry; +}; +TAILQ_HEAD(addresslist, address); + +/* + * imsg framework and privsep + */ + +struct buf { + TAILQ_ENTRY(buf) entry; + u_char *buf; + size_t size; + size_t max; + size_t wpos; + size_t rpos; + int fd; +}; + +struct msgbuf { + TAILQ_HEAD(, buf) bufs; + u_int32_t queued; + int fd; +}; + +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 8192 + +struct buf_read { + u_char buf[READ_BUF_SIZE]; + u_char *rptr; + size_t wpos; +}; + +struct imsg_fd { + TAILQ_ENTRY(imsg_fd) entry; + int fd; +}; + +struct imsgbuf { + TAILQ_HEAD(, imsg_fd) fds; + struct buf_read r; + struct msgbuf w; + struct event ev; + void (*handler)(int, short, void *); + int fd; + pid_t pid; + short events; +}; + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_OK, /* answer to snmpctl requests */ + IMSG_CTL_FAIL, + IMSG_CTL_END, + IMSG_CTL_NOTIFY +}; + +struct imsg_hdr { + u_int16_t type; + u_int16_t len; + u_int32_t peerid; + pid_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + void *data; +}; + +enum { + PROC_PARENT, /* Parent process and application interface */ + PROC_SNMPE /* SNMP engine */ +} snmpd_process; + +/* initially control.h */ +struct { + struct event ev; + int fd; +} control_state; + +enum blockmodes { + BM_NORMAL, + BM_NONBLOCK +}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + u_int8_t flags; +#define CTL_CONN_NOTIFY 0x01 + struct imsgbuf ibuf; + +}; +TAILQ_HEAD(ctl_connlist, ctl_conn); +extern struct ctl_connlist ctl_conns; + +/* + * kroute + */ + +struct kroute { + struct in_addr prefix; + struct in_addr nexthop; + u_int16_t flags; + u_int16_t rtlabel; + u_short if_index; + u_int8_t prefixlen; + u_long ticks; +}; + +struct kif_addr { + TAILQ_ENTRY(kif_addr) entry; + struct in_addr addr; + struct in_addr mask; + struct in_addr dstbrd; +}; + +struct kif { + char if_name[IF_NAMESIZE]; + char if_descr[IFDESCRSIZE]; + u_int8_t if_lladdr[ETHER_ADDR_LEN]; + int if_flags; + u_short if_index; + u_int8_t if_nhreachable; /* for nexthop verification */ + u_long if_ticks; + struct if_data if_data; +}; + +#define F_OSPFD_INSERTED 0x0001 +#define F_KERNEL 0x0002 +#define F_BGPD_INSERTED 0x0004 +#define F_CONNECTED 0x0008 +#define F_DOWN 0x0010 +#define F_STATIC 0x0020 +#define F_DYNAMIC 0x0040 +#define F_REDISTRIBUTED 0x0100 + +/* + * Message Processing Subsystem (mps) + */ + +struct oid { + struct ber_oid o_id; +#define o_oid o_id.bo_id +#define o_oidlen o_id.bo_n + + char *o_name; + + u_int o_flags; + + int (*o_get)(struct oid *, struct ber_oid *, + struct ber_element **); + int (*o_set)(struct oid *, struct ber_oid *, + struct ber_element **); + + void *o_data; + long long o_val; + + RB_ENTRY(oid) o_element; +}; + +#define OID_ROOT 0x00 +#define OID_RD 0x01 +#define OID_WR 0x02 +#define OID_IFSET 0x04 /* only if user-specified value */ +#define OID_DYNAMIC 0x08 /* free allocated data */ +#define OID_TABLE 0x10 /* dynamic sub-elements */ +#define OID_MIB 0x20 /* root-OID of a supported MIB */ + +#define OID_RS (OID_RD|OID_IFSET) +#define OID_WS (OID_WR|OID_IFSET) +#define OID_RW (OID_RD|OID_WR) +#define OID_RWS (OID_RW|OID_IFSET) + +#define OID_TRD (OID_RD|OID_TABLE) +#define OID_TWR (OID_WR|OID_TABLE) +#define OID_TRS (OID_RD|OID_IFSET|OID_TABLE) +#define OID_TWS (OID_WR|OID_IFSET|OID_TABLE) +#define OID_TRW (OID_RD|OID_WR|OID_TABLE) +#define OID_TRWS (OID_RW|OID_IFSET|OID_TABLE) + +#define OID_NOTSET(_oid) \ + (((_oid)->o_flags & OID_IFSET) && \ + ((_oid)->o_data == NULL) && ((_oid)->o_val == 0)) + +#define OID(_mib...) { { _mib } } +#define MIB(_mib...) { { MIB_##_mib } } + +/* + * daemon structures + */ + +struct snmp_message { + u_int8_t sm_version; + char sm_community[SNMPD_MAXCOMMUNITYLEN]; + u_int sm_context; + + struct ber_element *sm_header; + struct ber_element *sm_headerend; + + long long sm_request; + + long long sm_error; +#define sm_nonrepeaters sm_error + long long sm_errorindex; +#define sm_maxrepetitions sm_errorindex + + struct ber_element *sm_pdu; + struct ber_element *sm_pduend; + + struct ber_element *sm_varbind; + struct ber_element *sm_varbindresp; +}; + +/* Defined in SNMPv2-MIB.txt (RFC 3418) */ +struct snmp_stats { + u_int32_t snmp_inpkts; + u_int32_t snmp_outpkts; + u_int32_t snmp_inbadversions; + u_int32_t snmp_inbadcommunitynames; + u_int32_t snmp_inbadcommunityuses; + u_int32_t snmp_inasnparseerrs; + u_int32_t snmp_intoobigs; + u_int32_t snmp_innosuchnames; + u_int32_t snmp_inbadvalues; + u_int32_t snmp_inreadonlys; + u_int32_t snmp_ingenerrs; + u_int32_t snmp_intotalreqvars; + u_int32_t snmp_intotalsetvars; + u_int32_t snmp_ingetrequests; + u_int32_t snmp_ingetnexts; + u_int32_t snmp_insetrequests; + u_int32_t snmp_ingetresponses; + u_int32_t snmp_intraps; + u_int32_t snmp_outtoobigs; + u_int32_t snmp_outnosuchnames; + u_int32_t snmp_outbadvalues; + u_int32_t snmp_outgenerrs; + u_int32_t snmp_outgetrequests; + u_int32_t snmp_outgetnexts; + u_int32_t snmp_outsetrequests; + u_int32_t snmp_outgetresponses; + u_int32_t snmp_outtraps; + int snmp_enableauthentraps; + u_int32_t snmp_silentdrops; + u_int32_t snmp_proxydrops; +}; + +struct snmpd { + u_int8_t sc_flags; +#define SNMPD_F_VERBOSE 0x01 +#define SNMPD_F_NONAMES 0x02 + + const char *sc_confpath; + struct address sc_address; + int sc_sock; + struct event sc_ev; + struct timeval sc_starttime; + + char sc_rdcommunity[SNMPD_MAXCOMMUNITYLEN]; + char sc_rwcommunity[SNMPD_MAXCOMMUNITYLEN]; + char sc_trcommunity[SNMPD_MAXCOMMUNITYLEN]; + + struct snmp_stats sc_stats; +}; + +/* control.c */ +int control_init(void); +int control_listen(struct snmpd *, struct imsgbuf *); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +void control_imsg_forward(struct imsg *); +void control_cleanup(void); + +void session_socket_blockmode(int, enum blockmodes); + +/* parse.y */ +struct snmpd *parse_config(const char *, u_int); +int cmdline_symset(char *); + +/* log.c */ +void log_init(int); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +__dead void fatal(const char *); +__dead void fatalx(const char *); +const char *log_host(struct sockaddr_storage *, char *, size_t); + +/* buffer.c */ +struct buf *buf_open(size_t); +struct buf *buf_dynamic(size_t, size_t); +int buf_add(struct buf *, void *, size_t); +void *buf_reserve(struct buf *, size_t); +int buf_close(struct msgbuf *, struct buf *); +void buf_free(struct buf *); +void msgbuf_init(struct msgbuf *); +void msgbuf_clear(struct msgbuf *); +int msgbuf_write(struct msgbuf *); + +/* imsg.c */ +void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *)); +ssize_t imsg_read(struct imsgbuf *); +ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, + pid_t, int, void *, u_int16_t); +struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, + pid_t, u_int16_t); +int imsg_add(struct buf *, void *, u_int16_t); +int imsg_close(struct imsgbuf *, struct buf *); +void imsg_free(struct imsg *); +void imsg_event_add(struct imsgbuf *); /* provided externally */ +int imsg_get_fd(struct imsgbuf *); + +/* kroute.c */ +int kr_init(void); +void kr_shutdown(void); + +int kr_updateif(u_int); +u_int kr_ifnumber(void); +u_long kr_iflastchange(void); +struct kif *kr_getif(u_short); +struct kif *kr_getnextif(u_short); + +/* snmpe.c */ +pid_t snmpe(struct snmpd *, int [2]); + +/* mps.c */ +int mps_init(void); +u_long mps_getticks(void); +long mps_oid_cmp(struct oid *, struct oid *); +void mps_mibtree(struct oid *, size_t); +struct oid *mps_foreach(struct oid *, u_int); +void mps_oidlen(struct ber_oid *); +char *mps_oidstring(struct ber_oid *, char *, size_t); +struct ber_element * + mps_getreq(struct ber_element *, struct ber_oid *); +struct ber_element * + mps_getnextreq(struct ber_element *, struct ber_oid *); +int mps_setreq(struct ber_element *, struct ber_oid *); +int mps_set(struct ber_oid *, void *, long long); +void mps_delete(struct oid *); +void mps_insert(struct oid *); +int mps_getstr(struct oid *, struct ber_oid *, + struct ber_element **); +int mps_setstr(struct oid *, struct ber_oid *, + struct ber_element **); +int mps_getint(struct oid *, struct ber_oid *, + struct ber_element **); +int mps_setint(struct oid *, struct ber_oid *, + struct ber_element **); +int mps_getts(struct oid *, struct ber_oid *, + struct ber_element **); + +#endif /* _SNMPD_H */ 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); +} |