radius_parser/
radius_attr.rs

1use nom::bytes::streaming::take;
2use nom::combinator::{map, map_parser, verify};
3use nom::number::streaming::{be_u32, be_u8};
4use nom::{Err, IResult, Needed};
5use std::net::Ipv4Addr;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct RadiusAttributeType(pub u8);
9
10#[allow(non_upper_case_globals)]
11impl RadiusAttributeType {
12    pub const UserName: RadiusAttributeType = RadiusAttributeType(1);
13    pub const UserPassword: RadiusAttributeType = RadiusAttributeType(2);
14    pub const ChapPassword: RadiusAttributeType = RadiusAttributeType(3);
15    pub const NasIPAddress: RadiusAttributeType = RadiusAttributeType(4);
16    pub const NasPort: RadiusAttributeType = RadiusAttributeType(5);
17    pub const ServiceType: RadiusAttributeType = RadiusAttributeType(6);
18    pub const FramedProtocol: RadiusAttributeType = RadiusAttributeType(7);
19    pub const FramedIPAddress: RadiusAttributeType = RadiusAttributeType(8);
20    pub const FramedIPNetmask: RadiusAttributeType = RadiusAttributeType(9);
21    pub const FramedRouting: RadiusAttributeType = RadiusAttributeType(10);
22    pub const FilterId: RadiusAttributeType = RadiusAttributeType(11);
23    pub const FramedMTU: RadiusAttributeType = RadiusAttributeType(12);
24    pub const FramedCompression: RadiusAttributeType = RadiusAttributeType(13);
25    pub const VendorSpecific: RadiusAttributeType = RadiusAttributeType(26);
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct ServiceType(pub u32);
30
31#[allow(non_upper_case_globals)]
32impl ServiceType {
33    pub const Login: ServiceType = ServiceType(1);
34    pub const Framed: ServiceType = ServiceType(2);
35    pub const CallbackLogin: ServiceType = ServiceType(3);
36    pub const CallbackFramed: ServiceType = ServiceType(4);
37    pub const Outbound: ServiceType = ServiceType(5);
38    pub const Administrative: ServiceType = ServiceType(6);
39    pub const NasPrompt: ServiceType = ServiceType(7);
40    pub const AuthenticateOnly: ServiceType = ServiceType(8);
41    pub const CallbackNasPrompt: ServiceType = ServiceType(9);
42    pub const CallCheck: ServiceType = ServiceType(10);
43    pub const CallbackAdministrative: ServiceType = ServiceType(11);
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct FramedRouting(pub u32);
48
49#[allow(non_upper_case_globals)]
50impl FramedRouting {
51    pub const None: FramedRouting = FramedRouting(0);
52    pub const Send: FramedRouting = FramedRouting(1);
53    pub const Receive: FramedRouting = FramedRouting(2);
54    pub const SendReceive: FramedRouting = FramedRouting(3);
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub struct FramedProtocol(pub u32);
59
60#[allow(non_upper_case_globals)]
61impl FramedProtocol {
62    pub const Ppp: FramedProtocol = FramedProtocol(1);
63    pub const Slip: FramedProtocol = FramedProtocol(2);
64    /// AppleTalk Remote Access Protocol
65    pub const Arap: FramedProtocol = FramedProtocol(3);
66    /// Gandalf proprietary SingleLink/MultiLink protocol
67    pub const Gandalf: FramedProtocol = FramedProtocol(4);
68    /// Xylogics proprietary IPX/SLIP
69    pub const Xylogics: FramedProtocol = FramedProtocol(5);
70    /// X.75 Synchronous
71    pub const X75: FramedProtocol = FramedProtocol(6);
72}
73
74/// This Attribute indicates a compression protocol to be used for the
75/// link.  It MAY be used in Access-Accept packets.  It MAY be used in
76/// an Access-Request packet as a hint to the server that the NAS
77/// would prefer to use that compression, but the server is not
78/// required to honor the hint.
79///
80/// More than one compression protocol Attribute MAY be sent.  It is
81/// the responsibility of the NAS to apply the proper compression
82/// protocol to appropriate link traffic.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct FramedCompression(pub u32);
85
86#[allow(non_upper_case_globals)]
87impl FramedCompression {
88    /// No compression
89    pub const None: FramedCompression = FramedCompression(0);
90    /// VJ TCP/IP header compression (See RFC1144)
91    pub const TcpIp: FramedCompression = FramedCompression(1);
92    /// IPX header compression
93    pub const Ipx: FramedCompression = FramedCompression(2);
94    /// Stac-LZS compression
95    pub const StaticLzs: FramedCompression = FramedCompression(3);
96}
97
98#[derive(Clone, Debug, PartialEq)]
99pub enum RadiusAttribute<'a> {
100    UserName(&'a [u8]),
101    UserPassword(&'a [u8]),
102    ChapPassword(u8, &'a [u8]),
103    NasIPAddress(Ipv4Addr),
104    NasPort(u32),
105    ServiceType(ServiceType),
106    FramedProtocol(FramedProtocol),
107    FramedIPAddress(Ipv4Addr),
108    FramedIPNetmask(Ipv4Addr),
109    FramedRouting(FramedRouting),
110    FilterId(&'a [u8]),
111    FramedMTU(u32),
112    FramedCompression(FramedCompression),
113    VendorSpecific(u32, &'a [u8]),
114    CalledStationId(&'a [u8]),
115    CallingStationId(&'a [u8]),
116
117    Unknown(u8, &'a [u8]),
118}
119
120fn parse_attribute_content(i: &[u8], t: u8) -> IResult<&[u8], RadiusAttribute> {
121    const EMPTY: &[u8] = &[];
122    match t {
123        1 => Ok((EMPTY, RadiusAttribute::UserName(i))),
124        2 => Ok((EMPTY, RadiusAttribute::UserPassword(i))),
125        3 => {
126            if i.len() < 2 {
127                return Err(Err::Incomplete(Needed::new(2)));
128            }
129            Ok((EMPTY, RadiusAttribute::ChapPassword(i[0], &i[1..])))
130        }
131        4 => map(take(4usize), |v: &[u8]| {
132            RadiusAttribute::NasIPAddress(Ipv4Addr::new(v[0], v[1], v[2], v[3]))
133        })(i),
134        5 => map(be_u32, RadiusAttribute::NasPort)(i),
135        6 => map(be_u32, |v| RadiusAttribute::ServiceType(ServiceType(v)))(i),
136        7 => map(be_u32, |v| {
137            RadiusAttribute::FramedProtocol(FramedProtocol(v))
138        })(i),
139        8 => map(take(4usize), |v: &[u8]| {
140            RadiusAttribute::FramedIPAddress(Ipv4Addr::new(v[0], v[1], v[2], v[3]))
141        })(i),
142        9 => map(take(4usize), |v: &[u8]| {
143            RadiusAttribute::FramedIPNetmask(Ipv4Addr::new(v[0], v[1], v[2], v[3]))
144        })(i),
145        10 => map(be_u32, |v| RadiusAttribute::FramedRouting(FramedRouting(v)))(i),
146        11 => Ok((EMPTY, RadiusAttribute::FilterId(i))),
147        12 => map(be_u32, RadiusAttribute::FramedMTU)(i),
148        13 => map(be_u32, |v| {
149            RadiusAttribute::FramedCompression(FramedCompression(v))
150        })(i),
151        26 => {
152            if i.len() < 5 {
153                return Err(Err::Incomplete(Needed::new(5)));
154            }
155            let (i, vendorid) = be_u32(i)?;
156            let vendordata = i;
157            Ok((EMPTY, RadiusAttribute::VendorSpecific(vendorid, vendordata)))
158        }
159        30 => Ok((EMPTY, RadiusAttribute::CalledStationId(i))),
160        31 => Ok((EMPTY, RadiusAttribute::CallingStationId(i))),
161        _ => Ok((EMPTY, RadiusAttribute::Unknown(t, i))),
162    }
163}
164
165pub fn parse_radius_attribute(i: &[u8]) -> IResult<&[u8], RadiusAttribute> {
166    let (i, t) = be_u8(i)?;
167    let (i, l) = verify(be_u8, |&n| n >= 2)(i)?;
168    let (i, v) = map_parser(take(l - 2), |d| parse_attribute_content(d, t))(i)?;
169    Ok((i, v))
170}
171
172#[cfg(test)]
173mod tests {
174    use crate::radius_attr::*;
175    use nom::error::{make_error, ErrorKind};
176    use nom::Err;
177
178    #[test]
179    fn test_attribute_invalid() {
180        let data = &[255, 0, 2, 2];
181        assert_eq!(
182            parse_radius_attribute(data),
183            Err(Err::Error(make_error(&data[1..], ErrorKind::Verify)))
184        );
185    }
186
187    #[test]
188    fn test_attribute_empty() {
189        let data = &[255, 2, 2, 2];
190        assert_eq!(
191            parse_radius_attribute(data),
192            Ok((&data[2..], RadiusAttribute::Unknown(255, &[])))
193        );
194    }
195
196    #[test]
197    fn test_attribute() {
198        let data = &[255, 4, 2, 2];
199        assert_eq!(
200            parse_radius_attribute(data),
201            Ok((&b""[..], RadiusAttribute::Unknown(255, &[2, 2])))
202        );
203    }
204
205    #[test]
206    fn test_parse_vendor_specific() {
207        {
208            let data = &[26, 7, 0, 1, 2, 3, 120];
209            assert_eq!(
210                parse_radius_attribute(data),
211                Ok((
212                    &b""[..],
213                    RadiusAttribute::VendorSpecific(66051, "x".as_bytes())
214                ))
215            )
216        }
217        {
218            let data = &[26, 6, 0, 1, 2, 3];
219            assert_eq!(
220                parse_radius_attribute(data),
221                Err(Err::Incomplete(Needed::new(5)))
222            )
223        }
224    }
225
226    #[test]
227    fn test_parse_called_station_id() {
228        {
229            let data = &[
230                30, 19, 97, 97, 45, 98, 98, 45, 99, 99, 45, 100, 100, 45, 101, 101, 45, 102, 102,
231            ];
232            assert_eq!(
233                parse_radius_attribute(data),
234                Ok((
235                    &b""[..],
236                    RadiusAttribute::CalledStationId("aa-bb-cc-dd-ee-ff".as_bytes())
237                ))
238            )
239        }
240    }
241
242    #[test]
243    fn test_parse_calling_station_id() {
244        {
245            let data = &[
246                31, 19, 97, 97, 45, 98, 98, 45, 99, 99, 45, 100, 100, 45, 101, 101, 45, 102, 102,
247            ];
248            assert_eq!(
249                parse_radius_attribute(data),
250                Ok((
251                    &b""[..],
252                    RadiusAttribute::CallingStationId("aa-bb-cc-dd-ee-ff".as_bytes())
253                ))
254            )
255        }
256    }
257}