radius_server/
handler.rs

1use std::sync::Arc;
2use crate::{
3    dictionary::Dictionary,
4    packet::{RadiusAttribute, RadiusPacket},
5};
6use md5;
7use tokio::net::UdpSocket;
8
9/// Builds a RADIUS response packet with the proper Response Authenticator.
10pub fn build_response_with_auth(
11    mut packet: RadiusPacket,
12    request_authenticator: [u8; 16],
13    secret: &str,
14) -> RadiusPacket {
15    let mut buf = Vec::new();
16    buf.push(packet.code);
17    buf.push(packet.identifier);
18    buf.extend_from_slice(&[0x00, 0x00]); // placeholder for length
19    buf.extend_from_slice(&request_authenticator);
20
21    for attr in &packet.attributes {
22        buf.push(attr.typ);
23        buf.push(attr.len);
24        buf.extend_from_slice(&attr.value);
25    }
26
27    let length = buf.len() as u16;
28    buf[2] = (length >> 8) as u8;
29    buf[3] = (length & 0xFF) as u8;
30
31    buf.extend_from_slice(secret.as_bytes());
32
33    let hash = md5::compute(&buf);
34    let mut authenticator = [0u8; 16];
35    authenticator.copy_from_slice(&hash.0);
36
37    packet.length = length;
38    packet.authenticator = authenticator;
39
40    packet
41}
42
43/// Handles an incoming RADIUS packet and returns a response packet.
44pub fn handle(packet: RadiusPacket, dict: Arc<Dictionary>) -> Result<RadiusPacket, String> {
45    println!("🔍 Handling RADIUS packet ID: {}", packet.identifier);
46
47    for attr in &packet.attributes {
48        let attr_code = attr.typ as u32;
49        match dict.attributes.get(&attr_code) {
50            Some(def) => {
51                let name = &def.name;
52                match std::str::from_utf8(&attr.value) {
53                    Ok(s) => println!("→ {}: {}", name, s.trim()),
54                    Err(_) => println!("→ {}: {:?}", name, attr.value),
55                }
56            }
57            None => println!("→ Unknown Attribute Type {}: {:?}", attr.typ, attr.value),
58        }
59    }
60
61    let mut attributes = vec![
62        RadiusAttribute::reply_message("Access granted via Rust RADIUS server."),
63        RadiusAttribute::session_timeout(3600),
64        RadiusAttribute::idle_timeout(300),
65        RadiusAttribute::wispr_bandwidth_max_up(512_000),
66        RadiusAttribute::wispr_bandwidth_max_down(1_000_000),
67    ];
68
69    // Optional: Echo back username if present
70    if let Some(user_attr) = packet.attributes.iter().find(|a| a.typ == 1) {
71        if let Ok(username) = std::str::from_utf8(&user_attr.value) {
72            attributes.push(RadiusAttribute::user_name(username));
73        }
74    }
75
76    let accept = RadiusPacket::access_accept(packet.identifier, attributes);
77    let response = build_response_with_auth(accept, packet.authenticator, "test123");
78    Ok(response)
79}
80
81
82
83
84/// Verifies an Accounting-Request packet's Request Authenticator
85pub fn verify_accounting_request_authenticator(
86    packet: &[u8],
87    secret: &str,
88    received_auth: [u8; 16],
89) -> bool {
90    if packet.len() < 20 {
91        return false;
92    }
93
94    let mut data = Vec::from(&packet[0..4]);
95data.extend_from_slice(&[0u8; 16]); // ✅ 16 zero bytes as per RFC
96    data.extend_from_slice(&packet[20..]);
97    data.extend_from_slice(secret.as_bytes());
98
99    let hash = md5::compute(&data);
100    hash.0 == received_auth
101}
102
103/// Builds an Accounting-Response packet
104pub fn build_accounting_response(identifier: u8, request_auth: [u8; 16], secret: &str) -> Vec<u8> {
105    let mut buf = vec![5, identifier, 0x00, 0x14]; // Code=5, length=20
106    let mut temp = buf.clone();
107    temp.extend_from_slice(&request_auth);
108    temp.extend_from_slice(secret.as_bytes());
109
110    let hash = md5::compute(&temp);
111    buf.extend_from_slice(&hash.0);
112    buf
113}
114
115/// Serves incoming Accounting-Request packets and responds
116pub async fn serve_accounting_async<F, Fut>(
117    addr: &str,
118    dict: Arc<Dictionary>,
119    secret: &str,
120    handler: F,
121) -> Result<(), Box<dyn std::error::Error>>
122where
123    F: Fn(RadiusPacket) -> Fut + Send + Sync + 'static,
124    Fut: std::future::Future<Output = Result<(), String>> + Send,
125{
126    let socket = UdpSocket::bind(addr).await?;
127    println!("📡 Accounting server listening on {addr}");
128
129    let mut buf = [0u8; 1024];
130    loop {
131        let (len, src) = socket.recv_from(&mut buf).await?;
132        let raw_packet = &buf[..len];
133
134        let packet = match RadiusPacket::from_bytes(raw_packet) {
135            Ok(p) => p,
136            Err(e) => {
137                eprintln!("❌ Failed to parse: {e}");
138                continue;
139            }
140        };
141
142        if !verify_accounting_request_authenticator(raw_packet, secret, packet.authenticator) {
143            eprintln!("🚫 Invalid accounting authenticator.");
144            continue;
145        }
146
147        if let Err(e) = handler(packet.clone()).await {
148            eprintln!("⚠️  Handler error: {e}");
149        }
150
151        let response = build_accounting_response(packet.identifier, packet.authenticator, secret);
152        socket.send_to(&response, src).await?;
153    }
154}