snmp_parser/
snmpv3.rs

1//! SNMPv3 Parser
2//!
3//! SNMPv3 is defined in the following RFCs:
4//!   - [RFC2570](https://tools.ietf.org/html/rfc2570): Introduction to SNMP v3
5//!   - [RFC3412](https://tools.ietf.org/html/rfc3412): Message Processing and Dispatching for the
6//!     Simple Network Management Protocol (SNMP)
7//!
8//! See also:
9//!   - [RFC2578](https://tools.ietf.org/html/rfc2578): Structure of Management Information Version 2 (SMIv2)
10
11use asn1_rs::{Error, FromBer, Sequence};
12use nom::combinator::{map, map_res};
13use nom::{Err, IResult};
14use std::fmt;
15
16use crate::error::SnmpError;
17use crate::snmp::{parse_snmp_v2c_pdu, SnmpPdu};
18pub use crate::usm::{parse_usm_security_parameters, UsmSecurityParameters};
19
20#[derive(Clone, Copy, Eq, PartialEq)]
21pub struct SecurityModel(pub u32);
22
23#[allow(non_upper_case_globals)]
24impl SecurityModel {
25    pub const SnmpV1: SecurityModel = SecurityModel(1);
26    pub const SnmpV2c: SecurityModel = SecurityModel(2);
27    pub const USM: SecurityModel = SecurityModel(3);
28}
29
30impl fmt::Debug for SecurityModel {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match self.0 {
33            1 => f.write_str("SnmpV1"),
34            2 => f.write_str("SnmpV2c"),
35            3 => f.write_str("USM"),
36            n => f.debug_tuple("SecurityModel").field(&n).finish(),
37        }
38    }
39}
40
41impl<'a> FromBer<'a> for SecurityModel {
42    fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self> {
43        map(u32::from_ber, SecurityModel)(bytes)
44    }
45}
46
47#[derive(Debug, PartialEq)]
48#[allow(clippy::upper_case_acronyms)]
49pub enum SecurityParameters<'a> {
50    Raw(&'a [u8]),
51    USM(UsmSecurityParameters<'a>),
52}
53
54/// An SNMPv3 message
55///
56/// # Examples
57///
58/// ```rust
59/// use snmp_parser::{ScopedPduData, SecurityModel, SnmpV3Message};
60/// use snmp_parser::asn1_rs::FromBer;
61///
62/// static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin");
63///
64/// # fn main() {
65/// match SnmpV3Message::from_ber(&SNMPV3_REQ) {
66///   Ok((_, ref r)) => {
67///     assert!(r.version == 3);
68///     assert!(r.header_data.msg_security_model == SecurityModel::USM);
69///     match r.data {
70///       ScopedPduData::Plaintext(ref _pdu) => { },
71///       ScopedPduData::Encrypted(_) => (),
72///     }
73///   },
74///   Err(e) => panic!("{}", e),
75/// }
76/// # }
77/// ```
78#[derive(Debug, PartialEq)]
79pub struct SnmpV3Message<'a> {
80    /// Version, as raw-encoded: 3 for SNMPv3
81    pub version: u32,
82    pub header_data: HeaderData,
83    pub security_params: SecurityParameters<'a>,
84    pub data: ScopedPduData<'a>,
85}
86
87impl<'a> FromBer<'a, SnmpError> for SnmpV3Message<'a> {
88    fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self, SnmpError> {
89        Sequence::from_der_and_then(bytes, |i| {
90            let (i, version) = u32::from_ber(i).map_err(Err::convert)?;
91            let (i, header_data) = parse_snmp_v3_headerdata(i)?;
92            let (i, secp) = map_res(<&[u8]>::from_ber, |x| parse_secp(x, &header_data))(i)
93                .map_err(Err::convert)?;
94            let (i, data) = parse_snmp_v3_data(i, &header_data)?;
95            let msg = SnmpV3Message {
96                version,
97                header_data,
98                security_params: secp,
99                data,
100            };
101            Ok((i, msg))
102        })
103    }
104}
105
106#[derive(Clone, Copy, Debug, PartialEq)]
107pub struct HeaderData {
108    pub msg_id: u32,
109    pub msg_max_size: u32,
110    pub msg_flags: u8,
111    pub msg_security_model: SecurityModel,
112}
113
114impl HeaderData {
115    pub fn is_authenticated(&self) -> bool {
116        self.msg_flags & 0b001 != 0
117    }
118
119    pub fn is_encrypted(&self) -> bool {
120        self.msg_flags & 0b010 != 0
121    }
122
123    pub fn is_reportable(&self) -> bool {
124        self.msg_flags & 0b100 != 0
125    }
126}
127
128impl<'a> FromBer<'a> for HeaderData {
129    fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self> {
130        Sequence::from_ber_and_then(bytes, |i| {
131            let (i, msg_id) = u32::from_ber(i)?;
132            let (i, msg_max_size) = u32::from_ber(i)?;
133            let (i, b) = <&[u8]>::from_ber(i)?;
134            let msg_flags = if b.len() == 1 {
135                b[0]
136            } else {
137                return Err(Err::Error(Error::BerValueError));
138            };
139            let (i, msg_security_model) = map(u32::from_ber, SecurityModel)(i)?;
140            let hdr = HeaderData {
141                msg_id,
142                msg_max_size,
143                msg_flags,
144                msg_security_model,
145            };
146            Ok((i, hdr))
147        })
148    }
149}
150
151#[derive(Debug, PartialEq)]
152pub enum ScopedPduData<'a> {
153    Plaintext(ScopedPdu<'a>),
154    Encrypted(&'a [u8]),
155}
156
157#[derive(Debug, PartialEq)]
158pub struct ScopedPdu<'a> {
159    pub ctx_engine_id: &'a [u8],
160    pub ctx_engine_name: &'a [u8],
161    /// ANY -- e.g., PDUs as defined in [RFC3416](https://tools.ietf.org/html/rfc3416)
162    pub data: SnmpPdu<'a>,
163}
164
165pub(crate) fn parse_snmp_v3_data<'a>(
166    i: &'a [u8],
167    hdr: &HeaderData,
168) -> IResult<&'a [u8], ScopedPduData<'a>, SnmpError> {
169    if hdr.is_encrypted() {
170        map(<&[u8]>::from_ber, ScopedPduData::Encrypted)(i).map_err(Err::convert)
171    } else {
172        parse_snmp_v3_plaintext_pdu(i)
173    }
174}
175
176pub(crate) fn parse_secp<'a>(
177    i: &'a [u8],
178    hdr: &HeaderData,
179) -> Result<SecurityParameters<'a>, SnmpError> {
180    match hdr.msg_security_model {
181        SecurityModel::USM => match parse_usm_security_parameters(i) {
182            Ok((_, usm)) => Ok(SecurityParameters::USM(usm)),
183            _ => Err(SnmpError::InvalidSecurityModel),
184        },
185        _ => Ok(SecurityParameters::Raw(i)),
186    }
187}
188
189/// Parse an SNMPv3 top-level message
190///
191/// Example:
192///
193/// ```rust
194/// use snmp_parser::{parse_snmp_v3,ScopedPduData,SecurityModel};
195///
196/// static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin");
197///
198/// # fn main() {
199/// match parse_snmp_v3(&SNMPV3_REQ) {
200///   Ok((_, ref r)) => {
201///     assert!(r.version == 3);
202///     assert!(r.header_data.msg_security_model == SecurityModel::USM);
203///     match r.data {
204///       ScopedPduData::Plaintext(ref _pdu) => { },
205///       ScopedPduData::Encrypted(_) => (),
206///     }
207///   },
208///   Err(e) => panic!("{}", e),
209/// }
210/// # }
211/// ```
212pub fn parse_snmp_v3(bytes: &[u8]) -> IResult<&[u8], SnmpV3Message, SnmpError> {
213    SnmpV3Message::from_ber(bytes)
214}
215
216#[inline]
217pub(crate) fn parse_snmp_v3_headerdata(i: &[u8]) -> IResult<&[u8], HeaderData, SnmpError> {
218    HeaderData::from_ber(i).map_err(Err::convert)
219}
220
221fn parse_snmp_v3_plaintext_pdu(bytes: &[u8]) -> IResult<&[u8], ScopedPduData, SnmpError> {
222    Sequence::from_der_and_then(bytes, |i| {
223        let (i, ctx_engine_id) = <&[u8]>::from_ber(i).map_err(Err::convert)?;
224        let (i, ctx_engine_name) = <&[u8]>::from_ber(i).map_err(Err::convert)?;
225        let (i, data) = parse_snmp_v2c_pdu(i)?;
226        let pdu = ScopedPdu {
227            ctx_engine_id,
228            ctx_engine_name,
229            data,
230        };
231        Ok((i, ScopedPduData::Plaintext(pdu)))
232    })
233}