diff --git a/src/smtp/client.rs b/src/smtp/client.rs index f6421e1..89026f0 100644 --- a/src/smtp/client.rs +++ b/src/smtp/client.rs @@ -11,14 +11,16 @@ email_client.send_mail("user@example.org", [&"user@localhost"], "Message content # TODO +Think about RFC compliance in the SMTP library. + + + Support ESMTP : Parse server answer, and manage mail and rcpt options. * Client options: `mail_options` and `rcpt_options` lists * Server options: helo/ehlo, parse and store ehlo response -Manage errors - Support SSL/TLS */ @@ -32,15 +34,9 @@ use std::io::net::tcp::TcpStream; use std::io::net::addrinfo::get_host_addresses; use common::{SMTP_PORT, CRLF}; use commands; -use commands::{Command, SmtpCommand}; - -// // Define smtp_fail! and smtp_success! -// macro_rules! smtp_fail( -// ($command:expr $code:ident $message:expr) => ( -// fail!("{} failed: {:u} {:s}", $command, $code, $message); -// ); -// ) +use commands::{Command, SmtpCommand, EhloKeyword}; +// Define smtp_fail! and smtp_success! /// Contains an SMTP reply, with separed code and message #[deriving(Eq,Clone)] @@ -81,7 +77,7 @@ pub struct SmtpServerInfo { /// Does the server supports ESMTP does_esmtp: bool, /// ESMTP features supported by the server - esmtp_features: Option<~[~str]> + esmtp_features: Option<~[EhloKeyword]> } @@ -112,16 +108,7 @@ impl SmtpClient { } - -// use std::io::{stdin, BufferedReader}; -// fn main() { -// let mut stdin = BufferedReader::new(stdin()); -// for line in stdin.lines() { -// println!("{}", line); -// } -// } - - +// pub fn does_esmtp_feature(keyword: EhloKeyword) // fn parse_ehello_or_hello_response(response: &str) { // // split // } @@ -195,9 +182,14 @@ impl SmtpClient { } } + /// Print an SMTP response as info + fn smtp_success(&mut self, response: SmtpResponse) { + info!("{:u} {:s}", response.code, response.message); + } + /// Send a QUIT command and end the program fn smtp_fail(&mut self, command: ~str, response: SmtpResponse) { - self.send_command(commands::Quit, None); + self.send_command(commands::QUIT, None); fail!("{} failed: {:u} {:s}", command, response.code, response.message); } @@ -207,21 +199,22 @@ impl SmtpClient { // Connect match self.connect().with_code([220]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"CONNECT", response) } - // Ehello or Hello - match self.send_command(commands::Ehello, Some(my_hostname.clone())).with_code([250, 500]) { + // Extended Hello or Hello + match self.send_command(commands::EHLO, Some(my_hostname.clone())).with_code([250, 500]) { Ok(SmtpResponse{code: 250, message: message}) => { self.server_info = Some(SmtpServerInfo{name: message.clone(), does_esmtp: true, esmtp_features: None}); - info!("{:u} {:s}", 250u, message); + self.smtp_success(SmtpResponse{code: 250u, message: message}); }, - Ok(SmtpResponse{code: code, message: message}) => { - self.server_info = Some(SmtpServerInfo{name: message.clone(), does_esmtp: false, esmtp_features: None}); - info!("{:u} {:s}", code, message); - match self.send_command(commands::Ehello, Some(my_hostname.clone())).with_code([250]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + Ok(..) => { + match self.send_command(commands::HELO, Some(my_hostname.clone())).with_code([250]) { + Ok(response) => { + self.server_info = Some(SmtpServerInfo{name: response.message.clone(), does_esmtp: false, esmtp_features: None}); + self.smtp_success(response); + }, Err(response) => self.smtp_fail(~"HELO", response) } }, @@ -229,34 +222,34 @@ impl SmtpClient { } // Mail - match self.send_command(commands::Mail, Some(from_addr.to_owned())).with_code([250]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + match self.send_command(commands::MAIL, Some(from_addr.to_owned())).with_code([250]) { + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"MAIL", response) } // Recipient for &to_addr in to_addrs.iter() { - match self.send_command(commands::Recipient, Some(to_addr.to_owned())).with_code([250]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + match self.send_command(commands::RCPT, Some(to_addr.to_owned())).with_code([250]) { + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"RCPT", response) } } // Data - match self.send_command(commands::Data, None).with_code([354]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + match self.send_command(commands::DATA, None).with_code([354]) { + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"DATA", response) } // Message content match self.send_message(message.to_owned()).with_code([250]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"MESSAGE", response) } // Quit - match self.send_command(commands::Quit, None).with_code([221]) { - Ok(response) => info!("{:u} {:s}", response.code, response.message), + match self.send_command(commands::QUIT, None).with_code([221]) { + Ok(response) => self.smtp_success(response), Err(response) => self.smtp_fail(~"DATA", response) } } diff --git a/src/smtp/commands.rs b/src/smtp/commands.rs index 7ca4dce..9f555c2 100644 --- a/src/smtp/commands.rs +++ b/src/smtp/commands.rs @@ -5,8 +5,6 @@ */ use std::fmt; -// use std::from_str::FromStr; -// use std::to_str::ToStr; use std::io; /* @@ -30,77 +28,77 @@ use std::io; #[deriving(Eq,Clone)] pub enum Command { /// Hello command - Hello, - /// Ehello command - Ehello, + HELO, + /// Extended Hello command + EHLO, /// Mail command - Mail, + MAIL, /// Recipient command - Recipient, + RCPT, /// Data command - Data, + DATA, /// Reset command - Reset, - /// SendMail command - SendMail, - /// SendOrMail command - SendOrMail, - /// SendAndMail command - SendAndMail, + RSET, + /// Send command, deprecated in RFC 5321 + SEND, + /// Send Or Mail command, deprecated in RFC 5321 + SOML, + /// Send And Mail command, deprecated in RFC 5321 + SAML, /// Verify command - Verify, + VRFY, /// Expand command - Expand, + EXPN, /// Help command - Help, + HELP, /// Noop command - Noop, - /// Quit commandopenclassroom vim - Quit, + NOOP, + /// Quit command + QUIT, /// Turn command, deprecated in RFC 5321 - Turn, + TURN, } impl Command { /// Tell if the command accetps an string argument. pub fn takes_argument(&self) -> bool{ match *self { - Ehello => true, - Hello => true, - Mail => true, - Recipient => true, - Data => false, - Reset => false, - SendMail => true, - SendOrMail => true, - SendAndMail => true, - Verify => true, - Expand => true, - Help => true, - Noop => false, - Quit => false, - Turn => false, + EHLO => true, + HELO => true, + MAIL => true, + RCPT => true, + DATA => false, + RSET => false, + SEND => true, + SOML => true, + SAML => true, + VRFY => true, + EXPN => true, + HELP => true, + NOOP => false, + QUIT => false, + TURN => false, } } /// Tell if an argument is needed by the command. pub fn needs_argument(&self) -> bool { match *self { - Ehello => true, - Hello => true, - Mail => true, - Recipient => true, - Data => false, - Reset => false, - SendMail => true, - SendOrMail => true, - SendAndMail => true, - Verify => true, - Expand => true, - Help => false, - Noop => false, - Quit => false, - Turn => false, + EHLO => true, + HELO => true, + MAIL => true, + RCPT => true, + DATA => false, + RSET => false, + SEND => true, + SOML => true, + SAML => true, + VRFY => true, + EXPN => true, + HELP => false, + NOOP => false, + QUIT => false, + TURN => false, } } } @@ -109,22 +107,22 @@ impl fmt::Show for Command { /// Format SMTP command display fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), io::IoError> { f.buf.write(match *self { - Ehello => "EHLO".as_bytes(), - Hello => "HELO".as_bytes(), - Mail => "MAIL FROM:".as_bytes(), - Recipient => "RCPT TO:".as_bytes(), - Data => "DATA".as_bytes(), - Reset => "RSET".as_bytes(), - SendMail => "SEND TO:".as_bytes(), - SendOrMail => "SOML TO:".as_bytes(), - SendAndMail => "SAML TO:".as_bytes(), - Verify => "VRFY".as_bytes(), - Expand => "EXPN".as_bytes(), - Help => "HELP".as_bytes(), - Noop => "NOOP".as_bytes(), - Quit => "QUIT".as_bytes(), - Turn => "TURN".as_bytes() - }) + EHLO => "EHLO", + HELO => "HELO", + MAIL => "MAIL FROM:", + RCPT => "RCPT TO:", + DATA => "DATA", + RSET => "RSET", + SEND => "SEND TO:", + SOML => "SOML TO:", + SAML => "SAML TO:", + VRFY => "VRFY", + EXPN => "EXPN", + HELP => "HELP", + NOOP => "NOOP", + QUIT => "QUIT", + TURN => "TURN" + }.as_bytes()) } } @@ -160,39 +158,46 @@ impl fmt::Show for SmtpCommand { } } +/// Supported ESMTP keywords +#[deriving(Eq,Clone)] +pub enum EhloKeyword { + /// STARTTLS keyword + STARTTLS, + /// 8BITMIME keyword + BITMIME, + /// SMTP authentification + AUTH +} + + #[cfg(test)] mod test { use super::{Command, SmtpCommand}; #[test] fn test_command_parameters() { - assert!((super::Help).takes_argument() == true); - assert!((super::Reset).takes_argument() == false); - assert!((super::Hello).needs_argument() == true); + assert!((super::HELP).takes_argument() == true); + assert!((super::RSET).takes_argument() == false); + assert!((super::HELO).needs_argument() == true); } #[test] fn test_to_str() { - assert!(super::Turn.to_str() == ~"TURN"); + assert!(super::TURN.to_str() == ~"TURN"); } -// #[test] -// fn test_from_str() { -// assert!(from_str == ~"TURN"); -// } - #[test] fn test_fmt() { - assert!(format!("{}", super::Turn) == ~"TURN"); + assert!(format!("{}", super::TURN) == ~"TURN"); } #[test] fn test_get_simple_command() { - assert!(SmtpCommand::new(super::Turn, None).to_str() == ~"TURN"); + assert!(SmtpCommand::new(super::TURN, None).to_str() == ~"TURN"); } #[test] fn test_get_argument_command() { - assert!(SmtpCommand::new(super::Ehello, Some(~"example.example")).to_str() == ~"EHLO example.example"); + assert!(SmtpCommand::new(super::EHLO, Some(~"example.example")).to_str() == ~"EHLO example.example"); } } diff --git a/src/smtp/lib.rs b/src/smtp/lib.rs index e08fbb0..3518bef 100644 --- a/src/smtp/lib.rs +++ b/src/smtp/lib.rs @@ -16,6 +16,9 @@ #[deny(non_camel_case_types)]; #[deny(missing_doc)]; +#[feature(phase)]; +#[phase(syntax, link)] extern crate log; + pub mod commands; pub mod common; pub mod client;