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