diff options
Diffstat (limited to 'contrib/bsnmp')
30 files changed, 4921 insertions, 254 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/snmpcrypto.c b/contrib/bsnmp/lib/snmpcrypto.c new file mode 100644 index 0000000..81d8eac --- /dev/null +++ b/contrib/bsnmp/lib/snmpcrypto.c @@ -0,0 +1,404 @@ +/*- + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * This software was 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +#include <inttypes.h> +#endif +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <netinet/in.h> + +#ifdef HAVE_LIBCRYPTO +#include <openssl/evp.h> +#endif + +#include "asn1.h" +#include "snmp.h" +#include "snmppriv.h" + +#define SNMP_PRIV_AES_IV_SIZ 16 +#define SNMP_EXTENDED_KEY_SIZ 64 +#define SNMP_AUTH_KEY_LOOPCNT 1048576 +#define SNMP_AUTH_BUF_SIZE 72 + +static const uint8_t ipad = 0x36; +static const uint8_t opad = 0x5c; + +#ifdef HAVE_LIBCRYPTO + +static int32_t +snmp_digest_init(const struct snmp_user *user, EVP_MD_CTX *ctx, + const EVP_MD **dtype, uint32_t *keylen) +{ + if (user->auth_proto == SNMP_AUTH_HMAC_MD5) { + *dtype = EVP_md5(); + *keylen = SNMP_AUTH_HMACMD5_KEY_SIZ; + } else if (user->auth_proto == SNMP_AUTH_HMAC_SHA) { + *dtype = EVP_sha1(); + *keylen = SNMP_AUTH_HMACSHA_KEY_SIZ; + } else if (user->auth_proto == SNMP_AUTH_NOAUTH) + return (0); + else { + snmp_error("unknown authentication option - %d", + user->auth_proto); + return (-1); + } + + if (EVP_DigestInit(ctx, *dtype) != 1) + return (-1); + + return (1); +} + +enum snmp_code +snmp_pdu_calc_digest(struct asn_buf *b, const struct snmp_pdu *pdu, + uint8_t *digest) +{ + uint8_t md[EVP_MAX_MD_SIZE], extkey[SNMP_EXTENDED_KEY_SIZ]; + uint8_t key1[SNMP_EXTENDED_KEY_SIZ], key2[SNMP_EXTENDED_KEY_SIZ]; + uint32_t i, keylen, olen; + int32_t err; + const EVP_MD *dtype; + EVP_MD_CTX ctx; + + err = snmp_digest_init(&pdu->user, &ctx, &dtype, &keylen); + if (err < 0) + return (SNMP_CODE_BADDIGEST); + else if (err == 0) + return (SNMP_CODE_OK); + + memset(pdu->digest_ptr, 0, sizeof(pdu->msg_digest)); + memcpy(extkey, pdu->user.auth_key, keylen); + memset(extkey + keylen, 0, sizeof(extkey) - keylen); + + for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++) { + key1[i] = extkey[i] ^ ipad; + key2[i] = extkey[i] ^ opad; + } + + if (EVP_DigestUpdate(&ctx, key1, SNMP_EXTENDED_KEY_SIZ) != 1 || + EVP_DigestUpdate(&ctx, pdu->outer_ptr, pdu->outer_len) != 1 || + EVP_DigestFinal(&ctx, md, &olen) != 1) + goto failed; + + if (EVP_DigestInit(&ctx, dtype) != 1 || + EVP_DigestUpdate(&ctx, key2, SNMP_EXTENDED_KEY_SIZ) != 1 || + EVP_DigestUpdate(&ctx, md, olen) != 1 || + EVP_DigestFinal(&ctx, md, &olen) != 1) + goto failed; + + if (olen < SNMP_USM_AUTH_SIZE) { + snmp_error("bad digest size - %d", olen); + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_BADDIGEST); + } + + memcpy(digest, md, SNMP_USM_AUTH_SIZE); + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_OK); + +failed: + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_BADDIGEST); +} + +static int32_t +snmp_pdu_cipher_init(const struct snmp_pdu *pdu, int32_t len, + EVP_CIPHER_CTX *ctx, const EVP_CIPHER **ctype, uint8_t *piv) +{ + int i; + uint32_t netint; + + if (pdu->user.priv_proto == SNMP_PRIV_DES) { + if (len % 8 != 0) + return (-1); + *ctype = EVP_des_cbc(); + memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt)); + for (i = 0; i < 8; i++) + piv[i] = piv[i] ^ pdu->user.priv_key[8 + i]; + } else if (pdu->user.priv_proto == SNMP_PRIV_AES) { + *ctype = EVP_aes_128_cfb128(); + netint = htonl(pdu->engine.engine_boots); + memcpy(piv, &netint, sizeof(netint)); + piv += sizeof(netint); + netint = htonl(pdu->engine.engine_time); + memcpy(piv, &netint, sizeof(netint)); + piv += sizeof(netint); + memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt)); + } else if (pdu->user.priv_proto == SNMP_PRIV_NOPRIV) + return (0); + else { + snmp_error("unknown privacy option - %d", pdu->user.priv_proto); + return (-1); + } + + return (1); +} + +enum snmp_code +snmp_pdu_encrypt(struct asn_buf *b, const struct snmp_pdu *pdu) +{ + int32_t err, olen; + uint8_t iv[SNMP_PRIV_AES_IV_SIZ]; + const EVP_CIPHER *ctype; + EVP_CIPHER_CTX ctx; + + err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctx, &ctype, iv); + if (err < 0) + return (SNMP_CODE_EDECRYPT); + else if (err == 0) + return (SNMP_CODE_OK); + + if (EVP_EncryptInit(&ctx, ctype, pdu->user.priv_key, iv) != 1) + return (SNMP_CODE_FAILED); + + if (EVP_EncryptUpdate(&ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, + pdu->scoped_len) != 1 || + EVP_EncryptFinal(&ctx, pdu->scoped_ptr + olen, &olen) != 1) { + EVP_CIPHER_CTX_cleanup(&ctx); + return (SNMP_CODE_FAILED); + } + + EVP_CIPHER_CTX_cleanup(&ctx); + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_pdu_decrypt(struct asn_buf *b, const struct snmp_pdu *pdu) +{ + int32_t err, olen; + uint8_t iv[SNMP_PRIV_AES_IV_SIZ]; + const EVP_CIPHER *ctype; + EVP_CIPHER_CTX ctx; + + err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctx, &ctype, iv); + if (err < 0) + return (SNMP_CODE_EDECRYPT); + else if (err == 0) + return (SNMP_CODE_OK); + + if (EVP_DecryptInit(&ctx, ctype, pdu->user.priv_key, iv) != 1 || + EVP_CIPHER_CTX_set_padding(&ctx, 0) != 1) + return (SNMP_CODE_EDECRYPT); + + if (EVP_DecryptUpdate(&ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr, + pdu->scoped_len) != 1 || + EVP_DecryptFinal(&ctx, pdu->scoped_ptr + olen, &olen) != 1) { + EVP_CIPHER_CTX_cleanup(&ctx); + return (SNMP_CODE_EDECRYPT); + } + + EVP_CIPHER_CTX_cleanup(&ctx); + return (SNMP_CODE_OK); +} + +/* [RFC 3414] - A.2. Password to Key Algorithm */ +enum snmp_code +snmp_passwd_to_keys(struct snmp_user *user, char *passwd) +{ + int err, loop, i, pwdlen; + uint32_t keylen, olen; + const EVP_MD *dtype; + EVP_MD_CTX ctx; + uint8_t authbuf[SNMP_AUTH_BUF_SIZE]; + + if (passwd == NULL || user == NULL) + return (SNMP_CODE_FAILED); + + err = snmp_digest_init(user, &ctx, &dtype, &keylen); + if (err < 0) + return (SNMP_CODE_BADDIGEST); + else if (err == 0) + return (SNMP_CODE_OK); + + memset(user->auth_key, 0, sizeof(user->auth_key)); + pwdlen = strlen(passwd); + + for (loop = 0; loop < SNMP_AUTH_KEY_LOOPCNT; loop += i) { + for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++) + authbuf[i] = passwd[(loop + i) % pwdlen]; + if (EVP_DigestUpdate(&ctx, authbuf, SNMP_EXTENDED_KEY_SIZ) != 1) + goto failed; + } + + if (EVP_DigestFinal(&ctx, user->auth_key, &olen) != 1) + goto failed; + + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_OK); + +failed: + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_BADDIGEST); +} + +/* [RFC 3414] - 2.6. Key Localization Algorithm */ +enum snmp_code +snmp_get_local_keys(struct snmp_user *user, uint8_t *eid, uint32_t elen) +{ + int err; + uint32_t keylen, olen; + const EVP_MD *dtype; + EVP_MD_CTX ctx; + uint8_t authbuf[SNMP_AUTH_BUF_SIZE]; + + if (user == NULL || eid == NULL || elen > SNMP_ENGINE_ID_SIZ) + return (SNMP_CODE_FAILED); + + memset(user->priv_key, 0, sizeof(user->priv_key)); + memset(authbuf, 0, sizeof(authbuf)); + + err = snmp_digest_init(user, &ctx, &dtype, &keylen); + if (err < 0) + return (SNMP_CODE_BADDIGEST); + else if (err == 0) + return (SNMP_CODE_OK); + + memcpy(authbuf, user->auth_key, keylen); + memcpy(authbuf + keylen, eid, elen); + memcpy(authbuf + keylen + elen, user->auth_key, keylen); + + if (EVP_DigestUpdate(&ctx, authbuf, 2 * keylen + elen) != 1 || + EVP_DigestFinal(&ctx, user->auth_key, &olen) != 1) { + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_BADDIGEST); + } + EVP_MD_CTX_cleanup(&ctx); + + if (user->priv_proto != SNMP_PRIV_NOPRIV) + memcpy(user->priv_key, user->auth_key, sizeof(user->priv_key)); + + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_calc_keychange(struct snmp_user *user, uint8_t *keychange) +{ + int32_t i, err, rvalue[SNMP_AUTH_HMACSHA_KEY_SIZ / 4]; + uint32_t keylen, olen; + const EVP_MD *dtype; + EVP_MD_CTX ctx; + + err = snmp_digest_init(user, &ctx, &dtype, &keylen); + if (err < 0) + return (SNMP_CODE_BADDIGEST); + else if (err == 0) + return (SNMP_CODE_OK); + + for (i = 0; i < keylen / 4; i++) + rvalue[i] = random(); + + memcpy(keychange, user->auth_key, keylen); + memcpy(keychange + keylen, rvalue, keylen); + + if (EVP_DigestUpdate(&ctx, keychange, 2 * keylen) != 1 || + EVP_DigestFinal(&ctx, keychange, &olen) != 1) { + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_BADDIGEST); + } + + EVP_MD_CTX_cleanup(&ctx); + return (SNMP_CODE_OK); +} + +#else /* !HAVE_LIBCRYPTO */ + +enum snmp_code +snmp_pdu_calc_digest(struct asn_buf *b __unused, const struct snmp_pdu *pdu, + uint8_t *digest __unused) +{ + if (pdu->user.auth_proto != SNMP_AUTH_NOAUTH) + return (SNMP_CODE_BADSECLEVEL); + + + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_pdu_encrypt(struct asn_buf *b __unused, const struct snmp_pdu *pdu) +{ + if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV) + return (SNMP_CODE_BADSECLEVEL); + + return (SNMP_CODE_OK); +} + +enum snmp_code +snmp_pdu_decrypt(struct asn_buf *b __unused, const struct snmp_pdu *pdu) +{ + if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV) + return (SNMP_CODE_BADSECLEVEL); + + return (SNMP_CODE_OK); +} + +int +snmp_passwd_to_keys(struct snmp_user *user, char *passwd __unused) +{ + if (user->auth_proto == SNMP_AUTH_NOAUTH && + user->priv_proto == SNMP_PRIV_NOPRIV) + return (SNMP_CODE_OK); + + errno = EPROTONOSUPPORT; + + return (SNMP_CODE_FAILED); +} + +int +snmp_get_local_keys(struct snmp_user *user, uint8_t *eid __unused, + uint32_t elen __unused) +{ + if (user->auth_proto == SNMP_AUTH_NOAUTH && + user->priv_proto == SNMP_PRIV_NOPRIV) + return (SNMP_CODE_OK); + + errno = EPROTONOSUPPORT; + + return (SNMP_CODE_FAILED); +} + +enum snmp_code +snmp_calc_keychange(struct snmp_user *user __unused, + uint8_t *keychange __unused) +{ + errno = EPROTONOSUPPORT; + return (SNMP_CODE_FAILED); +} + +#endif /* HAVE_LIBCRYPTO */ 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" diff --git a/contrib/bsnmp/snmp_usm/snmp_usm.3 b/contrib/bsnmp/snmp_usm/snmp_usm.3 new file mode 100755 index 0000000..7fd0817 --- /dev/null +++ b/contrib/bsnmp/snmp_usm/snmp_usm.3 @@ -0,0 +1,132 @@ +.\"- +.\" Copyright (C) 2010 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This documentation was written 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: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 9, 2010 +.Dt SNMP_USM 3 +.Os +.Sh NAME +.Nm snmp_usm +.Nd "user-based security module for +.Xr bsnmpd 1 +.Sh LIBRARY +.Pq begemotSnmpdModulePath."usm" = "/usr/lib/snmp_usm.so" +.Sh DESCRIPTION +The +.Nm snmp_usm +module implements SNMPv3 User-Based Security Model MIB as defined in RFC 3414. +The module is used to manage the internal list of SNMPv3 USM active users in +.Nm bsnmpd . +The module must be loaded for +.Nm bsnmpd +to receive and process SNMPv3 USM PDUs correctly. +.Sh IMPLEMENTATION NOTES +A short description of the objects in the MIB follows. +.Bl -tag -width "XXXXXXXXX" +.It Va usmStats +The subtree contains statistics for the User-based Security Model PDU processing. +The statistics are reset each time the module is loaded. +.It Va usmUserSpinLock +An advisory lock used to coordinate several Command Generator Applications when +altering the SNMP USM users. +.It Va usmUserTable +The table contains all SNMP USM users configured in +.Nm bsnmpd. +The table contains the following objects +.Bl -tag -width ".It Va usmUserEngineID" +.It Va usmUserEngineID +An SNMP engine's administratively-unique identifier. Must be set to the same +Engine ID as +.Nm bsnmpd +so that the user will actually be allowed to communicate with the daemon. +The column is used as entry key and is not accessible for GET or SET operations. +.It Va usmUserName +The USM user name. The second entry key, again not accessible for GET or SET +operations. +.It Va usmUserSecurityName +The column has the exact same value as the +.Va usmUserName +column, however is accessible for GET operations. +.It Va usmUserCloneFrom +A GET on this column will return an empty OID. SET operations are currently not +supported. +.It Va usmUserAuthProtocol +The value of this column contains the OID corresponding to the authentication +protocol used by the USM user. The following protocols and their OIDs are known to +.Nm +module +.Bl -tag -width ".It Va NoAuthProtocol" +.It NoAuthProtocol 1.3.6.1.6.3.10.1.1.1 +.It HMACMD5AuthProtocol 1.3.6.1.6.3.10.1.1.2 +.It HMACSHAAuthProtocol 1.3.6.1.6.3.10.1.1.3 +.El +.It Va usmUserAuthKeyChange , Va usmUserOwnAuthKeyChange +These columns may be used to change the user's authentication key. +.It Va usmUserPrivProtocol +The value of this column contains the OID corresponding to the privacy +protocol used by the USM user. The following protocols and their OIDs are known to +.Nm +module +.Bl -tag -width ".It Va NoPrivProtocol" +.It NoPrivProtocol 1.3.6.1.6.3.10.1.2.1 +.It DESPrivProtoco 1.3.6.1.6.3.10.1.2.2 +.It AesCfb128Protocol 1.3.6.1.6.3.10.1.2.4 +.El +.It Va usmUserPrivKeyChange , Va usmUserOwnPrivKeyChange +These columns may be used to change the user's privacy key. +.It Va usmUserPublic +An arbitrary octet string that may be modified to confirm a SET operation on any +of the columns was successfull. +.It Va usmUserStorageType +This column always has either of two values. Entries created via +.Nm bsnmpd's +configuration file always have this column set to readOnly (5) and +it is not possible to modify those entries. Entries created by Command Generator +Applications always have this column set to volatile(2) and such entries are +lost when the module is restarted. A SET operation on this column is not +allowed. +.It Va usmUserStatus +This column is used to create new USM user entries or delete exsiting ones from +the table. +.El +.EL +.Sh FILES +.Bl -tag -width "XXXXXXXXX" +.It Pa /usr/share/snmp/defs/usm_tree.def +The description of the MIB tree implemented by +.Nm . +.El +.Sh SEE ALSO +.Xr bsnmpd 1 , +.Xr gensnmptree 1 , +.Xr snmpmod 3 +.Sh STANDARDS +IETF RFC 3414 +.Sh AUTHORS +.An Shteryana Shopova Aq syrinx@FreeBSD.org diff --git a/contrib/bsnmp/snmp_usm/usm_snmp.c b/contrib/bsnmp/snmp_usm/usm_snmp.c new file mode 100755 index 0000000..d5350bc --- /dev/null +++ b/contrib/bsnmp/snmp_usm/usm_snmp.c @@ -0,0 +1,614 @@ +/*- + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * This software was 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include <sys/queue.h> +#include <sys/types.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <syslog.h> + +#include "asn1.h" +#include "snmp.h" +#include "snmpmod.h" + +#include "usm_tree.h" +#include "usm_oid.h" + +static struct lmodule *usm_module; +/* For the registration. */ +static const struct asn_oid oid_usm = OIDX_snmpUsmMIB; + +static const struct asn_oid oid_usmNoAuthProtocol = OIDX_usmNoAuthProtocol; +static const struct asn_oid oid_usmHMACMD5AuthProtocol = \ + OIDX_usmHMACMD5AuthProtocol; +static const struct asn_oid oid_usmHMACSHAAuthProtocol = \ + OIDX_usmHMACSHAAuthProtocol; + +static const struct asn_oid oid_usmNoPrivProtocol = OIDX_usmNoPrivProtocol; +static const struct asn_oid oid_usmDESPrivProtocol = OIDX_usmDESPrivProtocol; +static const struct asn_oid oid_usmAesCfb128Protocol = OIDX_usmAesCfb128Protocol; + +static const struct asn_oid oid_usmUserSecurityName = OIDX_usmUserSecurityName; + +/* The registration. */ +static uint reg_usm; + +static int32_t usm_lock; + +static struct usm_user * usm_get_user(const struct asn_oid *, uint); +static struct usm_user * usm_get_next_user(const struct asn_oid *, uint); +static void usm_append_userindex(struct asn_oid *, uint, + const struct usm_user *); +static int usm_user_index_decode(const struct asn_oid *, uint, uint8_t *, + uint32_t *, char *); + +int +op_usm_stats(struct snmp_context *ctx __unused, struct snmp_value *val, + uint32_t sub __unused, uint32_t iidx __unused, enum snmp_op op) +{ + struct snmpd_usmstat *usmstats; + + if (op == SNMP_OP_SET) + return (SNMP_ERR_NOT_WRITEABLE); + + if ((usmstats = bsnmpd_get_usm_stats()) == NULL) + return (SNMP_ERR_GENERR); + + if (op == SNMP_OP_GET) { + switch (val->var.subs[sub - 1]) { + case LEAF_usmStatsUnsupportedSecLevels: + val->v.uint32 = usmstats->unsupported_seclevels; + break; + case LEAF_usmStatsNotInTimeWindows: + val->v.uint32 = usmstats->not_in_time_windows; + break; + case LEAF_usmStatsUnknownUserNames: + val->v.uint32 = usmstats->unknown_users; + break; + case LEAF_usmStatsUnknownEngineIDs: + val->v.uint32 = usmstats->unknown_engine_ids; + break; + case LEAF_usmStatsWrongDigests: + val->v.uint32 = usmstats->wrong_digests; + break; + case LEAF_usmStatsDecryptionErrors: + val->v.uint32 = usmstats->decrypt_errors; + break; + default: + return (SNMP_ERR_NOSUCHNAME); + } + return (SNMP_ERR_NOERROR); + } + abort(); +} + +int +op_usm_lock(struct snmp_context *ctx __unused, struct snmp_value *val, + uint32_t sub, uint32_t iidx __unused, enum snmp_op op) +{ + if (val->var.subs[sub - 1] != LEAF_usmUserSpinLock) + return (SNMP_ERR_NOSUCHNAME); + + switch (op) { + case SNMP_OP_GET: + if (++usm_lock == INT32_MAX) + usm_lock = 0; + val->v.integer = usm_lock; + break; + case SNMP_OP_GETNEXT: + abort(); + case SNMP_OP_SET: + if (val->v.integer != usm_lock) + return (SNMP_ERR_INCONS_VALUE); + break; + case SNMP_OP_ROLLBACK: + /* FALLTHROUGH */ + case SNMP_OP_COMMIT: + break; + } + + return (SNMP_ERR_NOERROR); +} + +int +op_usm_users(struct snmp_context *ctx, struct snmp_value *val, + uint32_t sub, uint32_t iidx __unused, enum snmp_op op) +{ + uint32_t elen; + struct usm_user *uuser, *clone; + char uname[SNMP_ADM_STR32_SIZ]; + uint8_t eid[SNMP_ENGINE_ID_SIZ]; + + switch (op) { + case SNMP_OP_GET: + if ((uuser = usm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_GETNEXT: + if ((uuser = usm_get_next_user(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + usm_append_userindex(&val->var, sub, uuser); + break; + + case SNMP_OP_SET: + if ((uuser = usm_get_user(&val->var, sub)) == NULL && + val->var.subs[sub - 1] != LEAF_usmUserStatus && + val->var.subs[sub - 1] != LEAF_usmUserCloneFrom) + return (SNMP_ERR_NOSUCHNAME); + + if (community != COMM_INITIALIZE && + uuser->type == StorageType_readOnly) + return (SNMP_ERR_NOT_WRITEABLE); + + switch (val->var.subs[sub - 1]) { + case LEAF_usmUserSecurityName: + return (SNMP_ERR_NOT_WRITEABLE); + + case LEAF_usmUserCloneFrom: + if (uuser != NULL || usm_user_index_decode(&val->var, + sub, eid, &elen, uname) < 0 || + !(asn_is_suboid(&oid_usmUserSecurityName, &val->v.oid))) + return (SNMP_ERR_WRONG_VALUE); + if ((clone = usm_get_user(&val->v.oid, sub)) == NULL) + return (SNMP_ERR_INCONS_VALUE); + if ((uuser = usm_new_user(eid, elen, uname)) == NULL) + return (SNMP_ERR_GENERR); + uuser->status = RowStatus_notReady; + if (community != COMM_INITIALIZE) + uuser->type = StorageType_volatile; + else + uuser->type = StorageType_readOnly; + + uuser->suser.auth_proto = clone->suser.auth_proto; + uuser->suser.priv_proto = clone->suser.priv_proto; + memcpy(uuser->suser.auth_key, clone->suser.auth_key, + sizeof(uuser->suser.auth_key)); + memcpy(uuser->suser.priv_key, clone->suser.priv_key, + sizeof(uuser->suser.priv_key)); + ctx->scratch->int1 = RowStatus_createAndWait; + break; + + case LEAF_usmUserAuthProtocol: + ctx->scratch->int1 = uuser->suser.auth_proto; + if (asn_compare_oid(&oid_usmNoAuthProtocol, + &val->v.oid) == 0) + uuser->suser.auth_proto = SNMP_AUTH_NOAUTH; + else if (asn_compare_oid(&oid_usmHMACMD5AuthProtocol, + &val->v.oid) == 0) + uuser->suser.auth_proto = SNMP_AUTH_HMAC_MD5; + else if (asn_compare_oid(&oid_usmHMACSHAAuthProtocol, + &val->v.oid) == 0) + uuser->suser.auth_proto = SNMP_AUTH_HMAC_SHA; + else + return (SNMP_ERR_WRONG_VALUE); + break; + + case LEAF_usmUserAuthKeyChange: + case LEAF_usmUserOwnAuthKeyChange: + if (val->var.subs[sub - 1] == + LEAF_usmUserOwnAuthKeyChange && + (usm_user == NULL || strcmp(uuser->suser.sec_name, + usm_user->suser.sec_name) != 0)) + return (SNMP_ERR_NO_ACCESS); + if (val->v.octetstring.len > SNMP_AUTH_KEY_SIZ) + return (SNMP_ERR_INCONS_VALUE); + ctx->scratch->ptr1 = malloc(SNMP_AUTH_KEY_SIZ); + if (ctx->scratch->ptr1 == NULL) + return (SNMP_ERR_GENERR); + memcpy(ctx->scratch->ptr1, uuser->suser.auth_key, + SNMP_AUTH_KEY_SIZ); + memcpy(uuser->suser.auth_key, val->v.octetstring.octets, + val->v.octetstring.len); + break; + + case LEAF_usmUserPrivProtocol: + ctx->scratch->int1 = uuser->suser.priv_proto; + if (asn_compare_oid(&oid_usmNoPrivProtocol, + &val->v.oid) == 0) + uuser->suser.priv_proto = SNMP_PRIV_NOPRIV; + else if (asn_compare_oid(&oid_usmDESPrivProtocol, + &val->v.oid) == 0) + uuser->suser.priv_proto = SNMP_PRIV_DES; + else if (asn_compare_oid(&oid_usmAesCfb128Protocol, + &val->v.oid) == 0) + uuser->suser.priv_proto = SNMP_PRIV_AES; + else + return (SNMP_ERR_WRONG_VALUE); + break; + + case LEAF_usmUserPrivKeyChange: + case LEAF_usmUserOwnPrivKeyChange: + if (val->var.subs[sub - 1] == + LEAF_usmUserOwnPrivKeyChange && + (usm_user == NULL || strcmp(uuser->suser.sec_name, + usm_user->suser.sec_name) != 0)) + return (SNMP_ERR_NO_ACCESS); + if (val->v.octetstring.len > SNMP_PRIV_KEY_SIZ) + return (SNMP_ERR_INCONS_VALUE); + ctx->scratch->ptr1 = malloc(SNMP_PRIV_KEY_SIZ); + if (ctx->scratch->ptr1 == NULL) + return (SNMP_ERR_GENERR); + memcpy(ctx->scratch->ptr1, uuser->suser.priv_key, + SNMP_PRIV_KEY_SIZ); + memcpy(uuser->suser.priv_key, val->v.octetstring.octets, + val->v.octetstring.len); + break; + + case LEAF_usmUserPublic: + if (val->v.octetstring.len > SNMP_ADM_STR32_SIZ) + return (SNMP_ERR_INCONS_VALUE); + if (uuser->user_public_len > 0) { + ctx->scratch->ptr2 = + malloc(uuser->user_public_len); + if (ctx->scratch->ptr2 == NULL) + return (SNMP_ERR_GENERR); + memcpy(ctx->scratch->ptr2, uuser->user_public, + uuser->user_public_len); + ctx->scratch->int2 = uuser->user_public_len; + } + if (val->v.octetstring.len > 0) { + memcpy(uuser->user_public, + val->v.octetstring.octets, + val->v.octetstring.len); + uuser->user_public_len = val->v.octetstring.len; + } else { + memset(uuser->user_public, 0, + SNMP_ADM_STR32_SIZ); + uuser->user_public_len = 0; + } + break; + + case LEAF_usmUserStorageType: + return (SNMP_ERR_INCONS_VALUE); + + case LEAF_usmUserStatus: + if (uuser == NULL) { + if (val->v.integer != RowStatus_createAndWait || + usm_user_index_decode(&val->var, sub, eid, + &elen, uname) < 0) + return (SNMP_ERR_INCONS_VALUE); + uuser = usm_new_user(eid, elen, uname); + if (uuser == NULL) + return (SNMP_ERR_GENERR); + uuser->status = RowStatus_notReady; + if (community != COMM_INITIALIZE) + uuser->type = StorageType_volatile; + else + uuser->type = StorageType_readOnly; + } else if (val->v.integer != RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + + uuser->status = val->v.integer; + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + switch (val->var.subs[sub - 1]) { + case LEAF_usmUserAuthKeyChange: + case LEAF_usmUserOwnAuthKeyChange: + case LEAF_usmUserPrivKeyChange: + case LEAF_usmUserOwnPrivKeyChange: + free(ctx->scratch->ptr1); + break; + case LEAF_usmUserPublic: + if (ctx->scratch->ptr2 != NULL) + free(ctx->scratch->ptr2); + break; + case LEAF_usmUserStatus: + if (val->v.integer != RowStatus_destroy) + break; + if ((uuser = usm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + usm_delete_user(uuser); + break; + default: + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + if ((uuser = usm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->var.subs[sub - 1]) { + case LEAF_usmUserAuthProtocol: + uuser->suser.auth_proto = ctx->scratch->int1; + break; + case LEAF_usmUserAuthKeyChange: + case LEAF_usmUserOwnAuthKeyChange: + memcpy(uuser->suser.auth_key, ctx->scratch->ptr1, + SNMP_AUTH_KEY_SIZ); + free(ctx->scratch->ptr1); + break; + case LEAF_usmUserPrivProtocol: + uuser->suser.priv_proto = ctx->scratch->int1; + break; + case LEAF_usmUserPrivKeyChange: + case LEAF_usmUserOwnPrivKeyChange: + memcpy(uuser->suser.priv_key, ctx->scratch->ptr1, + SNMP_AUTH_KEY_SIZ); + free(ctx->scratch->ptr1); + break; + case LEAF_usmUserPublic: + if (ctx->scratch->ptr2 != NULL) { + memcpy(uuser->user_public, ctx->scratch->ptr2, + ctx->scratch->int2); + uuser->user_public_len = ctx->scratch->int2; + free(ctx->scratch->ptr2); + } else { + memset(uuser->user_public, 0, + SNMP_ADM_STR32_SIZ); + uuser->user_public_len = 0; + } + break; + case LEAF_usmUserCloneFrom: + case LEAF_usmUserStatus: + if (ctx->scratch->int1 == RowStatus_createAndWait) + usm_delete_user(uuser); + break; + default: + break; + } + return (SNMP_ERR_NOERROR); + + default: + abort(); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_usmUserSecurityName: + return (string_get(val, uuser->suser.sec_name, -1)); + case LEAF_usmUserCloneFrom: + memcpy(&val->v.oid, &oid_zeroDotZero, sizeof(oid_zeroDotZero)); + break; + case LEAF_usmUserAuthProtocol: + switch (uuser->suser.auth_proto) { + case SNMP_AUTH_HMAC_MD5: + memcpy(&val->v.oid, &oid_usmHMACMD5AuthProtocol, + sizeof(oid_usmHMACMD5AuthProtocol)); + break; + case SNMP_AUTH_HMAC_SHA: + memcpy(&val->v.oid, &oid_usmHMACSHAAuthProtocol, + sizeof(oid_usmHMACSHAAuthProtocol)); + break; + default: + memcpy(&val->v.oid, &oid_usmNoAuthProtocol, + sizeof(oid_usmNoAuthProtocol)); + break; + } + break; + case LEAF_usmUserAuthKeyChange: + case LEAF_usmUserOwnAuthKeyChange: + return (string_get(val, (char *)uuser->suser.auth_key, 0)); + case LEAF_usmUserPrivProtocol: + switch (uuser->suser.priv_proto) { + case SNMP_PRIV_DES: + memcpy(&val->v.oid, &oid_usmDESPrivProtocol, + sizeof(oid_usmDESPrivProtocol)); + break; + case SNMP_PRIV_AES: + memcpy(&val->v.oid, &oid_usmAesCfb128Protocol, + sizeof(oid_usmAesCfb128Protocol)); + break; + default: + memcpy(&val->v.oid, &oid_usmNoPrivProtocol, + sizeof(oid_usmNoPrivProtocol)); + break; + } + break; + case LEAF_usmUserPrivKeyChange: + case LEAF_usmUserOwnPrivKeyChange: + return (string_get(val, (char *)uuser->suser.priv_key, 0)); + case LEAF_usmUserPublic: + return (string_get(val, uuser->user_public, + uuser->user_public_len)); + case LEAF_usmUserStorageType: + val->v.integer = uuser->type; + break; + case LEAF_usmUserStatus: + val->v.integer = uuser->status; + break; + } + + return (SNMP_ERR_NOERROR); +} + +static int +usm_user_index_decode(const struct asn_oid *oid, uint sub, uint8_t *engine, + uint32_t *elen, char *uname) +{ + uint32_t i, nlen; + int uname_off; + + if (oid->subs[sub] > SNMP_ENGINE_ID_SIZ) + return (-1); + + for (i = 0; i < oid->subs[sub]; i++) + engine[i] = oid->subs[sub + i + 1]; + *elen = i; + + uname_off = sub + oid->subs[sub] + 1; + if ((nlen = oid->subs[uname_off]) >= SNMP_ADM_STR32_SIZ) + return (-1); + + for (i = 0; i < nlen; i++) + uname[i] = oid->subs[uname_off + i + 1]; + uname[nlen] = '\0'; + + return (0); +} + +static void +usm_append_userindex(struct asn_oid *oid, uint sub, + const struct usm_user *uuser) +{ + uint32_t i; + + oid->len = sub + uuser->user_engine_len + strlen(uuser->suser.sec_name); + oid->len += 2; + oid->subs[sub] = uuser->user_engine_len; + for (i = 1; i < uuser->user_engine_len + 1; i++) + oid->subs[sub + i] = uuser->user_engine_id[i - 1]; + + sub += uuser->user_engine_len + 1; + oid->subs[sub] = strlen(uuser->suser.sec_name); + for (i = 1; i <= oid->subs[sub]; i++) + oid->subs[sub + i] = uuser->suser.sec_name[i - 1]; +} + +static struct usm_user * +usm_get_user(const struct asn_oid *oid, uint sub) +{ + uint32_t enginelen; + char username[SNMP_ADM_STR32_SIZ]; + uint8_t engineid[SNMP_ENGINE_ID_SIZ]; + + if (usm_user_index_decode(oid, sub, engineid, &enginelen, username) < 0) + return (NULL); + + return (usm_find_user(engineid, enginelen, username)); +} + +static struct usm_user * +usm_get_next_user(const struct asn_oid *oid, uint sub) +{ + uint32_t enginelen; + char username[SNMP_ADM_STR32_SIZ]; + uint8_t engineid[SNMP_ENGINE_ID_SIZ]; + struct usm_user *uuser; + + if (oid->len - sub == 0) + return (usm_first_user()); + + if (usm_user_index_decode(oid, sub, engineid, &enginelen, username) < 0) + return (NULL); + + if ((uuser = usm_find_user(engineid, enginelen, username)) != NULL) + return (usm_next_user(uuser)); + + return (NULL); +} + +/* + * USM snmp module initialization hook. + * Returns 0 on success, < 0 on error. + */ +static int +usm_init(struct lmodule * mod, int argc __unused, char *argv[] __unused) +{ + usm_module = mod; + usm_lock = random(); + bsnmpd_reset_usm_stats(); + return (0); +} + +/* + * USM snmp module finalization hook. + */ +static int +usm_fini(void) +{ + usm_flush_users(); + or_unregister(reg_usm); + + return (0); +} + +/* + * USM snmp module start operation. + */ +static void +usm_start(void) +{ + reg_usm = or_register(&oid_usm, + "The MIB module for managing SNMP User-Based Security Model.", + usm_module); +} + +static void +usm_dump(void) +{ + struct usm_user *uuser; + struct snmpd_usmstat *usmstats; + const char *const authstr[] = { + "noauth", + "md5", + "sha", + NULL + }; + const char *const privstr[] = { + "nopriv", + "des", + "aes", + NULL + }; + + if ((usmstats = bsnmpd_get_usm_stats()) != NULL) { + syslog(LOG_ERR, "UnsupportedSecLevels\t\t%u", + usmstats->unsupported_seclevels); + syslog(LOG_ERR, "NotInTimeWindows\t\t%u", + usmstats->not_in_time_windows); + syslog(LOG_ERR, "UnknownUserNames\t\t%u", + usmstats->unknown_users); + syslog(LOG_ERR, "UnknownEngineIDs\t\t%u", + usmstats->unknown_engine_ids); + syslog(LOG_ERR, "WrongDigests\t\t%u", + usmstats->wrong_digests); + syslog(LOG_ERR, "DecryptionErrors\t\t%u", + usmstats->decrypt_errors); + } + + syslog(LOG_ERR, "USM users"); + for (uuser = usm_first_user(); uuser != NULL; + (uuser = usm_next_user(uuser))) + syslog(LOG_ERR, "user %s\t\t%s, %s", uuser->suser.sec_name, + authstr[uuser->suser.auth_proto], + privstr[uuser->suser.priv_proto]); +} + +const char usm_comment[] = \ +"This module implements SNMP User-based Security Model defined in RFC 3414."; + +const struct snmp_module config = { + .comment = usm_comment, + .init = usm_init, + .fini = usm_fini, + .start = usm_start, + .tree = usm_ctree, + .dump = usm_dump, + .tree_size = usm_CTREE_SIZE, +}; diff --git a/contrib/bsnmp/snmp_usm/usm_tree.def b/contrib/bsnmp/snmp_usm/usm_tree.def new file mode 100755 index 0000000..8358d7b --- /dev/null +++ b/contrib/bsnmp/snmp_usm/usm_tree.def @@ -0,0 +1,109 @@ +#- +# Copyright (C) 2010 The FreeBSD Foundation +# All rights reserved. +# +# This software was 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: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +typedef StorageType ENUM ( + 1 other + 2 volatile + 3 nonVolatile + 4 permanent + 5 readOnly +) + +typedef RowStatus ENUM ( + 1 active + 2 notInService + 3 notReady + 4 createAndGo + 5 createAndWait + 6 destroy +) + +(1 internet + (6 snmpV2 + (3 snmpModules + (10 snmpFrameworkMIB + (1 snmpFrameworkAdmin + (1 snmpAuthProtocols + (1 usmNoAuthProtocol + ) + (2 usmHMACMD5AuthProtocol + ) + (3 usmHMACSHAAuthProtocol + ) + ) + (2 snmpPrivProtocols + (1 usmNoPrivProtocol + ) + (2 usmDESPrivProtocol + ) + (4 usmAesCfb128Protocol + ) + ) + ) + ) + (15 snmpUsmMIB + (1 usmMIBObjects + (1 usmStats + (1 usmStatsUnsupportedSecLevels COUNTER op_usm_stats GET) + (2 usmStatsNotInTimeWindows COUNTER op_usm_stats GET) + (3 usmStatsUnknownUserNames COUNTER op_usm_stats GET) + (4 usmStatsUnknownEngineIDs COUNTER op_usm_stats GET) + (5 usmStatsWrongDigests COUNTER op_usm_stats GET) + (6 usmStatsDecryptionErrors COUNTER op_usm_stats GET) + ) + (2 usmUser + (1 usmUserSpinLock INTEGER op_usm_lock GET SET) + (2 usmUserTable + (1 usmUserEntry : OCTETSTRING | SnmpEngineID OCTETSTRING op_usm_users + (1 usmUserEngineID OCTETSTRING | SnmpEngineID) + (2 usmUserName OCTETSTRING) + (3 usmUserSecurityName OCTETSTRING | SnmpAdminString GET) + (4 usmUserCloneFrom OID GET SET) + (5 usmUserAuthProtocol OID GET SET) + (6 usmUserAuthKeyChange OCTETSTRING | KeyChange GET SET) + (7 usmUserOwnAuthKeyChange OCTETSTRING | KeyChange GET SET) + (8 usmUserPrivProtocol OID GET SET) + (9 usmUserPrivKeyChange OCTETSTRING | KeyChange GET SET) + (10 usmUserOwnPrivKeyChange OCTETSTRING | KeyChange GET SET) + (11 usmUserPublic OCTETSTRING GET SET) + (12 usmUserStorageType StorageType GET SET) + (13 usmUserStatus RowStatus GET SET) + ) + ) + ) + ) + ) + (20 snmpUsmAesMIB + ) + ) + ) +) + diff --git a/contrib/bsnmp/snmp_vacm/snmp_vacm.3 b/contrib/bsnmp/snmp_vacm/snmp_vacm.3 new file mode 100755 index 0000000..9ad25be --- /dev/null +++ b/contrib/bsnmp/snmp_vacm/snmp_vacm.3 @@ -0,0 +1,94 @@ +.\"- +.\" Copyright (C) 2010 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This documentation was written 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: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd October 7, 2010 +.Dt SNMP_VACM 3 +.Os +.Sh NAME +.Nm snmp_vacm +.Nd "View-based Access Control module for +.Xr bsnmpd 1 +.Sh LIBRARY +.Pq begemotSnmpdModulePath."vacm" = "/usr/lib/snmp_vacm.so" +.Sh DESCRIPTION +The +.Nm snmp_vacm +module implements SNMPv3 View-based Access Control Model MIB as defined in +RFC 3415. The module is used to manage the internal lists of SNMPv1, v2c and +v3 user names and groups and their access rights to fetch or modify the values +of the MIB objects maintained by +.Nm bsnmpd +and the modules loaded in the daemon. +The module must be loaded for +.Nm bsnmpd +to implement proper view-based access control. If the module is not loaded, +access is granted to all configured SNMPv1 & SNMPv2c communities and SNMPv3 +USM users. +.Sh IMPLEMENTATION NOTES +An entry in any table implemented by this MIB may be created by setting the +relevant RowStatus column to createAndGo (4) - in fact, any other value for +those columns in a SET operation will cause an error. When an entry is created, +any of its columns that is not used as index, is set to the default value as +specified in the SNMP-VIEW-BASED-ACM-MIB. All entries maintained by the module +are persistent after reboot if created via +.Nm bsnmpd 's +config file, otherwise entries created via SNMP are lost after reboot. +A short description of the objects in the MIB follows. +.Bl -tag -width "XXXXXXXXX" +.It Va vacmContextTable +A read-only table that consists of a list of SNMP contexts available in +.Nm bsnmpd . +.It Va vacmSecurityToGroupTable +The table contains a list of SNMPv1, v2c and v3 user names and the groups +they belong to. +.It Va vacmAccessTable +The table contains a list of SNMP contexts to groups mappings and respectively +the names of the SNMP views under those contexts, to which users in the group +are granted read-only, read-write access or receive notifications for the +objects under the subtree in the relevant view. +.It Va vacmViewTreeFamilyTable +The table contains a list of SNMP views, i.e. entries specifying the OID of a +MIB subtree and whether access to the objects under this subtree is to be +allowed or forbiden. +.El +.Sh FILES +.Bl -tag -width "XXXXXXXXX" +.It Pa /usr/share/snmp/defs/vacm_tree.def +The description of the MIB tree implemented by +.Nm . +.El +.Sh SEE ALSO +.Xr bsnmpd 1 , +.Xr gensnmptree 1 , +.Xr snmpmod 3 +.Sh STANDARDS +IETF RFC 3415 +.Sh AUTHORS +.An Shteryana Shopova Aq syrinx@FreeBSD.org diff --git a/contrib/bsnmp/snmp_vacm/vacm_snmp.c b/contrib/bsnmp/snmp_vacm/vacm_snmp.c new file mode 100755 index 0000000..cdec9f4 --- /dev/null +++ b/contrib/bsnmp/snmp_vacm/vacm_snmp.c @@ -0,0 +1,1026 @@ +/*- + * Copyright (c) 2010 The FreeBSD Foundation + * All rights reserved. + * + * This software was 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include <sys/queue.h> +#include <sys/types.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <syslog.h> + +#include "asn1.h" +#include "snmp.h" +#include "snmpmod.h" + +#include "vacm_tree.h" +#include "vacm_oid.h" + +static struct lmodule *vacm_module; +/* For the registration. */ +static const struct asn_oid oid_vacm = OIDX_snmpVacmMIB; + +static uint reg_vacm; + +static int32_t vacm_lock; + +/* + * Internal datastructures and forward declarations. + */ +static void vacm_append_userindex(struct asn_oid *, + uint, const struct vacm_user *); +static int vacm_user_index_decode(const struct asn_oid *, + uint, int32_t *, char *); +static struct vacm_user *vacm_get_user(const struct asn_oid *, + uint); +static struct vacm_user *vacm_get_next_user(const struct asn_oid *, + uint); +static void vacm_append_access_rule_index(struct asn_oid *, + uint, const struct vacm_access *); +static int vacm_access_rule_index_decode(const struct asn_oid *, + uint, char *, char *, int32_t *, int32_t *); +static struct vacm_access * vacm_get_access_rule(const struct asn_oid *, + uint); +static struct vacm_access * vacm_get_next_access_rule(const struct asn_oid *, + uint); +static int vacm_view_index_decode(const struct asn_oid *, uint, + char *, struct asn_oid *); +static void vacm_append_viewindex(struct asn_oid *, uint, + const struct vacm_view *); +static struct vacm_view *vacm_get_view(const struct asn_oid *, uint); +static struct vacm_view *vacm_get_next_view(const struct asn_oid *, uint); +static struct vacm_view *vacm_get_view_by_name(u_char *, u_int); +static struct vacm_context *vacm_get_context(const struct asn_oid *, uint); +static struct vacm_context *vacm_get_next_context(const struct asn_oid *, + uint); +static void vacm_append_ctxindex(struct asn_oid *, uint, + const struct vacm_context *); + +int +op_vacm_context(struct snmp_context *ctx __unused, struct snmp_value *val, + uint32_t sub, uint32_t iidx __unused, enum snmp_op op) +{ + char cname[SNMP_ADM_STR32_SIZ]; + size_t cnamelen; + struct vacm_context *vacm_ctx; + + if (val->var.subs[sub - 1] != LEAF_vacmContextName) + abort(); + + switch (op) { + case SNMP_OP_GET: + if ((vacm_ctx = vacm_get_context(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_GETNEXT: + if ((vacm_ctx = vacm_get_next_context(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + vacm_append_ctxindex(&val->var, sub, vacm_ctx); + break; + + case SNMP_OP_SET: + if ((vacm_ctx = vacm_get_context(&val->var, sub)) != NULL) + return (SNMP_ERR_WRONG_VALUE); + if (community != COMM_INITIALIZE) + return (SNMP_ERR_NOT_WRITEABLE); + if (val->var.subs[sub] >= SNMP_ADM_STR32_SIZ) + return (SNMP_ERR_WRONG_VALUE); + if (index_decode(&val->var, sub, iidx, &cname, &cnamelen)) + return (SNMP_ERR_GENERR); + cname[cnamelen] = '\0'; + if ((vacm_ctx = vacm_add_context(cname, reg_vacm)) == NULL) + return (SNMP_ERR_GENERR); + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + /* FALLTHROUGH*/ + case SNMP_OP_ROLLBACK: + return (SNMP_ERR_NOERROR); + default: + abort(); + } + + return (string_get(val, vacm_ctx->ctxname, -1)); +} + +int +op_vacm_security_to_group(struct snmp_context *ctx, struct snmp_value *val, + uint32_t sub, uint32_t iidx __unused, enum snmp_op op) +{ + int32_t smodel; + char uname[SNMP_ADM_STR32_SIZ]; + struct vacm_user *user; + + switch (op) { + case SNMP_OP_GET: + if ((user = vacm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_GETNEXT: + if ((user = vacm_get_next_user(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + vacm_append_userindex(&val->var, sub, user); + break; + + case SNMP_OP_SET: + if ((user = vacm_get_user(&val->var, sub)) == NULL && + val->var.subs[sub - 1] != LEAF_vacmSecurityToGroupStatus) + return (SNMP_ERR_NOSUCHNAME); + + if (user != NULL) { + if (community != COMM_INITIALIZE && + user->type == StorageType_readOnly) + return (SNMP_ERR_NOT_WRITEABLE); + if (user->status == RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmGroupName: + ctx->scratch->ptr1 = user->group->groupname; + ctx->scratch->int1 = strlen(user->group->groupname); + return (vacm_user_set_group(user, + val->v.octetstring.octets,val->v.octetstring.len)); + + case LEAF_vacmSecurityToGroupStorageType: + return (SNMP_ERR_INCONS_VALUE); + + case LEAF_vacmSecurityToGroupStatus: + if (user == NULL) { + if (val->v.integer != RowStatus_createAndGo || + vacm_user_index_decode(&val->var, sub, + &smodel, uname) < 0) + return (SNMP_ERR_INCONS_VALUE); + user = vacm_new_user(smodel, uname); + if (user == NULL) + return (SNMP_ERR_GENERR); + user->status = RowStatus_destroy; + if (community != COMM_INITIALIZE) + user->type = StorageType_volatile; + else + user->type = StorageType_readOnly; + } else if (val->v.integer != RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + ctx->scratch->int1 = user->status; + user->status = val->v.integer; + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + if (val->var.subs[sub - 1] != LEAF_vacmSecurityToGroupStatus) + return (SNMP_ERR_NOERROR); + if ((user = vacm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->v.integer) { + case RowStatus_destroy: + return (vacm_delete_user(user)); + + case RowStatus_createAndGo: + user->status = RowStatus_active; + break; + + default: + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + if ((user = vacm_get_user(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->var.subs[sub - 1]) { + case LEAF_vacmGroupName: + return (vacm_user_set_group(user, ctx->scratch->ptr1, + ctx->scratch->int1)); + + case LEAF_vacmSecurityToGroupStatus: + if (ctx->scratch->int1 == RowStatus_destroy) + return (vacm_delete_user(user)); + user->status = ctx->scratch->int1; + break; + + default: + break; + } + return (SNMP_ERR_NOERROR); + + default: + abort(); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmGroupName: + return (string_get(val, user->group->groupname, -1)); + case LEAF_vacmSecurityToGroupStorageType: + val->v.integer = user->type; + break; + case LEAF_vacmSecurityToGroupStatus: + val->v.integer = user->status; + break; + default: + abort(); + } + + return (SNMP_ERR_NOERROR); +} + +int +op_vacm_access(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, + uint32_t iidx __unused, enum snmp_op op) +{ + int32_t smodel, slevel; + char gname[SNMP_ADM_STR32_SIZ], cprefix[SNMP_ADM_STR32_SIZ]; + struct vacm_access *acl; + + switch (op) { + case SNMP_OP_GET: + if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_GETNEXT: + if ((acl = vacm_get_next_access_rule(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + vacm_append_access_rule_index(&val->var, sub, acl); + break; + + case SNMP_OP_SET: + if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL && + val->var.subs[sub - 1] != LEAF_vacmAccessStatus) + return (SNMP_ERR_NOSUCHNAME); + if (acl != NULL && community != COMM_INITIALIZE && + acl->type == StorageType_readOnly) + return (SNMP_ERR_NOT_WRITEABLE); + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmAccessContextMatch: + ctx->scratch->int1 = acl->ctx_match; + if (val->v.integer == vacmAccessContextMatch_exact) + acl->ctx_match = 1; + else if (val->v.integer == vacmAccessContextMatch_prefix) + acl->ctx_match = 0; + else + return (SNMP_ERR_WRONG_VALUE); + break; + + case LEAF_vacmAccessReadViewName: + ctx->scratch->ptr1 = acl->read_view; + acl->read_view = vacm_get_view_by_name(val->v.octetstring.octets, val->v.octetstring.len); + if (acl->read_view == NULL) { + acl->read_view = ctx->scratch->ptr1; + return (SNMP_ERR_INCONS_VALUE); + } + return (SNMP_ERR_NOERROR); + + case LEAF_vacmAccessWriteViewName: + ctx->scratch->ptr1 = acl->write_view; + if ((acl->write_view = + vacm_get_view_by_name(val->v.octetstring.octets, + val->v.octetstring.len)) == NULL) { + acl->write_view = ctx->scratch->ptr1; + return (SNMP_ERR_INCONS_VALUE); + } + break; + + case LEAF_vacmAccessNotifyViewName: + ctx->scratch->ptr1 = acl->notify_view; + if ((acl->notify_view = + vacm_get_view_by_name(val->v.octetstring.octets, + val->v.octetstring.len)) == NULL) { + acl->notify_view = ctx->scratch->ptr1; + return (SNMP_ERR_INCONS_VALUE); + } + break; + + case LEAF_vacmAccessStorageType: + return (SNMP_ERR_INCONS_VALUE); + + case LEAF_vacmAccessStatus: + if (acl == NULL) { + if (val->v.integer != RowStatus_createAndGo || + vacm_access_rule_index_decode(&val->var, + sub, gname, cprefix, &smodel, &slevel) < 0) + return (SNMP_ERR_INCONS_VALUE); + if ((acl = vacm_new_access_rule(gname, cprefix, + smodel, slevel)) == NULL) + return (SNMP_ERR_GENERR); + acl->status = RowStatus_destroy; + if (community != COMM_INITIALIZE) + acl->type = StorageType_volatile; + else + acl->type = StorageType_readOnly; + } else if (val->v.integer != RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + ctx->scratch->int1 = acl->status; + acl->status = val->v.integer; + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + if (val->var.subs[sub - 1] != LEAF_vacmAccessStatus) + return (SNMP_ERR_NOERROR); + if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + if (val->v.integer == RowStatus_destroy) + return (vacm_delete_access_rule(acl)); + else + acl->status = RowStatus_active; + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + if ((acl = vacm_get_access_rule(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->var.subs[sub - 1]) { + case LEAF_vacmAccessContextMatch: + acl->ctx_match = ctx->scratch->int1; + break; + case LEAF_vacmAccessReadViewName: + acl->read_view = ctx->scratch->ptr1; + break; + case LEAF_vacmAccessWriteViewName: + acl->write_view = ctx->scratch->ptr1; + break; + case LEAF_vacmAccessNotifyViewName: + acl->notify_view = ctx->scratch->ptr1; + break; + case LEAF_vacmAccessStatus: + if (ctx->scratch->int1 == RowStatus_destroy) + return (vacm_delete_access_rule(acl)); + default: + break; + } + return (SNMP_ERR_NOERROR); + + default: + abort(); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmAccessContextMatch: + return (string_get(val, acl->ctx_prefix, -1)); + case LEAF_vacmAccessReadViewName: + if (acl->read_view != NULL) + return (string_get(val, acl->read_view->viewname, -1)); + else + return (string_get(val, NULL, 0)); + case LEAF_vacmAccessWriteViewName: + if (acl->write_view != NULL) + return (string_get(val, acl->write_view->viewname, -1)); + else + return (string_get(val, NULL, 0)); + case LEAF_vacmAccessNotifyViewName: + if (acl->notify_view != NULL) + return (string_get(val, acl->notify_view->viewname, -1)); + else + return (string_get(val, NULL, 0)); + case LEAF_vacmAccessStorageType: + val->v.integer = acl->type; + break; + case LEAF_vacmAccessStatus: + val->v.integer = acl->status; + break; + default: + abort(); + } + + return (SNMP_ERR_NOERROR); +} + +int +op_vacm_view_lock(struct snmp_context *ctx __unused, struct snmp_value *val, + uint32_t sub, uint32_t iidx __unused, enum snmp_op op) +{ + if (val->var.subs[sub - 1] != LEAF_vacmViewSpinLock) + return (SNMP_ERR_NOSUCHNAME); + + switch (op) { + case SNMP_OP_GET: + if (++vacm_lock == INT32_MAX) + vacm_lock = 0; + val->v.integer = vacm_lock; + break; + + case SNMP_OP_GETNEXT: + abort(); + + case SNMP_OP_SET: + if (val->v.integer != vacm_lock) + return (SNMP_ERR_INCONS_VALUE); + break; + + case SNMP_OP_ROLLBACK: + /* FALLTHROUGH */ + case SNMP_OP_COMMIT: + break; + } + + return (SNMP_ERR_NOERROR); +} + +int +op_vacm_view(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub, + uint32_t iidx __unused, enum snmp_op op) +{ + char vname[SNMP_ADM_STR32_SIZ]; + struct asn_oid oid; + struct vacm_view *view; + + switch (op) { + case SNMP_OP_GET: + if ((view = vacm_get_view(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + break; + + case SNMP_OP_GETNEXT: + if ((view = vacm_get_next_view(&val->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + vacm_append_viewindex(&val->var, sub, view); + break; + + case SNMP_OP_SET: + if ((view = vacm_get_view(&val->var, sub)) == NULL && + val->var.subs[sub - 1] != LEAF_vacmViewTreeFamilyStatus) + return (SNMP_ERR_NOSUCHNAME); + + if (view != NULL) { + if (community != COMM_INITIALIZE && + view->type == StorageType_readOnly) + return (SNMP_ERR_NOT_WRITEABLE); + if (view->status == RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmViewTreeFamilyMask: + if (val->v.octetstring.len > sizeof(view->mask)) + ctx->scratch->ptr1 = malloc(sizeof(view->mask)); + if (ctx->scratch->ptr1 == NULL) + return (SNMP_ERR_GENERR); + memset(ctx->scratch->ptr1, 0, sizeof(view->mask)); + memcpy(ctx->scratch->ptr1, view->mask, + sizeof(view->mask)); + memset(view->mask, 0, sizeof(view->mask)); + memcpy(view->mask, val->v.octetstring.octets, + val->v.octetstring.len); + break; + + case LEAF_vacmViewTreeFamilyType: + ctx->scratch->int1 = view->exclude; + if (val->v.integer == vacmViewTreeFamilyType_included) + view->exclude = 0; + else if (val->v.integer == vacmViewTreeFamilyType_excluded) + view->exclude = 1; + else + return (SNMP_ERR_WRONG_VALUE); + break; + + case LEAF_vacmViewTreeFamilyStorageType: + return (SNMP_ERR_INCONS_VALUE); + + case LEAF_vacmViewTreeFamilyStatus: + if (view == NULL) { + if (val->v.integer != RowStatus_createAndGo || + vacm_view_index_decode(&val->var, sub, vname, + &oid) < 0) + return (SNMP_ERR_INCONS_VALUE); + if ((view = vacm_new_view(vname, &oid)) == NULL) + return (SNMP_ERR_GENERR); + view->status = RowStatus_destroy; + if (community != COMM_INITIALIZE) + view->type = StorageType_volatile; + else + view->type = StorageType_readOnly; + } else if (val->v.integer != RowStatus_active && + val->v.integer != RowStatus_destroy) + return (SNMP_ERR_INCONS_VALUE); + ctx->scratch->int1 = view->status; + view->status = val->v.integer; + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + switch (val->var.subs[sub - 1]) { + case LEAF_vacmViewTreeFamilyMask: + free(ctx->scratch->ptr1); + break; + case LEAF_vacmViewTreeFamilyStatus: + if ((view = vacm_get_view(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->v.integer) { + case RowStatus_destroy: + return (vacm_delete_view(view)); + + case RowStatus_createAndGo: + view->status = RowStatus_active; + break; + + default: + /* NOTREACHED*/ + return (SNMP_ERR_GENERR); + } + default: + break; + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + if ((view = vacm_get_view(&val->var, sub)) == NULL) + return (SNMP_ERR_GENERR); + switch (val->var.subs[sub - 1]) { + case LEAF_vacmViewTreeFamilyMask: + memcpy(view->mask, ctx->scratch->ptr1, + sizeof(view->mask)); + free(ctx->scratch->ptr1); + break; + case LEAF_vacmViewTreeFamilyType: + view->exclude = ctx->scratch->int1; + break; + case LEAF_vacmViewTreeFamilyStatus: + if (ctx->scratch->int1 == RowStatus_destroy) + return (vacm_delete_view(view)); + break; + default: + break; + } + return (SNMP_ERR_NOERROR); + + default: + abort(); + } + + switch (val->var.subs[sub - 1]) { + case LEAF_vacmViewTreeFamilyMask: + return (string_get(val, view->mask, sizeof(view->mask))); + case LEAF_vacmViewTreeFamilyType: + if (view->exclude) + val->v.integer = vacmViewTreeFamilyType_excluded; + else + val->v.integer = vacmViewTreeFamilyType_included; + break; + case LEAF_vacmViewTreeFamilyStorageType: + val->v.integer = view->type; + break; + case LEAF_vacmViewTreeFamilyStatus: + val->v.integer = view->status; + break; + default: + abort(); + } + + return (SNMP_ERR_NOERROR); +} + +static void +vacm_append_userindex(struct asn_oid *oid, uint sub, + const struct vacm_user *user) +{ + uint32_t i; + + oid->len = sub + strlen(user->secname) + 2; + oid->subs[sub++] = user->sec_model; + oid->subs[sub] = strlen(user->secname); + for (i = 1; i <= strlen(user->secname); i++) + oid->subs[sub + i] = user->secname[i - 1]; +} + +static int +vacm_user_index_decode(const struct asn_oid *oid, uint sub, + int32_t *smodel, char *uname) +{ + uint32_t i; + + *smodel = oid->subs[sub++]; + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (-1); + + for (i = 0; i < oid->subs[sub]; i++) + uname[i] = oid->subs[sub + i + 1]; + uname[i] = '\0'; + + return (0); +} + +static struct vacm_user * +vacm_get_user(const struct asn_oid *oid, uint sub) +{ + int32_t smodel; + char uname[SNMP_ADM_STR32_SIZ]; + struct vacm_user *user; + + if (vacm_user_index_decode(oid, sub, &smodel, uname) < 0) + return (NULL); + + for (user = vacm_first_user(); user != NULL; user = vacm_next_user(user)) + if (strcmp(uname, user->secname) == 0 && + user->sec_model == smodel) + return (user); + + return (NULL); +} + +static struct vacm_user * +vacm_get_next_user(const struct asn_oid *oid, uint sub) +{ + int32_t smodel; + char uname[SNMP_ADM_STR32_SIZ]; + struct vacm_user *user; + + if (oid->len - sub == 0) + return (vacm_first_user()); + + if (vacm_user_index_decode(oid, sub, &smodel, uname) < 0) + return (NULL); + + for (user = vacm_first_user(); user != NULL; user = vacm_next_user(user)) + if (strcmp(uname, user->secname) == 0 && + user->sec_model == smodel) + return (vacm_next_user(user)); + + return (NULL); +} + +static void +vacm_append_access_rule_index(struct asn_oid *oid, uint sub, + const struct vacm_access *acl) +{ + uint32_t i; + + oid->len = sub + strlen(acl->group->groupname) + + strlen(acl->ctx_prefix) + 4; + + oid->subs[sub] = strlen(acl->group->groupname); + for (i = 1; i <= strlen(acl->group->groupname); i++) + oid->subs[sub + i] = acl->group->groupname[i - 1]; + sub += strlen(acl->group->groupname) + 1; + + oid->subs[sub] = strlen(acl->ctx_prefix); + for (i = 1; i <= strlen(acl->ctx_prefix); i++) + oid->subs[sub + i] = acl->ctx_prefix[i - 1]; + sub += strlen(acl->ctx_prefix) + 1; + oid->subs[sub++] = acl->sec_model; + oid->subs[sub] = acl->sec_level; +} + +static int +vacm_access_rule_index_decode(const struct asn_oid *oid, uint sub, char *gname, + char *cprefix, int32_t *smodel, int32_t *slevel) +{ + uint32_t i; + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (-1); + + for (i = 0; i < oid->subs[sub]; i++) + gname[i] = oid->subs[sub + i + 1]; + gname[i] = '\0'; + sub += strlen(gname) + 1; + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (-1); + + for (i = 0; i < oid->subs[sub]; i++) + cprefix[i] = oid->subs[sub + i + 1]; + cprefix[i] = '\0'; + sub += strlen(cprefix) + 1; + + *smodel = oid->subs[sub++]; + *slevel = oid->subs[sub]; + + return (0); +} + +struct vacm_access * +vacm_get_access_rule(const struct asn_oid *oid, uint sub) +{ + int32_t smodel, slevel; + char gname[SNMP_ADM_STR32_SIZ], prefix[SNMP_ADM_STR32_SIZ]; + struct vacm_access *acl; + + if (vacm_access_rule_index_decode(oid, sub, gname, prefix, &smodel, + &slevel) < 0) + return (NULL); + + for (acl = vacm_first_access_rule(); acl != NULL; + acl = vacm_next_access_rule(acl)) + if (strcmp(gname, acl->group->groupname) == 0 && + strcmp(prefix, acl->ctx_prefix) == 0 && + smodel == acl->sec_model && slevel == acl->sec_level) + return (acl); + + return (NULL); +} + +struct vacm_access * +vacm_get_next_access_rule(const struct asn_oid *oid __unused, uint sub __unused) +{ + int32_t smodel, slevel; + char gname[SNMP_ADM_STR32_SIZ], prefix[SNMP_ADM_STR32_SIZ]; + struct vacm_access *acl; + + if (oid->len - sub == 0) + return (vacm_first_access_rule()); + + if (vacm_access_rule_index_decode(oid, sub, gname, prefix, &smodel, + &slevel) < 0) + return (NULL); + + for (acl = vacm_first_access_rule(); acl != NULL; + acl = vacm_next_access_rule(acl)) + if (strcmp(gname, acl->group->groupname) == 0 && + strcmp(prefix, acl->ctx_prefix) == 0 && + smodel == acl->sec_model && slevel == acl->sec_model) + return (vacm_next_access_rule(acl)); + + return (NULL); +} + +static int +vacm_view_index_decode(const struct asn_oid *oid, uint sub, char *vname, + struct asn_oid *view_oid) +{ + uint32_t i; + int viod_off; + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (-1); + + for (i = 0; i < oid->subs[sub]; i++) + vname[i] = oid->subs[sub + i + 1]; + vname[i] = '\0'; + + viod_off = sub + oid->subs[sub] + 1; + if ((view_oid->len = oid->subs[viod_off]) > ASN_MAXOIDLEN) + return (-1); + + memcpy(&view_oid->subs[0], &oid->subs[viod_off + 1], + view_oid->len * sizeof(view_oid->subs[0])); + + return (0); +} + +static void +vacm_append_viewindex(struct asn_oid *oid, uint sub, const struct vacm_view *view) +{ + uint32_t i; + + oid->len = sub + strlen(view->viewname) + 1; + oid->subs[sub] = strlen(view->viewname); + for (i = 1; i <= strlen(view->viewname); i++) + oid->subs[sub + i] = view->viewname[i - 1]; + + sub += strlen(view->viewname) + 1; + oid->subs[sub] = view->subtree.len; + oid->len++; + asn_append_oid(oid, &view->subtree); +} + +struct vacm_view * +vacm_get_view(const struct asn_oid *oid, uint sub) +{ + char vname[SNMP_ADM_STR32_SIZ]; + struct asn_oid subtree; + struct vacm_view *view; + + if (vacm_view_index_decode(oid, sub, vname, &subtree) < 0) + return (NULL); + + for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) + if (strcmp(vname, view->viewname) == 0 && + asn_compare_oid(&subtree, &view->subtree)== 0) + return (view); + + return (NULL); +} + +struct vacm_view * +vacm_get_next_view(const struct asn_oid *oid, uint sub) +{ + char vname[SNMP_ADM_STR32_SIZ]; + struct asn_oid subtree; + struct vacm_view *view; + + if (oid->len - sub == 0) + return (vacm_first_view()); + + if (vacm_view_index_decode(oid, sub, vname, &subtree) < 0) + return (NULL); + + for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) + if (strcmp(vname, view->viewname) == 0 && + asn_compare_oid(&subtree, &view->subtree)== 0) + return (vacm_next_view(view)); + + return (NULL); +} + +static struct vacm_view * +vacm_get_view_by_name(u_char *octets, u_int len) +{ + struct vacm_view *view; + + for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) + if (strlen(view->viewname) == len && + memcmp(octets, view->viewname, len) == 0) + return (view); + + return (NULL); +} + +static struct vacm_context * +vacm_get_context(const struct asn_oid *oid, uint sub) +{ + char cname[SNMP_ADM_STR32_SIZ]; + size_t cnamelen; + u_int index_count; + struct vacm_context *vacm_ctx; + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (NULL); + + index_count = 0; + index_count = SNMP_INDEX(index_count, 1); + if (index_decode(oid, sub, index_count, &cname, &cnamelen)) + return (NULL); + + for (vacm_ctx = vacm_first_context(); vacm_ctx != NULL; + vacm_ctx = vacm_next_context(vacm_ctx)) + if (strcmp(cname, vacm_ctx->ctxname) == 0) + return (vacm_ctx); + + return (NULL); +} + +static struct vacm_context * +vacm_get_next_context(const struct asn_oid *oid, uint sub) +{ + char cname[SNMP_ADM_STR32_SIZ]; + size_t cnamelen; + u_int index_count; + struct vacm_context *vacm_ctx; + + if (oid->len - sub == 0) + return (vacm_first_context()); + + if (oid->subs[sub] >= SNMP_ADM_STR32_SIZ) + return (NULL); + + index_count = 0; + index_count = SNMP_INDEX(index_count, 1); + if (index_decode(oid, sub, index_count, &cname, &cnamelen)) + return (NULL); + + for (vacm_ctx = vacm_first_context(); vacm_ctx != NULL; + vacm_ctx = vacm_next_context(vacm_ctx)) + if (strcmp(cname, vacm_ctx->ctxname) == 0) + return (vacm_next_context(vacm_ctx)); + + return (NULL); +} + +static void +vacm_append_ctxindex(struct asn_oid *oid, uint sub, + const struct vacm_context *ctx) +{ + uint32_t i; + + oid->len = sub + strlen(ctx->ctxname) + 1; + oid->subs[sub] = strlen(ctx->ctxname); + for (i = 1; i <= strlen(ctx->ctxname); i++) + oid->subs[sub + i] = ctx->ctxname[i - 1]; +} + +/* + * VACM snmp module initialization hook. + * Returns 0 on success, < 0 on error. + */ +static int +vacm_init(struct lmodule *mod, int argc __unused, char *argv[] __unused) +{ + vacm_module = mod; + vacm_lock = random(); + vacm_groups_init(); + + /* XXX: TODO - initialize structures */ + return (0); +} + +/* + * VACM snmp module finalization hook. + */ +static int +vacm_fini(void) +{ + /* XXX: TODO - cleanup */ + vacm_flush_contexts(reg_vacm); + or_unregister(reg_vacm); + + return (0); +} + +/* + * VACM snmp module start operation. + */ +static void +vacm_start(void) +{ + static char dflt_ctx[] = ""; + + reg_vacm = or_register(&oid_vacm, + "The MIB module for managing SNMP View-based Access Control Model.", + vacm_module); + + (void)vacm_add_context(dflt_ctx, reg_vacm); +} + +static void +vacm_dump(void) +{ + struct vacm_context *vacmctx; + struct vacm_user *vuser; + struct vacm_access *vacl; + struct vacm_view *view; + static char oidbuf[ASN_OIDSTRLEN]; + + syslog(LOG_ERR, "\n"); + syslog(LOG_ERR, "Context list:"); + for (vacmctx = vacm_first_context(); vacmctx != NULL; + vacmctx = vacm_next_context(vacmctx)) + syslog(LOG_ERR, "Context \"%s\", module id %d", + vacmctx->ctxname, vacmctx->regid); + + syslog(LOG_ERR, "VACM users:"); + for (vuser = vacm_first_user(); vuser != NULL; + vuser = vacm_next_user(vuser)) + syslog(LOG_ERR, "Uname %s, Group %s, model %d", vuser->secname, + vuser->group!= NULL?vuser->group->groupname:"Unknown", + vuser->sec_model); + + syslog(LOG_ERR, "VACM Access rules:"); + for (vacl = vacm_first_access_rule(); vacl != NULL; + vacl = vacm_next_access_rule(vacl)) + syslog(LOG_ERR, "Group %s, CtxPrefix %s, Model %d, Level %d, " + "RV %s, WR %s, NV %s", vacl->group!=NULL? + vacl->group->groupname:"Unknown", vacl->ctx_prefix, + vacl->sec_model, vacl->sec_level, vacl->read_view!=NULL? + vacl->read_view->viewname:"None", vacl->write_view!=NULL? + vacl->write_view->viewname:"None", vacl->notify_view!=NULL? + vacl->notify_view->viewname:"None"); + + syslog(LOG_ERR, "VACM Views:"); + for (view = vacm_first_view(); view != NULL; view = vacm_next_view(view)) + syslog(LOG_ERR, "View %s, Tree %s - %s", view->viewname, + asn_oid2str_r(&view->subtree, oidbuf), view->exclude? + "excluded":"included"); +} + +const char vacm_comment[] = \ +"This module implements SNMP View-based Access Control Model defined in RFC 3415."; + +const struct snmp_module config = { + .comment = vacm_comment, + .init = vacm_init, + .fini = vacm_fini, + .start = vacm_start, + .tree = vacm_ctree, + .dump = vacm_dump, + .tree_size = vacm_CTREE_SIZE, +}; diff --git a/contrib/bsnmp/snmp_vacm/vacm_tree.def b/contrib/bsnmp/snmp_vacm/vacm_tree.def new file mode 100755 index 0000000..db70f7d --- /dev/null +++ b/contrib/bsnmp/snmp_vacm/vacm_tree.def @@ -0,0 +1,104 @@ +#- +# Copyright (C) 2010 The FreeBSD Foundation +# All rights reserved. +# +# This software was 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: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +typedef StorageType ENUM ( + 1 other + 2 volatile + 3 nonVolatile + 4 permanent + 5 readOnly +) + +typedef RowStatus ENUM ( + 1 active + 2 notInService + 3 notReady + 4 createAndGo + 5 createAndWait + 6 destroy +) + +(1 internet + (6 snmpV2 + (3 snmpModules + (16 snmpVacmMIB + (1 vacmMIBObjects + (1 vacmContextTable + (1 vacmContextEntry : OCTETSTRING op_vacm_context + (1 vacmContextName OCTETSTRING GET) + ) + ) + (2 vacmSecurityToGroupTable + (1 vacmSecurityToGroupEntry : INTEGER OCTETSTRING op_vacm_security_to_group + (1 vacmSecurityModel INTEGER) + (2 vacmSecurityName OCTETSTRING) + (3 vacmGroupName OCTETSTRING GET SET) + (4 vacmSecurityToGroupStorageType StorageType GET SET) + (5 vacmSecurityToGroupStatus RowStatus GET SET) + ) + ) + (4 vacmAccessTable + (1 vacmAccessEntry : OCTETSTRING OCTETSTRING INTEGER ENUM ( 1 noAuthNoPriv 2 authNoPriv 3 authPriv ) op_vacm_access + (1 vacmAccessContextPrefix OCTETSTRING) + (2 vacmAccessSecurityModel INTEGER) + (3 vacmAccessSecurityLevel ENUM ( 1 noAuthNoPriv 2 authNoPriv 3 authPriv )) + (4 vacmAccessContextMatch ENUM ( 1 exact 2 prefix ) GET SET) + (5 vacmAccessReadViewName OCTETSTRING GET SET) + (6 vacmAccessWriteViewName OCTETSTRING GET SET) + (7 vacmAccessNotifyViewName OCTETSTRING GET SET) + (8 vacmAccessStorageType StorageType GET SET) + (9 vacmAccessStatus RowStatus GET SET) + ) + ) + (5 vacmMIBViews + (1 vacmViewSpinLock INTEGER op_vacm_view_lock GET SET) + (2 vacmViewTreeFamilyTable + (1 vacmViewTreeFamilyEntry : OCTETSTRING OID op_vacm_view + (1 vacmViewTreeFamilyViewName OCTETSTRING) + (2 vacmViewTreeFamilySubtree OID) + (3 vacmViewTreeFamilyMask OCTETSTRING GET SET) + (4 vacmViewTreeFamilyType ENUM ( 1 included 2 excluded ) GET SET) + (5 vacmViewTreeFamilyStorageType StorageType GET SET) + (6 vacmViewTreeFamilyStatus RowStatus GET SET) + ) + ) + ) + ) + (2 vacmMIBConformance + (1 vacmMIBCompliances + ) + (2 vacmMIBGroups + ) + ) + ) + ) + ) +) diff --git a/contrib/bsnmp/snmpd/BEGEMOT-SNMPD.txt b/contrib/bsnmp/snmpd/BEGEMOT-SNMPD.txt index 40b2cf0..3262ada 100644 --- a/contrib/bsnmp/snmpd/BEGEMOT-SNMPD.txt +++ b/contrib/bsnmp/snmpd/BEGEMOT-SNMPD.txt @@ -139,7 +139,8 @@ begemotSnmpdVersionEnable OBJECT-TYPE bits are defined: 0x00000001 - SNMPv1 - 0x00000002 - SNMPv2c" + 0x00000002 - SNMPv2c + 0x00000004 - SNMPv3" DEFVAL { 3 } ::= { begemotSnmpdConfig 5 } diff --git a/contrib/bsnmp/snmpd/action.c b/contrib/bsnmp/snmpd/action.c index 3d91ce3..ebba0f5 100644 --- a/contrib/bsnmp/snmpd/action.c +++ b/contrib/bsnmp/snmpd/action.c @@ -34,6 +34,7 @@ * Variable access for SNMPd */ #include <sys/types.h> +#include <sys/queue.h> #include <sys/sysctl.h> #include <sys/un.h> #include <sys/utsname.h> @@ -42,6 +43,7 @@ #include <stdarg.h> #include <string.h> #include <ctype.h> +#include <errno.h> #include <syslog.h> #include "snmpmod.h" @@ -162,7 +164,83 @@ init_actvals(void) return (0); } +/* + * Initialize global variables of the snmpEngine group. + */ +int +init_snmpd_engine(void) +{ + char *hostid; + + snmpd_engine.engine_boots = 1; + snmpd_engine.engine_time = 1; + snmpd_engine.max_msg_size = 1500; /* XXX */ + + snmpd_engine.engine_id[0] = ((OID_freeBSD & 0xff000000) >> 24) | 0x80; + snmpd_engine.engine_id[1] = (OID_freeBSD & 0xff0000) >> 16; + snmpd_engine.engine_id[2] = (OID_freeBSD & 0xff00) >> 8; + snmpd_engine.engine_id[3] = OID_freeBSD & 0xff; + snmpd_engine.engine_id[4] = 128; + snmpd_engine.engine_len = 5; + + if ((hostid = act_getkernint(KERN_HOSTID)) == NULL) + return (-1); + + if (strlen(hostid) > SNMP_ENGINE_ID_SIZ - snmpd_engine.engine_len) { + memcpy(snmpd_engine.engine_id + snmpd_engine.engine_len, + hostid, SNMP_ENGINE_ID_SIZ - snmpd_engine.engine_len); + snmpd_engine.engine_len = SNMP_ENGINE_ID_SIZ; + } else { + memcpy(snmpd_engine.engine_id + snmpd_engine.engine_len, + hostid, strlen(hostid)); + snmpd_engine.engine_len += strlen(hostid); + } + + free(hostid); + + return (0); +} + +int +set_snmpd_engine(void) +{ + FILE *fp; + uint32_t i; + uint8_t *cptr, engine[2 * SNMP_ENGINE_ID_SIZ + 2]; + uint8_t myengine[2 * SNMP_ENGINE_ID_SIZ + 2]; + + if (engine_file[0] == '\0') + return (-1); + + cptr = myengine; + for (i = 0; i < snmpd_engine.engine_len; i++) + cptr += sprintf(cptr, "%.2x", snmpd_engine.engine_id[i]); + *cptr++ = '\n'; + *cptr++ = '\0'; + + if ((fp = fopen(engine_file, "r+")) != NULL) { + if (fgets(engine, sizeof(engine) - 1, fp) == NULL || + fscanf(fp, "%u", &snmpd_engine.engine_boots) <= 0) { + fclose(fp); + goto save_boots; + } + fclose(fp); + if (strcmp(myengine, engine) != 0) + snmpd_engine.engine_boots = 1; + else + snmpd_engine.engine_boots++; + } else if (errno != ENOENT) + return (-1); + +save_boots: + if ((fp = fopen(engine_file, "w+")) == NULL) + return (-1); + fprintf(fp, "%s%u\n", myengine, snmpd_engine.engine_boots); + fclose(fp); + + return (0); +} /************************************************************* * @@ -980,6 +1058,103 @@ op_snmp_set(struct snmp_context *ctx __unused, struct snmp_value *value, } /* + * SNMP Engine + */ +int +op_snmp_engine(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + + switch (op) { + case SNMP_OP_GETNEXT: + abort(); + + case SNMP_OP_GET: + break; + + case SNMP_OP_SET: + if (community != COMM_INITIALIZE) + return (SNMP_ERR_NOT_WRITEABLE); + switch (which) { + case LEAF_snmpEngineID: + if (value->v.octetstring.len > SNMP_ENGINE_ID_SIZ) + return (SNMP_ERR_WRONG_VALUE); + ctx->scratch->ptr1 = malloc(snmpd_engine.engine_len); + if (ctx->scratch->ptr1 == NULL) + return (SNMP_ERR_GENERR); + memcpy(ctx->scratch->ptr1, snmpd_engine.engine_id, + snmpd_engine.engine_len); + ctx->scratch->int1 = snmpd_engine.engine_len; + snmpd_engine.engine_len = value->v.octetstring.len; + memcpy(snmpd_engine.engine_id, + value->v.octetstring.octets, + value->v.octetstring.len); + break; + + case LEAF_snmpEngineMaxMessageSize: + ctx->scratch->int1 = snmpd_engine.max_msg_size; + snmpd_engine.max_msg_size = value->v.integer; + break; + + default: + return (SNMP_ERR_NOT_WRITEABLE); + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_ROLLBACK: + switch (which) { + case LEAF_snmpEngineID: + snmpd_engine.engine_len = ctx->scratch->int1; + memcpy(snmpd_engine.engine_id, ctx->scratch->ptr1, + snmpd_engine.engine_len); + free(ctx->scratch->ptr1); + break; + + case LEAF_snmpEngineMaxMessageSize: + snmpd_engine.max_msg_size = ctx->scratch->int1; + break; + + default: + abort(); + } + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + if (which == LEAF_snmpEngineID) { + if (set_snmpd_engine() < 0) { + snmpd_engine.engine_len = ctx->scratch->int1; + memcpy(snmpd_engine.engine_id, + ctx->scratch->ptr1, ctx->scratch->int1); + } + free(ctx->scratch->ptr1); + } + return (SNMP_ERR_NOERROR); + } + + + switch (which) { + case LEAF_snmpEngineID: + return (string_get(value, snmpd_engine.engine_id, + snmpd_engine.engine_len)); + case LEAF_snmpEngineBoots: + value->v.integer = snmpd_engine.engine_boots; + break; + case LEAF_snmpEngineTime: + snmpd_engine.engine_time = (get_ticks() - start_tick) / 100ULL; + value->v.integer = snmpd_engine.engine_time; + break; + case LEAF_snmpEngineMaxMessageSize: + value->v.integer = snmpd_engine.max_msg_size; + break; + default: + return (SNMP_ERR_NOSUCHNAME); + } + + return (SNMP_ERR_NOERROR); +} + +/* * Transport table */ int diff --git a/contrib/bsnmp/snmpd/bsnmpd.1 b/contrib/bsnmp/snmpd/bsnmpd.1 index b609efd..76141bc 100644 --- a/contrib/bsnmp/snmpd/bsnmpd.1 +++ b/contrib/bsnmp/snmpd/bsnmpd.1 @@ -31,7 +31,7 @@ .\" .\" $Begemot: bsnmp/snmpd/bsnmpd.1,v 1.12 2006/02/27 09:50:03 brandt_h Exp $ .\" -.Dd October 23, 2010 +.Dd September 9, 2010 .Dt BSNMPD 1 .Os .Sh NAME @@ -42,6 +42,7 @@ .Op Fl dh .Op Fl c Ar file .Op Fl D Ar options +.Op Fl e Ar file .Op Fl I Ar paths .Op Fl l Ar prefix .Op Fl m Ar variable Ns Op = Ns Ar value @@ -68,9 +69,11 @@ Use .Ar file as configuration file instead of the standard one. .It Fl D Ar options -Debugging options are specified as a comma separated string. +Debugging options are specified with a +.Fl o +flag followed by a comma separated string of options. The following options are available. -.Bl -tag -width "trace=level" +.Bl -tag -width ".It Cm trace Ns Cm = Ns Cm level" .It Cm dump Dump all sent and received PDUs to the terminal. .It Cm events @@ -80,8 +83,11 @@ to 10. .It Cm trace Ns Cm = Ns Cm level Set the snmp library trace flag to the specified value. -The value can be specified in the usual C-syntax for numbers. .El +The value can be specified in the usual C-syntax for numbers. +.It Fl e Ar file +Specify an alternate file where the agent's engine id and number of boots +are saved. .It Fl I Ar paths Specify a colon separated list of directories to search for configuration include files. @@ -246,6 +252,8 @@ Default configuration file, where the default .Aq prefix is .Dq snmpd . +.It Pa /var/ Ns Ao Ar prefix Ac Ns \&.engine +Default engine id file. .It Pa /var/run/ Ns Ao Ar prefix Ac Ns \&.pid Default pid file. .It Pa /etc:/usr/etc/:/usr/local/etc diff --git a/contrib/bsnmp/snmpd/config.c b/contrib/bsnmp/snmpd/config.c index 48044ec..c4f1188 100644 --- a/contrib/bsnmp/snmpd/config.c +++ b/contrib/bsnmp/snmpd/config.c @@ -31,6 +31,7 @@ * Parse configuration file. */ #include <sys/types.h> +#include <sys/queue.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> @@ -810,6 +811,7 @@ parse_oid(const char *varname, struct asn_oid *oid) struct snmp_node *node; u_int i; u_char ip[4]; + struct asn_oid str_oid; for (node = tree; node < &tree[tree_size]; node++) if (strcmp(varname, node->name) == 0) @@ -824,7 +826,19 @@ parse_oid(const char *varname, struct asn_oid *oid) report("subid too large %#"QUADXFMT, numval); if (oid->len == ASN_MAXOIDLEN) report("index too long"); - oid->subs[oid->len++] = numval; + if (gettoken() != ':') + oid->subs[oid->len++] = numval; + else { + str_oid.len = 0; + str_oid.subs[str_oid.len++] = numval; + while (gettoken() == TOK_NUM) { + str_oid.subs[str_oid.len++] = numval; + if (gettoken() != ':') + break; + } + oid->subs[oid->len++] = str_oid.len; + asn_append_oid(oid, &str_oid); + } } else if (token == TOK_STR) { if (strvallen + oid->len + 1 > ASN_MAXOIDLEN) @@ -832,6 +846,7 @@ parse_oid(const char *varname, struct asn_oid *oid) oid->subs[oid->len++] = strvallen; for (i = 0; i < strvallen; i++) oid->subs[oid->len++] = strval[i]; + gettoken(); } else if (token == TOK_HOST) { gethost(strval, ip); @@ -839,10 +854,9 @@ parse_oid(const char *varname, struct asn_oid *oid) report("index too long"); for (i = 0; i < 4; i++) oid->subs[oid->len++] = ip[i]; - + gettoken(); } else report("bad token in index"); - gettoken(); } return (node); @@ -1006,7 +1020,7 @@ parse_assign(const char *varname) node = parse_oid(varname, &vindex); if (token != '=') - report("'=' expected"); + report("'=' expected, got '%c'", token); gettoken(); if (ignore) { diff --git a/contrib/bsnmp/snmpd/export.c b/contrib/bsnmp/snmpd/export.c index 9498361..08fb672 100644 --- a/contrib/bsnmp/snmpd/export.c +++ b/contrib/bsnmp/snmpd/export.c @@ -31,6 +31,7 @@ * Support functions for modules. */ #include <sys/types.h> +#include <sys/queue.h> #include <sys/un.h> #include <stdio.h> #include <stdlib.h> diff --git a/contrib/bsnmp/snmpd/main.c b/contrib/bsnmp/snmpd/main.c index e3660c1..e008bf7 100644 --- a/contrib/bsnmp/snmpd/main.c +++ b/contrib/bsnmp/snmpd/main.c @@ -30,6 +30,8 @@ * * SNMPd main stuff. */ + +#include <sys/queue.h> #include <sys/param.h> #include <sys/un.h> #include <sys/ucred.h> @@ -60,6 +62,7 @@ #define PATH_PID "/var/run/%s.pid" #define PATH_CONFIG "/etc/%s.config" +#define PATH_ENGINE "/var/%s.engine" uint64_t this_tick; /* start of processing of current packet (absolute) */ uint64_t start_tick; /* start of processing */ @@ -87,6 +90,11 @@ struct snmpd snmpd = { }; struct snmpd_stats snmpd_stats; +struct snmpd_usmstat snmpd_usmstats; + +/* snmpEngine */ +struct snmp_engine snmpd_engine; + /* snmpSerialNo */ int32_t snmp_serial_no; @@ -102,6 +110,29 @@ static struct lmodules modules_start = TAILQ_HEAD_INITIALIZER(modules_start); /* list of all known communities */ struct community_list community_list = TAILQ_HEAD_INITIALIZER(community_list); +/* list of all known USM users */ +struct usm_userlist usm_userlist = SLIST_HEAD_INITIALIZER(usm_userlist); + +/* A list of all VACM users configured, including v1, v2c and v3 */ +struct vacm_userlist vacm_userlist = SLIST_HEAD_INITIALIZER(vacm_userlist); + +/* A list of all VACM groups */ +struct vacm_grouplist vacm_grouplist = SLIST_HEAD_INITIALIZER(vacm_grouplist); + +static struct vacm_group vacm_default_group = { + .groupname = "", +}; + +/* The list of configured access entries */ +struct vacm_accesslist vacm_accesslist = TAILQ_HEAD_INITIALIZER(vacm_accesslist); + +/* The list of configured views */ +struct vacm_viewlist vacm_viewlist = SLIST_HEAD_INITIALIZER(vacm_viewlist); + +/* The list of configured contexts */ +struct vacm_contextlist vacm_contextlist = + SLIST_HEAD_INITIALIZER(vacm_contextlist); + /* list of all installed object resources */ struct objres_list objres_list = TAILQ_HEAD_INITIALIZER(objres_list); @@ -128,9 +159,13 @@ static int nprogargs; u_int community; static struct community *comm; +/* current USM user */ +struct usm_user *usm_user; + /* file names */ static char config_file[MAXPATHLEN + 1]; static char pid_file[MAXPATHLEN + 1]; +char engine_file[MAXPATHLEN + 1]; #ifndef USE_LIBBEGEMOT /* event context */ @@ -154,6 +189,12 @@ static const struct asn_oid const struct asn_oid oid_zeroDotZero = { 2, { 0, 0 }}; +const struct asn_oid oid_usmUnknownEngineIDs = + { 11, { 1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0}}; + +const struct asn_oid oid_usmNotInTimeWindows = + { 11, { 1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0}}; + /* request id generator for traps */ u_int trap_reqid; @@ -161,13 +202,15 @@ u_int trap_reqid; static const char usgtxt[] = "\ Begemot simple SNMP daemon. Copyright (c) 2001-2002 Fraunhofer Institute for\n\ Open Communication Systems (FhG Fokus). All rights reserved.\n\ -usage: snmpd [-dh] [-c file] [-D options] [-I path] [-l prefix]\n\ - [-m variable=value] [-p file]\n\ +Copyright (c) 2010 The FreeBSD Foundation. All rights reserved.\n\ +usage: snmpd [-dh] [-c file] [-D options] [-e file] [-I path]\n\ + [-l prefix] [-m variable=value] [-p file]\n\ options:\n\ -d don't daemonize\n\ -h print this info\n\ -c file specify configuration file\n\ -D options debugging options\n\ + -e file specify engine id file\n\ -I path system include path\n\ -l prefix default basename for pid and config file\n\ -m var=val define variable\n\ @@ -243,7 +286,191 @@ snmp_output(struct snmp_pdu *pdu, u_char *sndbuf, size_t *sndlen, } /* - * SNMP input. Start: decode the PDU, find the community. + * Check USM PDU header credentials against local SNMP Engine & users. + */ +static enum snmp_code +snmp_pdu_auth_user(struct snmp_pdu *pdu) +{ + uint64_t etime; + usm_user = NULL; + + /* un-authenticated snmpEngineId discovery */ + if (pdu->engine.engine_len == 0 && strlen(pdu->user.sec_name) == 0) { + pdu->engine.engine_len = snmpd_engine.engine_len; + memcpy(pdu->engine.engine_id, snmpd_engine.engine_id, + snmpd_engine.engine_len); + pdu->engine.engine_boots = snmpd_engine.engine_boots; + pdu->engine.engine_time = snmpd_engine.engine_time; + pdu->flags |= SNMP_MSG_AUTODISCOVER; + return (SNMP_CODE_OK); + } + + if ((usm_user = usm_find_user(pdu->engine.engine_id, + pdu->engine.engine_len, pdu->user.sec_name)) == NULL || + usm_user->status != 1 /* active */) + return (SNMP_CODE_BADUSER); + + if (usm_user->user_engine_len != snmpd_engine.engine_len || + memcmp(usm_user->user_engine_id, snmpd_engine.engine_id, + snmpd_engine.engine_len) != 0) + return (SNMP_CODE_BADENGINE); + + pdu->user.priv_proto = usm_user->suser.priv_proto; + memcpy(pdu->user.priv_key, usm_user->suser.priv_key, + sizeof(pdu->user.priv_key)); + + /* authenticated snmpEngineId discovery */ + if ((pdu->flags & SNMP_MSG_AUTH_FLAG) != 0) { + etime = (get_ticks() - start_tick) / 100ULL; + if (etime < INT32_MAX) + snmpd_engine.engine_time = etime; + else { + start_tick = get_ticks(); + set_snmpd_engine(); + snmpd_engine.engine_time = start_tick; + } + + pdu->user.auth_proto = usm_user->suser.auth_proto; + memcpy(pdu->user.auth_key, usm_user->suser.auth_key, + sizeof(pdu->user.auth_key)); + + if (pdu->engine.engine_boots == 0 && + pdu->engine.engine_time == 0) { + pdu->flags |= SNMP_MSG_AUTODISCOVER; + return (SNMP_CODE_OK); + } + + if (pdu->engine.engine_boots != snmpd_engine.engine_boots || + abs(pdu->engine.engine_time - snmpd_engine.engine_time) > + SNMP_TIME_WINDOW) + return (SNMP_CODE_NOTINTIME); + } + + if (((pdu->flags & SNMP_MSG_PRIV_FLAG) != 0 && + (pdu->flags & SNMP_MSG_AUTH_FLAG) == 0) || + ((pdu->flags & SNMP_MSG_AUTH_FLAG) == 0 && + usm_user->suser.auth_proto != SNMP_AUTH_NOAUTH) || + ((pdu->flags & SNMP_MSG_PRIV_FLAG) == 0 && + usm_user->suser.priv_proto != SNMP_PRIV_NOPRIV)) + return (SNMP_CODE_BADSECLEVEL); + + return (SNMP_CODE_OK); +} + +/* + * Check whether access to each of var bindings in the PDU is allowed based + * on the user credentials against the configured User groups & VACM views. + */ +static enum snmp_code +snmp_pdu_auth_access(struct snmp_pdu *pdu, int32_t *ip) +{ + const char *uname; + int32_t suboid, smodel; + uint32_t i; + struct vacm_user *vuser; + struct vacm_access *acl; + struct vacm_context *vacmctx; + struct vacm_view *view; + + /* + * At least a default context exists if the snmpd_vacm(3) module is + * running. + */ + if (SLIST_EMPTY(&vacm_contextlist) || + (pdu->flags & SNMP_MSG_AUTODISCOVER) != 0) + return (SNMP_CODE_OK); + + switch (pdu->version) { + case SNMP_V1: + if ((uname = comm_string(community)) == NULL) + return (SNMP_CODE_FAILED); + smodel = SNMP_SECMODEL_SNMPv1; + break; + + case SNMP_V2c: + if ((uname = comm_string(community)) == NULL) + return (SNMP_CODE_FAILED); + smodel = SNMP_SECMODEL_SNMPv2c; + break; + + case SNMP_V3: + uname = pdu->user.sec_name; + if ((smodel = pdu->security_model) != SNMP_SECMODEL_USM) + return (SNMP_CODE_FAILED); + /* Compare the PDU context engine id against the agent's */ + if (pdu->context_engine_len != snmpd_engine.engine_len || + memcmp(pdu->context_engine, snmpd_engine.engine_id, + snmpd_engine.engine_len) != 0) + return (SNMP_CODE_FAILED); + break; + + default: + abort(); + } + + SLIST_FOREACH(vuser, &vacm_userlist, vvu) + if (strcmp(uname, vuser->secname) == 0 && + vuser->sec_model == smodel) + break; + + if (vuser == NULL || vuser->group == NULL) + return (SNMP_CODE_FAILED); + + /* XXX: shteryana - recheck */ + TAILQ_FOREACH_REVERSE(acl, &vacm_accesslist, vacm_accesslist, vva) { + if (acl->group != vuser->group) + continue; + SLIST_FOREACH(vacmctx, &vacm_contextlist, vcl) + if (memcmp(vacmctx->ctxname, acl->ctx_prefix, + acl->ctx_match) == 0) + goto match; + } + + return (SNMP_CODE_FAILED); + +match: + + switch (pdu->type) { + case SNMP_PDU_GET: + case SNMP_PDU_GETNEXT: + case SNMP_PDU_GETBULK: + if ((view = acl->read_view) == NULL) + return (SNMP_CODE_FAILED); + break; + + case SNMP_PDU_SET: + if ((view = acl->write_view) == NULL) + return (SNMP_CODE_FAILED); + break; + + case SNMP_PDU_TRAP: + case SNMP_PDU_INFORM: + case SNMP_PDU_TRAP2: + case SNMP_PDU_REPORT: + if ((view = acl->notify_view) == NULL) + return (SNMP_CODE_FAILED); + break; + case SNMP_PDU_RESPONSE: + /* NOTREACHED */ + return (SNMP_CODE_FAILED); + default: + abort(); + } + + for (i = 0; i < pdu->nbindings; i++) { + /* XXX - view->mask*/ + suboid = asn_is_suboid(&view->subtree, &pdu->bindings[i].var); + if ((!suboid && !view->exclude) || (suboid && view->exclude)) { + *ip = i + 1; + return (SNMP_CODE_FAILED); + } + } + + return (SNMP_CODE_OK); +} + +/* + * SNMP input. Start: decode the PDU, find the user or community. */ enum snmpd_input_err snmp_input_start(const u_char *buf, size_t len, const char *source, @@ -254,6 +481,9 @@ snmp_input_start(const u_char *buf, size_t len, const char *source, enum snmpd_input_err ret; int sret; + /* update uptime */ + this_tick = get_ticks(); + b.asn_cptr = buf; b.asn_len = len; @@ -269,11 +499,27 @@ snmp_input_start(const u_char *buf, size_t len, const char *source, } b.asn_len = *pdulen = (size_t)sret; - code = snmp_pdu_decode(&b, pdu, ip); + memset(pdu, 0, sizeof(*pdu)); + if ((code = snmp_pdu_decode_header(&b, pdu)) != SNMP_CODE_OK) + goto decoded; - snmpd_stats.inPkts++; + if (pdu->version == SNMP_V3) { + if (pdu->security_model != SNMP_SECMODEL_USM) { + code = SNMP_CODE_FAILED; + goto decoded; + } + if ((code = snmp_pdu_auth_user(pdu)) != SNMP_CODE_OK) + goto decoded; + if ((code = snmp_pdu_decode_secmode(&b, pdu)) != SNMP_CODE_OK) + goto decoded; + } + code = snmp_pdu_decode_scoped(&b, pdu, ip); ret = SNMPD_INPUT_OK; + +decoded: + snmpd_stats.inPkts++; + switch (code) { case SNMP_CODE_FAILED: @@ -300,6 +546,30 @@ snmp_input_start(const u_char *buf, size_t len, const char *source, ret = SNMPD_INPUT_VALBADENC; break; + case SNMP_CODE_BADSECLEVEL: + snmpd_usmstats.unsupported_seclevels++; + return (SNMPD_INPUT_FAILED); + + case SNMP_CODE_NOTINTIME: + snmpd_usmstats.not_in_time_windows++; + return (SNMPD_INPUT_FAILED); + + case SNMP_CODE_BADUSER: + snmpd_usmstats.unknown_users++; + return (SNMPD_INPUT_FAILED); + + case SNMP_CODE_BADENGINE: + snmpd_usmstats.unknown_engine_ids++; + return (SNMPD_INPUT_FAILED); + + case SNMP_CODE_BADDIGEST: + snmpd_usmstats.wrong_digests++; + return (SNMPD_INPUT_FAILED); + + case SNMP_CODE_EDECRYPT: + snmpd_usmstats.decrypt_errors++; + return (SNMPD_INPUT_FAILED); + case SNMP_CODE_OK: switch (pdu->version) { @@ -313,6 +583,11 @@ snmp_input_start(const u_char *buf, size_t len, const char *source, goto bad_vers; break; + case SNMP_V3: + if (!(snmpd.version_enable & VERS_ENABLE_V3)) + goto bad_vers; + break; + case SNMP_Verr: goto bad_vers; } @@ -325,25 +600,47 @@ snmp_input_start(const u_char *buf, size_t len, const char *source, } /* - * Look, whether we know the community + * Look, whether we know the community or user */ - TAILQ_FOREACH(comm, &community_list, link) - if (comm->string != NULL && - strcmp(comm->string, pdu->community) == 0) - break; - if (comm == NULL) { - snmpd_stats.inBadCommunityNames++; - snmp_pdu_free(pdu); - if (snmpd.auth_traps) - snmp_send_trap(&oid_authenticationFailure, - (struct snmp_value *)NULL); - ret = SNMPD_INPUT_BAD_COMM; - } else - community = comm->value; + if (pdu->version != SNMP_V3) { + TAILQ_FOREACH(comm, &community_list, link) + if (comm->string != NULL && + strcmp(comm->string, pdu->community) == 0) + break; - /* update uptime */ - this_tick = get_ticks(); + if (comm == NULL) { + snmpd_stats.inBadCommunityNames++; + snmp_pdu_free(pdu); + if (snmpd.auth_traps) + snmp_send_trap(&oid_authenticationFailure, + (struct snmp_value *)NULL); + ret = SNMPD_INPUT_BAD_COMM; + } else + community = comm->value; + } else if (pdu->nbindings == 0) { + /* RFC 3414 - snmpEngineID Discovery */ + if (strlen(pdu->user.sec_name) == 0) { + asn_append_oid(&(pdu->bindings[pdu->nbindings++].var), + &oid_usmUnknownEngineIDs); + pdu->context_engine_len = snmpd_engine.engine_len; + memcpy(pdu->context_engine, snmpd_engine.engine_id, + snmpd_engine.engine_len); + } else if (pdu->engine.engine_boots == 0 && + pdu->engine.engine_time == 0) { + asn_append_oid(&(pdu->bindings[pdu->nbindings++].var), + &oid_usmNotInTimeWindows); + pdu->engine.engine_boots = snmpd_engine.engine_boots; + pdu->engine.engine_time = snmpd_engine.engine_time; + } + } else if (usm_user->suser.auth_proto != SNMP_AUTH_NOAUTH && + (pdu->engine.engine_boots == 0 || pdu->engine.engine_time == 0)) { + snmpd_usmstats.not_in_time_windows++; + ret = SNMP_CODE_FAILED; + } + + if ((code = snmp_pdu_auth_access(pdu, ip)) != SNMP_CODE_OK) + ret = SNMP_CODE_FAILED; return (ret); } @@ -960,7 +1257,8 @@ snmpd_input(struct port_input *pi, struct tport *tport) * If that is a module community and the module has a proxy function, * the hand it over to the module. */ - if (comm->owner != NULL && comm->owner->config->proxy != NULL) { + if (comm != NULL && comm->owner != NULL && + comm->owner->config->proxy != NULL) { perr = (*comm->owner->config->proxy)(&pdu, tport->transport, &tport->index, pi->peer, pi->peerlen, ierr, vi, !pi->cred || pi->priv); @@ -1016,9 +1314,10 @@ snmpd_input(struct port_input *pi, struct tport *tport) /* * Check community */ - if ((pi->cred && !pi->priv && pdu.type == SNMP_PDU_SET) || + if (pdu.version < SNMP_V3 && + ((pi->cred && !pi->priv && pdu.type == SNMP_PDU_SET) || (community != COMM_WRITE && - (pdu.type == SNMP_PDU_SET || community != COMM_READ))) { + (pdu.type == SNMP_PDU_SET || community != COMM_READ)))) { snmpd_stats.inBadCommunityUses++; snmp_pdu_free(&pdu); snmp_input_consume(pi); @@ -1337,7 +1636,7 @@ main(int argc, char *argv[]) struct tport *p; const char *prefix = "snmpd"; struct lmodule *m; - char *value, *option; + char *value = NULL, *option; /* XXX */ struct transport *t; #define DBG_DUMP 0 @@ -1355,7 +1654,7 @@ main(int argc, char *argv[]) snmp_debug = snmp_debug_func; asn_error = asn_error_func; - while ((opt = getopt(argc, argv, "c:dD:hI:l:m:p:")) != EOF) + while ((opt = getopt(argc, argv, "c:dD:e:hI:l:m:p:")) != EOF) switch (opt) { case 'c': @@ -1401,6 +1700,9 @@ main(int argc, char *argv[]) } break; + case 'e': + strlcpy(engine_file, optarg, sizeof(engine_file)); + break; case 'h': fprintf(stderr, "%s", usgtxt); exit(0); @@ -1471,6 +1773,7 @@ main(int argc, char *argv[]) snprintf(config_file, sizeof(config_file), PATH_CONFIG, prefix); init_actvals(); + init_snmpd_engine(); this_tick = get_ticks(); start_tick = this_tick; @@ -1497,6 +1800,9 @@ main(int argc, char *argv[]) evSetDebug(evctx, 10, stderr); #endif + if (engine_file[0] == '\0') + snprintf(engine_file, sizeof(engine_file), PATH_ENGINE, prefix); + if (read_config(config_file, NULL)) { syslog(LOG_ERR, "error in config file"); exit(1); @@ -1601,7 +1907,7 @@ main(int argc, char *argv[]) } uint64_t -get_ticks() +get_ticks(void) { struct timeval tv; uint64_t ret; @@ -2374,3 +2680,566 @@ or_unregister(u_int idx) return; } } + +/* + * RFC 3414 User-based Security Model support + */ + +struct snmpd_usmstat * +bsnmpd_get_usm_stats(void) +{ + return (&snmpd_usmstats); +} + +void +bsnmpd_reset_usm_stats(void) +{ + memset(&snmpd_usmstats, 0, sizeof(&snmpd_usmstats)); +} + +struct usm_user * +usm_first_user(void) +{ + return (SLIST_FIRST(&usm_userlist)); +} + +struct usm_user * +usm_next_user(struct usm_user *uuser) +{ + if (uuser == NULL) + return (NULL); + + return (SLIST_NEXT(uuser, up)); +} + +struct usm_user * +usm_find_user(uint8_t *engine, uint32_t elen, char *uname) +{ + struct usm_user *uuser; + + SLIST_FOREACH(uuser, &usm_userlist, up) + if (uuser->user_engine_len == elen && + memcmp(uuser->user_engine_id, engine, elen) == 0 && + strlen(uuser->suser.sec_name) == strlen(uname) && + strcmp(uuser->suser.sec_name, uname) == 0) + break; + + return (uuser); +} + +static int +usm_compare_user(struct usm_user *u1, struct usm_user *u2) +{ + uint32_t i; + + if (u1->user_engine_len < u2->user_engine_len) + return (-1); + if (u1->user_engine_len > u2->user_engine_len) + return (1); + + for (i = 0; i < u1->user_engine_len; i++) { + if (u1->user_engine_id[i] < u2->user_engine_id[i]) + return (-1); + if (u1->user_engine_id[i] > u2->user_engine_id[i]) + return (1); + } + + if (strlen(u1->suser.sec_name) < strlen(u2->suser.sec_name)) + return (-1); + if (strlen(u1->suser.sec_name) > strlen(u2->suser.sec_name)) + return (1); + + for (i = 0; i < strlen(u1->suser.sec_name); i++) { + if (u1->suser.sec_name[i] < u2->suser.sec_name[i]) + return (-1); + if (u1->suser.sec_name[i] > u2->suser.sec_name[i]) + return (1); + } + + return (0); +} + +struct usm_user * +usm_new_user(uint8_t *eid, uint32_t elen, char *uname) +{ + int cmp; + struct usm_user *uuser, *temp, *prev; + + for (uuser = usm_first_user(); uuser != NULL; + (uuser = usm_next_user(uuser))) { + if (uuser->user_engine_len == elen && + strlen(uname) == strlen(uuser->suser.sec_name) && + strcmp(uname, uuser->suser.sec_name) == 0 && + memcmp(eid, uuser->user_engine_id, elen) == 0) + return (NULL); + } + + if ((uuser = (struct usm_user *)malloc(sizeof(*uuser))) == NULL) + return (NULL); + + memset(uuser, 0, sizeof(struct usm_user)); + strlcpy(uuser->suser.sec_name, uname, SNMP_ADM_STR32_SIZ); + memcpy(uuser->user_engine_id, eid, elen); + uuser->user_engine_len = elen; + + if ((prev = SLIST_FIRST(&usm_userlist)) == NULL || + usm_compare_user(uuser, prev) < 0) { + SLIST_INSERT_HEAD(&usm_userlist, uuser, up); + return (uuser); + } + + SLIST_FOREACH(temp, &usm_userlist, up) { + if ((cmp = usm_compare_user(uuser, temp)) <= 0) + break; + prev = temp; + } + + if (temp == NULL || cmp < 0) + SLIST_INSERT_AFTER(prev, uuser, up); + else if (cmp > 0) + SLIST_INSERT_AFTER(temp, uuser, up); + else { + syslog(LOG_ERR, "User %s exists", uuser->suser.sec_name); + free(uuser); + return (NULL); + } + + return (uuser); +} + +void +usm_delete_user(struct usm_user *uuser) +{ + SLIST_REMOVE(&usm_userlist, uuser, usm_user, up); + free(uuser); +} + +void +usm_flush_users(void) +{ + struct usm_user *uuser; + + while ((uuser = SLIST_FIRST(&usm_userlist)) != NULL) { + SLIST_REMOVE_HEAD(&usm_userlist, up); + free(uuser); + } + + SLIST_INIT(&usm_userlist); +} + +/* + * RFC 3415 View-based Access Control Model support + */ +struct vacm_user * +vacm_first_user(void) +{ + return (SLIST_FIRST(&vacm_userlist)); +} + +struct vacm_user * +vacm_next_user(struct vacm_user *vuser) +{ + if (vuser == NULL) + return (NULL); + + return (SLIST_NEXT(vuser, vvu)); +} + +static int +vacm_compare_user(struct vacm_user *v1, struct vacm_user *v2) +{ + uint32_t i; + + if (v1->sec_model < v2->sec_model) + return (-1); + if (v1->sec_model > v2->sec_model) + return (1); + + if (strlen(v1->secname) < strlen(v2->secname)) + return (-1); + if (strlen(v1->secname) > strlen(v2->secname)) + return (1); + + for (i = 0; i < strlen(v1->secname); i++) { + if (v1->secname[i] < v2->secname[i]) + return (-1); + if (v1->secname[i] > v2->secname[i]) + return (1); + } + + return (0); +} + +struct vacm_user * +vacm_new_user(int32_t smodel, char *uname) +{ + int cmp; + struct vacm_user *user, *temp, *prev; + + SLIST_FOREACH(user, &vacm_userlist, vvu) + if (strcmp(uname, user->secname) == 0 && + smodel == user->sec_model) + return (NULL); + + if ((user = (struct vacm_user *)malloc(sizeof(*user))) == NULL) + return (NULL); + + memset(user, 0, sizeof(*user)); + user->group = &vacm_default_group; + SLIST_INSERT_HEAD(&vacm_default_group.group_users, user, vvg); + user->sec_model = smodel; + strlcpy(user->secname, uname, sizeof(user->secname)); + + if ((prev = SLIST_FIRST(&vacm_userlist)) == NULL || + vacm_compare_user(user, prev) < 0) { + SLIST_INSERT_HEAD(&vacm_userlist, user, vvu); + return (user); + } + + SLIST_FOREACH(temp, &vacm_userlist, vvu) { + if ((cmp = vacm_compare_user(user, temp)) <= 0) + break; + prev = temp; + } + + if (temp == NULL || cmp < 0) + SLIST_INSERT_AFTER(prev, user, vvu); + else if (cmp > 0) + SLIST_INSERT_AFTER(temp, user, vvu); + else { + syslog(LOG_ERR, "User %s exists", user->secname); + free(user); + return (NULL); + } + + return (user); +} + +int +vacm_delete_user(struct vacm_user *user) +{ + if (user->group != NULL && user->group != &vacm_default_group) { + SLIST_REMOVE(&user->group->group_users, user, vacm_user, vvg); + if (SLIST_EMPTY(&user->group->group_users)) { + SLIST_REMOVE(&vacm_grouplist, user->group, + vacm_group, vge); + free(user->group); + } + } + + SLIST_REMOVE(&vacm_userlist, user, vacm_user, vvu); + free(user); + + return (0); +} + +int +vacm_user_set_group(struct vacm_user *user, u_char *octets, u_int len) +{ + struct vacm_group *group; + + if (len >= SNMP_ADM_STR32_SIZ) + return (-1); + + SLIST_FOREACH(group, &vacm_grouplist, vge) + if (strlen(group->groupname) == len && + memcmp(octets, group->groupname, len) == 0) + break; + + if (group == NULL) { + if ((group = (struct vacm_group *)malloc(sizeof(*group))) == NULL) + return (-1); + memset(group, 0, sizeof(*group)); + memcpy(group->groupname, octets, len); + group->groupname[len] = '\0'; + SLIST_INSERT_HEAD(&vacm_grouplist, group, vge); + } + + SLIST_REMOVE(&user->group->group_users, user, vacm_user, vvg); + SLIST_INSERT_HEAD(&group->group_users, user, vvg); + user->group = group; + + return (0); +} + +void +vacm_groups_init(void) +{ + SLIST_INSERT_HEAD(&vacm_grouplist, &vacm_default_group, vge); +} + +struct vacm_access * +vacm_first_access_rule(void) +{ + return (TAILQ_FIRST(&vacm_accesslist)); +} + +struct vacm_access * +vacm_next_access_rule(struct vacm_access *acl) +{ + if (acl == NULL) + return (NULL); + + return (TAILQ_NEXT(acl, vva)); +} + +static int +vacm_compare_access_rule(struct vacm_access *v1, struct vacm_access *v2) +{ + uint32_t i; + + if (strlen(v1->group->groupname) < strlen(v2->group->groupname)) + return (-1); + if (strlen(v1->group->groupname) > strlen(v2->group->groupname)) + return (1); + + for (i = 0; i < strlen(v1->group->groupname); i++) { + if (v1->group->groupname[i] < v2->group->groupname[i]) + return (-1); + if (v1->group->groupname[i] > v2->group->groupname[i]) + return (1); + } + + if (strlen(v1->ctx_prefix) < strlen(v2->ctx_prefix)) + return (-1); + if (strlen(v1->ctx_prefix) > strlen(v2->ctx_prefix)) + return (1); + + for (i = 0; i < strlen(v1->ctx_prefix); i++) { + if (v1->ctx_prefix[i] < v2->ctx_prefix[i]) + return (-1); + if (v1->ctx_prefix[i] > v2->ctx_prefix[i]) + return (1); + } + + if (v1->sec_model < v2->sec_model) + return (-1); + if (v1->sec_model > v2->sec_model) + return (1); + + if (v1->sec_level < v2->sec_level) + return (-1); + if (v1->sec_level > v2->sec_level) + return (1); + + return (0); +} + +struct vacm_access * +vacm_new_access_rule(char *gname, char *cprefix, int32_t smodel, int32_t slevel) +{ + struct vacm_group *group; + struct vacm_access *acl, *temp; + + TAILQ_FOREACH(acl, &vacm_accesslist, vva) { + if (acl->group == NULL) + continue; + if (strcmp(gname, acl->group->groupname) == 0 && + strcmp(cprefix, acl->ctx_prefix) == 0 && + acl->sec_model == smodel && acl->sec_level == slevel) + return (NULL); + } + + /* Make sure the group exists */ + SLIST_FOREACH(group, &vacm_grouplist, vge) + if (strcmp(gname, group->groupname) == 0) + break; + + if (group == NULL) + return (NULL); + + if ((acl = (struct vacm_access *)malloc(sizeof(*acl))) == NULL) + return (NULL); + + memset(acl, 0, sizeof(*acl)); + acl->group = group; + strlcpy(acl->ctx_prefix, cprefix, sizeof(acl->ctx_prefix)); + acl->sec_model = smodel; + acl->sec_level = slevel; + + if ((temp = TAILQ_FIRST(&vacm_accesslist)) == NULL || + vacm_compare_access_rule(acl, temp) < 0) { + TAILQ_INSERT_HEAD(&vacm_accesslist, acl, vva); + return (acl); + } + + TAILQ_FOREACH(temp, &vacm_accesslist, vva) + if (vacm_compare_access_rule(acl, temp) < 0) { + TAILQ_INSERT_BEFORE(temp, acl, vva); + return (acl); + } + + TAILQ_INSERT_TAIL(&vacm_accesslist, acl, vva); + + return (acl); +} + +int +vacm_delete_access_rule(struct vacm_access *acl) +{ + TAILQ_REMOVE(&vacm_accesslist, acl, vva); + free(acl); + + return (0); +} + +struct vacm_view * +vacm_first_view(void) +{ + return (SLIST_FIRST(&vacm_viewlist)); +} + +struct vacm_view * +vacm_next_view(struct vacm_view *view) +{ + if (view == NULL) + return (NULL); + + return (SLIST_NEXT(view, vvl)); +} + +static int +vacm_compare_view(struct vacm_view *v1, struct vacm_view *v2) +{ + uint32_t i; + + if (strlen(v1->viewname) < strlen(v2->viewname)) + return (-1); + if (strlen(v1->viewname) > strlen(v2->viewname)) + return (1); + + for (i = 0; i < strlen(v1->viewname); i++) { + if (v1->viewname[i] < v2->viewname[i]) + return (-1); + if (v1->viewname[i] > v2->viewname[i]) + return (1); + } + + return (asn_compare_oid(&v1->subtree, &v2->subtree)); +} + +struct vacm_view * +vacm_new_view(char *vname, struct asn_oid *oid) +{ + int cmp; + struct vacm_view *view, *temp, *prev; + + SLIST_FOREACH(view, &vacm_viewlist, vvl) + if (strcmp(vname, view->viewname) == 0) + return (NULL); + + if ((view = (struct vacm_view *)malloc(sizeof(*view))) == NULL) + return (NULL); + + memset(view, 0, sizeof(*view)); + strlcpy(view->viewname, vname, sizeof(view->viewname)); + asn_append_oid(&view->subtree, oid); + + if ((prev = SLIST_FIRST(&vacm_viewlist)) == NULL || + vacm_compare_view(view, prev) < 0) { + SLIST_INSERT_HEAD(&vacm_viewlist, view, vvl); + return (view); + } + + SLIST_FOREACH(temp, &vacm_viewlist, vvl) { + if ((cmp = vacm_compare_view(view, temp)) <= 0) + break; + prev = temp; + } + + if (temp == NULL || cmp < 0) + SLIST_INSERT_AFTER(prev, view, vvl); + else if (cmp > 0) + SLIST_INSERT_AFTER(temp, view, vvl); + else { + syslog(LOG_ERR, "View %s exists", view->viewname); + free(view); + return (NULL); + } + + return (view); +} + +int +vacm_delete_view(struct vacm_view *view) +{ + SLIST_REMOVE(&vacm_viewlist, view, vacm_view, vvl); + free(view); + + return (0); +} + +struct vacm_context * +vacm_first_context(void) +{ + return (SLIST_FIRST(&vacm_contextlist)); +} + +struct vacm_context * +vacm_next_context(struct vacm_context *vacmctx) +{ + if (vacmctx == NULL) + return (NULL); + + return (SLIST_NEXT(vacmctx, vcl)); +} + +struct vacm_context * +vacm_add_context(char *ctxname, int regid) +{ + int cmp; + struct vacm_context *ctx, *temp, *prev; + + SLIST_FOREACH(ctx, &vacm_contextlist, vcl) + if (strcmp(ctxname, ctx->ctxname) == 0) { + syslog(LOG_ERR, "Context %s exists", ctx->ctxname); + return (NULL); + } + + if ((ctx = (struct vacm_context *)malloc(sizeof(*ctx))) == NULL) + return (NULL); + + memset(ctx, 0, sizeof(*ctx)); + strlcpy(ctx->ctxname, ctxname, sizeof(ctx->ctxname)); + ctx->regid = regid; + + if ((prev = SLIST_FIRST(&vacm_contextlist)) == NULL || + strlen(ctx->ctxname) < strlen(prev->ctxname) || + strcmp(ctx->ctxname, prev->ctxname) < 0) { + SLIST_INSERT_HEAD(&vacm_contextlist, ctx, vcl); + return (ctx); + } + + SLIST_FOREACH(temp, &vacm_contextlist, vcl) { + if (strlen(ctx->ctxname) < strlen(temp->ctxname) || + strcmp(ctx->ctxname, temp->ctxname) < 0) { + cmp = -1; + break; + } + prev = temp; + } + + if (temp == NULL || cmp < 0) + SLIST_INSERT_AFTER(prev, ctx, vcl); + else if (cmp > 0) + SLIST_INSERT_AFTER(temp, ctx, vcl); + else { + syslog(LOG_ERR, "Context %s exists", ctx->ctxname); + free(ctx); + return (NULL); + } + + return (ctx); +} + +void +vacm_flush_contexts(int regid) +{ + struct vacm_context *ctx, *temp; + + SLIST_FOREACH_SAFE(ctx, &vacm_contextlist, vcl, temp) + if (ctx->regid == regid) { + SLIST_REMOVE(&vacm_contextlist, ctx, vacm_context, vcl); + free(ctx); + } +} diff --git a/contrib/bsnmp/snmpd/snmpd.h b/contrib/bsnmp/snmpd/snmpd.h index 39d90b7..79fc699 100644 --- a/contrib/bsnmp/snmpd/snmpd.h +++ b/contrib/bsnmp/snmpd/snmpd.h @@ -30,7 +30,7 @@ * * Private SNMPd data and functions. */ -#include <sys/queue.h> + #ifdef USE_LIBBEGEMOT #include <rpoll.h> #else @@ -247,7 +247,8 @@ extern struct snmpd snmpd; #define VERS_ENABLE_V1 0x00000001 #define VERS_ENABLE_V2C 0x00000002 -#define VERS_ENABLE_ALL 0x00000003 +#define VERS_ENABLE_V3 0x00000004 +#define VERS_ENABLE_ALL (VERS_ENABLE_V1 | VERS_ENABLE_V2C | VERS_ENABLE_V3) /* * The debug group @@ -280,6 +281,11 @@ struct snmpd_stats { extern struct snmpd_stats snmpd_stats; /* + * SNMPd Engine + */ +extern struct snmp_engine snmpd_engine; + +/* * OR Table */ struct objres { @@ -322,6 +328,11 @@ extern const char *syspath; extern int32_t snmp_serial_no; int init_actvals(void); + +extern char engine_file[]; +int init_snmpd_engine(void); +int set_snmpd_engine(void); + int read_config(const char *, struct lmodule *); int define_macro(const char *name, const char *value); diff --git a/contrib/bsnmp/snmpd/snmpmod.3 b/contrib/bsnmp/snmpd/snmpmod.3 index 6bea403..a142069 100644 --- a/contrib/bsnmp/snmpd/snmpmod.3 +++ b/contrib/bsnmp/snmpd/snmpmod.3 @@ -31,7 +31,7 @@ .\" .\" $Begemot: bsnmp/snmpd/snmpmod.3,v 1.14 2005/10/04 13:30:35 brandt_h Exp $ .\" -.Dd February 27, 2006 +.Dd September 9, 2010 .Dt SNMPMOD 3 .Os .Sh NAME @@ -60,6 +60,8 @@ .Nm comm_define , .Nm community , .Nm oid_zeroDotZero , +.Nm oid_usmUnknownEngineIDs , +.Nm oid_usmNotInTimeWindows , .Nm reqid_allocate , .Nm reqid_next , .Nm reqid_base , @@ -99,7 +101,16 @@ .Nm index_compare , .Nm index_compare_off , .Nm index_append , -.Nm index_append_off +.Nm index_append_off, +.Nm bsnmpd_get_usm_stats, +.Nm bsnmpd_reset_usm_stats, +.Nm usm_first_user, +.Nm usm_next_user, +.Nm usm_find_user, +.Nm usm_new_user, +.Nm usm_delete_user, +.Nm usm_flush_users, +.Nm usm_user .Nd "SNMP daemon loadable module interface" .Sh LIBRARY Begemot SNMP library @@ -228,6 +239,25 @@ Begemot SNMP library .Fn index_append "struct asn_oid *dst" "u_int sub" "const struct asn_oid *src" .Ft void .Fn index_append_off "struct asn_oid *dst" "u_int sub" "const struct asn_oid *src" "u_int off" +.Ft struct snmpd_usmstat * +.Fn bsnmpd_get_usm_stats "void" +.Ft void +.Fn bsnmpd_reset_usm_stats "void" +.Ft struct usm_user * +.Fn usm_first_user "void" +.Ft struct usm_user * +.Fn usm_next_user "struct usm_user *uuser" +.Ft struct usm_user * +.Fn usm_find_user "uint8_t *engine" "uint32_t elen" "char *uname" +.Ft struct usm_user * +.Fn usm_new_user "uint8_t *engine" "uint32_t elen" "char *uname" +.Ft void +.Fn usm_delete_user "struct usm_user *" +.Ft void +.Fn usm_flush_users "void" +.Vt extern struct usm_user *usm_user; +.Vt extern const struct asn_oid oid_usmUnknownEngineIDs; +.Vt extern const struct asn_oid oid_usmNotInTimeWindows; .Sh DESCRIPTION The .Xr bsnmpd 1 @@ -539,7 +569,7 @@ This is the initial community string. .El .Pp The function returns a globally unique community identifier. -If a PDU is +If a SNMPv1 or SNMPv2 PDU is received who's community string matches, this identifier is set into the global .Va community . .Pp @@ -549,10 +579,76 @@ returns the current community string for the given community. .Pp All communities defined by a module are automatically released when the module is unloaded. +.Ss THE USER-BASED SECURITY GROUP +The scalar statistics of the USM group are held in the global variable +.Va snmpd_usmstats : +.Bd -literal -offset indent +struct snmpd_usmstat { + uint32_t unsupported_seclevels; + uint32_t not_in_time_windows; + uint32_t unknown_users; + uint32_t unknown_engine_ids; + uint32_t wrong_digests; + uint32_t decrypt_errors; +}; +.Ed +.Fn bsnmpd_get_usm_stats +returns a pointer to the global structure containing the statistics. +.Fn bsnmpd_reset_usm_stats +clears the statistics of the USM group. +.Pp +A global list of configured USM users is maintained by the daemon. +.Bd -literal -offset indent +struct usm_user { + struct snmp_user suser; + uint8_t user_engine_id[SNMP_ENGINE_ID_SIZ]; + uint32_t user_engine_len; + char user_public[SNMP_USM_NAME_SIZ]; + uint32_t user_public_len; + int32_t status; + int32_t type; + SLIST_ENTRY(usm_user) up; +}; +.Ed +This structure represents an USM user. The daemon only responds to SNMPv3 PDUs +with user credentials matching an USM user entry in its global list. +If a SNMPv3 PDU is received, whose security model is USM, the global +.Va usm_user +is set to point at the user entry that matches the credentials contained in +the PDU. +However, the daemon does not create or remove USM users, it gives an interface +to external loadable module(s) to manage the list. +.Fn usm_new_user +adds an user entry in the list, and +.Fn usm_delete_user +deletes an existing entry from the list. +.Fn usm_flush_users +is used to remove all configured USM users. +.Fn usm_first_user +will return the first user in the list, or +.Li NULL +if the list is empty. +.Fn usm_next_user +will return the next user of a given entry if one exists, or +.Li NULL . +The list is sorted according to the USM user name and Engine ID. +.Fn usm_find_user +returns the USM user entry matching the given +.Fa engine +and +.Fa uname +or +.Li NULL +if an user with the specified name and engine id is not present in the list. .Ss WELL KNOWN OIDS The global variable .Va oid_zeroDotZero contains the OID 0.0. +The global variables +.Va oid_usmUnknownEngineIDs +.Va oid_usmNotInTimeWindows +contains the OIDs 1.3.6.1.6.3.15.1.1.4.0 and 1.3.6.1.6.3.15.1.1.2.0 used +in the SNMPv3 USM Engine Discovery. .Ss REQUEST ID RANGES For modules that implement SNMP client functions besides SNMP agent functions it may be necessary to identify SNMP requests by their identifier to allow diff --git a/contrib/bsnmp/snmpd/snmpmod.h b/contrib/bsnmp/snmpd/snmpmod.h index 5eba370..379070c 100644 --- a/contrib/bsnmp/snmpd/snmpmod.h +++ b/contrib/bsnmp/snmpd/snmpmod.h @@ -332,11 +332,137 @@ const char * comm_string(u_int); /* community for current packet */ extern u_int community; -/* +/* + * SNMP User-based Security Model data. Modified via the snmp_usm(3) module. + */ +struct snmpd_usmstat { + uint32_t unsupported_seclevels; + uint32_t not_in_time_windows; + uint32_t unknown_users; + uint32_t unknown_engine_ids; + uint32_t wrong_digests; + uint32_t decrypt_errors; +}; + +extern struct snmpd_usmstat snmpd_usmstats; +struct snmpd_usmstat *bsnmpd_get_usm_stats(void); +void bsnmpd_reset_usm_stats(void); + +struct usm_user { + struct snmp_user suser; + uint8_t user_engine_id[SNMP_ENGINE_ID_SIZ]; + uint32_t user_engine_len; + char user_public[SNMP_ADM_STR32_SIZ]; + uint32_t user_public_len; + int32_t status; + int32_t type; + SLIST_ENTRY(usm_user) up; +}; + +SLIST_HEAD(usm_userlist, usm_user); +struct usm_user *usm_first_user(void); +struct usm_user *usm_next_user(struct usm_user *); +struct usm_user *usm_find_user(uint8_t *, uint32_t, char *); +struct usm_user *usm_new_user(uint8_t *, uint32_t, char *); +void usm_delete_user(struct usm_user *); +void usm_flush_users(void); + +/* USM user for current packet */ +extern struct usm_user *usm_user; + +/* + * SNMP View-based Access Control Model data. Modified via the snmp_vacm(3) module. + */ +struct vacm_group; + +struct vacm_user { + /* Security user name from USM */ + char secname[SNMP_ADM_STR32_SIZ]; + int32_t sec_model; + /* Back pointer to user assigned group name */ + struct vacm_group *group; + int32_t type; + int32_t status; + SLIST_ENTRY(vacm_user) vvu; + SLIST_ENTRY(vacm_user) vvg; +}; + +SLIST_HEAD(vacm_userlist, vacm_user); + +struct vacm_group { + char groupname[SNMP_ADM_STR32_SIZ]; + struct vacm_userlist group_users; + SLIST_ENTRY(vacm_group) vge; +}; + +SLIST_HEAD(vacm_grouplist, vacm_group); + +struct vacm_access { + /* The group name is index, not a column in the table */ + struct vacm_group *group; + char ctx_prefix[SNMP_ADM_STR32_SIZ]; + int32_t sec_model; + int32_t sec_level; + int32_t ctx_match; + struct vacm_view *read_view; + struct vacm_view *write_view; + struct vacm_view *notify_view; + int32_t type; + int32_t status; + TAILQ_ENTRY(vacm_access) vva; +}; + +TAILQ_HEAD(vacm_accesslist, vacm_access); + +struct vacm_view { + char viewname[SNMP_ADM_STR32_SIZ]; /* key */ + struct asn_oid subtree; /* key */ + uint8_t mask[16]; + uint8_t exclude; + int32_t type; + int32_t status; + SLIST_ENTRY(vacm_view) vvl; +}; + +SLIST_HEAD(vacm_viewlist, vacm_view); + +struct vacm_context { + /* The ID of the module that registered this context */ + int32_t regid; + char ctxname[SNMP_ADM_STR32_SIZ]; + SLIST_ENTRY(vacm_context) vcl; +}; + +SLIST_HEAD(vacm_contextlist, vacm_context); + +void vacm_groups_init(void); +struct vacm_user *vacm_first_user(void); +struct vacm_user *vacm_next_user(struct vacm_user *); +struct vacm_user *vacm_new_user(int32_t, char *); +int vacm_delete_user(struct vacm_user *); +int vacm_user_set_group(struct vacm_user *, u_char *, u_int); +struct vacm_access *vacm_first_access_rule(void); +struct vacm_access *vacm_next_access_rule(struct vacm_access *); +struct vacm_access *vacm_new_access_rule(char *, char *, int32_t, int32_t); +int vacm_delete_access_rule(struct vacm_access *); +struct vacm_view *vacm_first_view(void); +struct vacm_view *vacm_next_view(struct vacm_view *); +struct vacm_view *vacm_new_view(char *, struct asn_oid *); +int vacm_delete_view(struct vacm_view *); +struct vacm_context *vacm_first_context(void); +struct vacm_context *vacm_next_context(struct vacm_context *); +struct vacm_context *vacm_add_context(char *, int32_t); +void vacm_flush_contexts(int32_t); + +/* * Well known OIDs */ extern const struct asn_oid oid_zeroDotZero; +/* SNMPv3 Engine Discovery */ +extern const struct asn_oid oid_usmUnknownEngineIDs; +extern const struct asn_oid oid_usmNotInTimeWindows; + /* * Request ID ranges. * diff --git a/contrib/bsnmp/snmpd/trans_lsock.c b/contrib/bsnmp/snmpd/trans_lsock.c index 3bcb333..2cddd48 100644 --- a/contrib/bsnmp/snmpd/trans_lsock.c +++ b/contrib/bsnmp/snmpd/trans_lsock.c @@ -31,6 +31,7 @@ * Local domain socket transport */ #include <sys/types.h> +#include <sys/queue.h> #include <sys/un.h> #include <sys/stat.h> diff --git a/contrib/bsnmp/snmpd/trans_udp.c b/contrib/bsnmp/snmpd/trans_udp.c index 7e308ee..acab70e 100644 --- a/contrib/bsnmp/snmpd/trans_udp.c +++ b/contrib/bsnmp/snmpd/trans_udp.c @@ -31,6 +31,7 @@ * UDP transport */ #include <sys/types.h> +#include <sys/queue.h> #include <stdlib.h> #include <syslog.h> diff --git a/contrib/bsnmp/snmpd/trap.c b/contrib/bsnmp/snmpd/trap.c index f37c1c8..18164cf 100644 --- a/contrib/bsnmp/snmpd/trap.c +++ b/contrib/bsnmp/snmpd/trap.c @@ -31,6 +31,7 @@ * TrapSinkTable */ #include <sys/types.h> +#include <sys/queue.h> #include <sys/sysctl.h> #include <sys/un.h> #include <stdio.h> diff --git a/contrib/bsnmp/snmpd/tree.def b/contrib/bsnmp/snmpd/tree.def index 613bcde..00672eb 100644 --- a/contrib/bsnmp/snmpd/tree.def +++ b/contrib/bsnmp/snmpd/tree.def @@ -196,5 +196,15 @@ ) ) ) + (10 snmpFrameworkMIB + (2 snmpFrameworkMIBObjects + (1 snmpEngine + (1 snmpEngineID OCTETSTRING | SnmpEngineID op_snmp_engine GET) + (2 snmpEngineBoots INTEGER op_snmp_engine GET) + (3 snmpEngineTime INTEGER op_snmp_engine GET) + (4 snmpEngineMaxMessageSize INTEGER op_snmp_engine GET) + ) + ) + ) )) ) |