radius_server/
packet.rs

1use std::str;
2pub const CODE_ACCOUNTING_REQUEST: u8 = 4;
3pub const CODE_ACCOUNTING_RESPONSE: u8 = 5;
4
5pub const ATTR_ACCT_STATUS_TYPE: u8 = 40;
6pub const ATTR_ACCT_SESSION_ID: u8 = 44;
7pub const ATTR_ACCT_SESSION_TIME: u8 = 46;
8
9#[derive(Debug,Clone)]
10pub struct RadiusPacket {
11    pub code: u8,
12    pub identifier: u8,
13    pub length: u16,
14    pub authenticator: [u8; 16],
15    pub attributes: Vec<RadiusAttribute>,
16}
17
18#[derive(Debug,Clone)]
19pub struct RadiusAttribute {
20    pub typ: u8,
21    pub len: u8,
22    pub value: Vec<u8>,
23}
24
25impl RadiusAttribute {
26    pub fn reply_message(msg: &str) -> Self {
27        let value = msg.as_bytes().to_vec();
28        let len = (value.len() + 2) as u8;
29
30        RadiusAttribute {
31            typ: 18,
32            len,
33            value,
34        }
35    }
36
37    pub fn user_name(name: &str) -> Self {
38        let value = name.as_bytes().to_vec();
39        let len = (value.len() + 2) as u8;
40
41        RadiusAttribute {
42            typ: 1,
43            len,
44            value,
45        }
46    }
47
48    pub fn vendor_specific(vendor_id: u32, payload: Vec<u8>) -> Self {
49        let mut value = Vec::new();
50        value.extend_from_slice(&vendor_id.to_be_bytes());
51        value.extend(payload);
52
53        RadiusAttribute {
54            typ: 26,
55            len: (2 + value.len()) as u8,
56            value,
57        }
58    }
59
60    pub fn wispr_bandwidth_max_up(bps: u32) -> Self {
61        let mut value = Vec::new();
62        value.extend_from_slice(&14122u32.to_be_bytes());
63        value.push(7);
64        value.push(6);
65        value.extend_from_slice(&bps.to_be_bytes());
66
67        RadiusAttribute {
68            typ: 26,
69            len: (2 + value.len()) as u8,
70            value,
71        }
72    }
73
74    pub fn wispr_bandwidth_max_down(bps: u32) -> Self {
75        let mut value = Vec::new();
76        value.extend_from_slice(&14122u32.to_be_bytes());
77        value.push(8);
78        value.push(6);
79        value.extend_from_slice(&bps.to_be_bytes());
80
81        RadiusAttribute {
82            typ: 26,
83            len: (2 + value.len()) as u8,
84            value,
85        }
86    }
87
88    pub fn session_timeout(seconds: u32) -> Self {
89        RadiusAttribute {
90            typ: 27,
91            len: 6,
92            value: seconds.to_be_bytes().to_vec(),
93        }
94    }
95
96    pub fn idle_timeout(seconds: u32) -> Self {
97        RadiusAttribute {
98            typ: 28,
99            len: 6,
100            value: seconds.to_be_bytes().to_vec(),
101        }
102    }
103}
104
105impl RadiusPacket {
106    pub fn from_bytes(buf: &[u8]) -> Result<Self, String> {
107        if buf.len() < 20 {
108            return Err("Packet too short".to_string());
109        }
110
111        let code = buf[0];
112        let identifier = buf[1];
113        let length = u16::from_be_bytes([buf[2], buf[3]]) as usize;
114
115        if buf.len() < length {
116            return Err(format!(
117                "Length mismatch: header says {}, but got {} bytes",
118                length,
119                buf.len()
120            ));
121        }
122
123        let authenticator: [u8; 16] = buf[4..20].try_into().unwrap();
124        let mut attributes = Vec::new();
125        let mut i = 20;
126
127        while i < length {
128            if i + 2 > length {
129                return Err("Truncated attribute header".to_string());
130            }
131
132            let typ = buf[i];
133            let len = buf[i + 1] as usize;
134
135            if len < 2 || i + len > length {
136                return Err(format!(
137                    "Invalid attribute length at index {}: len = {}",
138                    i, len
139                ));
140            }
141
142            let value = buf[i + 2..i + len].to_vec();
143            attributes.push(RadiusAttribute {
144                typ,
145                len: len as u8,
146                value,
147            });
148
149            i += len;
150        }
151
152        Ok(RadiusPacket {
153            code,
154            identifier,
155            length: length as u16,
156            authenticator,
157            attributes,
158        })
159    }
160
161    pub fn to_bytes(&self) -> Vec<u8> {
162        let mut buf = Vec::new();
163        buf.push(self.code);
164        buf.push(self.identifier);
165        buf.extend_from_slice(&[0x00, 0x00]);
166        buf.extend_from_slice(&self.authenticator);
167
168        for attr in &self.attributes {
169            buf.push(attr.typ);
170            buf.push(attr.len);
171            buf.extend_from_slice(&attr.value);
172        }
173
174        let length = buf.len() as u16;
175        buf[2] = (length >> 8) as u8;
176        buf[3] = length as u8;
177
178        buf
179    }
180
181    pub fn access_accept(identifier: u8, attributes: Vec<RadiusAttribute>) -> Self {
182        RadiusPacket {
183            code: 2,
184            identifier,
185            length: 0,
186            authenticator: [0u8; 16],
187            attributes,
188        }
189    }
190
191    pub fn access_reject(identifier: u8, msg: &str) -> Self {
192        RadiusPacket {
193            code: 3,
194            identifier,
195            length: 0,
196            authenticator: [0u8; 16],
197            attributes: vec![RadiusAttribute::reply_message(msg)],
198        }
199    }
200
201    pub fn access_challenge(identifier: u8, msg: &str) -> Self {
202        RadiusPacket {
203            code: 11,
204            identifier,
205            length: 0,
206            authenticator: [0u8; 16],
207            attributes: vec![RadiusAttribute::reply_message(msg)],
208        }
209    }
210
211    pub fn log(&self) {
212        let code_name = match self.code {
213            1 => "Access-Request",
214            2 => "Access-Accept",
215            3 => "Access-Reject",
216            _ => "Unknown",
217        };
218
219        println!("📨 RADIUS {} (id: {})", code_name, self.identifier);
220
221        for attr in &self.attributes {
222            let name = radius_type_name(attr.typ);
223            if attr.typ == 1 || attr.typ == 18 || attr.typ == 80 {
224                match str::from_utf8(&attr.value) {
225                    Ok(val) => println!("  • {}: {}", name, val.trim()),
226                    Err(_) => println!("  • {}: {:?}", name, attr.value),
227                }
228            } else if attr.typ == 26 {
229                println!("  • {}: {}", name, decode_vendor_specific(&attr.value));
230            } else {
231                println!("  • {}: {:?}", name, attr.value);
232            }
233        }
234    }
235      pub fn reply_accept(&self, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
236        RadiusPacket {
237            code: 2,
238            identifier: self.identifier,
239            length: 0,
240            authenticator: [0; 16],
241            attributes,
242        }
243    }
244
245    pub fn reply_reject(&self, message: &str) -> RadiusPacket {
246        RadiusPacket {
247            code: 3,
248            identifier: self.identifier,
249            length: 0,
250            authenticator: [0; 16],
251            attributes: vec![RadiusAttribute::reply_message(message)],
252        }
253    }
254
255    pub fn reply_challenge(&self, message: &str) -> RadiusPacket {
256        RadiusPacket {
257            code: 11,
258            identifier: self.identifier,
259            length: 0,
260            authenticator: [0; 16],
261            attributes: vec![RadiusAttribute::reply_message(message)],
262        }
263    }
264
265    pub fn username(&self) -> Option<String> {
266        self.attributes
267            .iter()
268            .find(|a| a.typ == 1)
269            .and_then(|a| String::from_utf8(a.value.clone()).ok())
270    }
271}
272
273fn radius_type_name(typ: u8) -> &'static str {
274    match typ {
275        1 => "User-Name",
276        2 => "User-Password",
277        3 => "CHAP-Password",
278        4 => "NAS-IP-Address",
279        18 => "Reply-Message",
280        26 => "Vendor-Specific",
281        27 => "Session-Timeout",
282        28 => "Idle-Timeout",
283        80 => "Message-Authenticator",
284        _ => "Unknown",
285    }
286}
287
288fn decode_vendor_specific(value: &[u8]) -> String {
289    if value.len() < 6 {
290        return "Invalid VSA".to_string();
291    }
292
293    let vendor_id = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
294    let vendor_type = value.get(4).copied().unwrap_or(0);
295    let vendor_len = value.get(5).copied().unwrap_or(0) as usize;
296
297    let data_start = 6;
298    let data_end = data_start + (vendor_len.saturating_sub(2));
299    let payload = &value.get(data_start..data_end).unwrap_or(&[]);
300
301    format!("VendorID={}, Type={}, Data={:?}", vendor_id, vendor_type, payload)
302}