1use crate::protocol::dictionary::Dictionary;
5use crate::protocol::error::RadiusError;
6use crate::protocol::host::Host;
7use crate::protocol::radius_packet::{ RadiusAttribute, RadiusPacket, RadiusMsgType, TypeCode };
8
9use hmac::{ Hmac, Mac };
10use md5::{ Digest, Md5 };
11
12
13type HmacMd5 = Hmac<Md5>;
14
15
16#[derive(Debug)]
17pub struct Client {
19 host: Host,
20 server: String,
21 secret: String,
22 retries: u16,
23 timeout: u16,
24}
25
26impl Client {
27 pub fn with_dictionary(dictionary: Dictionary) -> Client {
32 let host = Host::with_dictionary(dictionary);
33
34 Client {
35 host,
36 server: String::from(""),
37 secret: String::from(""),
38 retries: 1,
39 timeout: 2
40 }
41 }
42
43 pub fn set_server(mut self, server: String) -> Client {
47 self.server = server;
48 self
49 }
50
51 pub fn set_secret(mut self, secret: String) -> Client {
55 self.secret = secret;
56 self
57 }
58
59 pub fn set_port(mut self, msg_type: RadiusMsgType, port: u16) -> Client {
63 self.host.set_port(msg_type, port);
64 self
65 }
66
67 pub fn set_retries(mut self, retries: u16) -> Client {
71 self.retries = retries;
72 self
73 }
74
75 pub fn set_timeout(mut self, timeout: u16) -> Client {
79 self.timeout = timeout;
80 self
81 }
82 pub fn port(&self, code: &TypeCode) -> Option<u16> {
86 self.host.port(code)
87 }
88
89 pub fn server(&self) -> &str {
91 &self.server
92 }
93
94 pub fn secret(&self) -> &str {
96 &self.secret
97 }
98
99 pub fn retries(&self) -> u16 {
101 self.retries
102 }
103
104 pub fn timeout(&self) -> u16 {
106 self.timeout
107 }
108
109 pub fn create_packet(&self, code: TypeCode) -> RadiusPacket {
113 RadiusPacket::initialise_packet(code)
114 }
115
116 pub fn create_auth_packet(&self) -> RadiusPacket {
120 RadiusPacket::initialise_packet(TypeCode::AccessRequest)
121 }
122
123 pub fn create_acct_packet(&self) -> RadiusPacket {
127 RadiusPacket::initialise_packet(TypeCode::AccountingRequest)
128 }
129
130 pub fn create_coa_packet(&self) -> RadiusPacket {
134 RadiusPacket::initialise_packet(TypeCode::CoARequest)
135 }
136
137 pub fn create_attribute_by_name(&self, attribute_name: &str, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
158 self.host.create_attribute_by_name(attribute_name, value)
159 }
160
161 pub fn create_attribute_by_id(&self, attribute_id: u8, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
182 self.host.create_attribute_by_id(attribute_id, value)
183 }
184
185 #[deprecated(since="0.4.1", note="This function may work incorrectly, please use radius_packet's `generate_message_authenticator` instead")]
191 pub fn generate_message_hash(&self, packet: &mut RadiusPacket) -> Vec<u8> {
192 let mut hash = HmacMd5::new_from_slice(self.secret.as_bytes()).unwrap();
193
194 hash.update(&packet.to_bytes());
195 hash.finalize().into_bytes().to_vec()
196 }
197
198 pub fn radius_attr_original_string_value(&self, attribute: &RadiusAttribute) -> Result<String, RadiusError> {
202 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())} )?;
203 attribute.original_string_value(dict_attr.code_type())
204 }
205
206 pub fn radius_attr_original_integer_value(&self, attribute: &RadiusAttribute) -> Result<u32, RadiusError> {
210 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())} )?;
211 attribute.original_integer_value(dict_attr.code_type())
212 }
213
214 pub fn initialise_packet_from_bytes(&self, reply: &[u8]) -> Result<RadiusPacket, RadiusError> {
216 self.host.initialise_packet_from_bytes(reply)
217 }
218
219 pub fn verify_reply(&self, request: &RadiusPacket, reply: &[u8]) -> Result<(), RadiusError> {
221 if reply.is_empty() {
222 return Err( RadiusError::ValidationError { error: String::from("Empty reply") } )
223 }
224 if request.id() != reply[1] {
225 return Err( RadiusError::ValidationError { error: String::from("Packet identifier mismatch") } )
226 };
227
228 let mut md5_hasher = Md5::new();
229
230 md5_hasher.update(&reply[0..4]); md5_hasher.update(&request.authenticator()); md5_hasher.update(&reply[20..]); md5_hasher.update(&self.secret.as_bytes()); if md5_hasher.finalize().as_slice() == &reply[4..20] {
236 Ok(())
237 } else {
238 Err( RadiusError::ValidationError { error: String::from("Packet authenticator mismatch") } )
239 }
240 }
241
242 pub fn verify_message_authenticator(&self, packet: &[u8]) -> Result<(), RadiusError> {
244 self.host.verify_message_authenticator(&self.secret, &packet)
245 }
246
247 pub fn verify_packet_attributes(&self, packet: &[u8]) -> Result<(), RadiusError> {
249 self.host.verify_packet_attributes(&packet)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::tools::integer_to_bytes;
257
258 #[test]
259 fn test_get_radius_attr_original_string_value() {
260 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
261 let client = Client::with_dictionary(dictionary)
262 .set_server(String::from("127.0.0.1"))
263 .set_secret(String::from("secret"))
264 .set_retries(1)
265 .set_timeout(2)
266 .set_port(RadiusMsgType::AUTH, 1812)
267 .set_port(RadiusMsgType::ACCT, 1813)
268 .set_port(RadiusMsgType::COA, 3799);
269
270 let attributes = vec![client.create_attribute_by_name("User-Name", String::from("testing").into_bytes()).unwrap()];
271
272 match client.radius_attr_original_string_value(&attributes[0]) {
273 Ok(value) => assert_eq!(String::from("testing"), value),
274 _ => assert!(false)
275 }
276 }
277
278 #[test]
279 fn test_get_radius_attr_original_string_value_error() {
280 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
281 let client = Client::with_dictionary(dictionary)
282 .set_server(String::from("127.0.0.1"))
283 .set_secret(String::from("secret"))
284 .set_retries(1)
285 .set_timeout(2)
286 .set_port(RadiusMsgType::AUTH, 1812)
287 .set_port(RadiusMsgType::ACCT, 1813)
288 .set_port(RadiusMsgType::COA, 3799);
289
290 let invalid_string = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
291 let attributes = vec![client.create_attribute_by_name("User-Name", invalid_string).unwrap()];
292
293 match client.radius_attr_original_string_value(&attributes[0]) {
294 Ok(_) => assert!(false),
295 Err(error) => assert_eq!(String::from("Attribute in Radius packet is malformed: invalid ASCII bytes"), error.to_string())
296 }
297 }
298
299 #[test]
300 fn test_get_radius_attr_original_integer_value() {
301 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
302 let client = Client::with_dictionary(dictionary)
303 .set_server(String::from("127.0.0.1"))
304 .set_secret(String::from("secret"))
305 .set_retries(1)
306 .set_timeout(2)
307 .set_port(RadiusMsgType::AUTH, 1812)
308 .set_port(RadiusMsgType::ACCT, 1813)
309 .set_port(RadiusMsgType::COA, 3799);
310
311 let attributes = vec![client.create_attribute_by_name("NAS-Port-Id", integer_to_bytes(0)).unwrap()];
312
313 match client.radius_attr_original_integer_value(&attributes[0]) {
314 Ok(value) => assert_eq!(0, value),
315 _ => assert!(false)
316 }
317 }
318
319 #[test]
320 fn test_get_radius_attr_original_integer_value_error() {
321 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
322 let client = Client::with_dictionary(dictionary)
323 .set_server(String::from("127.0.0.1"))
324 .set_secret(String::from("secret"))
325 .set_retries(1)
326 .set_timeout(2)
327 .set_port(RadiusMsgType::AUTH, 1812)
328 .set_port(RadiusMsgType::ACCT, 1813)
329 .set_port(RadiusMsgType::COA, 3799);
330
331 let invalid_integer = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
332 let attributes = vec![client.create_attribute_by_name("NAS-Port-Id", invalid_integer).unwrap()];
333
334 match client.radius_attr_original_integer_value(&attributes[0]) {
335 Ok(_) => assert!(false),
336 Err(error) => assert_eq!(String::from("Attribute in Radius packet is malformed: invalid Integer bytes"), error.to_string())
337 }
338 }
339
340 #[test]
341 fn test_verify_empty_reply() {
342 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
343 let attributes = vec![ RadiusAttribute::create_by_name(&dictionary, "Calling-Station-Id", String::from("00-01-24-80-B3-9C").into_bytes()).unwrap() ];
344
345 let client = Client::with_dictionary(dictionary)
346 .set_server(String::from("127.0.0.1"))
347 .set_secret(String::from("secret"))
348 .set_retries(1)
349 .set_timeout(2)
350 .set_port(RadiusMsgType::AUTH, 1812)
351 .set_port(RadiusMsgType::ACCT, 1813)
352 .set_port(RadiusMsgType::COA, 3799);
353
354 let authenticator = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
355 let reply = vec![];
356 let mut radius_packet = RadiusPacket::initialise_packet(TypeCode::AccountingRequest);
357
358 radius_packet.set_attributes(attributes);
359 radius_packet.override_id(43);
360 radius_packet.override_authenticator(authenticator);
361
362 match client.verify_reply(&radius_packet, &reply) {
363 Err(error) => assert_eq!(String::from("Verification failed for incoming Radius packet: Empty reply"), error.to_string()),
364 _ => assert!(false)
365 }
366 }
367
368 #[test]
369 fn test_verify_malformed_reply() {
370 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
371 let attributes = vec![ RadiusAttribute::create_by_name(&dictionary, "Calling-Station-Id", String::from("00-01-24-80-B3-9C").into_bytes()).unwrap() ];
372
373 let client = Client::with_dictionary(dictionary)
374 .set_server(String::from("127.0.0.1"))
375 .set_secret(String::from("secret"))
376 .set_retries(1)
377 .set_timeout(2)
378 .set_port(RadiusMsgType::AUTH, 1812)
379 .set_port(RadiusMsgType::ACCT, 1813)
380 .set_port(RadiusMsgType::COA, 3799);
381
382 let authenticator = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
383 let reply = vec![43, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
384 let mut radius_packet = RadiusPacket::initialise_packet(TypeCode::AccountingRequest);
385
386 radius_packet.set_attributes(attributes);
387 radius_packet.override_id(43);
388 radius_packet.override_authenticator(authenticator);
389
390 match client.verify_reply(&radius_packet, &reply) {
391 Err(error) => assert_eq!(String::from("Verification failed for incoming Radius packet: Packet identifier mismatch"), error.to_string()),
392 _ => assert!(false)
393 }
394 }
395
396 #[test]
397 fn test_verify_reply() {
398 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
399 let authenticator = vec![152, 137, 115, 14, 56, 250, 103, 56, 57, 57, 104, 246, 226, 80, 71, 167];
400 let attributes = vec![ RadiusAttribute::create_by_name(&dictionary, "User-Name", String::from("testing").into_bytes()).unwrap() ];
401
402 let reply = vec![2, 220, 0, 52, 165, 196, 239, 87, 197, 230, 219, 74, 148, 177, 209, 155, 35, 36, 236, 63, 6, 6, 0, 0, 0, 2, 8, 6, 192, 168, 0, 1, 97, 20, 0, 64, 252, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
403 let mut radius_packet = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
404 radius_packet.set_attributes(attributes);
405 radius_packet.override_id(220);
406 radius_packet.override_authenticator(authenticator);
407
408 let client = Client::with_dictionary(dictionary)
409 .set_server(String::from("127.0.0.1"))
410 .set_secret(String::from("secret"))
411 .set_retries(1)
412 .set_timeout(2)
413 .set_port(RadiusMsgType::AUTH, 1812)
414 .set_port(RadiusMsgType::ACCT, 1813)
415 .set_port(RadiusMsgType::COA, 3799);
416
417 match client.verify_reply(&radius_packet, &reply) {
418 Err(_error) => assert!(false),
419 _ => assert!(true)
420 }
421 }
422
423 #[test]
424 fn test_generate_message_hash() {
425 let expected_message_autthenticator = vec![85, 134, 2, 170, 83, 101, 202, 79, 109, 163, 59, 12, 66, 170, 183, 220];
426
427 let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
428 let authenticator = vec![152, 137, 115, 14, 56, 250, 103, 56, 57, 57, 104, 246, 226, 80, 71, 167];
429 let attributes = vec![
430 RadiusAttribute::create_by_name(&dictionary, "User-Name", String::from("testing").into_bytes()).unwrap(),
431 RadiusAttribute::create_by_name(&dictionary, "Message-Authenticator", [0;16].to_vec()).unwrap()
432 ];
433
434 let mut radius_packet = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
435 radius_packet.set_attributes(attributes);
436 radius_packet.override_id(220);
437 radius_packet.override_authenticator(authenticator);
438
439 let client = Client::with_dictionary(dictionary)
440 .set_server(String::from("127.0.0.1"))
441 .set_secret(String::from("secret"))
442 .set_retries(1)
443 .set_timeout(2)
444 .set_port(RadiusMsgType::AUTH, 1812)
445 .set_port(RadiusMsgType::ACCT, 1813)
446 .set_port(RadiusMsgType::COA, 3799);
447
448 let generated_message_authenticator = client.generate_message_hash(&mut radius_packet);
449
450 assert_eq!(expected_message_autthenticator, generated_message_authenticator)
451 }
452}