use super::dictionary::{ Dictionary, DictionaryAttribute, DictionaryValue };
use super::error::RadiusError;
use super::radius_packet::{ RadiusAttribute, RadiusMsgType, RadiusPacket, TypeCode };
use hmac::{ Hmac, Mac };
use md5::Md5;
const IGNORE_VERIFY_ATTRIBUTE: &str = "Message-Authenticator";
type HmacMd5 = Hmac<Md5>;
#[derive(Debug)]
pub struct Host {
auth_port: u16,
acct_port: u16,
coa_port: u16,
dictionary: Dictionary
}
impl Host{
pub fn with_dictionary(dictionary: Dictionary) -> Host {
Host {
auth_port: 0,
acct_port: 0,
coa_port: 0,
dictionary
}
}
pub fn set_port(&mut self, msg_type: RadiusMsgType, port: u16) {
match msg_type {
RadiusMsgType::AUTH => self.auth_port = port,
RadiusMsgType::ACCT => self.acct_port = port,
RadiusMsgType::COA => self.coa_port = port,
}
}
#[allow(dead_code)]
pub fn initialise_host(auth_port: u16, acct_port: u16, coa_port: u16, dictionary: Dictionary) -> Host {
Host { auth_port, acct_port, coa_port, dictionary }
}
pub fn create_attribute_by_name(&self, attribute_name: &str, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
RadiusAttribute::create_by_name(&self.dictionary, attribute_name, value).ok_or(RadiusError::MalformedAttributeError { error: format!("Failed to create: {:?} attribute. Check if attribute exists in provided dictionary file", attribute_name) })
}
pub fn create_attribute_by_id(&self, attribute_id: u8, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
RadiusAttribute::create_by_id(&self.dictionary, attribute_id, value).ok_or(RadiusError::MalformedAttributeError { error: format!("Failed to create: attribute with ID {}. Check if attribute exists in provided dictionary file", attribute_id) })
}
pub fn port(&self, code: &TypeCode) -> Option<u16> {
match code {
TypeCode::AccessRequest => Some(self.auth_port),
TypeCode::AccountingRequest => Some(self.acct_port),
TypeCode::CoARequest => Some(self.coa_port),
_ => None
}
}
pub fn dictionary(&self) -> &Dictionary {
&self.dictionary
}
#[allow(dead_code)]
pub fn dictionary_value_by_attr_and_value_name(&self, attr_name: &str, value_name: &str) -> Option<&DictionaryValue> {
self.dictionary.values().iter().find(|&value| value.name() == value_name && value.attribute_name() == attr_name)
}
pub fn dictionary_attribute_by_id(&self, packet_attr_id: u8) -> Option<&DictionaryAttribute> {
self.dictionary.attributes().iter().find(|&attr| attr.code() == packet_attr_id)
}
#[allow(dead_code)]
pub fn dictionary_attribute_by_name(&self, packet_attr_name: &str) -> Option<&DictionaryAttribute> {
self.dictionary.attributes().iter().find(|&attr| attr.name() == packet_attr_name)
}
pub fn initialise_packet_from_bytes(&self, packet: &[u8]) -> Result<RadiusPacket, RadiusError> {
RadiusPacket::initialise_packet_from_bytes(&self.dictionary, packet)
}
pub fn verify_packet_attributes(&self, packet: &[u8]) -> Result<(), RadiusError> {
let _packet_tmp = RadiusPacket::initialise_packet_from_bytes(&self.dictionary, &packet)?;
for packet_attr in _packet_tmp.attributes().iter().filter(|&attr| attr.name() != IGNORE_VERIFY_ATTRIBUTE) {
match self.dictionary_attribute_by_id(packet_attr.id()) {
None => return Err( RadiusError::ValidationError {error: format!("Attribute with ID {} may not exist in provided dictionary file, thus verification failed", packet_attr.id())} ),
Some(_dict_attr) => {
let _dict_attr_data_type = _dict_attr.code_type();
match packet_attr.verify_original_value(_dict_attr_data_type) {
Err(err) => return Err( RadiusError::ValidationError {error: err.to_string()} ),
_ => continue
}
}
}
}
Ok(())
}
pub fn verify_message_authenticator(&self, secret: &str, packet: &[u8]) -> Result<(), RadiusError> {
let mut _packet_tmp = RadiusPacket::initialise_packet_from_bytes(&self.dictionary, &packet)?;
let original_msg_auth = _packet_tmp.message_authenticator()?.to_vec();
let zeroed_authenticator = [0; 16];
_packet_tmp.override_message_authenticator(zeroed_authenticator.to_vec())?;
let mut calculated_msg_auth = HmacMd5::new_from_slice(secret.as_bytes()).map_err(|error| RadiusError::ValidationError { error: error.to_string() })?;
calculated_msg_auth.update(&_packet_tmp.to_bytes());
match calculated_msg_auth.verify_slice(&original_msg_auth) {
Ok(()) => Ok(()),
Err(_) => Err( RadiusError::ValidationError {error: String::from("Packet Message-Authenticator mismatch")} )
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::dictionary::SupportedAttributeTypes;
#[test]
fn test_get_dictionary_value_by_attr_and_value_name() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let dict_value = host.dictionary_value_by_attr_and_value_name("Service-Type", "Login-User").unwrap();
assert_eq!("Service-Type", dict_value.attribute_name());
assert_eq!("Login-User", dict_value.name());
assert_eq!("1", dict_value.value());
}
#[test]
fn test_get_dictionary_value_by_attr_and_value_name_error() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let dict_value = host.dictionary_value_by_attr_and_value_name("Service-Type", "Lin-User");
assert_eq!(None, dict_value);
}
#[test]
fn test_get_dictionary_attribute_by_id() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let dict_attr = host.dictionary_attribute_by_id(80).unwrap();
assert_eq!("Message-Authenticator", dict_attr.name());
assert_eq!(80, dict_attr.code());
assert_eq!(&Some(SupportedAttributeTypes::ByteString), dict_attr.code_type());
}
#[test]
fn test_get_dictionary_attribute_by_id_error() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let dict_attr = host.dictionary_attribute_by_id(255);
assert_eq!(None, dict_attr);
}
#[test]
fn test_verify_packet_attributes() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let packet_bytes = [4, 43, 0, 86, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
match host.verify_packet_attributes(&packet_bytes) {
Err(_err) => {
assert!(false)
},
_ => assert!(true)
}
}
#[test]
fn test_verify_packet_attributes_fail() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let packet_bytes = [4, 43, 0, 85, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 5, 192, 168, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
match host.verify_packet_attributes(&packet_bytes) {
Err(err) => {
assert_eq!(err.to_string(), String::from("Verification failed for incoming Radius packet: Attribute in Radius packet is malformed: invalid IPv4 bytes"))
},
_ => assert!(false)
}
}
#[test]
fn test_verify_message_authenticator_valid() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let secret = "secret";
let packet_bytes = [1, 120, 0, 185, 49, 79, 108, 150, 27, 203, 166, 51, 193, 68, 15, 76, 208, 114, 171, 48, 1, 9, 116, 101, 115, 116, 105, 110, 103, 80, 18, 164, 201, 132, 0, 209, 101, 200, 189, 252, 251, 120, 224, 74, 190, 232, 197, 2, 66, 85, 125, 163, 190, 40, 210, 235, 231, 112, 96, 7, 94, 27, 95, 241, 63, 23, 81, 25, 136, 36, 209, 238, 119, 131, 113, 118, 14, 160, 16, 94, 184, 143, 37, 193, 138, 124, 238, 85, 197, 21, 17, 206, 158, 87, 132, 239, 59, 82, 183, 175, 54, 124, 138, 5, 245, 166, 195, 181, 106, 41, 31, 129, 183, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 6, 6, 0, 0, 0, 2, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
match host.verify_message_authenticator(&secret, &packet_bytes) {
Err(_err) => {
assert!(false)
},
_ => assert!(true)
}
}
#[test]
fn test_verify_message_authenticator_wo_authenticator() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let secret = "secret";
let packet_bytes = [4, 43, 0, 86, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
match host.verify_message_authenticator(&secret, &packet_bytes) {
Err(err) => {
assert_eq!(err.to_string(), String::from("Radius packet is malformed: Message-Authenticator attribute not found in packet"))
},
_ => assert!(false)
}
}
#[test]
fn test_verify_message_authenticator_invalid() {
let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
let host = Host::initialise_host(1812, 1813, 3799, dictionary);
let secret = "secret";
let packet_bytes = [1, 94, 0, 190, 241, 228, 181, 142, 185, 194, 157, 205, 159, 0, 91, 199, 171, 119, 68, 44, 1, 9, 116, 101, 115, 116, 105, 110, 103, 80, 23, 109, 101, 115, 115, 97, 103, 101, 45, 97, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 2, 66, 167, 81, 185, 84, 173, 104, 91, 10, 145, 109, 156, 169, 227, 109, 100, 76, 86, 227, 61, 253, 129, 35, 109, 115, 54, 140, 66, 106, 193, 70, 145, 39, 106, 105, 142, 215, 21, 166, 142, 80, 145, 217, 202, 252, 172, 33, 17, 12, 159, 105, 157, 144, 221, 221, 94, 48, 158, 22, 62, 191, 16, 177, 137, 131, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 6, 6, 0, 0, 0, 2, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
match host.verify_message_authenticator(&secret, &packet_bytes) {
Err(err) => {
assert_eq!(err.to_string(), String::from("Verification failed for incoming Radius packet: Packet Message-Authenticator mismatch"))
},
_ => assert!(false)
}
}
}