diff options
author | syrinx <syrinx@FreeBSD.org> | 2010-12-08 13:51:38 +0000 |
---|---|---|
committer | syrinx <syrinx@FreeBSD.org> | 2010-12-08 13:51:38 +0000 |
commit | ed79f703fb1803e10222fccd98100334741138ff (patch) | |
tree | 30b62404bfa57932ef78fb729b57635b158c2a4a /contrib/bsnmp/lib | |
parent | 6e855a313aac604a57c7b9d8561a9a4e5c2f6666 (diff) | |
download | FreeBSD-src-ed79f703fb1803e10222fccd98100334741138ff.zip FreeBSD-src-ed79f703fb1803e10222fccd98100334741138ff.tar.gz |
In bsnmpd(1) add support for SNMPv3 message processing model, including message authentication, packet encryption & view-based access control (RFC 3412, 3414, 3415).
Sponsored by: The FreeBSD Foundation
Reviewed by: philip@ (mostly)
Approved by: philip@
Diffstat (limited to 'contrib/bsnmp/lib')
-rw-r--r-- | contrib/bsnmp/lib/asn1.c | 18 | ||||
-rw-r--r-- | contrib/bsnmp/lib/asn1.h | 3 | ||||
-rw-r--r-- | contrib/bsnmp/lib/bsnmpclient.3 | 91 | ||||
-rw-r--r-- | contrib/bsnmp/lib/bsnmplib.3 | 233 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmp.c | 551 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmp.h | 145 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpagent.c | 58 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpclient.c | 131 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmpclient.h | 52 | ||||
-rw-r--r-- | contrib/bsnmp/lib/snmppriv.h | 12 |
10 files changed, 1082 insertions, 212 deletions
diff --git a/contrib/bsnmp/lib/asn1.c b/contrib/bsnmp/lib/asn1.c index 16b523d..4d9704f 100644 --- a/contrib/bsnmp/lib/asn1.c +++ b/contrib/bsnmp/lib/asn1.c @@ -196,7 +196,7 @@ asn_put_temp_header(struct asn_buf *b, u_char type, u_char **ptr) return (ret); } enum asn_err -asn_commit_header(struct asn_buf *b, u_char *ptr) +asn_commit_header(struct asn_buf *b, u_char *ptr, size_t *moved) { asn_len_t len; u_int lenlen, shift; @@ -215,6 +215,8 @@ asn_commit_header(struct asn_buf *b, u_char *ptr) memmove(ptr + 1 + lenlen, ptr + TEMP_LEN, len); b->asn_ptr -= shift; b->asn_len += shift; + if (moved != NULL) + *moved = shift; } return (ASN_ERR_OK); } @@ -913,6 +915,20 @@ asn_skip(struct asn_buf *b, asn_len_t len) } /* + * Add a padding + */ +enum asn_err +asn_pad(struct asn_buf *b, asn_len_t len) +{ + if (b->asn_len < len) + return (ASN_ERR_EOBUF); + b->asn_ptr += len; + b->asn_len -= len; + + return (ASN_ERR_OK); +} + +/* * Compare two OIDs. * * o1 < o2 : -1 diff --git a/contrib/bsnmp/lib/asn1.h b/contrib/bsnmp/lib/asn1.h index 5748b14..cf9a727 100644 --- a/contrib/bsnmp/lib/asn1.h +++ b/contrib/bsnmp/lib/asn1.h @@ -93,7 +93,7 @@ enum asn_err asn_get_header(struct asn_buf *, u_char *, asn_len_t *); enum asn_err asn_put_header(struct asn_buf *, u_char, asn_len_t); enum asn_err asn_put_temp_header(struct asn_buf *, u_char, u_char **); -enum asn_err asn_commit_header(struct asn_buf *, u_char *); +enum asn_err asn_commit_header(struct asn_buf *, u_char *, size_t *); enum asn_err asn_get_integer_raw(struct asn_buf *, asn_len_t, int32_t *); enum asn_err asn_get_integer(struct asn_buf *, int32_t *); @@ -129,6 +129,7 @@ enum asn_err asn_get_timeticks(struct asn_buf *, uint32_t *); enum asn_err asn_put_timeticks(struct asn_buf *, uint32_t); enum asn_err asn_skip(struct asn_buf *, asn_len_t); +enum asn_err asn_pad(struct asn_buf *, asn_len_t); /* * Utility functions for OIDs diff --git a/contrib/bsnmp/lib/bsnmpclient.3 b/contrib/bsnmp/lib/bsnmpclient.3 index 870f546..5fe9a9a 100644 --- a/contrib/bsnmp/lib/bsnmpclient.3 +++ b/contrib/bsnmp/lib/bsnmpclient.3 @@ -31,7 +31,7 @@ .\" .\" $Begemot: bsnmp/lib/bsnmpclient.3,v 1.12 2005/10/04 08:46:50 brandt_h Exp $ .\" -.Dd October 4, 2005 +.Dd September 9, 2010 .Dt BSNMPCLIENT 3 .Os .Sh NAME @@ -52,7 +52,8 @@ .Nm snmp_table_cb_f , .Nm snmp_table_fetch , .Nm snmp_table_fetch_async , -.Nm snmp_dialog +.Nm snmp_dialog , +.Nm snmp_discover_engine .Nd "SNMP client library" .Sh LIBRARY Begemot SNMP library @@ -102,44 +103,56 @@ Begemot SNMP library .Fn snmp_table_fetch_async "const struct snmp_table *descr" "void *list" "snmp_table_cb_f callback" "void *uarg" .Ft int .Fn snmp_dialog "struct snmp_pdu *req" "struct snmp_pdu *resp" +.Ft int +.Fn snmp_discover_engine "void" .Sh DESCRIPTION The SNMP library contains routines to easily build SNMP client applications -that use SNMP versions 1 or 2. +that use SNMP versions 1, 2 or 3. Most of the routines use a .Vt struct snmp_client : .Bd -literal -offset indent struct snmp_client { - enum snmp_version version; - int trans; /* transport type to use */ + enum snmp_version version; + int trans; /* which transport to use */ /* these two are read-only for the application */ - char *cport; /* port number as string */ - char *chost; /* host name or IP address as string */ + char *cport; /* port number as string */ + char *chost; /* host name or IP address as string */ + + char read_community[SNMP_COMMUNITY_MAXLEN + 1]; + char write_community[SNMP_COMMUNITY_MAXLEN + 1]; + + /* SNMPv3 specific fields */ + int32_t identifier; + int32_t security_model; + struct snmp_engine engine; + struct snmp_user user; - char read_community[SNMP_COMMUNITY_MAXLEN + 1]; - char write_community[SNMP_COMMUNITY_MAXLEN + 1]; + /* SNMPv3 Access control - VACM*/ + uint32_t clen; + uint8_t cengine[SNMP_ENGINE_ID_SIZ]; + char cname[SNMP_CONTEXT_NAME_SIZ]; - struct timeval timeout; - u_int retries; + struct timeval timeout; + u_int retries; - int dump_pdus; + int dump_pdus; - size_t txbuflen; - size_t rxbuflen; + size_t txbuflen; + size_t rxbuflen; - int fd; + int fd; - int32_t next_reqid; - int32_t max_reqid; - int32_t min_reqid; + int32_t next_reqid; + int32_t max_reqid; + int32_t min_reqid; - char error[SNMP_STRERROR_LEN]; + char error[SNMP_STRERROR_LEN]; - snmp_timeout_start_f timeout_start; - snmp_timeout_stop_f timeout_stop; + snmp_timeout_start_f timeout_start; + snmp_timeout_stop_f timeout_stop; - /* private */ - char local_path[sizeof(SNMP_LOCAL_PATH)]; + char local_path[sizeof(SNMP_LOCAL_PATH)]; }; .Ed .Pp @@ -194,6 +207,23 @@ The default is The community name to be used for SET requests. The default is .Sq private . +.It Va identifier +The message indentifier value to be used with SNMPv3 PDUs. Incremented with +each transmitted PDU. +.It Va security_model +The security model to be used with SNMPv3 PDUs. Currently only User-Based +Security model specified by RFC 3414 (value 3) is supported. +.It Va engine +The authorative SNMP engine parameters to be used with SNMPv3 PDUs. +.It Va user +The USM SNMP user credentials to be used with SNMPv3 PDUs. +.It Va clen +The length of the context engine id to be used with SNMPv3 PDUs. +.It Va cengine +The context engine id to be used with SNMPv3 PDUs. Default is empty. +.It Va cname +The context name to be used with SNMPv3 PDUs. Default is +.Sq "" . .It Va timeout The maximum time to wait for responses to requests. If the time elapses, the request is resent up to @@ -617,6 +647,21 @@ returns -1. If a response was received 0 is returned. .Pp The function +.Fn snmp_discover_engine +is used to discover the authorative snmpEngineId of a remote SNMPv3 agent. +A request PDU with empty USM user name is sent and the client's engine +parameters are set according to the snmpEngine parameters received in the +response PDU. +If the client is configured to use authentication and/or privacy and the +snmpEngineBoots and/or snmpEngineTime in the response had zero values, an +additional request (possibly encrypted) with the appropriate user credentials +is sent to fetch the missing values. +Note, that the function blocks until the discovery proccess is completed. +If no response could be received after all timeouts and retries, or the +response contained errors the function returns -1. +If the discovery proccess was completed 0 is returned. +.Pp +The function .Fn snmp_parse_server is used to parse an SNMP server specification string and fill in the fields of a diff --git a/contrib/bsnmp/lib/bsnmplib.3 b/contrib/bsnmp/lib/bsnmplib.3 index dfbffc4..af36879 100644 --- a/contrib/bsnmp/lib/bsnmplib.3 +++ b/contrib/bsnmp/lib/bsnmplib.3 @@ -1,4 +1,10 @@ .\" +.\" Copyright (c) 2010 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" Portions of this documentation were written by Shteryana Sotirova Shopova +.\" under sponsorship from the FreeBSD Foundation. +.\" .\" Copyright (c) 2004-2005 .\" Hartmut Brandt. .\" All rights reserved. @@ -31,7 +37,7 @@ .\" .\" $Begemot: bsnmp/lib/bsnmplib.3,v 1.9 2005/10/04 08:46:51 brandt_h Exp $ .\" -.Dd October 4, 2005 +.Dd September 9, 2010 .Dt BSNMPLIB 3 .Os .Sh NAME @@ -39,9 +45,15 @@ .Nm snmp_value_parse , .Nm snmp_value_copy , .Nm snmp_pdu_free , -.Nm snmp_code snmp_pdu_decode , -.Nm snmp_code snmp_pdu_encode , +.Nm snmp_pdu_decode , +.Nm snmp_pdu_encode , +.Nm snmp_pdu_decode_header , +.Nm snmp_pdu_decode_scoped , +.Nm snmp_pdu_decode_secmode , .Nm snmp_pdu_dump , +.Nm snmp_passwd_to_keys , +.Nm snmp_get_local_keys , +.Nm snmp_calc_keychange , .Nm TRUTH_MK , .Nm TRUTH_GET , .Nm TRUTH_OK @@ -64,8 +76,20 @@ Begemot SNMP library .Fn snmp_pdu_decode "struct asn_buf *buf" "struct snmp_pdu *pdu" "int32_t *ip" .Ft enum snmp_code .Fn snmp_pdu_encode "struct snmp_pdu *pdu" "struct asn_buf *buf" +.Ft enum snmp_code +.Fn snmp_pdu_decode_header "struct snmp_pdu *pdu" "struct asn_buf *buf" +.Ft enum snmp_code +.Fn snmp_pdu_decode_scoped "struct asn_buf *buf" "struct snmp_pdu *pdu" "int32_t *ip" +.Ft enum snmp_code +.Fn snmp_pdu_decode_secmode "struct asn_buf *buf" "struct snmp_pdu *pdu" .Ft void .Fn snmp_pdu_dump "const struct snmp_pdu *pdu" +.Ft enum snmp_code +.Fn snmp_passwd_to_keys "struct snmp_user *user" "char *passwd" +.Ft enum snmp_code +.Fn snmp_get_local_keys "struct snmp_user *user" "uint8_t *eid" "uint32_t elen" +.Ft enum snmp_code +.Fn snmp_calc_keychange "struct snmp_user *user" "uint8_t *keychange" .Ft int .Fn TRUTH_MK "F" .Ft int @@ -73,8 +97,8 @@ Begemot SNMP library .Ft int .Fn TRUTH_OK "T" .Sh DESCRIPTION -The SNMP library contains routines to handle SNMP version 1 and 2 PDUs. -There are two basic structures used throughout the library: +The SNMP library contains routines to handle SNMP version 1, 2 and 3 PDUs. +There are several basic structures used throughout the library: .Bd -literal -offset indent struct snmp_value { struct asn_oid var; @@ -134,34 +158,126 @@ is not zero, .Fa v.octetstring.octets points to a string allocated by .Xr malloc 3 . +.Pp +.Bd -literal -offset indent +#define SNMP_ENGINE_ID_SIZ 32 + +struct snmp_engine { + uint8_t engine_id[SNMP_ENGINE_ID_SIZ]; + uint32_t engine_len; + int32_t engine_boots; + int32_t engine_time; + int32_t max_msg_size; +}; +.Ed +.Pp +This structure represents an SNMP engine as specified by the SNMP Management +Architecture described in RFC 3411. +.Pp +.Bd -literal -offset indent +#define SNMP_USM_NAME_SIZ (32 + 1) +#define SNMP_AUTH_KEY_SIZ 40 +#define SNMP_PRIV_KEY_SIZ 32 + +struct snmp_user { + char sec_name[SNMP_USM_NAME_SIZ]; + enum snmp_authentication auth_proto; + enum snmp_privacy priv_proto; + uint8_t auth_key[SNMP_AUTH_KEY_SIZ]; + uint8_t priv_key[SNMP_PRIV_KEY_SIZ]; +}; +.Ed +.Pp +This structure represents an SNMPv3 user as specified by the User-based +Security Model (USM) described in RFC 3414. The field +.Fa sec_name +is a human readable string containing the security user name. +.Fa auth_proto +contains the id of the authentication protocol in use by the user and may be one +of: +.Bd -literal -offset indent +enum snmp_authentication { + SNMP_AUTH_NOAUTH = 0, + SNMP_AUTH_HMAC_MD5, + SNMP_AUTH_HMAC_SHA +}; +.Ed +.Fa priv_proto +contains the id of the privacy protocol in use by the user and may be one +of: +.Bd -literal -offset indent +enum snmp_privacy { + SNMP_PRIV_NOPRIV = 0, + SNMP_PRIV_DES = 1, + SNMP_PRIV_AES +}; +.Ed +.Fa auth_key +and +.Fa priv_key +contain the authentication and privacy keys for the user. +.Pp .Bd -literal -offset indent -#define SNMP_COMMUNITY_MAXLEN 128 -#define SNMP_MAX_BINDINGS 100 +#define SNMP_COMMUNITY_MAXLEN 128 +#define SNMP_MAX_BINDINGS 100 +#define SNMP_CONTEXT_NAME_SIZ (32 + 1) +#define SNMP_TIME_WINDOW 150 + +#define SNMP_USM_AUTH_SIZE 12 +#define SNMP_USM_PRIV_SIZE 8 + +#define SNMP_MSG_AUTH_FLAG 0x1 +#define SNMP_MSG_PRIV_FLAG 0x2 +#define SNMP_MSG_REPORT_FLAG 0x4 + +#define SNMP_SECMODEL_USM 3 struct snmp_pdu { - char community[SNMP_COMMUNITY_MAXLEN + 1]; - enum snmp_version version; - u_int type; + char community[SNMP_COMMUNITY_MAXLEN + 1]; + enum snmp_version version; + u_int type; + + /* SNMPv3 PDU header fields */ + int32_t identifier; + uint8_t flags; + int32_t security_model; + struct snmp_engine engine; + + /* Associated USM user parameters */ + struct snmp_user user; + uint8_t msg_digest[SNMP_USM_AUTH_SIZE]; + uint8_t msg_salt[SNMP_USM_PRIV_SIZE]; + + /* View-based Access Model */ + uint32_t context_engine_len; + uint8_t context_engine[SNMP_ENGINE_ID_SIZ]; + char context_name[SNMP_CONTEXT_NAME_SIZ]; /* trap only */ - struct asn_oid enterprise; - u_char agent_addr[4]; - int32_t generic_trap; - int32_t specific_trap; - u_int32_t time_stamp; + struct asn_oid enterprise; + u_char agent_addr[4]; + int32_t generic_trap; + int32_t specific_trap; + uint32_t time_stamp; /* others */ - int32_t request_id; - int32_t error_status; - int32_t error_index; + int32_t request_id; + int32_t error_status; + int32_t error_index; /* fixes for encoding */ - u_char *outer_ptr; - u_char *pdu_ptr; - u_char *vars_ptr; + size_t outer_len; + size_t scoped_len; + u_char *outer_ptr; + u_char *digest_ptr; + u_char *encrypted_ptr; + u_char *scoped_ptr; + u_char *pdu_ptr; + u_char *vars_ptr; + - struct snmp_value bindings[SNMP_MAX_BINDINGS]; - u_int nbindings; + struct snmp_value bindings[SNMP_MAX_BINDINGS]; + u_int nbindings; }; .Ed This structure contains a decoded SNMP PDU. @@ -172,11 +288,15 @@ enum snmp_version { SNMP_Verr = 0, SNMP_V1 = 1, SNMP_V2c, + SNMP_V3 }; .Ed and .Fa type is the type of the PDU. +.Fa security_model +is the security model used for SNMPv3 PDUs. The only supported +value currently is 3 (User-based Security Model). .Pp The function .Fn snmp_value_free @@ -223,15 +343,60 @@ The function .Fn snmp_pdu_encode encodes the PDU .Fa pdu -into the an octetstring in buffer +into the an octetstring in buffer, and if authentication and privacy are used, +calculates a message digest and encrypts the PDU data in the buffer +.Fa buf . +.Pp +The function +.Fn snmp_pdu_decode_header +decodes the header of the PDU pointed to by +.Fa buf . +The uncoded PDU contents remain in the buffer. +.Pp +The function +.Fn snmp_pdu_decode_scoped +decodes the scoped PDU pointed to by .Fa buf . .Pp The function +.Fn snmp_pdu_decode_secmode +verifies the authentication parameter contained in the PDU (if present) and +if the PDU is encrypted, decrypts the PDU contents pointed to by +.Fa buf . +If successfull, a plain text scoped PDU is stored in the buffer. +.Pp +The function .Fn snmp_pdu_dump dumps the PDU in a human readable form by calling .Fn snmp_printf . .Pp The function +.Fn snmp_passwd_to_keys +calculates a binary private authentication key corresponding to a plain text human +readable password string. The calculated key is placed in the +.Fa auth_key +field of the +.Fa user . +.Pp +The function +.Fn snmp_get_local_keys +calculates a localazied authentication and privacy keys for a specified SNMPv3 +engine. The calculateds keys are placed in the +.Fa auth_key +and +.Fa priv_key +fields of the +.Fa user . +.Pp +The function +.Fn snmp_calc_keychange +calculates a binary key change octet string based on the contents of an old and +a new binary localized key. The rezult is placed in the buffer pointer to by +.Fa keychange +and may be used by an SNMPv3 user who wishes to change his/her password +or localized key. +.Pp +The function .Fn TRUTH_MK takes a C truth value (zero or non-zero) and makes an SNMP truth value (2 or 1). The function @@ -281,6 +446,13 @@ A variable binding value was out of the allowed range. The PDU is of an unsupported version. .It Bq Er SNMP_CODE_BADENQ There was an ASN.1 value with an unsupported tag. +.It Bq Er SNMP_CODE_BADSECLEVEL +The requested securityLevel contained in the PDU is not supported. +.It Bq Er SNMP_CODE_BADDIGEST +The PDU authentication parameter received in the PDU did not match the +calculated message digest. +.It Bq Er SNMP_CODE_EDECRYPT +Error occured while trying to decrypt the PDU. .El .Pp .Fn snmp_pdu_encode @@ -297,8 +469,21 @@ Encoding failed. .Xr bsnmpagent 3 , .Xr bsnmpclient 3 , .Xr bsnmplib 3 +.Sh CAVEAT +The SNMPv3 message digests, encryption and decryption, and key routines use +the cryptographic functions from +.Xr crypto 3 . +The library may optionally be built without references to the +.Xr crypto 3 +library. In such case only plain text SNMPv3 PDUs without message digests +may be proccessed correctly. .Sh STANDARDS This implementation conforms to the applicable IETF RFCs and ITU-T recommendations. .Sh AUTHORS +The Begemot SNMP library was originally written by .An Hartmut Brandt Aq harti@FreeBSD.org +.Pp +.An Shteryana Shopova Aq syrinx@FreeBSD.org +added support for the SNMPv3 message proccessing and User-Based +Security model message authentication and privacy. diff --git a/contrib/bsnmp/lib/snmp.c b/contrib/bsnmp/lib/snmp.c index b2700b1..322ac9f 100644 --- a/contrib/bsnmp/lib/snmp.c +++ b/contrib/bsnmp/lib/snmp.c @@ -5,6 +5,12 @@ * * Author: Harti Brandt <harti@freebsd.org> * + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by Shteryana Sotirova Shopova + * under sponsorship from the FreeBSD Foundation. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -271,47 +277,310 @@ parse_pdus(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) return (err); } + +static enum asn_err +parse_secparams(struct asn_buf *b, struct snmp_pdu *pdu) +{ + asn_len_t octs_len; + u_char buf[256]; /* XXX: calc max possible size here */ + struct asn_buf tb; + + memset(buf, 0, 256); + tb.asn_ptr = buf; + tb.asn_len = 256; + + if (asn_get_octetstring(b, buf, &tb.asn_len) != ASN_ERR_OK) { + snmp_error("cannot parse usm header"); + return (ASN_ERR_FAILED); + } + + if (asn_get_sequence(&tb, &octs_len) != ASN_ERR_OK) { + snmp_error("cannot decode usm header"); + return (ASN_ERR_FAILED); + } + + octs_len = SNMP_ENGINE_ID_SIZ; + if (asn_get_octetstring(&tb, (u_char *)&pdu->engine.engine_id, + &octs_len) != ASN_ERR_OK) { + snmp_error("cannot decode msg engine id"); + return (ASN_ERR_FAILED); + } + pdu->engine.engine_len = octs_len; + + if (asn_get_integer(&tb, &pdu->engine.engine_boots) != ASN_ERR_OK) { + snmp_error("cannot decode msg engine boots"); + return (ASN_ERR_FAILED); + } + + if (asn_get_integer(&tb, &pdu->engine.engine_time) != ASN_ERR_OK) { + snmp_error("cannot decode msg engine time"); + return (ASN_ERR_FAILED); + } + + octs_len = SNMP_ADM_STR32_SIZ - 1; + if (asn_get_octetstring(&tb, (u_char *)&pdu->user.sec_name, &octs_len) + != ASN_ERR_OK) { + snmp_error("cannot decode msg user name"); + return (ASN_ERR_FAILED); + } + pdu->user.sec_name[octs_len] = '\0'; + + octs_len = sizeof(pdu->msg_digest); + if (asn_get_octetstring(&tb, (u_char *)&pdu->msg_digest, &octs_len) != + ASN_ERR_OK || ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0 && + octs_len != sizeof(pdu->msg_digest))) { + snmp_error("cannot decode msg authentication param"); + return (ASN_ERR_FAILED); + } + + octs_len = sizeof(pdu->msg_salt); + if (asn_get_octetstring(&tb, (u_char *)&pdu->msg_salt, &octs_len) != + ASN_ERR_OK ||((pdu->flags & SNMP_MSG_PRIV_FLAG) != 0 && + octs_len != sizeof(pdu->msg_salt))) { + snmp_error("cannot decode msg authentication param"); + return (ASN_ERR_FAILED); + } + + if ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0) { + pdu->digest_ptr = b->asn_ptr - SNMP_USM_AUTH_SIZE; + pdu->digest_ptr -= octs_len + ASN_MAXLENLEN; + } + + return (ASN_ERR_OK); +} + +static enum snmp_code +pdu_encode_secparams(struct asn_buf *b, struct snmp_pdu *pdu) +{ + u_char buf[256], *sptr; + struct asn_buf tb; + size_t auth_off, moved = 0; + + auth_off = 0; + memset(buf, 0, 256); + tb.asn_ptr = buf; + tb.asn_len = 256; + + if (asn_put_temp_header(&tb, (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED), + &sptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_octetstring(&tb, (u_char *)pdu->engine.engine_id, + pdu->engine.engine_len) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(&tb, pdu->engine.engine_boots) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(&tb, pdu->engine.engine_time) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_octetstring(&tb, (u_char *)pdu->user.sec_name, + strlen(pdu->user.sec_name)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0) { + auth_off = sizeof(buf) - tb.asn_len + ASN_MAXLENLEN; + if (asn_put_octetstring(&tb, (u_char *)pdu->msg_digest, + sizeof(pdu->msg_digest)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } else { + if (asn_put_octetstring(&tb, (u_char *)pdu->msg_digest, 0) + != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } + + if ((pdu->flags & SNMP_MSG_PRIV_FLAG) != 0) { + if (asn_put_octetstring(&tb, (u_char *)pdu->msg_salt, + sizeof(pdu->msg_salt)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } else { + if (asn_put_octetstring(&tb, (u_char *)pdu->msg_salt, 0) + != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } + + if (asn_commit_header(&tb, sptr, &moved) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0) + pdu->digest_ptr = b->asn_ptr + auth_off - moved; + + if (asn_put_octetstring(b, buf, sizeof(buf) - tb.asn_len) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + pdu->digest_ptr += ASN_MAXLENLEN; + + if ((pdu->flags & SNMP_MSG_PRIV_FLAG) != 0 && asn_put_temp_header(b, + ASN_TYPE_OCTETSTRING, &pdu->encrypted_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + return (SNMP_CODE_OK); +} + /* - * Parse the outer SEQUENCE value. ASN_ERR_TAG means 'bad version'. + * Decode the PDU except for the variable bindings itself. + * If decoding fails because of a bad binding, but the rest can be + * decoded, ip points to the index of the failed variable (errors + * OORANGE, BADLEN or BADVERS). */ -enum asn_err -snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp) +enum snmp_code +snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +{ + enum snmp_code code; + + if ((code = snmp_pdu_decode_header(b, pdu)) != SNMP_CODE_OK) + return (code); + + if (pdu->version == SNMP_V3) { + if (pdu->security_model != SNMP_SECMODEL_USM) + return (SNMP_CODE_FAILED); + if ((code = snmp_pdu_decode_secmode(b, pdu)) != SNMP_CODE_OK) + return (code); + } + + code = snmp_pdu_decode_scoped(b, pdu, ip); + + switch (code) { + case SNMP_CODE_FAILED: + snmp_pdu_free(pdu); + break; + + case SNMP_CODE_BADENC: + if (pdu->version == SNMP_Verr) + return (SNMP_CODE_BADVERS); + + default: + break; + } + + return (code); +} + +enum snmp_code +snmp_pdu_decode_header(struct asn_buf *b, struct snmp_pdu *pdu) { int32_t version; - u_char type; - u_int comm_len; + u_int octs_len; + asn_len_t len; + + pdu->outer_ptr = b->asn_ptr; + pdu->outer_len = b->asn_len; + + if (asn_get_sequence(b, &len) != ASN_ERR_OK) { + snmp_error("cannot decode pdu header"); + return (SNMP_CODE_FAILED); + } + if (b->asn_len < len) { + snmp_error("outer sequence value too short"); + return (SNMP_CODE_FAILED); + } + if (b->asn_len != len) { + snmp_error("ignoring trailing junk in message"); + b->asn_len = len; + } if (asn_get_integer(b, &version) != ASN_ERR_OK) { snmp_error("cannot decode version"); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } - if (version == 0) { + if (version == 0) pdu->version = SNMP_V1; - } else if (version == 1) { + else if (version == 1) pdu->version = SNMP_V2c; - } else { + else if (version == 3) + pdu->version = SNMP_V3; + else { pdu->version = SNMP_Verr; snmp_error("unsupported SNMP version"); - return (ASN_ERR_TAG); + return (SNMP_CODE_BADENC); } - comm_len = SNMP_COMMUNITY_MAXLEN; - if (asn_get_octetstring(b, (u_char *)pdu->community, - &comm_len) != ASN_ERR_OK) { - snmp_error("cannot decode community"); - return (ASN_ERR_FAILED); + if (pdu->version == SNMP_V3) { + if (asn_get_sequence(b, &len) != ASN_ERR_OK) { + snmp_error("cannot decode pdu global data header"); + return (SNMP_CODE_FAILED); + } + + if (asn_get_integer(b, &pdu->identifier) != ASN_ERR_OK) { + snmp_error("cannot decode msg indetifier"); + return (SNMP_CODE_FAILED); + } + + if (asn_get_integer(b, &pdu->engine.max_msg_size) + != ASN_ERR_OK) { + snmp_error("cannot decode msg size"); + return (SNMP_CODE_FAILED); + } + + octs_len = 1; + if (asn_get_octetstring(b, (u_char *)&pdu->flags, + &octs_len) != ASN_ERR_OK) { + snmp_error("cannot decode msg flags"); + return (SNMP_CODE_FAILED); + } + + if (asn_get_integer(b, &pdu->security_model) != ASN_ERR_OK) { + snmp_error("cannot decode msg size"); + return (SNMP_CODE_FAILED); + } + + if (pdu->security_model != SNMP_SECMODEL_USM) + return (SNMP_CODE_FAILED); + + if (parse_secparams(b, pdu) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } else { + octs_len = SNMP_COMMUNITY_MAXLEN; + if (asn_get_octetstring(b, (u_char *)pdu->community, + &octs_len) != ASN_ERR_OK) { + snmp_error("cannot decode community"); + return (SNMP_CODE_FAILED); + } + pdu->community[octs_len] = '\0'; + } + + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_pdu_decode_scoped(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +{ + u_char type; + asn_len_t len, trailer; + enum asn_err err; + + if (pdu->version == SNMP_V3) { + if (asn_get_sequence(b, &len) != ASN_ERR_OK) { + snmp_error("cannot decode scoped pdu header"); + return (SNMP_CODE_FAILED); + } + + len = SNMP_ENGINE_ID_SIZ; + if (asn_get_octetstring(b, (u_char *)&pdu->context_engine, + &len) != ASN_ERR_OK) { + snmp_error("cannot decode msg context engine"); + return (SNMP_CODE_FAILED); + } + pdu->context_engine_len = len; + + len = SNMP_CONTEXT_NAME_SIZ; + if (asn_get_octetstring(b, (u_char *)&pdu->context_name, + &len) != ASN_ERR_OK) { + snmp_error("cannot decode msg context name"); + return (SNMP_CODE_FAILED); + } + pdu->context_name[len] = '\0'; } - pdu->community[comm_len] = '\0'; - if (asn_get_header(b, &type, lenp) != ASN_ERR_OK) { + if (asn_get_header(b, &type, &len) != ASN_ERR_OK) { snmp_error("cannot get pdu header"); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } if ((type & ~ASN_TYPE_MASK) != (ASN_TYPE_CONSTRUCTED | ASN_CLASS_CONTEXT)) { snmp_error("bad pdu header tag"); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } pdu->type = type & ASN_TYPE_MASK; @@ -326,7 +595,7 @@ snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp) case SNMP_PDU_TRAP: if (pdu->version != SNMP_V1) { snmp_error("bad pdu type %u", pdu->type); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } break; @@ -336,99 +605,64 @@ snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp) case SNMP_PDU_REPORT: if (pdu->version == SNMP_V1) { snmp_error("bad pdu type %u", pdu->type); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } break; default: snmp_error("bad pdu type %u", pdu->type); - return (ASN_ERR_FAILED); - } - - - if (*lenp > b->asn_len) { - snmp_error("pdu length too long"); - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); } - return (ASN_ERR_OK); -} - -static enum asn_err -parse_message(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) -{ - enum asn_err err; - asn_len_t len, trailer; - - err = snmp_parse_message_hdr(b, pdu, &len); - if (ASN_ERR_STOPPED(err)) - return (err); - trailer = b->asn_len - len; b->asn_len = len; err = parse_pdus(b, pdu, ip); if (ASN_ERR_STOPPED(err)) - return (ASN_ERR_FAILED); + return (SNMP_CODE_FAILED); if (b->asn_len != 0) snmp_error("ignoring trailing junk after pdu"); b->asn_len = trailer; - return (err); + return (SNMP_CODE_OK); } -/* - * Decode the PDU except for the variable bindings itself. - * If decoding fails because of a bad binding, but the rest can be - * decoded, ip points to the index of the failed variable (errors - * OORANGE, BADLEN or BADVERS). - */ enum snmp_code -snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) +snmp_pdu_decode_secmode(struct asn_buf *b, struct snmp_pdu *pdu) { - asn_len_t len; + u_char type; + enum snmp_code code; + uint8_t digest[SNMP_USM_AUTH_SIZE]; - memset(pdu, 0, sizeof(*pdu)); + if (pdu->user.auth_proto != SNMP_AUTH_NOAUTH && + (pdu->flags & SNMP_MSG_AUTH_FLAG) == 0) + return (SNMP_CODE_BADSECLEVEL); - if (asn_get_sequence(b, &len) != ASN_ERR_OK) { - snmp_error("cannot decode pdu header"); - return (SNMP_CODE_FAILED); - } - if (b->asn_len < len) { - snmp_error("outer sequence value too short"); + if ((code = snmp_pdu_calc_digest(b, pdu, digest)) != + SNMP_CODE_OK) return (SNMP_CODE_FAILED); - } - if (b->asn_len != len) { - snmp_error("ignoring trailing junk in message"); - b->asn_len = len; - } - - switch (parse_message(b, pdu, ip)) { - case ASN_ERR_OK: - return (SNMP_CODE_OK); + if (pdu->user.auth_proto != SNMP_AUTH_NOAUTH && + memcmp(digest, pdu->msg_digest, sizeof(pdu->msg_digest)) != 0) + return (SNMP_CODE_BADDIGEST); - case ASN_ERR_FAILED: - case ASN_ERR_EOBUF: - snmp_pdu_free(pdu); + if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV && (asn_get_header(b, &type, + &pdu->scoped_len) != ASN_ERR_OK || type != ASN_TYPE_OCTETSTRING)) { + snmp_error("cannot decode encrypted pdu"); return (SNMP_CODE_FAILED); + } + pdu->scoped_ptr = b->asn_ptr; - case ASN_ERR_BADLEN: - return (SNMP_CODE_BADLEN); - - case ASN_ERR_RANGE: - return (SNMP_CODE_OORANGE); + if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV && + (pdu->flags & SNMP_MSG_PRIV_FLAG) == 0) + return (SNMP_CODE_BADSECLEVEL); - case ASN_ERR_TAG: - if (pdu->version == SNMP_Verr) - return (SNMP_CODE_BADVERS); - else - return (SNMP_CODE_BADENC); - } + if ((code = snmp_pdu_decrypt(b, pdu)) != SNMP_CODE_OK) + return (SNMP_CODE_FAILED); - return (SNMP_CODE_OK); + return (code); } /* @@ -500,6 +734,7 @@ enum snmp_code snmp_pdu_encode_header(struct asn_buf *b, struct snmp_pdu *pdu) { enum asn_err err; + u_char *v3_hdr_ptr; if (asn_put_temp_header(b, (ASN_TYPE_SEQUENCE|ASN_TYPE_CONSTRUCTED), &pdu->outer_ptr) != ASN_ERR_OK) @@ -509,14 +744,62 @@ snmp_pdu_encode_header(struct asn_buf *b, struct snmp_pdu *pdu) err = asn_put_integer(b, 0); else if (pdu->version == SNMP_V2c) err = asn_put_integer(b, 1); + else if (pdu->version == SNMP_V3) + err = asn_put_integer(b, 3); else return (SNMP_CODE_BADVERS); if (err != ASN_ERR_OK) return (SNMP_CODE_FAILED); - if (asn_put_octetstring(b, (u_char *)pdu->community, - strlen(pdu->community)) != ASN_ERR_OK) - return (SNMP_CODE_FAILED); + if (pdu->version == SNMP_V3) { + if (asn_put_temp_header(b, (ASN_TYPE_SEQUENCE | + ASN_TYPE_CONSTRUCTED), &v3_hdr_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(b, pdu->identifier) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(b, pdu->engine.max_msg_size) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (pdu->type != SNMP_PDU_RESPONSE && + pdu->type != SNMP_PDU_TRAP && + pdu->type != SNMP_PDU_REPORT) + pdu->flags |= SNMP_MSG_REPORT_FLAG; + + if (asn_put_octetstring(b, (u_char *)&pdu->flags, 1) + != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_integer(b, pdu->security_model) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_commit_header(b, v3_hdr_ptr, NULL) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (pdu->security_model != SNMP_SECMODEL_USM) + return (SNMP_CODE_FAILED); + + if (pdu_encode_secparams(b, pdu) != SNMP_CODE_OK) + return (SNMP_CODE_FAILED); + + /* View-based Access Conntrol information */ + if (asn_put_temp_header(b, (ASN_TYPE_SEQUENCE | + ASN_TYPE_CONSTRUCTED), &pdu->scoped_ptr) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_octetstring(b, (u_char *)pdu->context_engine, + pdu->context_engine_len) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (asn_put_octetstring(b, (u_char *)pdu->context_name, + strlen(pdu->context_name)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } else { + if (asn_put_octetstring(b, (u_char *)pdu->community, + strlen(pdu->community)) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } if (asn_put_temp_header(b, (ASN_TYPE_CONSTRUCTED | ASN_CLASS_CONTEXT | pdu->type), &pdu->pdu_ptr) != ASN_ERR_OK) @@ -550,13 +833,66 @@ snmp_pdu_encode_header(struct asn_buf *b, struct snmp_pdu *pdu) return (SNMP_CODE_OK); } +static enum asn_err +snmp_pdu_fix_padd(struct asn_buf *b, struct snmp_pdu *pdu) +{ + asn_len_t padlen; + + if (pdu->user.priv_proto == SNMP_PRIV_DES && pdu->scoped_len % 8 != 0) { + padlen = 8 - (pdu->scoped_len % 8); + if (asn_pad(b, padlen) != ASN_ERR_OK) + return (ASN_ERR_FAILED); + pdu->scoped_len += padlen; + } + + return (ASN_ERR_OK); +} + enum snmp_code -snmp_fix_encoding(struct asn_buf *b, const struct snmp_pdu *pdu) +snmp_fix_encoding(struct asn_buf *b, struct snmp_pdu *pdu) { - if (asn_commit_header(b, pdu->vars_ptr) != ASN_ERR_OK || - asn_commit_header(b, pdu->pdu_ptr) != ASN_ERR_OK || - asn_commit_header(b, pdu->outer_ptr) != ASN_ERR_OK) + size_t moved = 0; + enum snmp_code code; + + if (asn_commit_header(b, pdu->vars_ptr, NULL) != ASN_ERR_OK || + asn_commit_header(b, pdu->pdu_ptr, NULL) != ASN_ERR_OK) return (SNMP_CODE_FAILED); + + if (pdu->version == SNMP_V3) { + if (asn_commit_header(b, pdu->scoped_ptr, NULL) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + pdu->scoped_len = b->asn_ptr - pdu->scoped_ptr; + if ((code = snmp_pdu_fix_padd(b, pdu))!= ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + if (pdu->security_model != SNMP_SECMODEL_USM) + return (SNMP_CODE_FAILED); + + if (snmp_pdu_encrypt(b, pdu) != SNMP_CODE_OK) + return (SNMP_CODE_FAILED); + + if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV && + asn_commit_header(b, pdu->encrypted_ptr, NULL) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + } + + if (asn_commit_header(b, pdu->outer_ptr, &moved) != ASN_ERR_OK) + return (SNMP_CODE_FAILED); + + pdu->outer_len = b->asn_ptr - pdu->outer_ptr; + pdu->digest_ptr -= moved; + + if (pdu->version == SNMP_V3) { + if ((code = snmp_pdu_calc_digest(b, pdu, pdu->msg_digest)) != + SNMP_CODE_OK) + return (SNMP_CODE_FAILED); + + if ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0) + memcpy(pdu->digest_ptr, pdu->msg_digest, + sizeof(pdu->msg_digest)); + } + return (SNMP_CODE_OK); } @@ -639,7 +975,7 @@ snmp_binding_encode(struct asn_buf *b, const struct snmp_value *binding) return (err); } - err = asn_commit_header(b, ptr); + err = asn_commit_header(b, ptr, NULL); if (err != ASN_ERR_OK) { *b = save; return (err); @@ -775,6 +1111,8 @@ snmp_pdu_dump(const struct snmp_pdu *pdu) vers = "SNMPv1"; else if (pdu->version == SNMP_V2c) vers = "SNMPv2c"; + else if (pdu->version == SNMP_V3) + vers = "SNMPv3"; else vers = "v?"; @@ -838,6 +1176,39 @@ snmp_value_copy(struct snmp_value *to, const struct snmp_value *from) } void +snmp_pdu_init_secparams(struct snmp_pdu *pdu, struct snmp_engine *eng, + struct snmp_user *user) +{ + int32_t rval; + + memcpy(&pdu->engine, eng, sizeof(pdu->engine)); + memcpy(&pdu->user, user, sizeof(pdu->user)); + + if (user->auth_proto != SNMP_AUTH_NOAUTH) + pdu->flags |= SNMP_MSG_AUTH_FLAG; + + switch (user->priv_proto) { + case SNMP_PRIV_DES: + memcpy(pdu->msg_salt, &eng->engine_boots, + sizeof(eng->engine_boots)); + rval = random(); + memcpy(pdu->msg_salt + sizeof(eng->engine_boots), &rval, + sizeof(int32_t)); + pdu->flags |= SNMP_MSG_PRIV_FLAG; + break; + case SNMP_PRIV_AES: + rval = random(); + memcpy(pdu->msg_salt, &rval, sizeof(int32_t)); + rval = random(); + memcpy(pdu->msg_salt + sizeof(int32_t), &rval, sizeof(int32_t)); + pdu->flags |= SNMP_MSG_PRIV_FLAG; + break; + default: + break; + } +} + +void snmp_pdu_free(struct snmp_pdu *pdu) { u_int i; diff --git a/contrib/bsnmp/lib/snmp.h b/contrib/bsnmp/lib/snmp.h index 43af22e..3a6cec7 100644 --- a/contrib/bsnmp/lib/snmp.h +++ b/contrib/bsnmp/lib/snmp.h @@ -5,6 +5,13 @@ * * Author: Harti Brandt <harti@freebsd.org> * + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by Shteryana Sotirova Shopova + * under sponsorship from the FreeBSD Foundation. + * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -35,8 +42,11 @@ #include <sys/types.h> -#define SNMP_COMMUNITY_MAXLEN 128 -#define SNMP_MAX_BINDINGS 100 +#define SNMP_COMMUNITY_MAXLEN 128 +#define SNMP_MAX_BINDINGS 100 +#define SNMP_CONTEXT_NAME_SIZ (32 + 1) +#define SNMP_ENGINE_ID_SIZ 32 +#define SNMP_TIME_WINDOW 150 enum snmp_syntax { SNMP_SYNTAX_NULL = 0, @@ -75,33 +85,110 @@ struct snmp_value { enum snmp_version { SNMP_Verr = 0, SNMP_V1 = 1, - SNMP_V2c, + SNMP_V2c = 2, + SNMP_V3, +}; + +#define SNMP_ADM_STR32_SIZ (32 + 1) +#define SNMP_AUTH_KEY_SIZ 40 +#define SNMP_PRIV_KEY_SIZ 32 +#define SNMP_USM_AUTH_SIZE 12 +#define SNMP_USM_PRIV_SIZE 8 +#define SNMP_AUTH_HMACMD5_KEY_SIZ 16 +#define SNMP_AUTH_HMACSHA_KEY_SIZ 20 +#define SNMP_PRIV_AES_KEY_SIZ 16 +#define SNMP_PRIV_DES_KEY_SIZ 8 + + +enum snmp_secmodel { + SNMP_SECMODEL_ANY = 0, + SNMP_SECMODEL_SNMPv1 = 1, + SNMP_SECMODEL_SNMPv2c = 2, + SNMP_SECMODEL_USM = 3, + SNMP_SECMODEL_UNKNOWN +}; + +enum snmp_usm_level { + SNMP_noAuthNoPriv = 1, + SNMP_authNoPriv = 2, + SNMP_authPriv = 3 +}; + +enum snmp_authentication { + SNMP_AUTH_NOAUTH = 0, + SNMP_AUTH_HMAC_MD5, + SNMP_AUTH_HMAC_SHA +}; + +enum snmp_privacy { + SNMP_PRIV_NOPRIV = 0, + SNMP_PRIV_DES = 1, + SNMP_PRIV_AES +}; + +struct snmp_engine { + uint8_t engine_id[SNMP_ENGINE_ID_SIZ]; + uint32_t engine_len; + int32_t engine_boots; + int32_t engine_time; + int32_t max_msg_size; +}; + +struct snmp_user { + char sec_name[SNMP_ADM_STR32_SIZ]; + enum snmp_authentication auth_proto; + enum snmp_privacy priv_proto; + uint8_t auth_key[SNMP_AUTH_KEY_SIZ]; + uint8_t priv_key[SNMP_PRIV_KEY_SIZ]; }; struct snmp_pdu { - char community[SNMP_COMMUNITY_MAXLEN + 1]; - enum snmp_version version; - u_int type; + char community[SNMP_COMMUNITY_MAXLEN + 1]; + enum snmp_version version; + u_int type; + + /* SNMPv3 PDU header fields */ + int32_t identifier; + uint8_t flags; + int32_t security_model; + struct snmp_engine engine; + + /* Associated USM user parameters */ + struct snmp_user user; + uint8_t msg_digest[SNMP_USM_AUTH_SIZE]; + uint8_t msg_salt[SNMP_USM_PRIV_SIZE]; + + /* View-based Access Model */ + /* XXX: put in separate structure - conflicts with struct snmp_context */ + uint32_t context_engine_len; + uint8_t context_engine[SNMP_ENGINE_ID_SIZ]; + char context_name[SNMP_CONTEXT_NAME_SIZ]; /* trap only */ - struct asn_oid enterprise; - u_char agent_addr[4]; - int32_t generic_trap; - int32_t specific_trap; - uint32_t time_stamp; + struct asn_oid enterprise; + u_char agent_addr[4]; + int32_t generic_trap; + int32_t specific_trap; + uint32_t time_stamp; /* others */ - int32_t request_id; - int32_t error_status; - int32_t error_index; + int32_t request_id; + int32_t error_status; + int32_t error_index; /* fixes for encoding */ - u_char *outer_ptr; - u_char *pdu_ptr; - u_char *vars_ptr; + size_t outer_len; + size_t scoped_len; + u_char *outer_ptr; + u_char *digest_ptr; + u_char *encrypted_ptr; + u_char *scoped_ptr; + u_char *pdu_ptr; + u_char *vars_ptr; + - struct snmp_value bindings[SNMP_MAX_BINDINGS]; - u_int nbindings; + struct snmp_value bindings[SNMP_MAX_BINDINGS]; + u_int nbindings; }; #define snmp_v1_pdu snmp_pdu @@ -150,20 +237,38 @@ enum snmp_code { SNMP_CODE_BADLEN, SNMP_CODE_BADENC, SNMP_CODE_OORANGE, + SNMP_CODE_BADSECLEVEL, + SNMP_CODE_NOTINTIME, + SNMP_CODE_BADUSER, + SNMP_CODE_BADENGINE, + SNMP_CODE_BADDIGEST, + SNMP_CODE_EDECRYPT }; +#define SNMP_MSG_AUTH_FLAG 0x1 +#define SNMP_MSG_PRIV_FLAG 0x2 +#define SNMP_MSG_REPORT_FLAG 0x4 +#define SNMP_MSG_AUTODISCOVER 0x80 + void snmp_value_free(struct snmp_value *); int snmp_value_parse(const char *, enum snmp_syntax, union snmp_values *); int snmp_value_copy(struct snmp_value *, const struct snmp_value *); void snmp_pdu_free(struct snmp_pdu *); enum snmp_code snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *); -enum snmp_code snmp_pdu_encode(struct snmp_pdu *pdu, struct asn_buf *resp_b); +enum snmp_code snmp_pdu_decode_header(struct asn_buf *, struct snmp_pdu *); +enum snmp_code snmp_pdu_decode_scoped(struct asn_buf *, struct snmp_pdu *, int32_t *); +enum snmp_code snmp_pdu_encode(struct snmp_pdu *, struct asn_buf *); +enum snmp_code snmp_pdu_decode_secmode(struct asn_buf *, struct snmp_pdu *); int snmp_pdu_snoop(const struct asn_buf *); void snmp_pdu_dump(const struct snmp_pdu *pdu); +enum snmp_code snmp_passwd_to_keys(struct snmp_user *, char *); +enum snmp_code snmp_get_local_keys(struct snmp_user *, uint8_t *, uint32_t); +enum snmp_code snmp_calc_keychange(struct snmp_user *, uint8_t *); + extern void (*snmp_error)(const char *, ...); extern void (*snmp_printf)(const char *, ...); diff --git a/contrib/bsnmp/lib/snmpagent.c b/contrib/bsnmp/lib/snmpagent.c index ca9ccbd..9cd9676 100644 --- a/contrib/bsnmp/lib/snmpagent.c +++ b/contrib/bsnmp/lib/snmpagent.c @@ -165,6 +165,29 @@ find_subnode(const struct snmp_value *value) return (NULL); } +static void +snmp_pdu_create_response(struct snmp_pdu *pdu, struct snmp_pdu *resp) +{ + memset(resp, 0, sizeof(*resp)); + strcpy(resp->community, pdu->community); + resp->version = pdu->version; + resp->type = SNMP_PDU_RESPONSE; + resp->request_id = pdu->request_id; + resp->version = pdu->version; + + if (resp->version != SNMP_V3) + return; + + snmp_pdu_init_secparams(resp, &pdu->engine, &pdu->user); + resp->identifier = pdu->identifier; + resp->security_model = pdu->security_model; + resp->context_engine_len = pdu->context_engine_len; + memcpy(resp->context_engine, pdu->context_engine, + resp->context_engine_len); + strlcpy(resp->context_name, pdu->context_name, + sizeof(resp->context_name)); +} + /* * Execute a GET operation. The tree is rooted at the global 'root'. * Build the response PDU on the fly. If the return code is SNMP_RET_ERR @@ -184,12 +207,7 @@ snmp_get(struct snmp_pdu *pdu, struct asn_buf *resp_b, memset(&context, 0, sizeof(context)); context.ctx.data = data; - memset(resp, 0, sizeof(*resp)); - strcpy(resp->community, pdu->community); - resp->version = pdu->version; - resp->type = SNMP_PDU_RESPONSE; - resp->request_id = pdu->request_id; - resp->version = pdu->version; + snmp_pdu_create_response(pdu, resp); if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) /* cannot even encode header - very bad */ @@ -384,11 +402,7 @@ snmp_getnext(struct snmp_pdu *pdu, struct asn_buf *resp_b, memset(&context, 0, sizeof(context)); context.ctx.data = data; - memset(resp, 0, sizeof(*resp)); - strcpy(resp->community, pdu->community); - resp->type = SNMP_PDU_RESPONSE; - resp->request_id = pdu->request_id; - resp->version = pdu->version; + snmp_pdu_create_response(pdu, resp); if (snmp_pdu_encode_header(resp_b, resp)) return (SNMP_RET_IGN); @@ -440,12 +454,7 @@ snmp_getbulk(struct snmp_pdu *pdu, struct asn_buf *resp_b, memset(&context, 0, sizeof(context)); context.ctx.data = data; - memset(resp, 0, sizeof(*resp)); - strcpy(resp->community, pdu->community); - resp->version = pdu->version; - resp->type = SNMP_PDU_RESPONSE; - resp->request_id = pdu->request_id; - resp->version = pdu->version; + snmp_pdu_create_response(pdu, resp); if (snmp_pdu_encode_header(resp_b, resp) != SNMP_CODE_OK) /* cannot even encode header - very bad */ @@ -652,11 +661,7 @@ snmp_set(struct snmp_pdu *pdu, struct asn_buf *resp_b, TAILQ_INIT(&context.dlist); context.ctx.data = data; - memset(resp, 0, sizeof(*resp)); - strcpy(resp->community, pdu->community); - resp->type = SNMP_PDU_RESPONSE; - resp->request_id = pdu->request_id; - resp->version = pdu->version; + snmp_pdu_create_response(pdu, resp); if (snmp_pdu_encode_header(resp_b, resp)) return (SNMP_RET_IGN); @@ -951,16 +956,9 @@ snmp_make_errresp(const struct snmp_pdu *pdu, struct asn_buf *pdu_b, enum snmp_code code; memset(&resp, 0, sizeof(resp)); - - /* Message sequence */ - if (asn_get_sequence(pdu_b, &len) != ASN_ERR_OK) - return (SNMP_RET_IGN); - if (pdu_b->asn_len < len) + if ((code = snmp_pdu_decode_header(pdu_b, &resp)) != SNMP_CODE_OK) return (SNMP_RET_IGN); - err = snmp_parse_message_hdr(pdu_b, &resp, &len); - if (ASN_ERR_STOPPED(err)) - return (SNMP_RET_IGN); if (pdu_b->asn_len < len) return (SNMP_RET_IGN); pdu_b->asn_len = len; diff --git a/contrib/bsnmp/lib/snmpclient.c b/contrib/bsnmp/lib/snmpclient.c index b77543a..103ea69 100644 --- a/contrib/bsnmp/lib/snmpclient.c +++ b/contrib/bsnmp/lib/snmpclient.c @@ -852,6 +852,9 @@ snmp_client_init(struct snmp_client *c) strcpy(c->read_community, "public"); strcpy(c->write_community, "private"); + + c->security_model = SNMP_SECMODEL_USM; + strcpy(c->cname, ""); c->timeout.tv_sec = 3; c->timeout.tv_usec = 0; @@ -864,6 +867,8 @@ snmp_client_init(struct snmp_client *c) c->max_reqid = INT32_MAX; c->min_reqid = 0; c->next_reqid = 0; + + c->engine.max_msg_size = 1500; /* XXX */ } @@ -1132,7 +1137,8 @@ snmp_close(void) void snmp_pdu_create(struct snmp_pdu *pdu, u_int op) { - memset(pdu,0,sizeof(struct snmp_pdu)); + memset(pdu, 0, sizeof(struct snmp_pdu)); + if (op == SNMP_PDU_SET) strlcpy(pdu->community, snmp_client.write_community, sizeof(pdu->community)); @@ -1145,6 +1151,33 @@ snmp_pdu_create(struct snmp_pdu *pdu, u_int op) pdu->error_status = 0; pdu->error_index = 0; pdu->nbindings = 0; + + if (snmp_client.version != SNMP_V3) + return; + + pdu->identifier = ++snmp_client.identifier; + pdu->engine.max_msg_size = snmp_client.engine.max_msg_size; + pdu->flags = 0; + pdu->security_model = snmp_client.security_model; + + if (snmp_client.security_model == SNMP_SECMODEL_USM) + snmp_pdu_init_secparams(pdu, &snmp_client.engine, + &snmp_client.user); + else + seterr(&snmp_client, "unknown security model"); + + if (snmp_client.clen > 0) { + memcpy(pdu->context_engine, snmp_client.cengine, + snmp_client.clen); + pdu->context_engine_len = snmp_client.clen; + } else { + memcpy(pdu->context_engine, snmp_client.engine.engine_id, + snmp_client.engine.engine_len); + pdu->context_engine_len = snmp_client.engine.engine_len; + } + + strlcpy(pdu->context_name, snmp_client.cname, + sizeof(pdu->context_name)); } /* add pairs of (struct asn_oid, enum snmp_syntax) to an existing pdu */ @@ -1406,15 +1439,24 @@ snmp_receive_packet(struct snmp_pdu *pdu, struct timeval *tv) abuf.asn_ptr = buf; abuf.asn_len = ret; + memset(pdu, 0, sizeof(*pdu)); + if (snmp_client.security_model == SNMP_SECMODEL_USM) + snmp_pdu_init_secparams(pdu, &snmp_client.engine, + &snmp_client.user); + if (SNMP_CODE_OK != (ret = snmp_pdu_decode(&abuf, pdu, &ip))) { seterr(&snmp_client, "snmp_decode_pdu: failed %d", ret); free(buf); return (-1); } + free(buf); if (snmp_client.dump_pdus) snmp_pdu_dump(pdu); + snmp_client.engine.engine_time = pdu->engine.engine_time; + snmp_client.engine.engine_boots = pdu->engine.engine_boots; + return (+1); } @@ -1685,6 +1727,93 @@ snmp_dialog(struct snmp_v1_pdu *req, struct snmp_v1_pdu *resp) } int +snmp_discover_engine(char *passwd) +{ + char cname[SNMP_ADM_STR32_SIZ]; + enum snmp_authentication cap; + enum snmp_privacy cpp; + struct snmp_pdu req, resp; + + if (snmp_client.version != SNMP_V3) + seterr(&snmp_client, "wrong version"); + + strlcpy(cname, snmp_client.user.sec_name, sizeof(cname)); + cap = snmp_client.user.auth_proto; + cpp = snmp_client.user.priv_proto; + + snmp_client.engine.engine_len = 0; + snmp_client.engine.engine_boots = 0; + snmp_client.engine.engine_time = 0; + snmp_client.user.auth_proto = SNMP_AUTH_NOAUTH; + snmp_client.user.priv_proto = SNMP_PRIV_NOPRIV; + memset(snmp_client.user.sec_name, 0, sizeof(snmp_client.user.sec_name)); + + snmp_pdu_create(&req, SNMP_PDU_GET); + + if (snmp_dialog(&req, &resp) == -1) + return (-1); + + if (resp.version != req.version) { + seterr(&snmp_client, "wrong version"); + return (-1); + } + + if (resp.error_status != SNMP_ERR_NOERROR) { + seterr(&snmp_client, "Error %d in responce", resp.error_status); + return (-1); + } + + snmp_client.engine.engine_len = resp.engine.engine_len; + snmp_client.engine.max_msg_size = resp.engine.max_msg_size; + memcpy(snmp_client.engine.engine_id, resp.engine.engine_id, + resp.engine.engine_len); + + strlcpy(snmp_client.user.sec_name, cname, + sizeof(snmp_client.user.sec_name)); + snmp_client.user.auth_proto = cap; + snmp_client.user.priv_proto = cpp; + + if (snmp_client.user.auth_proto == SNMP_AUTH_NOAUTH) + return (0); + + if (passwd == NULL || + snmp_passwd_to_keys(&snmp_client.user, passwd) != SNMP_CODE_OK || + snmp_get_local_keys(&snmp_client.user, snmp_client.engine.engine_id, + snmp_client.engine.engine_len) != SNMP_CODE_OK) + return (-1); + + if (resp.engine.engine_boots != 0) + snmp_client.engine.engine_boots = resp.engine.engine_boots; + + if (resp.engine.engine_time != 0) { + snmp_client.engine.engine_time = resp.engine.engine_time; + return (0); + } + + snmp_pdu_create(&req, SNMP_PDU_GET); + req.engine.engine_boots = 0; + req.engine.engine_time = 0; + + if (snmp_dialog(&req, &resp) == -1) + return (-1); + + if (resp.version != req.version) { + seterr(&snmp_client, "wrong version"); + return (-1); + } + + if (resp.error_status != SNMP_ERR_NOERROR) { + seterr(&snmp_client, "Error %d in responce", resp.error_status); + return (-1); + } + + snmp_client.engine.engine_boots = resp.engine.engine_boots; + snmp_client.engine.engine_time = resp.engine.engine_time; + + return (0); +} + +int snmp_client_set_host(struct snmp_client *cl, const char *h) { char *np; diff --git a/contrib/bsnmp/lib/snmpclient.h b/contrib/bsnmp/lib/snmpclient.h index e137b4c..619f726 100644 --- a/contrib/bsnmp/lib/snmpclient.h +++ b/contrib/bsnmp/lib/snmpclient.h @@ -69,36 +69,47 @@ typedef void (*snmp_timeout_stop_f)(void *timeout_id); * Client context. */ struct snmp_client { - enum snmp_version version; - int trans; /* which transport to use */ + enum snmp_version version; + int trans; /* which transport to use */ /* these two are read-only for the application */ - char *cport; /* port number as string */ - char *chost; /* host name or IP address as string */ + char *cport; /* port number as string */ + char *chost; /* host name or IP address as string */ - char read_community[SNMP_COMMUNITY_MAXLEN + 1]; - char write_community[SNMP_COMMUNITY_MAXLEN + 1]; + char read_community[SNMP_COMMUNITY_MAXLEN + 1]; + char write_community[SNMP_COMMUNITY_MAXLEN + 1]; - struct timeval timeout; - u_int retries; + /* SNMPv3 specific fields */ + int32_t identifier; + int32_t security_model; + struct snmp_engine engine; + struct snmp_user user; - int dump_pdus; + /* SNMPv3 Access control - VACM*/ + uint32_t clen; + uint8_t cengine[SNMP_ENGINE_ID_SIZ]; + char cname[SNMP_CONTEXT_NAME_SIZ]; - size_t txbuflen; - size_t rxbuflen; + struct timeval timeout; + u_int retries; - int fd; + int dump_pdus; - int32_t next_reqid; - int32_t max_reqid; - int32_t min_reqid; + size_t txbuflen; + size_t rxbuflen; - char error[SNMP_STRERROR_LEN]; + int fd; - snmp_timeout_start_f timeout_start; - snmp_timeout_stop_f timeout_stop; + int32_t next_reqid; + int32_t max_reqid; + int32_t min_reqid; - char local_path[sizeof(SNMP_LOCAL_PATH)]; + char error[SNMP_STRERROR_LEN]; + + snmp_timeout_start_f timeout_start; + snmp_timeout_stop_f timeout_stop; + + char local_path[sizeof(SNMP_LOCAL_PATH)]; }; /* the global context */ @@ -181,6 +192,9 @@ int snmp_table_fetch_async(const struct snmp_table *, void *, /* send a request and wait for the response */ int snmp_dialog(struct snmp_pdu *_req, struct snmp_pdu *_resp); +/* discover an authorative snmpEngineId */ +int snmp_discover_engine(char *); + /* parse a server specification */ int snmp_parse_server(struct snmp_client *, const char *); diff --git a/contrib/bsnmp/lib/snmppriv.h b/contrib/bsnmp/lib/snmppriv.h index 535e0e9..98ac7e0 100644 --- a/contrib/bsnmp/lib/snmppriv.h +++ b/contrib/bsnmp/lib/snmppriv.h @@ -34,12 +34,18 @@ enum asn_err snmp_binding_encode(struct asn_buf *, const struct snmp_value *); enum snmp_code snmp_pdu_encode_header(struct asn_buf *, struct snmp_pdu *); -enum snmp_code snmp_fix_encoding(struct asn_buf *, const struct snmp_pdu *); -enum asn_err snmp_parse_message_hdr(struct asn_buf *b, struct snmp_pdu *pdu, - asn_len_t *lenp); +enum snmp_code snmp_fix_encoding(struct asn_buf *, struct snmp_pdu *); enum asn_err snmp_parse_pdus_hdr(struct asn_buf *b, struct snmp_pdu *pdu, asn_len_t *lenp); +void snmp_pdu_init_secparams(struct snmp_pdu *, struct snmp_engine *, + struct snmp_user *); + +enum snmp_code snmp_pdu_calc_digest(struct asn_buf *, const struct snmp_pdu *, + uint8_t *); +enum snmp_code snmp_pdu_encrypt(struct asn_buf *, const struct snmp_pdu *); +enum snmp_code snmp_pdu_decrypt(struct asn_buf *, const struct snmp_pdu *); + #define DEFAULT_HOST "localhost" #define DEFAULT_PORT "snmp" #define DEFAULT_LOCAL "/var/run/snmp.sock" |