use crate::protocol::dictionary::Dictionary;
use crate::protocol::error::RadiusError;
use crate::protocol::host::Host;
use crate::protocol::radius_packet::{ RadiusAttribute, RadiusPacket, RadiusMsgType, TypeCode };
use crypto::digest::Digest;
use crypto::hmac::Hmac;
use crypto::mac::Mac;
use crypto::md5::Md5;
use log::debug;
#[derive(Debug)]
pub struct Client {
host: Host,
server: String,
secret: String,
retries: u16,
timeout: u16,
}
impl Client {
pub fn with_dictionary(dictionary: Dictionary) -> Client {
let host = Host::with_dictionary(dictionary);
Client {
host: host,
server: String::from(""),
secret: String::from(""),
retries: 1,
timeout: 2
}
}
pub fn set_server(mut self, server: String) -> Client {
self.server = server;
self
}
pub fn set_secret(mut self, secret: String) -> Client {
self.secret = secret;
self
}
pub fn set_port(mut self, msg_type: RadiusMsgType, port: u16) -> Client {
self.host.set_port(msg_type, port);
self
}
pub fn set_retries(mut self, retries: u16) -> Client {
self.retries = retries;
self
}
pub fn set_timeout(mut self, timeout: u16) -> Client {
self.timeout = timeout;
self
}
pub fn port(&self, code: &TypeCode) -> Option<u16> {
self.host.port(code)
}
pub fn server(&self) -> &str {
&self.server
}
pub fn retries(&self) -> u16 {
self.retries
}
pub fn timeout(&self) -> u16 {
self.timeout
}
pub fn create_packet(&self, code: TypeCode, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
RadiusPacket::initialise_packet(code, attributes)
}
pub fn create_auth_packet(&self, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
RadiusPacket::initialise_packet(TypeCode::AccessRequest, attributes)
}
pub fn create_acct_packet(&self, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
RadiusPacket::initialise_packet(TypeCode::AccountingRequest, attributes)
}
pub fn create_coa_packet(&self, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
RadiusPacket::initialise_packet(TypeCode::CoARequest, attributes)
}
pub fn create_attribute_by_name(&self, attribute_name: &str, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
self.host.create_attribute_by_name(attribute_name, value)
}
pub fn create_attribute_by_id(&self, attribute_id: u8, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
self.host.create_attribute_by_id(attribute_id, value)
}
pub fn generate_message_hash(&self, packet: &mut RadiusPacket) -> Vec<u8> {
let mut hash = Hmac::new(Md5::new(), self.secret.as_bytes());
hash.input(&packet.to_bytes());
hash.result().code().to_vec()
}
pub fn radius_attr_original_string_value(&self, attribute: &RadiusAttribute) -> Result<String, RadiusError> {
let dict_attr = self.host.dictionary_attribute_by_id(attribute.id()).ok_or_else(|| RadiusError::MalformedAttributeError {error: format!("No attribute with ID: {} found in dictionary", attribute.id())} )?;
attribute.original_string_value(dict_attr.code_type())
}
pub fn radius_attr_original_integer_value(&self, attribute: &RadiusAttribute) -> Result<u64, RadiusError> {
let dict_attr = self.host.dictionary_attribute_by_id(attribute.id()).ok_or_else(|| RadiusError::MalformedAttributeError {error: format!("No attribute with ID: {} found in dictionary", attribute.id())} )?;
attribute.original_integer_value(dict_attr.code_type())
}
pub fn initialise_packet_from_bytes(&self, reply: &[u8]) -> Result<RadiusPacket, RadiusError> {
self.host.initialise_packet_from_bytes(reply)
}
pub fn verify_reply(&self, request: &RadiusPacket, reply: &[u8]) -> Result<(), RadiusError> {
if request.id() != reply[1] {
return Err( RadiusError::ValidationError { error: String::from("Packet identifier mismatch") } )
};
let mut md5_hasher = Md5::new();
let mut hash = [0; 16];
md5_hasher.input(&reply[0..4]);
md5_hasher.input(&request.authenticator());
md5_hasher.input(&reply[20..]);
md5_hasher.input(&self.secret.as_bytes());
md5_hasher.result(&mut hash);
debug!("{:?}", &hash);
debug!("{:?}", &reply[4..20]);
if hash == reply[4..20] {
Ok(())
} else {
Err( RadiusError::ValidationError { error: String::from("Packet authenticator mismatch") } )
}
}
pub fn verify_message_authenticator(&self, packet: &[u8]) -> Result<(), RadiusError> {
self.host.verify_message_authenticator(&self.secret, &packet)
}
pub fn verify_packet_attributes(&self, packet: &[u8]) -> Result<(), RadiusError> {
self.host.verify_packet_attributes(&packet)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::integer_to_bytes;
#[test]
fn test_get_radius_attr_original_string_value() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let client = Client::with_dictionary(dictionary)
.set_server(String::from("127.0.0.1"))
.set_secret(String::from("secret"))
.set_retries(1)
.set_timeout(2)
.set_port(RadiusMsgType::AUTH, 1812)
.set_port(RadiusMsgType::ACCT, 1813)
.set_port(RadiusMsgType::COA, 3799);
let attributes = vec![client.create_attribute_by_name("User-Name", String::from("testing").into_bytes()).unwrap()];
match client.radius_attr_original_string_value(&attributes[0]) {
Ok(value) => assert_eq!(String::from("testing"), value),
_ => assert!(false)
}
}
#[test]
fn test_get_radius_attr_original_string_value_error() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let client = Client::with_dictionary(dictionary)
.set_server(String::from("127.0.0.1"))
.set_secret(String::from("secret"))
.set_retries(1)
.set_timeout(2)
.set_port(RadiusMsgType::AUTH, 1812)
.set_port(RadiusMsgType::ACCT, 1813)
.set_port(RadiusMsgType::COA, 3799);
let invalid_string = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
let attributes = vec![client.create_attribute_by_name("User-Name", invalid_string).unwrap()];
match client.radius_attr_original_string_value(&attributes[0]) {
Ok(_) => assert!(false),
Err(error) => assert_eq!(String::from("Radius packet attribute is malformed"), error.to_string())
}
}
#[test]
fn test_get_radius_attr_original_integer_value() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let client = Client::with_dictionary(dictionary)
.set_server(String::from("127.0.0.1"))
.set_secret(String::from("secret"))
.set_retries(1)
.set_timeout(2)
.set_port(RadiusMsgType::AUTH, 1812)
.set_port(RadiusMsgType::ACCT, 1813)
.set_port(RadiusMsgType::COA, 3799);
let attributes = vec![client.create_attribute_by_name("NAS-Port-Id", integer_to_bytes(0)).unwrap()];
match client.radius_attr_original_integer_value(&attributes[0]) {
Ok(value) => assert_eq!(0, value),
_ => assert!(false)
}
}
#[test]
fn test_get_radius_attr_original_integer_value_error() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let client = Client::with_dictionary(dictionary)
.set_server(String::from("127.0.0.1"))
.set_secret(String::from("secret"))
.set_retries(1)
.set_timeout(2)
.set_port(RadiusMsgType::AUTH, 1812)
.set_port(RadiusMsgType::ACCT, 1813)
.set_port(RadiusMsgType::COA, 3799);
let invalid_integer = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
let attributes = vec![client.create_attribute_by_name("NAS-Port-Id", invalid_integer).unwrap()];
match client.radius_attr_original_integer_value(&attributes[0]) {
Ok(_) => assert!(false),
Err(error) => assert_eq!(String::from("Radius packet attribute is malformed"), error.to_string())
}
}
}