diff options
Diffstat (limited to 'rust/vendor/snmp-parser')
21 files changed, 1648 insertions, 0 deletions
diff --git a/rust/vendor/snmp-parser/.cargo-checksum.json b/rust/vendor/snmp-parser/.cargo-checksum.json new file mode 100644 index 0000000..4bf11f6 --- /dev/null +++ b/rust/vendor/snmp-parser/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{".travis.yml":"b316d06e27869c19edac3cf3a58c41e33340bd920a4d5f0267803fbd52d50375","Cargo.toml":"f652ec51313e53b344b0b31da74bdaaf6eb787c8f1379b375f81e189f796c313","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513","README.md":"3169f9e35c2dc533aa2df40fa8a411996e6a730fed13d54e6f2c23c35b5efe56","assets/snmpv1_req.bin":"cd6b44f78504bcf50b7f62cd95a63a08cf58ee564ecb636212730885bb95b880","assets/snmpv1_trap_coldstart.bin":"197168942d66edd91601f3eb1ed70fc9f5082906f17bf7ac28ca360127769c7f","assets/snmpv2c-get-response.bin":"f5361a89714172de3a87c00544e10365374f9d640c8d9ff82b541853c0c692a3","assets/snmpv3-report.bin":"282d887513d6ea7e7b2c9f763dc3121a226e5c15e569e050ea7e8275f2868fee","assets/snmpv3_req.bin":"97f2bbe0eacc957203df3a304056cc740321cf7fbac47cafa443ed98d1e2d73d","assets/snmpv3_req_encrypted.bin":"244ffc47aab71801b86533f231459efceca222279f6b241fb6117cca9f690d78","src/error.rs":"586a15d7ce66351fefed241ad8cbe6e2bf8db3efefd8230c43e7fdd981d29028","src/generic.rs":"e68cadd705efdcc7e1f43440247c557fac024f4fedb507317d19cc4ab6c4c5e9","src/lib.rs":"75425f472cbd8581aa6b5c074916f7b986278c0a8c76b768682ccd4eaa2b73b1","src/snmp.rs":"6e7d01144f306e0e1fdfcd235c4158903d5a1ce56d0b9fdd70d1209da5ef9b47","src/snmpv3.rs":"1abc9076e9e481c7e4134954a100773ff28bed9a6a330695b39da727bd6028ac","src/usm.rs":"2656b161da0cd1bdf11cacc54ef7197cc3d29af3fb36f6ccc0e2222d47528dfb","tests/v1.rs":"a53eede08c24d333df66b74e4cec88edf708c5c393549e9ce5eaf61668dc66f5","tests/v2c.rs":"e22f0cd364de5c9c6efd6b66c4d0cd4c6f3f8520afe9ac01d4961022a839c418","tests/v3.rs":"3e65b96c4459e34ea3a539f94627f67301c4940f943caecf9d288e913bb3937a"},"package":"773a26ad6742636f4259e7cc32262efb31feabd56bc34f0b2f28de9801aa24b3"}
\ No newline at end of file diff --git a/rust/vendor/snmp-parser/.travis.yml b/rust/vendor/snmp-parser/.travis.yml new file mode 100644 index 0000000..6b7a3e0 --- /dev/null +++ b/rust/vendor/snmp-parser/.travis.yml @@ -0,0 +1,17 @@ +language: rust +sudo: false +matrix: + include: + - rust: stable + env: + - NAME="stable" + - FEATURES='' + - rust: nightly + env: + - NAME="nightly" + - FEATURES='' +script: + - | + cargo build --verbose --features "$FEATURES" && + cargo test --verbose --features "$FEATURES" && + ([ "$BENCH" != 1 ] || cargo bench --verbose --features "$FEATURES") diff --git a/rust/vendor/snmp-parser/Cargo.toml b/rust/vendor/snmp-parser/Cargo.toml new file mode 100644 index 0000000..921daa8 --- /dev/null +++ b/rust/vendor/snmp-parser/Cargo.toml @@ -0,0 +1,56 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "snmp-parser" +version = "0.9.0" +authors = ["Pierre Chifflier <chifflier@wzdftpd.net>"] +include = [ + "LICENSE-*", + "README.md", + ".gitignore", + ".travis.yml", + "Cargo.toml", + "assets/*.bin", + "src/*.rs", + "tests/*.rs", +] +description = "Parser for the SNMP protocol" +homepage = "https://github.com/rusticata/snmp-parser" +readme = "README.md" +keywords = [ + "SNMP", + "protocol", + "parser", + "nom", +] +categories = ["parser-implementations"] +license = "MIT/Apache-2.0" +repository = "https://github.com/rusticata/snmp-parser.git" + +[dependencies.asn1-rs] +version = "0.5" + +[dependencies.nom] +version = "7.0" + +[dependencies.rusticata-macros] +version = "4.0" + +[dependencies.thiserror] +version = "1.0" + +[dev-dependencies.hex-literal] +version = "0.3" + +[dev-dependencies.pretty_assertions] +version = "1.0" diff --git a/rust/vendor/snmp-parser/LICENSE-APACHE b/rust/vendor/snmp-parser/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/rust/vendor/snmp-parser/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rust/vendor/snmp-parser/LICENSE-MIT b/rust/vendor/snmp-parser/LICENSE-MIT new file mode 100644 index 0000000..290e7b9 --- /dev/null +++ b/rust/vendor/snmp-parser/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/rust/vendor/snmp-parser/README.md b/rust/vendor/snmp-parser/README.md new file mode 100644 index 0000000..339300f --- /dev/null +++ b/rust/vendor/snmp-parser/README.md @@ -0,0 +1,73 @@ +<!-- cargo-sync-readme start --> + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) +[![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) +[![Build Status](https://travis-ci.org/rusticata/snmp-parser.svg?branch=master)](https://travis-ci.org/rusticata/snmp-parser) +[![Crates.io Version](https://img.shields.io/crates/v/snmp-parser.svg)](https://crates.io/crates/snmp-parser) + +# SNMP Parser + +A SNMP parser, implemented with the [nom](https://github.com/Geal/nom) +parser combinator framework. + +The goal of this parser is to implement SNMP messages analysis, for example +to use rules from a network IDS. + +To read a message, different functions must be used depending on the expected message +version. The main functions for parsing are [`parse_snmp_v1`](https://docs.rs/snmp-parser/latest/snmp_parser/snmp/fn.parse_snmp_v1.html), +[`parse_snmp_v2c`](https://docs.rs/snmp-parser/latest/snmp_parser/snmp/fn.parse_snmp_v2c.html) and +[`parse_snmp_v3`](https://docs.rs/snmp-parser/latest/snmp_parser/snmpv3/fn.parse_snmp_v3.html). +If you don't know the version of the message and want to parse a generic SNMP message, +use the [`parse_snmp_generic_message`](https://docs.rs/snmp-parser/latest/snmp_parser/fn.parse_snmp_generic_message.html) function. + +The code is available on [Github](https://github.com/rusticata/snmp-parser) +and is part of the [Rusticata](https://github.com/rusticata) project. +<!-- cargo-sync-readme end --> + +## Changes + +### 0.9.0 + +- Convert to asn1-rs +- Set MSRV to 1.57 + +### 0.8.0 + +- Upgrade to nom 7 / der-parser 6 + +### 0.7.0 + +- Upgrade to nom 6 / der-parser 5 + +### 0.6.0 + +- Upgrade to der-parser 4 + +### 0.5.2 + +- Use `parse_ber_u32` from der-parser crate + +### 0.5.1 + +- Fix parsing: use BER parsing so DER constraints are not applied + +### 0.5.0 + +- Upgrade to nom 5 and der-parser 3 + +## License + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rust/vendor/snmp-parser/assets/snmpv1_req.bin b/rust/vendor/snmp-parser/assets/snmpv1_req.bin Binary files differnew file mode 100644 index 0000000..0bb89d5 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv1_req.bin diff --git a/rust/vendor/snmp-parser/assets/snmpv1_trap_coldstart.bin b/rust/vendor/snmp-parser/assets/snmpv1_trap_coldstart.bin Binary files differnew file mode 100644 index 0000000..35c3f64 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv1_trap_coldstart.bin diff --git a/rust/vendor/snmp-parser/assets/snmpv2c-get-response.bin b/rust/vendor/snmp-parser/assets/snmpv2c-get-response.bin Binary files differnew file mode 100644 index 0000000..19e2df4 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv2c-get-response.bin diff --git a/rust/vendor/snmp-parser/assets/snmpv3-report.bin b/rust/vendor/snmp-parser/assets/snmpv3-report.bin Binary files differnew file mode 100644 index 0000000..2887f43 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv3-report.bin diff --git a/rust/vendor/snmp-parser/assets/snmpv3_req.bin b/rust/vendor/snmp-parser/assets/snmpv3_req.bin Binary files differnew file mode 100644 index 0000000..87d5e71 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv3_req.bin diff --git a/rust/vendor/snmp-parser/assets/snmpv3_req_encrypted.bin b/rust/vendor/snmp-parser/assets/snmpv3_req_encrypted.bin Binary files differnew file mode 100644 index 0000000..f8ee966 --- /dev/null +++ b/rust/vendor/snmp-parser/assets/snmpv3_req_encrypted.bin diff --git a/rust/vendor/snmp-parser/src/error.rs b/rust/vendor/snmp-parser/src/error.rs new file mode 100644 index 0000000..69e4cd9 --- /dev/null +++ b/rust/vendor/snmp-parser/src/error.rs @@ -0,0 +1,46 @@ +use asn1_rs::Error; +use nom::error::{ErrorKind, ParseError}; +use std::convert::From; + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum SnmpError { + #[error("Invalid message: not a DER sequence, or unexpected number of items, etc.")] + InvalidMessage, + #[error("Invalid version: not a number, or not in supported range (1, 2 or 3)")] + InvalidVersion, + #[error("Unknown or invalid PDU type")] + InvalidPduType, + #[error("Invalid PDU: content does not match type, or content cannot be decoded")] + InvalidPdu, + #[error("Invalid SNMPv3 header data")] + InvalidHeaderData, + #[error("Invalid SNMPv3 scoped PDU")] + InvalidScopedPduData, + #[error("Invalid SNMPv3 security model")] + InvalidSecurityModel, + #[error("Nom error")] + NomError(ErrorKind), + #[error("BER error")] + BerError(Error), +} + +impl<I> ParseError<I> for SnmpError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + SnmpError::NomError(kind) + } + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + SnmpError::NomError(kind) + } +} + +impl From<Error> for SnmpError { + fn from(e: Error) -> SnmpError { + SnmpError::BerError(e) + } +} + +impl From<SnmpError> for nom::Err<SnmpError> { + fn from(e: SnmpError) -> nom::Err<SnmpError> { + nom::Err::Error(e) + } +} diff --git a/rust/vendor/snmp-parser/src/generic.rs b/rust/vendor/snmp-parser/src/generic.rs new file mode 100644 index 0000000..11d17f5 --- /dev/null +++ b/rust/vendor/snmp-parser/src/generic.rs @@ -0,0 +1,72 @@ +use crate::error::SnmpError; +use crate::snmp::*; +use crate::snmpv3::*; +use asn1_rs::{Any, FromBer, Tag}; +use nom::combinator::map_res; +use nom::{Err, IResult}; + +#[derive(Debug, PartialEq)] +pub enum SnmpGenericMessage<'a> { + V1(SnmpMessage<'a>), + V2(SnmpMessage<'a>), + V3(SnmpV3Message<'a>), +} + +fn parse_snmp_v1_pdu_content(i: &[u8]) -> IResult<&[u8], SnmpMessage, SnmpError> { + let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?; + let (i, pdu) = parse_snmp_v1_pdu(i)?; + let msg = SnmpMessage { + version: 0, + community: community.to_string(), + pdu, + }; + Ok((i, msg)) +} + +fn parse_snmp_v2c_pdu_content(i: &[u8]) -> IResult<&[u8], SnmpMessage, SnmpError> { + let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?; + let (i, pdu) = parse_snmp_v2c_pdu(i)?; + let msg = SnmpMessage { + version: 1, + community: community.to_string(), + pdu, + }; + Ok((i, msg)) +} + +fn parse_snmp_v3_pdu_content(i: &[u8]) -> IResult<&[u8], SnmpV3Message, SnmpError> { + let (i, hdr) = parse_snmp_v3_headerdata(i)?; + let (i, secp) = map_res(<&[u8]>::from_ber, |x| parse_secp(x, &hdr))(i).map_err(Err::convert)?; + let (i, data) = parse_snmp_v3_data(i, &hdr)?; + let msg = SnmpV3Message { + version: 3, + header_data: hdr, + security_params: secp, + data, + }; + Ok((i, msg)) +} + +pub fn parse_snmp_generic_message(i: &[u8]) -> IResult<&[u8], SnmpGenericMessage, SnmpError> { + let (rem, any) = Any::from_ber(i).or(Err(Err::Error(SnmpError::InvalidMessage)))?; + if any.tag() != Tag::Sequence { + return Err(Err::Error(SnmpError::InvalidMessage)); + } + let (r, version) = u32::from_ber(any.data).map_err(Err::convert)?; + let (_, msg) = match version { + 0 => { + let (rem, msg) = parse_snmp_v1_pdu_content(r)?; + (rem, SnmpGenericMessage::V1(msg)) + } + 1 => { + let (rem, msg) = parse_snmp_v2c_pdu_content(r)?; + (rem, SnmpGenericMessage::V2(msg)) + } + 3 => { + let (rem, msg) = parse_snmp_v3_pdu_content(r)?; + (rem, SnmpGenericMessage::V3(msg)) + } + _ => return Err(Err::Error(SnmpError::InvalidVersion)), + }; + Ok((rem, msg)) +} diff --git a/rust/vendor/snmp-parser/src/lib.rs b/rust/vendor/snmp-parser/src/lib.rs new file mode 100644 index 0000000..acc4918 --- /dev/null +++ b/rust/vendor/snmp-parser/src/lib.rs @@ -0,0 +1,50 @@ +//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) +//! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) +//! [![Build Status](https://travis-ci.org/rusticata/snmp-parser.svg?branch=master)](https://travis-ci.org/rusticata/snmp-parser) +//! [![Crates.io Version](https://img.shields.io/crates/v/snmp-parser.svg)](https://crates.io/crates/snmp-parser) +//! +//! # SNMP Parser +//! +//! A SNMP parser, implemented with the [nom](https://github.com/Geal/nom) +//! parser combinator framework. +//! +//! The goal of this parser is to implement SNMP messages analysis, for example +//! to use rules from a network IDS. +//! +//! To read a message, different functions must be used depending on the expected message +//! version. The main functions for parsing are [`parse_snmp_v1`](snmp/fn.parse_snmp_v1.html), +//! [`parse_snmp_v2c`](snmp/fn.parse_snmp_v2c.html) and +//! [`parse_snmp_v3`](snmpv3/fn.parse_snmp_v3.html). +//! If you don't know the version of the message and want to parse a generic SNMP message, +//! use the [`parse_snmp_generic_message`](fn.parse_snmp_generic_message.html) function. +//! +//! The code is available on [Github](https://github.com/rusticata/snmp-parser) +//! and is part of the [Rusticata](https://github.com/rusticata) project. + +#![deny(/*missing_docs,*/unsafe_code, + unstable_features, + unused_import_braces, unused_qualifications)] +#![warn( + missing_debug_implementations, + /* missing_docs, + rust_2018_idioms,*/ + unreachable_pub +)] +#![forbid(unsafe_code)] +#![deny(broken_intra_doc_links)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod generic; +mod usm; + +pub mod error; +pub mod snmp; +pub mod snmpv3; + +pub use generic::*; +pub use snmp::*; +pub use snmpv3::*; diff --git a/rust/vendor/snmp-parser/src/snmp.rs b/rust/vendor/snmp-parser/src/snmp.rs new file mode 100644 index 0000000..a350c23 --- /dev/null +++ b/rust/vendor/snmp-parser/src/snmp.rs @@ -0,0 +1,634 @@ +//! SNMP Parser (v1 and v2c) +//! +//! SNMP is defined in the following RFCs: +//! - [RFC1157](https://tools.ietf.org/html/rfc1157): SNMP v1 +//! - [RFC1442](https://tools.ietf.org/html/rfc1442): Structure of Management Information for version 2 of the Simple Network Management Protocol (SNMPv2) +//! - [RFC1902](https://tools.ietf.org/html/rfc1902): SNMP v2 SMI +//! - [RFC2578](https://tools.ietf.org/html/rfc2578): Structure of Management Information Version 2 (SMIv2) +//! - [RFC3416](https://tools.ietf.org/html/rfc3416): SNMP v2 +//! - [RFC2570](https://tools.ietf.org/html/rfc2570): Introduction to SNMP v3 + +use crate::error::SnmpError; +use asn1_rs::{ + Any, BitString, Class, Error, FromBer, Header, Implicit, Integer, Oid, Sequence, Tag, + TaggedValue, +}; +use nom::combinator::map; +use nom::{Err, IResult}; +use std::convert::TryFrom; +use std::net::Ipv4Addr; +use std::slice::Iter; +use std::{fmt, str}; + +// This will be merged in next release of asn1-rs +type Application<T, E, TagKind, const TAG: u32> = TaggedValue<T, E, TagKind, 0b01, TAG>; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct PduType(pub u32); + +#[allow(non_upper_case_globals)] +impl PduType { + pub const GetRequest: PduType = PduType(0); + pub const GetNextRequest: PduType = PduType(1); + pub const Response: PduType = PduType(2); + pub const SetRequest: PduType = PduType(3); + pub const TrapV1: PduType = PduType(4); // Obsolete, was the old Trap-PDU in SNMPv1 + pub const GetBulkRequest: PduType = PduType(5); + pub const InformRequest: PduType = PduType(6); + pub const TrapV2: PduType = PduType(7); + pub const Report: PduType = PduType(8); +} + +impl fmt::Debug for PduType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + 0 => f.write_str("GetRequest"), + 1 => f.write_str("GetNextRequest"), + 2 => f.write_str("Response"), + 3 => f.write_str("SetRequest"), + 4 => f.write_str("TrapV1"), + 5 => f.write_str("GetBulkRequest"), + 6 => f.write_str("InformRequest"), + 7 => f.write_str("TrapV2"), + 8 => f.write_str("Report"), + n => f.debug_tuple("PduType").field(&n).finish(), + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct TrapType(pub u8); + +impl TrapType { + pub const COLD_START: TrapType = TrapType(0); + pub const WARM_START: TrapType = TrapType(1); + pub const LINK_DOWN: TrapType = TrapType(2); + pub const LINK_UP: TrapType = TrapType(3); + pub const AUTHENTICATION_FAILURE: TrapType = TrapType(4); + pub const EGP_NEIGHBOR_LOSS: TrapType = TrapType(5); + pub const ENTERPRISE_SPECIFIC: TrapType = TrapType(6); +} + +impl fmt::Debug for TrapType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + 0 => f.write_str("coldStart"), + 1 => f.write_str("warmStart"), + 2 => f.write_str("linkDown"), + 3 => f.write_str("linkUp"), + 4 => f.write_str("authenticationFailure"), + 5 => f.write_str("egpNeighborLoss"), + 6 => f.write_str("enterpriseSpecific"), + n => f.debug_tuple("TrapType").field(&n).finish(), + } + } +} + +/// This CHOICE represents an address from one of possibly several +/// protocol families. Currently, only one protocol family, the Internet +/// family, is present in this CHOICE. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum NetworkAddress { + IPv4(Ipv4Addr), +} + +/// This application-wide type represents a non-negative integer which +/// monotonically increases until it reaches a maximum value, when it +/// wraps around and starts increasing again from zero. This memo +/// specifies a maximum value of 2^32-1 (4294967295 decimal) for +/// counters. +pub type Counter = u32; + +/// This application-wide type represents a non-negative integer, which +/// may increase or decrease, but which latches at a maximum value. This +/// memo specifies a maximum value of 2^32-1 (4294967295 decimal) for +/// gauges. +pub type Gauge = u32; + +/// This application-wide type represents a non-negative integer which +/// counts the time in hundredths of a second since some epoch. When +/// object types are defined in the MIB which use this ASN.1 type, the +/// description of the object type identifies the reference epoch. +pub type TimeTicks = u32; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct ErrorStatus(pub u32); + +#[allow(non_upper_case_globals)] +impl ErrorStatus { + pub const NoError: ErrorStatus = ErrorStatus(0); + pub const TooBig: ErrorStatus = ErrorStatus(1); + pub const NoSuchName: ErrorStatus = ErrorStatus(2); + pub const BadValue: ErrorStatus = ErrorStatus(3); + pub const ReadOnly: ErrorStatus = ErrorStatus(4); + pub const GenErr: ErrorStatus = ErrorStatus(5); +} + +impl fmt::Debug for ErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + 0 => f.write_str("NoError"), + 1 => f.write_str("TooBig"), + 2 => f.write_str("NoSuchName"), + 3 => f.write_str("BadValue"), + 4 => f.write_str("ReadOnly"), + 5 => f.write_str("GenErr"), + n => f.debug_tuple("ErrorStatus").field(&n).finish(), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct SnmpGenericPdu<'a> { + pub pdu_type: PduType, + pub req_id: u32, + pub err: ErrorStatus, + pub err_index: u32, + pub var: Vec<SnmpVariable<'a>>, +} + +#[derive(Debug, PartialEq)] +pub struct SnmpBulkPdu<'a> { + pub req_id: u32, + pub non_repeaters: u32, + pub max_repetitions: u32, + pub var: Vec<SnmpVariable<'a>>, +} + +#[derive(Debug, PartialEq)] +pub struct SnmpTrapPdu<'a> { + pub enterprise: Oid<'a>, + pub agent_addr: NetworkAddress, + pub generic_trap: TrapType, + pub specific_trap: u32, + pub timestamp: TimeTicks, + pub var: Vec<SnmpVariable<'a>>, +} + +#[derive(Debug, PartialEq)] +pub enum SnmpPdu<'a> { + Generic(SnmpGenericPdu<'a>), + Bulk(SnmpBulkPdu<'a>), + TrapV1(SnmpTrapPdu<'a>), +} + +/// An SNMPv1 or SNMPv2c message +#[derive(Debug, PartialEq)] +pub struct SnmpMessage<'a> { + /// Version, as raw-encoded: 0 for SNMPv1, 1 for SNMPv2c + pub version: u32, + pub community: String, + pub pdu: SnmpPdu<'a>, +} + +impl<'a> SnmpGenericPdu<'a> { + pub fn vars_iter(&'a self) -> Iter<SnmpVariable> { + self.var.iter() + } +} + +impl<'a> SnmpTrapPdu<'a> { + pub fn vars_iter(&'a self) -> Iter<SnmpVariable> { + self.var.iter() + } +} + +impl<'a> SnmpPdu<'a> { + pub fn pdu_type(&self) -> PduType { + match *self { + SnmpPdu::Generic(ref pdu) => pdu.pdu_type, + SnmpPdu::Bulk(_) => PduType::GetBulkRequest, + SnmpPdu::TrapV1(_) => PduType::TrapV1, + } + } + + pub fn vars_iter(&'a self) -> Iter<SnmpVariable> { + match *self { + SnmpPdu::Generic(ref pdu) => pdu.var.iter(), + SnmpPdu::Bulk(ref pdu) => pdu.var.iter(), + SnmpPdu::TrapV1(ref pdu) => pdu.var.iter(), + } + } +} + +impl<'a> SnmpMessage<'a> { + pub fn pdu_type(&self) -> PduType { + self.pdu.pdu_type() + } + + pub fn vars_iter(&'a self) -> Iter<SnmpVariable> { + self.pdu.vars_iter() + } +} + +#[derive(Debug, PartialEq)] +pub struct SnmpVariable<'a> { + pub oid: Oid<'a>, + pub val: VarBindValue<'a>, +} + +#[derive(Debug, PartialEq)] +pub enum VarBindValue<'a> { + Value(ObjectSyntax<'a>), + Unspecified, + NoSuchObject, + NoSuchInstance, + EndOfMibView, +} + +/// <pre> +/// VarBind ::= SEQUENCE { +/// name ObjectName, +/// +/// CHOICE { +/// value ObjectSyntax, +/// unSpecified NULL, -- in retrieval requests +/// +/// -- exceptions in responses +/// noSuchObject [0] IMPLICIT NULL, +/// noSuchInstance [1] IMPLICIT NULL, +/// endOfMibView [2] IMPLICIT NULL +/// } +/// } +/// </pre> +impl<'a> TryFrom<Any<'a>> for SnmpVariable<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<SnmpVariable<'a>, Self::Error> { + let (rem, oid) = Oid::from_ber(any.data)?; + let (_, choice) = Any::from_ber(rem)?; + let val = if choice.header.is_contextspecific() { + match choice.tag().0 { + 0 => VarBindValue::NoSuchObject, + 1 => VarBindValue::NoSuchInstance, + 2 => VarBindValue::EndOfMibView, + _ => { + return Err(Error::invalid_value( + choice.tag(), + "invalid VarBind tag".to_string(), + )) + } + } + } else if choice.tag() == Tag::Null { + VarBindValue::Unspecified + } else { + VarBindValue::Value(ObjectSyntax::try_from(choice)?) + }; + let var_bind = SnmpVariable { oid, val }; + Ok(var_bind) + } +} + +#[derive(Debug, PartialEq)] +pub enum ObjectSyntax<'a> { + Number(i32), + String(&'a [u8]), + Object(Oid<'a>), + BitString(BitString<'a>), + Empty, + UnknownSimple(Any<'a>), + IpAddress(NetworkAddress), + Counter32(Counter), + Gauge32(Gauge), + TimeTicks(TimeTicks), + Opaque(&'a [u8]), + NsapAddress(&'a [u8]), + Counter64(u64), + UInteger32(u32), + UnknownApplication(Any<'a>), +} + +/// <pre> +/// ObjectSyntax ::= CHOICE { +/// simple SimpleSyntax, +/// application-wide ApplicationSyntax } +/// +/// SimpleSyntax ::= CHOICE { +/// integer-value INTEGER (-2147483648..2147483647), +/// string-value OCTET STRING (SIZE (0..65535)), +/// objectID-value OBJECT IDENTIFIER } +/// +/// ApplicationSyntax ::= CHOICE { +/// ipAddress-value IpAddress, +/// counter-value Counter32, +/// timeticks-value TimeTicks, +/// arbitrary-value Opaque, +/// big-counter-value Counter64, +/// unsigned-integer-value Unsigned32 } +/// </pre> +impl<'a> TryFrom<Any<'a>> for ObjectSyntax<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<ObjectSyntax<'a>, Self::Error> { + if any.header.is_application() { + // ApplicationSyntax + match any.header.tag().0 { + 0 => { + // IpAddress ::= + // [APPLICATION 0] + // IMPLICIT OCTET STRING (SIZE (4)) + let s = any.data; + if s.len() == 4 { + let ipv4 = NetworkAddress::IPv4(Ipv4Addr::new(s[0], s[1], s[2], s[3])); + Ok(ObjectSyntax::IpAddress(ipv4)) + } else { + Err(Error::InvalidTag) + } + } + tag @ 1..=3 => { + // -- this wraps + // Counter32 ::= + // [APPLICATION 1] + // IMPLICIT INTEGER (0..4294967295) + // + // -- this doesn't wrap + // Gauge32 ::= + // [APPLICATION 2] + // IMPLICIT INTEGER (0..4294967295) + // + // -- an unsigned 32-bit quantity + // -- indistinguishable from Gauge32 + // Unsigned32 ::= + // [APPLICATION 2] + // IMPLICIT INTEGER (0..4294967295) + // + // -- hundredths of seconds since an epoch + // TimeTicks ::= + // [APPLICATION 3] + // IMPLICIT INTEGER (0..4294967295) + let x = Integer::new(any.data).as_u32()?; + let obj = match tag { + 1 => ObjectSyntax::Counter32(x), + 2 => ObjectSyntax::Gauge32(x), + 3 => ObjectSyntax::TimeTicks(x), + _ => unreachable!(), + }; + Ok(obj) + } + 4 => Ok(ObjectSyntax::Opaque(any.data)), + 5 => Ok(ObjectSyntax::NsapAddress(any.data)), + 6 => { + let counter = Integer::new(any.data).as_u64()?; + Ok(ObjectSyntax::Counter64(counter)) + } + 7 => { + let number = Integer::new(any.data).as_u32()?; + Ok(ObjectSyntax::UInteger32(number)) + } + _ => Ok(ObjectSyntax::UnknownApplication(any)), + } + } else { + // SimpleSyntax + + // Some implementations do not send NULL, but empty objects + // Treat 0-length objects as ObjectSyntax::Empty + if any.data.is_empty() { + return Ok(ObjectSyntax::Empty); + } + let obj = match any.header.tag() { + Tag::BitString => ObjectSyntax::BitString(any.bitstring()?), + Tag::Integer => { + let number = any.integer()?.as_i32()?; + ObjectSyntax::Number(number) + } + Tag::Null => ObjectSyntax::Empty, + Tag::Oid => ObjectSyntax::Object(any.oid()?), + Tag::OctetString => ObjectSyntax::String(any.data), + _ => ObjectSyntax::UnknownSimple(any), + }; + Ok(obj) + } + } +} + +#[inline] +pub(crate) fn parse_ber_octetstring_as_str(i: &[u8]) -> IResult<&[u8], &str, Error> { + let (rem, b) = <&[u8]>::from_ber(i)?; + let s = core::str::from_utf8(b).map_err(|_| Error::StringInvalidCharset)?; + Ok((rem, s)) +} + +fn parse_varbind_list(i: &[u8]) -> IResult<&[u8], Vec<SnmpVariable>, Error> { + // parse_ber_sequence_of_v(parse_varbind)(i) + <Vec<SnmpVariable>>::from_ber(i) +} + +/// <pre> +/// NetworkAddress ::= +/// CHOICE { +/// internet +/// IpAddress +/// } +/// IpAddress ::= +/// [APPLICATION 0] -- in network-byte order +/// IMPLICIT OCTET STRING (SIZE (4)) +/// </pre> +impl<'a> TryFrom<Any<'a>> for NetworkAddress { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self, Self::Error> { + any.class().assert_eq(Class::Application)?; + let s = any.data; + if s.len() == 4 { + Ok(NetworkAddress::IPv4(Ipv4Addr::new(s[0], s[1], s[2], s[3]))) + } else { + Err(Error::invalid_value( + Tag::OctetString, + "NetworkAddress invalid length".to_string(), + )) + } + } +} + +/// <pre> +/// TimeTicks ::= +/// [APPLICATION 3] +/// IMPLICIT INTEGER (0..4294967295) +/// </pre> +fn parse_timeticks(i: &[u8]) -> IResult<&[u8], TimeTicks, Error> { + let (rem, tagged) = Application::<u32, _, Implicit, 3>::from_ber(i)?; + Ok((rem, tagged.into_inner())) +} + +fn parse_snmp_v1_generic_pdu(pdu: &[u8], tag: PduType) -> IResult<&[u8], SnmpPdu, SnmpError> { + let (i, req_id) = u32::from_ber(pdu).map_err(Err::convert)?; + let (i, err) = map(u32::from_ber, ErrorStatus)(i).map_err(Err::convert)?; + let (i, err_index) = u32::from_ber(i).map_err(Err::convert)?; + let (i, var) = parse_varbind_list(i).map_err(Err::convert)?; + let pdu = SnmpPdu::Generic(SnmpGenericPdu { + pdu_type: tag, + req_id, + err, + err_index, + var, + }); + Ok((i, pdu)) +} + +fn parse_snmp_v1_bulk_pdu(i: &[u8]) -> IResult<&[u8], SnmpPdu, SnmpError> { + let (i, req_id) = u32::from_ber(i).map_err(Err::convert)?; + let (i, non_repeaters) = u32::from_ber(i).map_err(Err::convert)?; + let (i, max_repetitions) = u32::from_ber(i).map_err(Err::convert)?; + let (i, var) = parse_varbind_list(i).map_err(Err::convert)?; + let pdu = SnmpBulkPdu { + req_id, + non_repeaters, + max_repetitions, + var, + }; + Ok((i, SnmpPdu::Bulk(pdu))) +} + +fn parse_snmp_v1_trap_pdu(i: &[u8]) -> IResult<&[u8], SnmpPdu, SnmpError> { + let (i, enterprise) = Oid::from_ber(i).map_err(Err::convert)?; + let (i, agent_addr) = NetworkAddress::from_ber(i).map_err(Err::convert)?; + let (i, generic_trap) = u32::from_ber(i).map_err(Err::convert)?; + let (i, specific_trap) = u32::from_ber(i).map_err(Err::convert)?; + let (i, timestamp) = parse_timeticks(i).map_err(Err::convert)?; + let (i, var) = parse_varbind_list(i).map_err(Err::convert)?; + let pdu = SnmpTrapPdu { + enterprise, + agent_addr, + generic_trap: TrapType(generic_trap as u8), + specific_trap, + timestamp, + var, + }; + Ok((i, SnmpPdu::TrapV1(pdu))) +} + +/// Parse a SNMP v1 message. +/// +/// Top-level message +/// +/// <pre> +/// Message ::= +/// SEQUENCE { +/// version -- version-1 for this RFC +/// INTEGER { +/// version-1(0) +/// }, +/// +/// community -- community name +/// OCTET STRING, +/// +/// data -- e.g., PDUs if trivial +/// ANY -- authentication is being used +/// } +/// </pre> +/// +/// Example: +/// +/// ```rust +/// use snmp_parser::parse_snmp_v1; +/// +/// static SNMPV1_REQ: &[u8] = include_bytes!("../assets/snmpv1_req.bin"); +/// +/// # fn main() { +/// match parse_snmp_v1(&SNMPV1_REQ) { +/// Ok((_, ref r)) => { +/// assert!(r.version == 0); +/// assert!(r.community == String::from("public")); +/// assert!(r.vars_iter().count() == 1); +/// }, +/// Err(e) => panic!("{}", e), +/// } +/// # } +/// ``` +pub fn parse_snmp_v1(bytes: &[u8]) -> IResult<&[u8], SnmpMessage, SnmpError> { + Sequence::from_der_and_then(bytes, |i| { + let (i, version) = u32::from_ber(i).map_err(Err::convert)?; + if version != 0 { + return Err(Err::Error(SnmpError::InvalidVersion)); + } + let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?; + let (i, pdu) = parse_snmp_v1_pdu(i)?; + let msg = SnmpMessage { + version, + community: community.to_string(), + pdu, + }; + Ok((i, msg)) + }) + //.map_err(Err::convert) +} + +pub(crate) fn parse_snmp_v1_pdu(i: &[u8]) -> IResult<&[u8], SnmpPdu, SnmpError> { + match Header::from_ber(i) { + Ok((rem, hdr)) => { + match PduType(hdr.tag().0) { + PduType::GetRequest | + PduType::GetNextRequest | + PduType::Response | + PduType::SetRequest => parse_snmp_v1_generic_pdu(rem, PduType(hdr.tag().0)), + PduType::TrapV1 => parse_snmp_v1_trap_pdu(rem), + _ => Err(Err::Error(SnmpError::InvalidPduType)), + // _ => { return IResult::Error(error_code!(ErrorKind::Custom(SnmpError::InvalidPdu))); }, + } + }, + Err(e) => Err(Err::convert(e)) + // IResult::Incomplete(i) => IResult::Incomplete(i), + // IResult::Error(_) => IResult::Error(error_code!(ErrorKind::Custom(129))), + // // IResult::Error(_) => IResult::Error(error_code!(ErrorKind::Custom(SnmpError::InvalidScopedPduData))), + } +} + +/// Parse a SNMP v2c message. +/// +/// Top-level message +/// +/// <pre> +/// Message ::= +/// SEQUENCE { +/// version +/// INTEGER { +/// version(1) -- modified from RFC 1157 +/// }, +/// +/// community -- community name +/// OCTET STRING, +/// +/// data -- PDUs as defined in [4] +/// ANY +/// } +/// </pre> +pub fn parse_snmp_v2c(bytes: &[u8]) -> IResult<&[u8], SnmpMessage, SnmpError> { + Sequence::from_der_and_then(bytes, |i| { + let (i, version) = u32::from_ber(i).map_err(Err::convert)?; + if version != 1 { + return Err(Err::Error(SnmpError::InvalidVersion)); + } + let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?; + let (i, pdu) = parse_snmp_v2c_pdu(i)?; + let msg = SnmpMessage { + version, + community: community.to_string(), + pdu, + }; + Ok((i, msg)) + }) +} + +pub(crate) fn parse_snmp_v2c_pdu(i: &[u8]) -> IResult<&[u8], SnmpPdu, SnmpError> { + match Header::from_ber(i) { + Ok((rem, hdr)) => { + match PduType(hdr.tag().0) { + PduType::GetRequest | + PduType::GetNextRequest | + PduType::Response | + PduType::SetRequest | + PduType::InformRequest | + PduType::TrapV2 | + PduType::Report => parse_snmp_v1_generic_pdu(rem, PduType(hdr.tag().0)), + PduType::GetBulkRequest => parse_snmp_v1_bulk_pdu(rem), + PduType::TrapV1 => parse_snmp_v1_trap_pdu(rem), + _ => Err(Err::Error(SnmpError::InvalidPduType)), + // _ => { return IResult::Error(error_code!(ErrorKind::Custom(SnmpError::InvalidPdu))); }, + } + }, + Err(e) => Err(Err::convert(e)) + // IResult::Incomplete(i) => IResult::Incomplete(i), + // IResult::Error(_) => IResult::Error(error_code!(ErrorKind::Custom(129))), + // // IResult::Error(_) => IResult::Error(error_code!(ErrorKind::Custom(SnmpError::InvalidScopedPduData))), + } +} diff --git a/rust/vendor/snmp-parser/src/snmpv3.rs b/rust/vendor/snmp-parser/src/snmpv3.rs new file mode 100644 index 0000000..3d1e5d7 --- /dev/null +++ b/rust/vendor/snmp-parser/src/snmpv3.rs @@ -0,0 +1,204 @@ +//! SNMPv3 Parser +//! +//! SNMPv3 is defined in the following RFCs: +//! - [RFC2570](https://tools.ietf.org/html/rfc2570): Introduction to SNMP v3 +//! - [RFC3412](https://tools.ietf.org/html/rfc3412): Message Processing and Dispatching for the +//! Simple Network Management Protocol (SNMP) +//! +//! See also: +//! - [RFC2578](https://tools.ietf.org/html/rfc2578): Structure of Management Information Version 2 (SMIv2) + +use asn1_rs::{Error, FromBer, Sequence}; +use nom::combinator::{map, map_res}; +use nom::{Err, IResult}; +use std::fmt; + +use crate::error::SnmpError; +use crate::snmp::{parse_snmp_v2c_pdu, SnmpPdu}; +pub use crate::usm::{parse_usm_security_parameters, UsmSecurityParameters}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct SecurityModel(pub u32); + +#[allow(non_upper_case_globals)] +impl SecurityModel { + pub const SnmpV1: SecurityModel = SecurityModel(1); + pub const SnmpV2c: SecurityModel = SecurityModel(2); + pub const USM: SecurityModel = SecurityModel(3); +} + +impl fmt::Debug for SecurityModel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + 1 => f.write_str("SnmpV1"), + 2 => f.write_str("SnmpV2c"), + 3 => f.write_str("USM"), + n => f.debug_tuple("SecurityModel").field(&n).finish(), + } + } +} + +impl<'a> FromBer<'a> for SecurityModel { + fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self> { + map(u32::from_ber, SecurityModel)(bytes) + } +} + +#[derive(Debug, PartialEq)] +#[allow(clippy::upper_case_acronyms)] +pub enum SecurityParameters<'a> { + Raw(&'a [u8]), + USM(UsmSecurityParameters<'a>), +} + +/// An SNMPv3 message +#[derive(Debug, PartialEq)] +pub struct SnmpV3Message<'a> { + /// Version, as raw-encoded: 3 for SNMPv3 + pub version: u32, + pub header_data: HeaderData, + pub security_params: SecurityParameters<'a>, + pub data: ScopedPduData<'a>, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct HeaderData { + pub msg_id: u32, + pub msg_max_size: u32, + pub msg_flags: u8, + pub msg_security_model: SecurityModel, +} + +impl HeaderData { + pub fn is_authenticated(&self) -> bool { + self.msg_flags & 0b001 != 0 + } + + pub fn is_encrypted(&self) -> bool { + self.msg_flags & 0b010 != 0 + } + + pub fn is_reportable(&self) -> bool { + self.msg_flags & 0b100 != 0 + } +} + +impl<'a> FromBer<'a> for HeaderData { + fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self> { + Sequence::from_ber_and_then(bytes, |i| { + let (i, msg_id) = u32::from_ber(i)?; + let (i, msg_max_size) = u32::from_ber(i)?; + let (i, b) = <&[u8]>::from_ber(i)?; + let msg_flags = if b.len() == 1 { + b[0] + } else { + return Err(Err::Error(Error::BerValueError)); + }; + let (i, msg_security_model) = map(u32::from_ber, SecurityModel)(i)?; + let hdr = HeaderData { + msg_id, + msg_max_size, + msg_flags, + msg_security_model, + }; + Ok((i, hdr)) + }) + } +} + +#[derive(Debug, PartialEq)] +pub enum ScopedPduData<'a> { + Plaintext(ScopedPdu<'a>), + Encrypted(&'a [u8]), +} + +#[derive(Debug, PartialEq)] +pub struct ScopedPdu<'a> { + pub ctx_engine_id: &'a [u8], + pub ctx_engine_name: &'a [u8], + /// ANY -- e.g., PDUs as defined in [RFC3416](https://tools.ietf.org/html/rfc3416) + pub data: SnmpPdu<'a>, +} + +pub(crate) fn parse_snmp_v3_data<'a>( + i: &'a [u8], + hdr: &HeaderData, +) -> IResult<&'a [u8], ScopedPduData<'a>, SnmpError> { + if hdr.is_encrypted() { + map(<&[u8]>::from_ber, ScopedPduData::Encrypted)(i).map_err(Err::convert) + } else { + parse_snmp_v3_plaintext_pdu(i) + } +} + +pub(crate) fn parse_secp<'a>( + i: &'a [u8], + hdr: &HeaderData, +) -> Result<SecurityParameters<'a>, SnmpError> { + match hdr.msg_security_model { + SecurityModel::USM => match parse_usm_security_parameters(i) { + Ok((_, usm)) => Ok(SecurityParameters::USM(usm)), + _ => Err(SnmpError::InvalidSecurityModel), + }, + _ => Ok(SecurityParameters::Raw(i)), + } +} + +/// Parse an SNMPv3 top-level message +/// +/// Example: +/// +/// ```rust +/// use snmp_parser::{parse_snmp_v3,ScopedPduData,SecurityModel}; +/// +/// static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin"); +/// +/// # fn main() { +/// match parse_snmp_v3(&SNMPV3_REQ) { +/// Ok((_, ref r)) => { +/// assert!(r.version == 3); +/// assert!(r.header_data.msg_security_model == SecurityModel::USM); +/// match r.data { +/// ScopedPduData::Plaintext(ref _pdu) => { }, +/// ScopedPduData::Encrypted(_) => (), +/// } +/// }, +/// Err(e) => panic!("{}", e), +/// } +/// # } +/// ``` +pub fn parse_snmp_v3(bytes: &[u8]) -> IResult<&[u8], SnmpV3Message, SnmpError> { + Sequence::from_der_and_then(bytes, |i| { + let (i, version) = u32::from_ber(i).map_err(Err::convert)?; + let (i, header_data) = parse_snmp_v3_headerdata(i)?; + let (i, secp) = + map_res(<&[u8]>::from_ber, |x| parse_secp(x, &header_data))(i).map_err(Err::convert)?; + let (i, data) = parse_snmp_v3_data(i, &header_data)?; + let msg = SnmpV3Message { + version, + header_data, + security_params: secp, + data, + }; + Ok((i, msg)) + }) +} + +#[inline] +pub(crate) fn parse_snmp_v3_headerdata(i: &[u8]) -> IResult<&[u8], HeaderData, SnmpError> { + HeaderData::from_ber(i).map_err(Err::convert) +} + +fn parse_snmp_v3_plaintext_pdu(bytes: &[u8]) -> IResult<&[u8], ScopedPduData, SnmpError> { + Sequence::from_der_and_then(bytes, |i| { + let (i, ctx_engine_id) = <&[u8]>::from_ber(i).map_err(Err::convert)?; + let (i, ctx_engine_name) = <&[u8]>::from_ber(i).map_err(Err::convert)?; + let (i, data) = parse_snmp_v2c_pdu(i)?; + let pdu = ScopedPdu { + ctx_engine_id, + ctx_engine_name, + data, + }; + Ok((i, ScopedPduData::Plaintext(pdu))) + }) +} diff --git a/rust/vendor/snmp-parser/src/usm.rs b/rust/vendor/snmp-parser/src/usm.rs new file mode 100644 index 0000000..ecdbcad --- /dev/null +++ b/rust/vendor/snmp-parser/src/usm.rs @@ -0,0 +1,35 @@ +//! RFC2274 - User-based Security Model (USM) for version 3 of the Simple Network Management Protocol (SNMPv3) + +use crate::parse_ber_octetstring_as_str; +use asn1_rs::{Error, FromBer, Sequence}; +use nom::IResult; + +#[derive(Debug, PartialEq)] +pub struct UsmSecurityParameters<'a> { + pub msg_authoritative_engine_id: &'a [u8], + pub msg_authoritative_engine_boots: u32, + pub msg_authoritative_engine_time: u32, + pub msg_user_name: String, + pub msg_authentication_parameters: &'a [u8], + pub msg_privacy_parameters: &'a [u8], +} + +pub fn parse_usm_security_parameters(bytes: &[u8]) -> IResult<&[u8], UsmSecurityParameters, Error> { + Sequence::from_der_and_then(bytes, |i| { + let (i, msg_authoritative_engine_id) = <&[u8]>::from_ber(i)?; + let (i, msg_authoritative_engine_boots) = u32::from_ber(i)?; + let (i, msg_authoritative_engine_time) = u32::from_ber(i)?; + let (i, msg_user_name) = parse_ber_octetstring_as_str(i)?; + let (i, msg_authentication_parameters) = <&[u8]>::from_ber(i)?; + let (i, msg_privacy_parameters) = <&[u8]>::from_ber(i)?; + let usm = UsmSecurityParameters { + msg_authoritative_engine_id, + msg_authoritative_engine_boots, + msg_authoritative_engine_time, + msg_user_name: msg_user_name.to_string(), + msg_authentication_parameters, + msg_privacy_parameters, + }; + Ok((i, usm)) + }) +} diff --git a/rust/vendor/snmp-parser/tests/v1.rs b/rust/vendor/snmp-parser/tests/v1.rs new file mode 100644 index 0000000..fac9c60 --- /dev/null +++ b/rust/vendor/snmp-parser/tests/v1.rs @@ -0,0 +1,95 @@ +#[macro_use] +extern crate hex_literal; +extern crate nom; +extern crate snmp_parser; + +use asn1_rs::Oid; +use snmp_parser::*; +use std::net::Ipv4Addr; + +const SNMPV1_RESPONSE: &[u8] = &hex!( + " +30 82 00 59 02 01 00 04 06 70 75 62 6c 69 63 a2 +82 00 4a 02 02 26 72 02 01 00 02 01 00 30 82 00 +3c 30 82 00 10 06 0b 2b 06 01 02 01 19 03 02 01 +05 01 02 01 03 30 82 00 10 06 0b 2b 06 01 02 01 +19 03 05 01 01 01 02 01 03 30 82 00 10 06 0b 2b +06 01 02 01 19 03 05 01 02 01 04 01 a0 +" +); + +#[test] +fn test_snmp_v1_response() { + let bytes: &[u8] = SNMPV1_RESPONSE; + match parse_snmp_v1(bytes) { + Ok((rem, pdu)) => { + // println!("pdu: {:?}", pdu); + assert!(rem.is_empty()); + assert_eq!(pdu.version, 0); + assert_eq!(&pdu.community as &str, "public"); + assert_eq!(pdu.pdu_type(), PduType::Response); + } + e => panic!("Error: {:?}", e), + } +} + +static SNMPV1_REQ: &[u8] = include_bytes!("../assets/snmpv1_req.bin"); + +#[test] +fn test_snmp_v1_req() { + let bytes = SNMPV1_REQ; + let expected = SnmpMessage { + version: 0, + community: String::from("public"), + pdu: SnmpPdu::Generic(SnmpGenericPdu { + pdu_type: PduType::GetRequest, + req_id: 38, + err: ErrorStatus(0), + err_index: 0, + var: vec![SnmpVariable { + oid: Oid::from(&[1, 3, 6, 1, 2, 1, 1, 2, 0]).unwrap(), + val: VarBindValue::Unspecified, + }], + }), + }; + let (rem, r) = parse_snmp_v1(bytes).expect("parsing failed"); + // debug!("r: {:?}",r); + eprintln!( + "SNMP: v={}, c={:?}, pdu_type={:?}", + r.version, + r.community, + r.pdu_type() + ); + // debug!("PDU: type={}, {:?}", pdu_type, pdu_res); + for v in r.vars_iter() { + eprintln!("v: {:?}", v); + } + assert!(rem.is_empty()); + assert_eq!(r, expected); +} + +static SNMPV1_TRAP_COLDSTART: &[u8] = include_bytes!("../assets/snmpv1_trap_coldstart.bin"); + +#[test] +fn test_snmp_v1_trap_coldstart() { + let bytes = SNMPV1_TRAP_COLDSTART; + let (rem, pdu) = parse_snmp_v1(bytes).expect("parsing failed"); + // println!("pdu: {:?}", pdu); + assert!(rem.is_empty()); + assert_eq!(pdu.version, 0); + assert_eq!(&pdu.community as &str, "public"); + assert_eq!(pdu.pdu_type(), PduType::TrapV1); + match pdu.pdu { + SnmpPdu::TrapV1(trap) => { + assert_eq!( + trap.enterprise, + Oid::from(&[1, 3, 6, 1, 4, 1, 4, 1, 2, 21]).unwrap() + ); + assert_eq!( + trap.agent_addr, + NetworkAddress::IPv4(Ipv4Addr::new(127, 0, 0, 1)) + ); + } + _ => panic!("unexpected pdu type"), + } +} diff --git a/rust/vendor/snmp-parser/tests/v2c.rs b/rust/vendor/snmp-parser/tests/v2c.rs new file mode 100644 index 0000000..e15c6e2 --- /dev/null +++ b/rust/vendor/snmp-parser/tests/v2c.rs @@ -0,0 +1,53 @@ +#[macro_use] +extern crate pretty_assertions; +extern crate nom; +extern crate snmp_parser; + +use asn1_rs::Oid; +use snmp_parser::*; + +static SNMPV2_GET: &[u8] = include_bytes!("../assets/snmpv2c-get-response.bin"); + +#[test] +fn test_snmp_v2_get() { + let bytes = SNMPV2_GET; + let expected = SnmpMessage { + version: 1, + community: String::from("public"), + pdu: SnmpPdu::Generic(SnmpGenericPdu { + pdu_type: PduType::Response, + req_id: 97083662, + err: ErrorStatus(0), + err_index: 0, + var: vec![ + SnmpVariable { + oid: Oid::from(&[1, 3, 6, 1, 2, 1, 25, 1, 1, 0]).unwrap(), + val: VarBindValue::Value(ObjectSyntax::TimeTicks(970069)), + }, + SnmpVariable { + oid: Oid::from(&[1, 3, 6, 1, 2, 1, 25, 1, 5, 0]).unwrap(), + val: VarBindValue::Value(ObjectSyntax::Gauge32(3)), + }, + SnmpVariable { + oid: Oid::from(&[1, 3, 6, 1, 2, 1, 25, 1, 5, 1]).unwrap(), + val: VarBindValue::NoSuchInstance, + }, + ], + }), + }; + let (rem, r) = parse_snmp_v2c(bytes).expect("parsing failed"); + + // debug!("r: {:?}",r); + eprintln!( + "SNMP: v={}, c={:?}, pdu_type={:?}", + r.version, + r.community, + r.pdu_type() + ); + // debug!("PDU: type={}, {:?}", pdu_type, pdu_res); + for v in r.vars_iter() { + eprintln!("v: {:?}", v); + } + assert!(rem.is_empty()); + assert_eq!(r, expected); +} diff --git a/rust/vendor/snmp-parser/tests/v3.rs b/rust/vendor/snmp-parser/tests/v3.rs new file mode 100644 index 0000000..bad61d7 --- /dev/null +++ b/rust/vendor/snmp-parser/tests/v3.rs @@ -0,0 +1,86 @@ +#[macro_use] +extern crate pretty_assertions; + +extern crate nom; +extern crate snmp_parser; + +use snmp_parser::*; + +static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin"); + +#[test] +fn test_snmp_v3_req() { + let bytes = SNMPV3_REQ; + let sp = SecurityParameters::USM(UsmSecurityParameters { + msg_authoritative_engine_id: b"", + msg_authoritative_engine_boots: 0, + msg_authoritative_engine_time: 0, + msg_user_name: String::from(""), + msg_authentication_parameters: b"", + msg_privacy_parameters: b"", + }); + let cei = [ + 0x80, 0x00, 0x1f, 0x88, 0x80, 0x59, 0xdc, 0x48, 0x61, 0x45, 0xa2, 0x63, 0x22, + ]; + let data = SnmpPdu::Generic(SnmpGenericPdu { + pdu_type: PduType::GetRequest, + req_id: 2098071598, + err: ErrorStatus::NoError, + err_index: 0, + var: vec![], + }); + let expected = SnmpV3Message { + version: 3, + header_data: HeaderData { + msg_id: 821490644, + msg_max_size: 65507, + msg_flags: 4, + msg_security_model: SecurityModel::USM, + }, + security_params: sp, + data: ScopedPduData::Plaintext(ScopedPdu { + ctx_engine_id: &cei, + ctx_engine_name: b"", + data, + }), + }; + let (rem, res) = parse_snmp_v3(bytes).expect("parsing failed"); + // eprintln!("{:?}", res); + assert!(rem.is_empty()); + assert_eq!(res, expected); +} + +#[test] +fn test_snmp_v3_req_encrypted() { + let bytes = include_bytes!("../assets/snmpv3_req_encrypted.bin"); + let (rem, msg) = parse_snmp_v3(bytes).expect("parsing failed"); + // eprintln!("{:?}", res); + assert!(rem.is_empty()); + assert_eq!(msg.version, 3); + assert_eq!(msg.header_data.msg_security_model, SecurityModel::USM); +} + +#[test] +fn test_snmp_v3_report() { + let bytes = include_bytes!("../assets/snmpv3-report.bin"); + let (rem, msg) = parse_snmp_v3(bytes).expect("parsing failed"); + // eprintln!("{:?}", res); + assert!(rem.is_empty()); + assert_eq!(msg.version, 3); + assert_eq!(msg.header_data.msg_security_model, SecurityModel::USM); +} + +#[test] +fn test_snmp_v3_generic() { + let bytes = SNMPV3_REQ; + let res = parse_snmp_generic_message(bytes); + // eprintln!("{:?}", res); + let (rem, m) = res.expect("parse_snmp_generic_message"); + assert!(rem.is_empty()); + if let SnmpGenericMessage::V3(msg) = m { + assert_eq!(msg.version, 3); + assert_eq!(msg.header_data.msg_security_model, SecurityModel::USM); + } else { + panic!("unexpected PDU type"); + } +} |