diff options
-rw-r--r-- | usr.sbin/snmpd/Makefile | 6 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.3 | 15 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.c | 67 | ||||
-rw-r--r-- | usr.sbin/snmpd/ber.h | 16 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.c | 80 | ||||
-rw-r--r-- | usr.sbin/snmpd/mib.h | 48 | ||||
-rw-r--r-- | usr.sbin/snmpd/mps.c | 16 | ||||
-rw-r--r-- | usr.sbin/snmpd/parse.y | 91 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmp.h | 17 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.c | 61 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf.5 | 77 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.h | 116 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpe.c | 187 | ||||
-rw-r--r-- | usr.sbin/snmpd/usm.c | 658 |
14 files changed, 1356 insertions, 99 deletions
diff --git a/usr.sbin/snmpd/Makefile b/usr.sbin/snmpd/Makefile index a344792bd8d..9746eda9a87 100644 --- a/usr.sbin/snmpd/Makefile +++ b/usr.sbin/snmpd/Makefile @@ -1,12 +1,12 @@ -# $OpenBSD: Makefile,v 1.8 2012/03/20 03:01:26 joel Exp $ +# $OpenBSD: Makefile,v 1.9 2012/09/17 16:30:34 reyk Exp $ PROG= snmpd MAN= snmpd.8 snmpd.conf.5 SRCS= parse.y ber.c log.c control.c snmpe.c \ mps.c trap.c mib.c smi.c kroute.c snmpd.c timer.c \ - pf.c + pf.c usm.c -LDADD= -levent -lutil -lkvm +LDADD= -levent -lutil -lkvm -lcrypto DPADD= ${LIBEVENT} ${LIBUTIL} CFLAGS+= -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes diff --git a/usr.sbin/snmpd/ber.3 b/usr.sbin/snmpd/ber.3 index e69ee7cd560..904e787855b 100644 --- a/usr.sbin/snmpd/ber.3 +++ b/usr.sbin/snmpd/ber.3 @@ -1,6 +1,6 @@ -.\" $OpenBSD: ber.3,v 1.9 2010/02/25 09:59:55 jmc Exp $ +.\" $OpenBSD: ber.3,v 1.10 2012/09/17 16:30:34 reyk Exp $ .\" -.\" Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> +.\" Copyright (c) 2007, 2012 Reyk Floeter <reyk@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 @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: February 25 2010 $ +.Dd $Mdocdate: September 17 2012 $ .Dt BER 3 .Os .Sh NAME @@ -50,9 +50,11 @@ .Nm ber_write_elements , .Nm ber_set_readbuf , .Nm ber_read_elements , +.Nm ber_getpos , .Nm ber_free_elements , .Nm ber_calc_len , .Nm ber_set_application , +.Nm ber_set_writecallback .Nm ber_free .Nd parse ASN.1 with Basic Encoding Rules .Sh SYNOPSIS @@ -121,6 +123,8 @@ .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 off_t +.Fn "ber_getpos" "struct ber_element *elm" .Ft "void" .Fn "ber_free_elements" "struct ber_element *root" .Ft "size_t" @@ -128,6 +132,8 @@ .Ft "void" .Fn "ber_set_application" "struct ber *ber" "unsigned long (*cb)(struct ber_element *)" .Ft "void" +.Fn "ber_set_writecallback" "struct ber_element *elm" "void (*cb)(void *arg, size_t offs)" "void *arg" +.Ft "void" .Fn "ber_free" "struct ber *ber" .Sh DESCRIPTION The @@ -188,8 +194,10 @@ struct ber_oid { .Fn ber_write_elements , .Fn ber_set_readbuf , .Fn ber_read_elements , +.Fn ber_getpos , .Fn ber_free_elements , .Fn ber_set_application , +.Fn ber_set_writecallback , .Fn ber_free .Sh RETURN VALUES Upon successful completion @@ -226,5 +234,4 @@ library was written by 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 index 462e46bbef1..2a9ddd7114f 100644 --- a/usr.sbin/snmpd/ber.c +++ b/usr.sbin/snmpd/ber.c @@ -1,7 +1,7 @@ -/* $OpenBSD: ber.c,v 1.23 2010/09/20 08:30:13 martinh Exp $ */ +/* $OpenBSD: ber.c,v 1.24 2012/09/17 16:30:34 reyk Exp $ */ /* - * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2007, 2012 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org> * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org> * @@ -269,7 +269,7 @@ ber_add_nstring(struct ber_element *prev, const char *string0, size_t len) struct ber_element *elm; char *string; - if ((string = calloc(1, len)) == NULL) + if ((string = calloc(1, len + 1)) == NULL) return NULL; if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) { free(string); @@ -618,6 +618,7 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...) void **ptr; size_t *len, ret = 0, n = strlen(fmt); char **s; + off_t *pos; struct ber_oid *o; struct ber_element *parent[_MAX_SEQ], **e; @@ -695,6 +696,11 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...) goto fail; ret++; break; + case 'p': + pos = va_arg(ap, off_t *); + *pos = ber_getpos(ber); + ret++; + continue; case '{': case '(': if (ber->be_encoding != BER_TYPE_SEQUENCE && @@ -712,7 +718,7 @@ ber_scanf_elements(struct ber_element *ber, char *fmt, ...) goto fail; ber = parent[level--]; ret++; - continue; + break; default: goto fail; } @@ -808,6 +814,12 @@ ber_read_elements(struct ber *ber, struct ber_element *elm) return root; } +off_t +ber_getpos(struct ber_element *elm) +{ + return elm->be_offs; +} + void ber_free_elements(struct ber_element *root) { @@ -867,6 +879,8 @@ ber_dump_element(struct ber *ber, struct ber_element *root) uint8_t u; ber_dump_header(ber, root); + if (root->be_cb) + root->be_cb(root->be_cbarg, ber->br_wptr - ber->br_wbuf); switch (root->be_encoding) { case BER_TYPE_BOOLEAN: @@ -1017,6 +1031,12 @@ get_len(struct ber *b, ssize_t *len) return 1; } + if (u == 0x80) { + /* Indefinite length not supported. */ + errno = EINVAL; + return -1; + } + n = u & ~BER_TAG_MORE; if (sizeof(ssize_t) < n) { errno = ERANGE; @@ -1036,12 +1056,6 @@ get_len(struct ber *b, ssize_t *len) return -1; } - if (s == 0) { - /* invalid encoding */ - errno = EINVAL; - return -1; - } - *len = s; return r; } @@ -1066,8 +1080,16 @@ ber_read_element(struct ber *ber, struct ber_element *elm) DPRINTF("ber read element size %zd\n", len); totlen += r + len; + /* If using an external buffer and the total size of the element + * is larger then the external buffer don't bother to continue. */ + if (ber->fd == -1 && len > ber->br_rend - ber->br_rptr) { + errno = ECANCELED; + return -1; + } + elm->be_type = type; elm->be_len = len; + elm->be_offs = ber->br_offs; /* element position within stream */ elm->be_class = class; if (elm->be_encoding == 0) { @@ -1199,6 +1221,15 @@ ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *)) } void +ber_set_writecallback(struct ber_element *elm, void (*cb)(void *, size_t), + void *arg) +{ + elm->be_cb = cb; + elm->be_cbarg = arg; +} + + +void ber_free(struct ber *b) { if (b->br_wbuf != NULL) @@ -1208,17 +1239,7 @@ ber_free(struct ber *b) 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 obvious 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); - return r; + return ber_read(b, c, 1); } static ssize_t @@ -1248,5 +1269,7 @@ ber_read(struct ber *ber, void *buf, size_t len) b += r; remain -= r; } - return (b - (u_char *)buf); + r = b - (u_char *)buf; + ber->br_offs += r; + return r; } diff --git a/usr.sbin/snmpd/ber.h b/usr.sbin/snmpd/ber.h index 907a6c5971a..fd43a3a2900 100644 --- a/usr.sbin/snmpd/ber.h +++ b/usr.sbin/snmpd/ber.h @@ -1,7 +1,7 @@ -/* $OpenBSD: ber.h,v 1.7 2009/01/03 18:41:41 aschrijver Exp $ */ +/* $OpenBSD: ber.h,v 1.8 2012/09/17 16:30:34 reyk Exp $ */ /* - * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2007, 2012 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any @@ -17,13 +17,19 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef _BER_H +#define _BER_H + struct ber_element { struct ber_element *be_next; unsigned long be_type; unsigned long be_encoding; size_t be_len; + off_t be_offs; int be_free; u_int8_t be_class; + void (*be_cb)(void *, size_t); + void *be_cbarg; union { struct ber_element *bv_sub; void *bv_val; @@ -36,6 +42,7 @@ struct ber_element { struct ber { int fd; + off_t br_offs; u_char *br_wbuf; u_char *br_wptr; u_char *br_wend; @@ -120,9 +127,14 @@ 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 *); +off_t ber_getpos(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 *)); +void ber_set_writecallback(struct ber_element *, + void (*)(void *, size_t), void *); void ber_free(struct ber *); __END_DECLS + +#endif /* _BER_H */ diff --git a/usr.sbin/snmpd/mib.c b/usr.sbin/snmpd/mib.c index 7f726fb6304..e081a521afd 100644 --- a/usr.sbin/snmpd/mib.c +++ b/usr.sbin/snmpd/mib.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mib.c,v 1.56 2012/07/08 11:24:43 blambert Exp $ */ +/* $OpenBSD: mib.c,v 1.57 2012/09/17 16:30:34 reyk Exp $ */ /* * Copyright (c) 2012 Joel Knight <joel@openbsd.org> @@ -330,6 +330,79 @@ mib_setsnmp(struct oid *oid, struct ber_oid *o, struct ber_element **elm) } /* + * Defined in SNMP-USER-BASED-SM-MIB.txt (RFC 3414) + */ +int mib_engine(struct oid *, struct ber_oid *, struct ber_element **); +int mib_usmstats(struct oid *, struct ber_oid *, struct ber_element **); + +static struct oid usm_mib[] = { + { MIB(snmpEngine), OID_MIB }, + { MIB(snmpEngineID), OID_RD, mib_engine }, + { MIB(snmpEngineBoots), OID_RD, mib_engine }, + { MIB(snmpEngineTime), OID_RD, mib_engine }, + { MIB(snmpEngineMaxMsgSize), OID_RD, mib_engine }, + { MIB(usmStats), OID_MIB }, + { MIB(usmStatsUnsupportedSecLevels), OID_RD, mib_usmstats }, + { MIB(usmStatsNotInTimeWindow), OID_RD, mib_usmstats }, + { MIB(usmStatsUnknownUserNames), OID_RD, mib_usmstats }, + { MIB(usmStatsUnknownEngineId), OID_RD, mib_usmstats }, + { MIB(usmStatsWrongDigests), OID_RD, mib_usmstats }, + { MIB(usmStatsDecryptionErrors), OID_RD, mib_usmstats }, + { MIBEND } +}; + +int +mib_engine(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + switch (oid->o_oid[OIDIDX_snmpEngine]) { + case 1: + *elm = ber_add_nstring(*elm, env->sc_engineid, + env->sc_engineid_len); + break; + case 2: + *elm = ber_add_integer(*elm, env->sc_engine_boots); + break; + case 3: + *elm = ber_add_integer(*elm, snmpd_engine_time()); + break; + case 4: + *elm = ber_add_integer(*elm, READ_BUF_SIZE); + break; + default: + return -1; + } + return 0; +} + +int +mib_usmstats(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[] = { + { OIDVAL_usmErrSecLevel, &stats->snmp_usmbadseclevel }, + { OIDVAL_usmErrTimeWindow, &stats->snmp_usmtimewindow }, + { OIDVAL_usmErrUserName, &stats->snmp_usmnosuchuser }, + { OIDVAL_usmErrEngineId, &stats->snmp_usmnosuchengine }, + { OIDVAL_usmErrDigest, &stats->snmp_usmwrongdigest }, + { OIDVAL_usmErrDecrypt, &stats->snmp_usmdecrypterr }, + }; + + for (i = 0; (u_int)i < (sizeof(mapping) / sizeof(mapping[0])); i++) { + if (oid->o_oid[OIDIDX_usmStats] == 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); +} + +/* * Defined in HOST-RESOURCES-MIB.txt (RFC 2790) */ @@ -723,7 +796,7 @@ mib_hrswrun(struct oid *oid, struct ber_oid *o, struct ber_element **elm) /* Get and verify the current row index */ if (kinfo_proc(o->bo_id[OIDIDX_hrSWRunEntry], &kinfo) == -1) - return (-1); + return (1); if (kinfo == NULL) return (1); @@ -3533,6 +3606,9 @@ mib_init(void) /* SNMPv2-MIB */ smi_mibtree(base_mib); + /* SNMP-USER-BASED-SM-MIB */ + smi_mibtree(usm_mib); + /* HOST-RESOURCES-MIB */ smi_mibtree(hr_mib); diff --git a/usr.sbin/snmpd/mib.h b/usr.sbin/snmpd/mib.h index 44e694aa674..b03c3475479 100644 --- a/usr.sbin/snmpd/mib.h +++ b/usr.sbin/snmpd/mib.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mib.h,v 1.26 2012/06/14 17:31:32 matthew Exp $ */ +/* $OpenBSD: mib.h,v 1.27 2012/09/17 16:30:34 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -106,6 +106,32 @@ #define MIB_authenticationFailure MIB_snmpTraps, 5 #define MIB_egpNeighborLoss MIB_snmpTraps, 6 +/* SNMP-USER-BASED-SM-MIB */ +#define MIB_framework MIB_snmpModules, 10 +#define MIB_frameworkObjects MIB_framework, 2 +#define OIDIDX_snmpEngine 9 +#define MIB_snmpEngine MIB_frameworkObjects, 1 +#define MIB_snmpEngineID MIB_snmpEngine, 1 +#define MIB_snmpEngineBoots MIB_snmpEngine, 2 +#define MIB_snmpEngineTime MIB_snmpEngine, 3 +#define MIB_snmpEngineMaxMsgSize MIB_snmpEngine, 4 +#define MIB_usm MIB_snmpModules, 15 +#define MIB_usmObjects MIB_usm, 1 +#define MIB_usmStats MIB_usmObjects, 1 +#define OIDIDX_usmStats 9 +#define OIDVAL_usmErrSecLevel 1 +#define OIDVAL_usmErrTimeWindow 2 +#define OIDVAL_usmErrUserName 3 +#define OIDVAL_usmErrEngineId 4 +#define OIDVAL_usmErrDigest 5 +#define OIDVAL_usmErrDecrypt 6 +#define MIB_usmStatsUnsupportedSecLevels MIB_usmStats, OIDVAL_usmErrSecLevel +#define MIB_usmStatsNotInTimeWindow MIB_usmStats, OIDVAL_usmErrTimeWindow +#define MIB_usmStatsUnknownUserNames MIB_usmStats, OIDVAL_usmErrUserName +#define MIB_usmStatsUnknownEngineId MIB_usmStats, OIDVAL_usmErrEngineId +#define MIB_usmStatsWrongDigests MIB_usmStats, OIDVAL_usmErrDigest +#define MIB_usmStatsDecryptionErrors MIB_usmStats, OIDVAL_usmErrDecrypt + /* HOST-RESOURCES-MIB */ #define MIB_host MIB_mib_2, 25 #define MIB_hrSystem MIB_host, 1 @@ -395,7 +421,8 @@ #define MIB_sFlow MIB_enterprises, 14706 #define MIB_microSystems MIB_enterprises, 18623 #define MIB_vantronix MIB_enterprises, 26766 -#define MIB_openBSD MIB_enterprises, 30155 +#define OIDVAL_openBSD_eid 30155 +#define MIB_openBSD MIB_enterprises, OIDVAL_openBSD_eid /* UCD-DISKIO-MIB */ #define MIB_ucdExperimental MIB_ucDavis, 13 @@ -729,6 +756,23 @@ { MIBDECL(authenticationFailure) }, \ { MIBDECL(egpNeighborLoss) }, \ \ + { MIBDECL(framework) }, \ + { MIBDECL(frameworkObjects) }, \ + { MIBDECL(snmpEngine) }, \ + { MIBDECL(snmpEngineID) }, \ + { MIBDECL(snmpEngineBoots) }, \ + { MIBDECL(snmpEngineTime) }, \ + { MIBDECL(snmpEngineMaxMsgSize) }, \ + { MIBDECL(usm) }, \ + { MIBDECL(usmObjects) }, \ + { MIBDECL(usmStats) }, \ + { MIBDECL(usmStatsUnsupportedSecLevels) }, \ + { MIBDECL(usmStatsNotInTimeWindow) }, \ + { MIBDECL(usmStatsUnknownUserNames) }, \ + { MIBDECL(usmStatsUnknownEngineId) }, \ + { MIBDECL(usmStatsWrongDigests) }, \ + { MIBDECL(usmStatsDecryptionErrors) }, \ + \ { MIBDECL(host) }, \ { MIBDECL(hrSystem) }, \ { MIBDECL(hrSystemUptime) }, \ diff --git a/usr.sbin/snmpd/mps.c b/usr.sbin/snmpd/mps.c index dcdbc9d3853..6e87e804d70 100644 --- a/usr.sbin/snmpd/mps.c +++ b/usr.sbin/snmpd/mps.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mps.c,v 1.14 2010/09/20 08:56:16 martinh Exp $ */ +/* $OpenBSD: mps.c,v 1.15 2012/09/17 16:30:34 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -219,6 +219,7 @@ mps_getnextreq(struct ber_element *root, struct ber_oid *o) return (ber); } +getnext: for (next = value; next != NULL;) { next = smi_next(next); if (next == NULL) @@ -231,11 +232,18 @@ mps_getnextreq(struct ber_element *root, struct ber_oid *o) if (next->o_flags & OID_TABLE) { /* Get the next table row for this column */ - if (mps_table(next, o, &no) == NULL) - return (ber); + if (mps_table(next, o, &no) == NULL) { + value = next; + goto getnext; + } bcopy(&no, o, sizeof(*o)); - if ((ret = next->o_get(next, o, &ber)) != 0) + if ((ret = next->o_get(next, o, &ber)) != 0) { + if (ret == 1) { + value = next; + goto getnext; + } return (NULL); + } } else { bcopy(&next->o_id, o, sizeof(*o)); ber = ber_add_noid(ber, &next->o_id, diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y index b6503233529..1815f88d459 100644 --- a/usr.sbin/snmpd/parse.y +++ b/usr.sbin/snmpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.20 2011/04/21 14:55:22 sthen Exp $ */ +/* $OpenBSD: parse.y,v 1.21 2012/09/17 16:30:34 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -84,6 +84,7 @@ char *symget(const char *); struct snmpd *conf = NULL; static int errors = 0; static struct addresslist *hlist; +static struct usmuser *user = NULL; struct address *host_v4(const char *); struct address *host_v6(const char *); @@ -104,6 +105,8 @@ typedef struct { void *data; long long value; } data; + enum usmauth auth; + enum usmpriv enc; } v; int lineno; } YYSTYPE; @@ -114,13 +117,15 @@ typedef struct { %token LISTEN ON %token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER %token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER -%token ERROR +%token SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR %token <v.string> STRING %token <v.number> NUMBER %type <v.string> hostcmn -%type <v.number> optwrite yesno +%type <v.number> optwrite yesno seclevel %type <v.data> objtype %type <v.oid> oid hostoid +%type <v.auth> auth +%type <v.enc> enc %% @@ -236,6 +241,25 @@ main : LISTEN ON STRING { else conf->sc_rtfilter = 0; } + | SECLEVEL seclevel { + conf->sc_min_seclevel = $2; + } + | USER STRING { + const char *errstr; + user = usm_newuser($2, &errstr); + if (user == NULL) { + yyerror(errstr); + free($2); + YYERROR; + } + } userspecs { + const char *errstr; + if (usm_checkuser(user, &errstr) < 0) { + yyerror(errstr); + YYERROR; + } + user = NULL; + } ; system : SYSTEM sysmib @@ -367,6 +391,58 @@ comma : /* empty */ | ',' ; +seclevel : NONE { $$ = 0; } + | AUTH { $$ = SNMP_MSGFLAG_AUTH; } + | ENC { $$ = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV; } + ; + +userspecs : /* empty */ + | userspecs userspec + ; + +userspec : AUTHKEY STRING { + user->uu_authkey = $2; + } + | AUTH auth { + user->uu_auth = $2; + } + | ENCKEY STRING { + user->uu_privkey = $2; + } + | ENC enc { + user->uu_priv = $2; + } + ; + +auth : STRING { + if (!strcasecmp($1, "hmac-md5")) + $$ = AUTH_MD5; + else if (!strcasecmp($1, "hmac-sha1")) + $$ = AUTH_SHA1; + else { + yyerror("syntax error, bad auth hmac"); + free($1); + YYERROR; + } + free($1); + } + ; + +enc : STRING { + if (!strcasecmp($1, "des")) + $$ = PRIV_DES; + else if (!strcasecmp($1, "aes")) + $$ = PRIV_AES; + else { + yyerror("syntax error, bad encryption cipher"); + free($1); + YYERROR; + } + free($1); + + } + ; + %% struct keywords { @@ -399,24 +475,31 @@ lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { + { "auth", AUTH }, + { "authkey", AUTHKEY }, { "community", COMMUNITY }, { "contact", CONTACT }, { "description", DESCR }, + { "enc", ENC }, + { "enckey", ENCKEY }, { "filter-routes", RTFILTER }, { "include", INCLUDE }, { "integer", INTEGER }, { "listen", LISTEN }, { "location", LOCATION }, { "name", NAME }, + { "none", NONE }, { "oid", OBJECTID }, { "on", ON }, { "read-only", READONLY }, { "read-write", READWRITE }, { "receiver", RECEIVER }, + { "seclevel", SECLEVEL }, { "services", SERVICES }, { "string", OCTETSTRING }, { "system", SYSTEM }, - { "trap", TRAP } + { "trap", TRAP }, + { "user", USER } }; const struct keywords *p; diff --git a/usr.sbin/snmpd/snmp.h b/usr.sbin/snmpd/snmp.h index e44f6758fb3..642d90d0c6c 100644 --- a/usr.sbin/snmpd/snmp.h +++ b/usr.sbin/snmpd/snmp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmp.h,v 1.8 2009/11/26 17:32:47 reyk Exp $ */ +/* $OpenBSD: snmp.h,v 1.9 2012/09/17 16:30:34 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -135,4 +135,19 @@ enum snmp_error { SNMP_ERROR_INCONNAME = 18 }; +enum snmp_security_model { + SNMP_SEC_ANY = 0, + SNMP_SEC_SNMPv1 = 1, + SNMP_SEC_SNMPv2c = 2, + SNMP_SEC_USM = 3, + SNMP_SEC_TSM = 4 +}; + +#define SNMP_MSGFLAG_AUTH 0x01 +#define SNMP_MSGFLAG_PRIV 0x02 +#define SNMP_MSGFLAG_SECMASK (SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV) +#define SNMP_MSGFLAG_REPORT 0x04 + +#define SNMP_MAX_TIMEWINDOW 150 /* RFC3414 */ + #endif /* SNMP_HEADER */ diff --git a/usr.sbin/snmpd/snmpd.c b/usr.sbin/snmpd/snmpd.c index b1d70834846..61b9f6ffac5 100644 --- a/usr.sbin/snmpd/snmpd.c +++ b/usr.sbin/snmpd/snmpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.c,v 1.11 2012/05/28 20:55:40 joel Exp $ */ +/* $OpenBSD: snmpd.c,v 1.12 2012/09/17 16:30:35 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -37,6 +37,7 @@ #include <pwd.h> #include "snmpd.h" +#include "mib.h" __dead void usage(void); @@ -44,6 +45,7 @@ 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 *); +void snmpd_generate_engineid(struct snmpd *); struct snmpd *snmpd_env; @@ -171,6 +173,7 @@ main(int argc, char *argv[]) } gettimeofday(&env->sc_starttime, NULL); + env->sc_engine_boots = 0; log_info("startup"); @@ -183,6 +186,8 @@ main(int argc, char *argv[]) session_socket_blockmode(pipe_parent2snmpe[0], BM_NONBLOCK); session_socket_blockmode(pipe_parent2snmpe[1], BM_NONBLOCK); + snmpd_generate_engineid(env); + snmpe_pid = snmpe(env, pipe_parent2snmpe); setproctitle("parent"); @@ -339,3 +344,57 @@ snmpd_socket_af(struct sockaddr_storage *ss, in_port_t port) return (s); } +void +snmpd_generate_engineid(struct snmpd *env) +{ + u_int32_t oid_enterprise, rnd, tim; + + /* RFC 3411 */ + memset(env->sc_engineid, 0, sizeof (env->sc_engineid)); + oid_enterprise = htonl(OIDVAL_openBSD_eid); + memcpy(env->sc_engineid, &oid_enterprise, sizeof (oid_enterprise)); + env->sc_engineid[0] |= SNMP_ENGINEID_NEW; + env->sc_engineid_len = sizeof (oid_enterprise); + /* XXX alternatively configure engine id via snmpd.conf */ + env->sc_engineid[(env->sc_engineid_len)++] = SNMP_ENGINEID_FMT_EID; + rnd = arc4random(); + memcpy(&env->sc_engineid[env->sc_engineid_len], &rnd, sizeof (rnd)); + env->sc_engineid_len += sizeof (rnd); + tim = htonl(env->sc_starttime.tv_sec); + memcpy(&env->sc_engineid[env->sc_engineid_len], &tim, sizeof (tim)); + env->sc_engineid_len += sizeof (tim); + return; +} + +u_long +snmpd_engine_time(void) +{ + struct timeval now; + + /* + * snmpEngineBoots should be stored in a non-volatile storage. + * snmpEngineTime is the number of seconds since snmpEngineBoots + * was last incremented. We don't rely on non-volatile storage. + * snmpEngineBoots is set to zero and snmpEngineTime to the system + * clock. Hence, the tuple (snmpEngineBoots, snmpEngineTime) is + * still unique and protects us against replay attacks. It only + * 'expires' a little bit sooner than the RFC3414 method. + */ + gettimeofday(&now, NULL); + return now.tv_sec; +} + +char * +tohexstr(u_int8_t *str, int len) +{ +#define MAXHEXSTRLEN 256 + static char hstr[2 * MAXHEXSTRLEN + 1]; + char *r = hstr; + + if (len > MAXHEXSTRLEN) + len = MAXHEXSTRLEN; /* truncate */ + while (len-- > 0) + r += snprintf(r, len * 2, "%0*x", 2, *str++); + *r = '\0'; + return hstr; +} diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 index ff8093240f2..768ad06b99f 100644 --- a/usr.sbin/snmpd/snmpd.conf.5 +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: snmpd.conf.5,v 1.17 2012/04/24 14:56:09 jmc Exp $ +.\" $OpenBSD: snmpd.conf.5,v 1.18 2012/09/17 16:30:35 reyk Exp $ .\" .\" Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: April 24 2012 $ +.Dd $Mdocdate: September 17 2012 $ .Dt SNMPD.CONF 5 .Os .Sh NAME @@ -36,6 +36,8 @@ configuration file. .It Sy Global Configuration Global runtime settings for .Xr snmpd 8 . +.It Sy User Configuration +USM user definitions. .It Sy OID Configuration Custom configuration of SNMP object identifiers and values. .El @@ -94,7 +96,7 @@ The default value is .Pp .It Xo .Ic filter-routes -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes \*(Ba\ no .Xc If set to .Ic yes , @@ -104,6 +106,33 @@ reduced during bulk updates. The default is .Ic no . .Pp +.It Xo +.Ic seclevel +.Pq Ic none \*(Ba\ auth \*(Ba\ enc +.Xc +Specify the lowest security level that +.Xr snmpd 8 +accepts: +.Bl -tag -width "auth" -offset ident +.It Ic none +Both authentication and encryption of messages is optional. +This is the default value. +.It Ic auth +Authentication of messages is mandatory. +.Xr snmpd 8 +will discard any messages that don't have a valid digest. +Encryption of messages is optional. +.It Ic enc +Messages must be encrypted and must have a valid digest for authentication. +Otherwise they will be discarded. +.El +.Pp +If the chosen value is different from +.Ic none +.Xr snmpd 8 +will accept only SNMPv3 requests since older versions neither support +authentication nor encryption. +.Pp .It Ic system contact Ar string Specify the name or description of the system contact, typically a name or an e-mail address. @@ -170,6 +199,48 @@ The default community is specified by the global option. .Pp .El +.Sh User Configuration +Users for the SNMP User-based Security Model (USM, RFC3414) must be +defined in the configuration file: +.Pp +.Bl -tag -width xxxx +.It Xo +.Ic user Ar name +.Op Ic authkey Ar key Ic auth Ar hmac +.Op Ic enckey Ar key Ic enc Ar cipher +.Xc +Defines a known user. The +.Ic authkey +keyword is required to specifiy the digest key used to authenticate +messages. If this keyword is omitted then authentication is disabled +for this user account. Optionally the HMAC algorithm used for authentication +can be specified. +.Ar hmac +must be either +.Ic hmac-md5 +or +.Ic hmac-sha1 . +If omitted the default is +.Ic hmac-sha1 . + +With +.Ic enckey +the encryption key used to encrypt and decrypt messages for privacy is defined. +Without an +.Ic enckey +specification the user account will neither accept encrypted incoming +messages nor will it encrypt outgoing messsages. The +.Ar enc +algorithm can be either +.Ic des +or +.Ic aes +and defaults to +.Ic des . + +Any user account that has encryption enabled requires authentication to +be enabled, too. +.El .Sh OID CONFIGURATION It is possible to specify user-defined OIDs in the configuration file: .Pp diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index 3e2a7cd53af..5e3a1eb6e80 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.h,v 1.35 2012/05/28 20:55:40 joel Exp $ */ +/* $OpenBSD: snmpd.h,v 1.36 2012/09/17 16:30:35 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -25,7 +25,7 @@ #include <net/pfvar.h> #include <net/route.h> -#include <ber.h> +#include "ber.h" #include <snmp.h> #include <imsg.h> @@ -44,12 +44,30 @@ #define SNMPD_MAXCOMMUNITYLEN SNMPD_MAXSTRLEN #define SNMPD_MAXVARBIND 0x7fffffff #define SNMPD_MAXVARBINDLEN 1210 +#define SNMPD_MAXENGINEIDLEN 32 +#define SNMPD_MAXUSERNAMELEN 32 +#define SNMPD_MAXCONTEXNAMELEN 32 + +#define SNMP_USM_DIGESTLEN 12 +#define SNMP_USM_SALTLEN 8 +#define SNMP_USM_KEYLEN 64 +#define SNMP_CIPHER_KEYLEN 16 #define SMALL_READ_BUF_SIZE 1024 #define READ_BUF_SIZE 65535 #define RT_BUF_SIZE 16384 #define MAX_RTSOCK_BUF (128 * 1024) +#define SNMP_ENGINEID_OLD 0x00 +#define SNMP_ENGINEID_NEW 0x80 /* RFC3411 */ + +#define SNMP_ENGINEID_FMT_IPv4 1 +#define SNMP_ENGINEID_FMT_IPv6 2 +#define SNMP_ENGINEID_FMT_MAC 3 +#define SNMP_ENGINEID_FMT_TEXT 4 +#define SNMP_ENGINEID_FMT_OCT 5 +#define SNMP_ENGINEID_FMT_EID 128 + enum imsg_type { IMSG_NONE, IMSG_CTL_OK, /* answer to snmpctl requests */ @@ -238,13 +256,39 @@ struct pfr_buffer { * daemon structures */ +#define MSG_HAS_AUTH(m) (((m)->sm_flags & SNMP_MSGFLAG_AUTH) != 0) +#define MSG_HAS_PRIV(m) (((m)->sm_flags & SNMP_MSGFLAG_PRIV) != 0) +#define MSG_SECLEVEL(m) ((m)->sm_flags & SNMP_MSGFLAG_SECMASK) +#define MSG_REPORT(m) (((m)->sm_flags & SNMP_MSGFLAG_REPORT) != 0) + struct snmp_message { + struct ber_element *sm_resp; + u_int8_t sm_data[READ_BUF_SIZE]; + size_t sm_datalen; + u_int sm_version; - char sm_community[SNMPD_MAXCOMMUNITYLEN]; - u_int sm_context; - struct ber_element *sm_header; - struct ber_element *sm_headerend; + /* V1, V2c */ + char sm_community[SNMPD_MAXCOMMUNITYLEN]; + int sm_context; + + /* V3 */ + long long sm_msgid; + long long sm_max_msg_size; + u_int8_t sm_flags; + long long sm_secmodel; + u_int32_t sm_engine_boots; + u_int32_t sm_engine_time; + char sm_ctxengineid[SNMPD_MAXENGINEIDLEN]; + size_t sm_ctxengineid_len; + char sm_ctxname[SNMPD_MAXCONTEXNAMELEN+1]; + + /* USM */ + char sm_username[SNMPD_MAXUSERNAMELEN+1]; + struct usmuser *sm_user; + size_t sm_digest_offs; + char sm_salt[SNMP_USM_SALTLEN]; + int sm_usmerr; long long sm_request; @@ -292,6 +336,14 @@ struct snmp_stats { int snmp_enableauthentraps; u_int32_t snmp_silentdrops; u_int32_t snmp_proxydrops; + + /* USM stats (RFC 3414) */ + u_int32_t snmp_usmbadseclevel; + u_int32_t snmp_usmtimewindow; + u_int32_t snmp_usmnosuchuser; + u_int32_t snmp_usmnosuchengine; + u_int32_t snmp_usmwrongdigest; + u_int32_t snmp_usmdecrypterr; }; struct address { @@ -306,6 +358,37 @@ struct address { }; TAILQ_HEAD(addresslist, address); +enum usmauth { + AUTH_NONE = 0, + AUTH_MD5, /* HMAC-MD5-96, RFC3414 */ + AUTH_SHA1 /* HMAC-SHA-96, RFC3414 */ +}; + +#define AUTH_DEFAULT AUTH_SHA1 /* Default digest */ + +enum usmpriv { + PRIV_NONE = 0, + PRIV_DES, /* CBC-DES, RFC3414 */ + PRIV_AES /* CFB128-AES-128, RFC3826 */ +}; + +#define PRIV_DEFAULT PRIV_DES /* Default cipher */ + +struct usmuser { + char *uu_name; + + enum usmauth uu_auth; + char *uu_authkey; + unsigned uu_authkeylen; + + + enum usmpriv uu_priv; + char *uu_privkey; + unsigned long long uu_salt; + + SLIST_ENTRY(usmuser) uu_next; +}; + struct snmpd { u_int8_t sc_flags; #define SNMPD_F_VERBOSE 0x01 @@ -316,6 +399,7 @@ struct snmpd { int sc_sock; struct event sc_ev; struct timeval sc_starttime; + u_int32_t sc_engine_boots; struct control_sock sc_csock; struct control_sock sc_rcsock; @@ -324,6 +408,9 @@ struct snmpd { char sc_rwcommunity[SNMPD_MAXCOMMUNITYLEN]; char sc_trcommunity[SNMPD_MAXCOMMUNITYLEN]; + char sc_engineid[SNMPD_MAXENGINEIDLEN]; + size_t sc_engineid_len; + struct snmp_stats sc_stats; struct addresslist sc_trapreceivers; @@ -331,6 +418,8 @@ struct snmpd { int sc_ncpu; int64_t *sc_cpustates; int sc_rtfilter; + + int sc_min_seclevel; }; /* control.c */ @@ -449,5 +538,18 @@ void timer_init(void); /* snmpd.c */ int snmpd_socket_af(struct sockaddr_storage *, in_port_t); - +u_long snmpd_engine_time(void); +char *tohexstr(u_int8_t *, int); + +/* usm.c */ +void usm_generate_keys(void); +struct usmuser *usm_newuser(char *name, const char **); +struct usmuser *usm_finduser(char *name); +int usm_checkuser(struct usmuser *, const char **); +struct ber_element *usm_decode(struct snmp_message *, struct ber_element *, + const char **); +struct ber_element *usm_encode(struct snmp_message *, struct ber_element *); +struct ber_element *usm_encrypt(struct snmp_message *, struct ber_element *); +void usm_finalize_digest(struct snmp_message *, char *, ssize_t); +void usm_make_report(struct snmp_message *); #endif /* _SNMPD_H */ diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c index 5614afd1db8..4b017dca28f 100644 --- a/usr.sbin/snmpd/snmpe.c +++ b/usr.sbin/snmpd/snmpe.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpe.c,v 1.28 2010/09/20 12:32:41 martinh Exp $ */ +/* $OpenBSD: snmpe.c,v 1.29 2012/09/17 16:30:35 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@vantronix.net> @@ -39,6 +39,7 @@ #include <vis.h> #include "snmpd.h" +#include "mib.h" int snmpe_parse(struct sockaddr_storage *, struct ber_element *, struct snmp_message *); @@ -49,6 +50,7 @@ void snmpe_shutdown(void); void snmpe_dispatch_parent(int, short, void *); int snmpe_bind(struct address *); void snmpe_recvmsg(int fd, short, void *); +int snmpe_encode(struct snmp_message *); struct snmpd *env = NULL; @@ -164,6 +166,8 @@ snmpe(struct snmpd *x_env, int pipe_parent2snmpe[2]) trap_init(); timer_init(); + usm_generate_keys(); + event_dispatch(); snmpe_shutdown(); @@ -495,29 +499,67 @@ snmpe_parse(struct sockaddr_storage *ss, unsigned long type; u_int class, state, i = 0, j = 0; char *comn, buf[BUFSIZ], host[MAXHOSTNAMELEN]; + char *flagstr, *ctxname; 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) + if (ber_scanf_elements(root, "{ie", &ver, &a) != 0) goto parsefail; /* SNMP version and community */ - switch (ver) { + msg->sm_version = ver; + switch (msg->sm_version) { case SNMP_V1: case SNMP_V2: - msg->sm_version = ver; + if (env->sc_min_seclevel != 0) + goto badversion; + if (ber_scanf_elements(a, "se", &comn, &msg->sm_pdu) != 0) + goto parsefail; + if (strlcpy(msg->sm_community, comn, + sizeof(msg->sm_community)) >= sizeof(msg->sm_community)) { + stats->snmp_inbadcommunitynames++; + errstr = "community name too long"; + goto fail; + } break; case SNMP_V3: + if (ber_scanf_elements(a, "{iisi}e", + &msg->sm_msgid, &msg->sm_max_msg_size, &flagstr, + &msg->sm_secmodel, &a) != 0) + goto parsefail; + + msg->sm_flags = *flagstr; + if (MSG_SECLEVEL(msg) < env->sc_min_seclevel || + msg->sm_secmodel != SNMP_SEC_USM) { + /* XXX currently only USM supported */ + errstr = "unsupported security model"; + stats->snmp_usmbadseclevel++; + msg->sm_usmerr = OIDVAL_usmErrSecLevel; + goto parsefail; + } + + if ((a = usm_decode(msg, a, &errstr)) == NULL) + goto parsefail; + + if (ber_scanf_elements(a, "{xxe", + &msg->sm_ctxengineid, &msg->sm_ctxengineid_len, + &ctxname, &len, &msg->sm_pdu) != 0) + goto parsefail; + if (len > SNMPD_MAXCONTEXNAMELEN) + goto parsefail; + memcpy(msg->sm_ctxname, ctxname, len); + msg->sm_ctxname[len] = '\0'; + break; default: + badversion: stats->snmp_inbadversions++; errstr = "bad snmp version"; goto fail; } + if (ber_scanf_elements(msg->sm_pdu, "t{e", &class, &type, &a) != 0) + goto parsefail; + /* SNMP PDU context */ if (class != BER_CLASS_CONTEXT) goto parsefail; @@ -535,8 +577,9 @@ snmpe_parse(struct sockaddr_storage *ss, case SNMP_C_GETNEXTREQ: if (type == SNMP_C_GETNEXTREQ) stats->snmp_ingetnexts++; - if (strcmp(env->sc_rdcommunity, comn) != 0 && - strcmp(env->sc_rwcommunity, comn) != 0) { + if (msg->sm_version != SNMP_V3 && + strcmp(env->sc_rdcommunity, msg->sm_community) != 0 && + strcmp(env->sc_rwcommunity, msg->sm_community) != 0) { stats->snmp_inbadcommunitynames++; errstr = "wrong read community"; goto fail; @@ -545,8 +588,9 @@ snmpe_parse(struct sockaddr_storage *ss, break; case SNMP_C_SETREQ: stats->snmp_insetrequests++; - if (strcmp(env->sc_rwcommunity, comn) != 0) { - if (strcmp(env->sc_rdcommunity, comn) != 0) + if (msg->sm_version != SNMP_V3 && + strcmp(env->sc_rwcommunity, msg->sm_community) != 0) { + if (strcmp(env->sc_rdcommunity, msg->sm_community) != 0) stats->snmp_inbadcommunitynames++; else stats->snmp_inbadcommunityuses++; @@ -561,7 +605,8 @@ snmpe_parse(struct sockaddr_storage *ss, goto parsefail; case SNMP_C_TRAP: case SNMP_C_TRAPV2: - if (strcmp(env->sc_trcommunity, comn) != 0) { + if (msg->sm_version != SNMP_V3 && + strcmp(env->sc_trcommunity, msg->sm_community) != 0) { stats->snmp_inbadcommunitynames++; errstr = "wrong trap community"; goto fail; @@ -574,13 +619,6 @@ snmpe_parse(struct sockaddr_storage *ss, goto parsefail; } - if (strlcpy(msg->sm_community, comn, sizeof(msg->sm_community)) >= - sizeof(msg->sm_community)) { - stats->snmp_inbadcommunitynames++; - errstr = "community name too long"; - goto fail; - } - /* SNMP PDU */ if (ber_scanf_elements(a, "iiie{et", &req, &errval, &erridx, &msg->sm_pduend, @@ -600,9 +638,17 @@ snmpe_parse(struct sockaddr_storage *ss, msg->sm_errorindex = erridx; print_host(ss, host, sizeof(host)); - log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d request %lld", - host, msg->sm_version + 1, msg->sm_community, msg->sm_context, - msg->sm_request); + if (msg->sm_version == SNMP_V3) + log_debug("snmpe_parse: %s: SNMPv3 context %d, flags %#x, " + "secmodel %lld, user '%s', ctx-engine %s, ctx-name '%s', " + "request %lld", host, msg->sm_context, msg->sm_flags, + msg->sm_secmodel, msg->sm_username, + tohexstr(msg->sm_ctxengineid, msg->sm_ctxengineid_len), + msg->sm_ctxname, msg->sm_request); + else + log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d " + "request %lld", 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; @@ -719,40 +765,47 @@ 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 = NULL; + u_int8_t *ptr = NULL; socklen_t slen; ssize_t len; struct ber ber; - struct ber_element *req = NULL, *resp = NULL; + struct ber_element *req = NULL; struct snmp_message msg; + bzero(&msg, sizeof(msg)); slen = sizeof(ss); - if ((len = recvfrom(fd, buf, sizeof(buf), 0, + if ((len = recvfrom(fd, msg.sm_data, sizeof(msg.sm_data), 0, (struct sockaddr *)&ss, &slen)) < 1) return; stats->snmp_inpkts++; + msg.sm_datalen = (size_t)len; bzero(&ber, sizeof(ber)); ber.fd = -1; ber_set_application(&ber, snmpe_application); - ber_set_readbuf(&ber, buf, len); + ber_set_readbuf(&ber, msg.sm_data, msg.sm_datalen); req = ber_read_elements(&ber, NULL); - if (req == NULL) { stats->snmp_inasnparseerrs++; goto done; } #ifdef DEBUG + fprintf(stderr, "recv msg:\n"); snmpe_debug_elements(req); #endif - if (snmpe_parse(&ss, req, &msg) == -1) - goto done; + if (snmpe_parse(&ss, req, &msg) == -1) { + if (msg.sm_usmerr != 0 && MSG_REPORT(&msg)) + usm_make_report(&msg); + else + goto done; + } else + msg.sm_context = SNMP_C_GETRESP; - if (msg.sm_varbindresp == NULL) + if (msg.sm_varbindresp == NULL && msg.sm_pduend != NULL) msg.sm_varbindresp = ber_unlink_elements(msg.sm_pduend); switch (msg.sm_error) { @@ -775,21 +828,14 @@ snmpe_recvmsg(int fd, short sig, void *arg) } /* Create new SNMP packet */ - resp = ber_add_sequence(NULL); - ber_printf_elements(resp, "ds{tiii{e}}.", - msg.sm_version, msg.sm_community, - BER_CLASS_CONTEXT, SNMP_C_GETRESP, - msg.sm_request, msg.sm_error, msg.sm_errorindex, - msg.sm_varbindresp); - -#ifdef DEBUG - snmpe_debug_elements(resp); -#endif + if (snmpe_encode(&msg) < 0) + goto done; - len = ber_write_elements(&ber, resp); + len = ber_write_elements(&ber, msg.sm_resp); if (ber_get_writebuf(&ber, (void *)&ptr) == -1) goto done; + usm_finalize_digest(&msg, ptr, len); len = sendto(fd, ptr, len, 0, (struct sockaddr *)&ss, slen); if (len != -1) stats->snmp_outpkts++; @@ -798,6 +844,59 @@ snmpe_recvmsg(int fd, short sig, void *arg) ber_free(&ber); if (req != NULL) ber_free_elements(req); - if (resp != NULL) - ber_free_elements(resp); + if (msg.sm_resp != NULL) + ber_free_elements(msg.sm_resp); +} + +int +snmpe_encode(struct snmp_message *msg) +{ + struct ber_element *ehdr; + struct ber_element *pdu, *epdu; + + msg->sm_resp = ber_add_sequence(NULL); + if ((ehdr = ber_add_integer(msg->sm_resp, msg->sm_version)) == NULL) + return -1; + if (msg->sm_version == SNMP_V3) { + char f = MSG_SECLEVEL(msg); + + if ((ehdr = ber_printf_elements(ehdr, "{iixi}", msg->sm_msgid, + msg->sm_max_msg_size, &f, sizeof (f), + msg->sm_secmodel)) == NULL) + return -1; + + /* XXX currently only USM supported */ + if ((ehdr = usm_encode(msg, ehdr)) == NULL) + return -1; + } else { + if ((ehdr = ber_add_string(ehdr, msg->sm_community)) == NULL) + return -1; + } + + pdu = epdu = ber_add_sequence(NULL); + if (msg->sm_version == SNMP_V3) { + if ((epdu = ber_printf_elements(epdu, "xs{", env->sc_engineid, + env->sc_engineid_len, msg->sm_ctxname)) == NULL) { + ber_free_elements(pdu); + return -1; + } + } + + if (!ber_printf_elements(epdu, "tiii{e}.", BER_CLASS_CONTEXT, + msg->sm_context, msg->sm_request, + msg->sm_error, msg->sm_errorindex, + msg->sm_varbindresp)) { + ber_free_elements(pdu); + return -1; + } + + if (MSG_HAS_PRIV(msg)) + pdu = usm_encrypt(msg, pdu); + ber_link_elements(ehdr, pdu); + +#ifdef DEBUG + fprintf(stderr, "resp msg:\n"); + snmpe_debug_elements(msg->sm_resp); +#endif + return 0; } diff --git a/usr.sbin/snmpd/usm.c b/usr.sbin/snmpd/usm.c new file mode 100644 index 00000000000..643fcf537d9 --- /dev/null +++ b/usr.sbin/snmpd/usm.c @@ -0,0 +1,658 @@ +/* $OpenBSD: usm.c,v 1.1 2012/09/17 16:30:35 reyk Exp $ */ + +/* + * Copyright (c) 2012 GeNUA mbH + * + * 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> +#ifdef DEBUG +#include <assert.h> +#endif + +#include <openssl/evp.h> +#include <openssl/hmac.h> + +#include "snmpd.h" +#include "mib.h" + +extern struct snmpd *env; + +SLIST_HEAD(, usmuser) usmuserlist; + +const EVP_MD *usm_get_md(enum usmauth); +const EVP_CIPHER *usm_get_cipher(enum usmpriv); +void usm_cb_digest(void *, size_t); +int usm_valid_digest(struct snmp_message *, off_t, char *, + size_t); +struct ber_element *usm_decrypt(struct snmp_message *, + struct ber_element *); +ssize_t usm_crypt(struct snmp_message *, u_char *, int, + u_char *, int); +char *usm_passwd2key(const EVP_MD *, char *, int *); + +void +usm_generate_keys(void) +{ + struct usmuser *up; + const EVP_MD *md; + char *key; + int len; + + SLIST_FOREACH(up, &usmuserlist, uu_next) { + if ((md = usm_get_md(up->uu_auth)) == NULL) + continue; + + /* convert auth password to key */ + len = 0; + key = usm_passwd2key(md, up->uu_authkey, &len); + free(up->uu_authkey); + up->uu_authkey = key; + up->uu_authkeylen = len; + + /* optionally convert privacy password to key */ + if (up->uu_priv != PRIV_NONE) { + arc4random_buf(&up->uu_salt, sizeof (up->uu_salt)); + + len = SNMP_CIPHER_KEYLEN; + key = usm_passwd2key(md, up->uu_privkey, &len); + free(up->uu_privkey); + up->uu_privkey = key; + } + } + return; +} + +const EVP_MD * +usm_get_md(enum usmauth ua) +{ + switch (ua) { + case AUTH_MD5: + return EVP_md5(); + case AUTH_SHA1: + return EVP_sha1(); + case AUTH_NONE: + default: + return NULL; + } +} + +const EVP_CIPHER * +usm_get_cipher(enum usmpriv up) +{ + switch (up) { + case PRIV_DES: + return EVP_des_cbc(); + case PRIV_AES: + return EVP_aes_128_cfb128(); + case PRIV_NONE: + default: + return NULL; + } +} + +struct usmuser * +usm_newuser(char *name, const char **errp) +{ + struct usmuser *up = usm_finduser(name); + if (up != NULL) { + *errp = "user redefined"; + return NULL; + } + if ((up = calloc(1, sizeof (*up))) == NULL) + fatal("usm"); + up->uu_name = name; + SLIST_INSERT_HEAD(&usmuserlist, up, uu_next); + return up; +} + +struct usmuser * +usm_finduser(char *name) +{ + struct usmuser *up; + + SLIST_FOREACH(up, &usmuserlist, uu_next) { + if (!strcmp(up->uu_name, name)) + return up; + } + return NULL; +} + +int +usm_checkuser(struct usmuser *up, const char **errp) +{ + char *auth, *priv; + + if (up->uu_auth != AUTH_NONE && up->uu_authkey == NULL) { + *errp = "missing auth passphrase"; + goto fail; + } else if (up->uu_auth == AUTH_NONE && up->uu_authkey != NULL) + up->uu_auth = AUTH_DEFAULT; + + if (up->uu_priv != PRIV_NONE && up->uu_privkey == NULL) { + *errp = "missing priv passphrase"; + goto fail; + } else if (up->uu_priv == PRIV_NONE && up->uu_privkey != NULL) + up->uu_priv = PRIV_DEFAULT; + + if (up->uu_auth == AUTH_NONE && up->uu_priv != PRIV_NONE) { + /* Standard prohibits noAuthPriv */ + *errp = "auth is mandatory with enc"; + goto fail; + } + + switch (up->uu_auth) { + case AUTH_NONE: + auth = "none"; + break; + case AUTH_MD5: + auth = "HMAC-MD5-96"; + break; + case AUTH_SHA1: + auth = "HMAC-SHA-96"; + break; + } + + switch (up->uu_priv) { + case PRIV_NONE: + priv = "none"; + break; + case PRIV_DES: + priv = "CBC-DES"; + break; + case PRIV_AES: + priv = "CFB128-AES-128"; + break; + } + log_debug("USM user '%s', auth %s, enc %s", up->uu_name, auth, priv); + return 0; + +fail: + free(up->uu_name); + free(up->uu_authkey); + free(up->uu_privkey); + SLIST_REMOVE(&usmuserlist, up, usmuser, uu_next); + free(up); + return -1; +} + +struct ber_element * +usm_decode(struct snmp_message *msg, struct ber_element *elm, const char **errp) +{ + struct snmp_stats *stats = &env->sc_stats; + off_t offs, offs2; + char *usmparams; + size_t len; + size_t enginelen, userlen, digestlen, saltlen; + struct ber ber; + struct ber_element *usm = NULL, *next = NULL, *decr; + char *engineid; + char *user; + char *digest, *salt; + u_long now; + long long engine_boots, engine_time; + + bzero(&ber, sizeof(ber)); + offs = ber_getpos(elm); + + if (ber_get_nstring(elm, (void *)&usmparams, &len) < 0) { + *errp = "cannot decode security params"; + goto done; + } + + ber.fd = -1; + ber_set_readbuf(&ber, usmparams, len); + usm = ber_read_elements(&ber, NULL); + if (usm == NULL) { + *errp = "cannot decode security params"; + goto done; + } + +#ifdef DEBUG + fprintf(stderr, "decode USM parameters:\n"); + snmpe_debug_elements(usm); +#endif + + if (ber_scanf_elements(usm, "{xiixpxx", &engineid, &enginelen, + &engine_boots, &engine_time, &user, &userlen, &offs2, + &digest, &digestlen, &salt, &saltlen) != 0) { + *errp = "cannot decode USM params"; + goto done; + } + + log_debug("USM: engineid '%s', engine boots %lld, engine time %lld, " + "user '%s'", tohexstr(engineid, enginelen), engine_boots, + engine_time, user); + + if (enginelen > SNMPD_MAXENGINEIDLEN || + userlen > SNMPD_MAXUSERNAMELEN || + (digestlen != (MSG_HAS_AUTH(msg) ? SNMP_USM_DIGESTLEN : 0)) || + (saltlen != (MSG_HAS_PRIV(msg) ? SNMP_USM_SALTLEN : 0))) { + *errp = "bad field length"; + goto done; + } + + if (enginelen != env->sc_engineid_len || + memcmp(engineid, env->sc_engineid, enginelen) != 0) { + *errp = "unknown engine id"; + msg->sm_usmerr = OIDVAL_usmErrEngineId; + stats->snmp_usmnosuchengine++; + goto done; + } + + if (engine_boots != 0LL && engine_time != 0LL) { + now = snmpd_engine_time(); + if (engine_boots != env->sc_engine_boots || + engine_time < (now - SNMP_MAX_TIMEWINDOW) || + engine_time > (now + SNMP_MAX_TIMEWINDOW)) { + *errp = "out of time window"; + msg->sm_usmerr = MIB_usmStatsNotInTimeWindow; + stats->snmp_usmtimewindow++; + goto done; + } + } + + msg->sm_engine_boots = (u_int32_t)engine_boots; + msg->sm_engine_time = (u_int32_t)engine_time; + + memcpy(msg->sm_username, user, userlen); + msg->sm_username[userlen] = '\0'; + if (MSG_SECLEVEL(msg) > 0) { + msg->sm_user = usm_finduser(msg->sm_username); + if (msg->sm_user == NULL) { + *errp = "no such user"; + msg->sm_usmerr = OIDVAL_usmErrUserName; + stats->snmp_usmnosuchuser++; + goto done; + } + } + + /* + * offs is the offset of the USM string within the serialized msg + * and offs2 the offset of the digest within the USM string. + */ + if (!usm_valid_digest(msg, offs + offs2, digest, digestlen)) { + *errp = "bad msg digest"; + msg->sm_usmerr = OIDVAL_usmErrDigest; + stats->snmp_usmwrongdigest++; + goto done; + } + + if (MSG_HAS_PRIV(msg)) { + memcpy(msg->sm_salt, salt, saltlen); + if ((decr = usm_decrypt(msg, elm->be_next)) == NULL) { + *errp = "cannot decrypt msg"; + msg->sm_usmerr = OIDVAL_usmErrDecrypt; + stats->snmp_usmdecrypterr++; + goto done; + } + ber_replace_elements(elm, decr); + } + next = elm->be_next; + +done: + ber_free(&ber); + if (usm != NULL) + ber_free_elements(usm); + return next; +} + +struct ber_element * +usm_encode(struct snmp_message *msg, struct ber_element *e) +{ + struct ber ber; + struct ber_element *usm, *a, *res = NULL; + void *ptr; + char digest[SNMP_USM_DIGESTLEN]; + size_t digestlen, saltlen, len; + + msg->sm_digest_offs = 0; + bzero(&ber, sizeof(ber)); + ber.fd = -1; + + usm = ber_add_sequence(NULL); + + if (MSG_HAS_AUTH(msg)) { + /* + * Fill in enough zeroes and remember the position within the + * messages. The digest will be calculated once the message + * is complete. + */ +#ifdef DEBUG + assert(msg->sm_user != NULL); +#endif + bzero(digest, sizeof(digest)); + digestlen = sizeof(digest); + } else + digestlen = 0; + + if (MSG_HAS_PRIV(msg)) { +#ifdef DEBUG + assert(msg->sm_user != NULL); +#endif + ++(msg->sm_user->uu_salt); + memcpy(msg->sm_salt, &msg->sm_user->uu_salt, + sizeof (msg->sm_salt)); + saltlen = sizeof (msg->sm_salt); + } else + saltlen = 0; + + msg->sm_engine_boots = (u_int32_t)env->sc_engine_boots; + msg->sm_engine_time = (u_int32_t)snmpd_engine_time(); + if ((a = ber_printf_elements(usm, "xdds", + env->sc_engineid, env->sc_engineid_len, msg->sm_engine_boots, + msg->sm_engine_time, msg->sm_username)) == NULL) + goto done; + + if ((a = ber_add_nstring(a, digest, digestlen)) == NULL) + goto done; + if (digestlen > 0) + ber_set_writecallback(a, usm_cb_digest, msg); + + if ((a = ber_add_nstring(a, msg->sm_salt, saltlen)) == NULL) + goto done; + +#ifdef DEBUG + fprintf(stderr, "encode USM parameters:\n"); + snmpe_debug_elements(usm); +#endif + len = ber_write_elements(&ber, usm); + if (ber_get_writebuf(&ber, &ptr) > 0) { + res = ber_add_nstring(e, (char *)ptr, len); + if (digestlen > 0) + ber_set_writecallback(res, usm_cb_digest, msg); + } + +done: + ber_free(&ber); + ber_free_elements(usm); + return res; +} + +void +usm_cb_digest(void *arg, size_t offs) +{ + struct snmp_message *msg = arg; + msg->sm_digest_offs += offs; +} + +struct ber_element * +usm_encrypt(struct snmp_message *msg, struct ber_element *pdu) +{ + struct ber ber; + struct ber_element *encrpdu = NULL; + void *ptr; + int len; + ssize_t elen; + u_char encbuf[READ_BUF_SIZE]; + + if (!MSG_HAS_PRIV(msg)) + return pdu; + + bzero(&ber, sizeof(ber)); + ber.fd = -1; + +#ifdef DEBUG + fprintf(stderr, "encrypted PDU:\n"); + snmpe_debug_elements(pdu); +#endif + + len = ber_write_elements(&ber, pdu); + if (ber_get_writebuf(&ber, &ptr) > 0) { + elen = usm_crypt(msg, ptr, len, encbuf, 1); + if (elen > 0) + encrpdu = ber_add_nstring(NULL, (char *)encbuf, elen); + } + + ber_free(&ber); + ber_free_elements(pdu); + return encrpdu; +} + +/* + * Calculate message digest and replace within message + */ +void +usm_finalize_digest(struct snmp_message *msg, char *buf, ssize_t len) +{ + const EVP_MD *md; + u_char digest[EVP_MAX_MD_SIZE]; + unsigned hlen; + + if (msg->sm_resp == NULL || + !MSG_HAS_AUTH(msg) || + msg->sm_user == NULL || + msg->sm_digest_offs == 0 || + len <= 0) + return; + bzero(digest, SNMP_USM_DIGESTLEN); +#ifdef DEBUG + assert(msg->sm_digest_offs + SNMP_USM_DIGESTLEN <= (size_t)len); + assert(!memcmp(buf + msg->sm_digest_offs, digest, SNMP_USM_DIGESTLEN)); +#endif + + if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL) + return; + + HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen, + (u_char*)buf, (size_t)len, digest, &hlen); + + memcpy(buf + msg->sm_digest_offs, digest, SNMP_USM_DIGESTLEN); + return; +} + +void +usm_make_report(struct snmp_message *msg) +{ + struct ber_oid usmstat = OID(MIB_usmStats, 0, 0); + + /* Always send report in clear-text */ + msg->sm_flags = 0; + msg->sm_context = SNMP_C_REPORT; + msg->sm_username[0] = '\0'; + usmstat.bo_id[OIDIDX_usmStats] = msg->sm_usmerr; + usmstat.bo_n = OIDIDX_usmStats + 2; + if (msg->sm_varbindresp != NULL) + ber_free_elements(msg->sm_varbindresp); + msg->sm_varbindresp = ber_add_sequence(NULL); + mps_getreq(msg->sm_varbindresp, &usmstat, msg->sm_version); + return; +} + +int +usm_valid_digest(struct snmp_message *msg, off_t offs, + char *digest, size_t digestlen) +{ + const EVP_MD *md; + u_char exp_digest[EVP_MAX_MD_SIZE]; + unsigned hlen; + + if (!MSG_HAS_AUTH(msg) || msg->sm_user == NULL) + return 1; + + if (digestlen != SNMP_USM_DIGESTLEN) + return 0; + +#ifdef DEBUG + assert(offs + digestlen <= msg->sm_datalen); + assert(bcmp(&msg->sm_data[offs], digest, digestlen) == 0); +#endif + + /* Ignore provided digest if user has no auth passphrase */ + if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL) + return 1; + + memset(&msg->sm_data[offs], 0, digestlen); + HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen, + msg->sm_data, msg->sm_datalen, exp_digest, &hlen); + /* we don't bother to restore the original message */ + + if (hlen < digestlen) + return 0; + + return memcmp(digest, exp_digest, digestlen) == 0; +} + +struct ber_element * +usm_decrypt(struct snmp_message *msg, struct ber_element *encr) +{ + u_char *privstr; + size_t privlen; + u_char buf[READ_BUF_SIZE]; + struct ber ber; + struct ber_element *scoped_pdu = NULL; + ssize_t scoped_pdu_len; + + if (ber_get_nstring(encr, (void *)&privstr, &privlen) < 0) + return NULL; + + scoped_pdu_len = usm_crypt(msg, privstr, (int)privlen, buf, 0); + if (scoped_pdu_len < 0) + return NULL; + + bzero(&ber, sizeof(ber)); + ber.fd = -1; + ber_set_readbuf(&ber, buf, scoped_pdu_len); + scoped_pdu = ber_read_elements(&ber, NULL); + +#ifdef DEBUG + if (scoped_pdu != NULL) { + fprintf(stderr, "decrypted scoped PDU:\n"); + snmpe_debug_elements(scoped_pdu); + } +#endif + + ber_free(&ber); + return scoped_pdu; +} + +ssize_t +usm_crypt(struct snmp_message *msg, u_char *inbuf, int inlen, u_char *outbuf, + int do_encrypt) +{ + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX ctx; + u_char *privkey; + int i; + u_char iv[EVP_MAX_IV_LENGTH]; + int len, len2; + int rv; + u_int32_t ivv; + + if ((cipher = usm_get_cipher(msg->sm_user->uu_priv)) == NULL) + return -1; + + privkey = (u_char *)msg->sm_user->uu_privkey; +#ifdef DEBUG + assert(privkey != NULL); +#endif + switch (msg->sm_user->uu_priv) { + case PRIV_DES: + /* RFC3414, chap 8.1.1.1. */ + for (i = 0; i < 8; i++) + iv[i] = msg->sm_salt[i] ^ privkey[SNMP_USM_SALTLEN + i]; + break; + case PRIV_AES: + /* RFC3826, chap 3.1.2.1. */ + ivv = htobe32(msg->sm_engine_boots); + memcpy(iv, &ivv, sizeof (ivv)); + ivv = htobe32(msg->sm_engine_time); + memcpy(iv + sizeof (ivv), &ivv, sizeof (ivv)); + memcpy(iv + 2 * sizeof (ivv), msg->sm_salt, SNMP_USM_SALTLEN); + break; + default: + return -1; + } + + if (!EVP_CipherInit(&ctx, cipher, privkey, iv, do_encrypt)) + return -1; + + if (!do_encrypt) + EVP_CIPHER_CTX_set_padding(&ctx, 0); + + if (EVP_CipherUpdate(&ctx, outbuf, &len, inbuf, inlen) && + EVP_CipherFinal(&ctx, outbuf + len, &len2)) + rv = len + len2; + else + rv = -1; + + EVP_CIPHER_CTX_cleanup(&ctx); + return rv; +} + +/* + * RFC3414, Password to Key Algorithm + */ +char * +usm_passwd2key(const EVP_MD *md, char *passwd, int *maxlen) +{ + EVP_MD_CTX ctx; + int i, count; + u_char *pw, *c; + u_char pwbuf[2 * EVP_MAX_MD_SIZE + SNMPD_MAXENGINEIDLEN]; + u_char keybuf[EVP_MAX_MD_SIZE]; + unsigned dlen; + char *key; + + EVP_DigestInit(&ctx, md); + pw = (u_char *)passwd; + for (count = 0; count < 1048576; count += 64) { + c = pwbuf; + for (i = 0; i < 64; i++) { + if (*pw == '\0') + pw = (u_char *)passwd; + *c++ = *pw++; + } + EVP_DigestUpdate(&ctx, pwbuf, 64); + } + EVP_DigestFinal(&ctx, keybuf, &dlen); + EVP_MD_CTX_cleanup(&ctx); + + /* Localize the key */ +#ifdef DEBUG + assert(env->sc_engineid_len <= SNMPD_MAXENGINEIDLEN); +#endif + memcpy(pwbuf, keybuf, dlen); + memcpy(pwbuf + dlen, env->sc_engineid, env->sc_engineid_len); + memcpy(pwbuf + dlen + env->sc_engineid_len, keybuf, dlen); + + EVP_DigestInit(&ctx, md); + EVP_DigestUpdate(&ctx, pwbuf, 2 * dlen + env->sc_engineid_len); + EVP_DigestFinal(&ctx, keybuf, &dlen); + EVP_MD_CTX_cleanup(&ctx); + + if (*maxlen > 0 && dlen > (unsigned)*maxlen) + dlen = (unsigned)*maxlen; + if ((key = malloc(dlen)) == NULL) + fatal("key"); + memcpy(key, keybuf, dlen); + *maxlen = (int)dlen; + return key; +} |