From 2a7f8531fd12cf13c50fa346c4e97f6e02002d24 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sun, 1 Mar 2015 15:08:36 +0100 Subject: [PATCH] Add PLAIN authentication mecanism --- src/client/authentication/mod.rs | 22 +++++++++++ src/client/authentication/plain.rs | 39 +++++++++++++++++++ src/client/mod.rs | 1 + src/extension.rs | 60 +++++++++++++++++------------- src/lib.rs | 2 +- 5 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 src/client/authentication/mod.rs create mode 100644 src/client/authentication/plain.rs diff --git a/src/client/authentication/mod.rs b/src/client/authentication/mod.rs new file mode 100644 index 0000000..7a1ee2e --- /dev/null +++ b/src/client/authentication/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2014 Alexis Mousset. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! PLAIN authentication mecanism + +pub mod plain; + +/// Trait representing an authentication mecanism +pub trait AuthenticationMecanism { + /// Create an authentication + fn new(username: String, password: String) -> Self; + /// Initial response if available + fn initial_response(&self) -> Option; + /// Response to the given challenge + fn response(&self, challenge: &str) -> String; +} diff --git a/src/client/authentication/plain.rs b/src/client/authentication/plain.rs new file mode 100644 index 0000000..45ca155 --- /dev/null +++ b/src/client/authentication/plain.rs @@ -0,0 +1,39 @@ +// Copyright 2014 Alexis Mousset. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Authentication mecanisms + +use common::NUL; +use client::authentication::AuthenticationMecanism; + +struct Plain { + identity: String, + username: String, + password: String, +} + +impl AuthenticationMecanism for Plain { + fn new(username: String, password: String) -> Plain { + Plain { + identity: "".to_string(), + username: username, + password: password, + } + } + + fn initial_response(&self) -> Option { + Some(self.response("")) + } + + fn response(&self, challenge: &str) -> String { + // We do not need a challenge in PLAIN authentication + let _ = challenge; + format!("{}{}{}{}{}", self.identity, NUL, self.username, NUL, self.password) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index cc160e7..a9fcc63 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -33,6 +33,7 @@ use sendable_email::SendableEmail; pub mod server_info; pub mod connecter; pub mod stream; +pub mod authentication; /// Represents the configuration of a client #[derive(Debug)] diff --git a/src/extension.rs b/src/extension.rs index 9e58545..1d74b3a 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -41,28 +41,32 @@ pub enum Extension { CramMd5Authentication, } -impl FromStr for Extension { +impl Extension { // TODO: check RFC - type Err = &'static str; - fn from_str(s: &str) -> Result { - let splitted : Vec<&str> = s.splitn(1, ' ').collect(); - match splitted.len() { - 1 => match splitted[0] { - "8BITMIME" => Ok(EightBitMime), - "SMTPUTF8" => Ok(SmtpUtfEight), - "STARTTLS" => Ok(StartTls), - _ => Err("Unknown extension"), - }, - 2 => match (splitted[0], splitted[1].parse::()) { - ("SIZE", Ok(size)) => Ok(Size(size)), - _ => Err("Can't parse size"), - }, - _ => Err("Empty extension?"), + fn from_str(s: &str) -> Result, &'static str> { + let splitted : Vec<&str> = s.split(' ').collect(); + match (splitted[0], splitted.len()) { + ("8BITMIME", 1) => Ok(vec!(EightBitMime)), + ("SMTPUTF8", 1) => Ok(vec!(SmtpUtfEight)), + ("STARTTLS", 1) => Ok(vec!(StartTls)), + ("SIZE", 2) => match splitted[1].parse::() { + Ok(size) => Ok(vec!(Size(size))), + _ => Err("Can't parse size"), + }, + ("AUTH", _) => { + let mut mecanisms: Vec = vec!(); + for &mecanism in &splitted[1..] { + match mecanism { + "PLAIN" => mecanisms.push(PlainAuthentication), + _ => (), + } + } + Ok(mecanisms) + }, + _ => Err("Unknown extension"), } } -} -impl Extension { /// Checks if the ESMTP keyword is the same pub fn same_extension_as(&self, other: &Extension) -> bool { if self == other { @@ -76,11 +80,11 @@ impl Extension { /// Parses supported ESMTP features pub fn parse_esmtp_response(message: &str) -> Vec { - let mut esmtp_features = Vec::new(); + let mut esmtp_features: Vec = Vec::new(); for line in message.split(CRLF) { if let Ok(Response{code: 250, message}) = line.parse::() { - if let Ok(keyword) = message.unwrap().as_slice().parse::() { - esmtp_features.push(keyword); + if let Ok(keywords) = Extension::from_str(message.unwrap().as_slice()) { + esmtp_features.push_all(&keywords); }; } } @@ -102,12 +106,16 @@ mod test { #[test] fn test_from_str() { - assert_eq!("8BITMIME".parse::(), Ok(Extension::EightBitMime)); - assert_eq!("SIZE 42".parse::(), Ok(Extension::Size(42))); - assert!("SIZ 42".parse::().is_err()); - assert!("SIZE 4a2".parse::().is_err()); + assert_eq!(Extension::from_str("8BITMIME"), Ok(vec!(Extension::EightBitMime))); + assert_eq!(Extension::from_str("SIZE 42"), Ok(vec!(Extension::Size(42)))); + assert_eq!(Extension::from_str("AUTH PLAIN"), Ok(vec!(Extension::PlainAuthentication))); + assert_eq!(Extension::from_str("AUTH PLAIN CRAM-MD5"), Ok(vec!(Extension::PlainAuthentication))); + assert_eq!(Extension::from_str("AUTH CRAM-MD5 PLAIN"), Ok(vec!(Extension::PlainAuthentication))); + assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec!(Extension::PlainAuthentication))); + assert!(Extension::from_str("SIZ 42").is_err()); + assert!(Extension::from_str("SIZE 4a2").is_err()); // TODO: accept trailing spaces ? - assert!("SIZE 42 ".parse::().is_err()); + assert!(Extension::from_str("SIZE 42 ").is_err()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index dce0a46..24be3a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,7 +128,7 @@ #![deny(missing_docs)] -#![feature(plugin,core,old_io,io)] +#![feature(plugin,core,old_io,io,collections)] #[macro_use] extern crate log; extern crate time;