From 03aa25a6996d04e9a4503fc0f666497f522b10e2 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sun, 1 Mar 2015 23:18:35 +0100 Subject: [PATCH] Add PLAIN AUTH support --- Cargo.toml | 1 + src/client/authentication/mod.rs | 22 ---------------- src/client/authentication/plain.rs | 39 --------------------------- src/client/mod.rs | 42 +++++++++++++++++++++++++++--- src/lib.rs | 3 ++- 5 files changed, 42 insertions(+), 65 deletions(-) delete mode 100644 src/client/authentication/mod.rs delete mode 100644 src/client/authentication/plain.rs diff --git a/Cargo.toml b/Cargo.toml index eee6f0a..6153c17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ time = "*" uuid = "*" log = "*" env_logger = "*" +rustc-serialize = "*" diff --git a/src/client/authentication/mod.rs b/src/client/authentication/mod.rs deleted file mode 100644 index 7a1ee2e..0000000 --- a/src/client/authentication/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 deleted file mode 100644 index 45ca155..0000000 --- a/src/client/authentication/plain.rs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 6b2310e..d050195 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,6 @@ //! SMTP client use std::slice::Iter; -use std::ascii::AsciiExt; use std::string::String; use std::error::FromError; use std::old_io::net::tcp::TcpStream; @@ -18,9 +17,10 @@ use std::old_io::net::ip::{SocketAddr, ToSocketAddr}; use log::LogLevel::Info; use uuid::Uuid; +use serialize::base64::{self, ToBase64}; use tools::get_first_word; -use common::{CRLF, MESSAGE_ENDING, SMTP_PORT}; +use common::{NUL, CRLF, MESSAGE_ENDING, SMTP_PORT}; use response::Response; use extension::Extension; use error::{SmtpResult, ErrorKind}; @@ -32,7 +32,6 @@ use client::stream::ClientStream; pub mod server_info; pub mod connecter; pub mod stream; -pub mod authentication; /// Represents the configuration of a client #[derive(Debug)] @@ -60,6 +59,14 @@ pub struct State { pub current_message: Option, } +/// Represents the credentials +#[derive(Debug, Clone)] +pub struct Credentials { + /// Username + pub username: String, + /// Password + pub password: String, +} /// Structure that implements the SMTP client pub struct Client { @@ -75,6 +82,8 @@ pub struct Client { state: State, /// Configuration of the client configuration: Configuration, + /// Client credentials + credentials: Option, } macro_rules! try_smtp ( @@ -132,6 +141,7 @@ impl Client { connection_reuse_count: 0, current_message: None, }, + credentials: None, } } @@ -156,6 +166,14 @@ impl Client { pub fn set_connection_reuse_count_limit(&mut self, count: u16) { self.configuration.connection_reuse_count_limit = count } + + /// Set the client credentials + pub fn set_credentials(&mut self, username: &str, password: &str) { + self.credentials = Some(Credentials { + username: username.to_string(), + password: password.to_string(), + }) + } } impl Client { @@ -207,6 +225,18 @@ impl Client { debug!("server {}", self.server_info.as_ref().unwrap()); } + // Use PLAIN AUTH if possible + if self.credentials.is_some() { + if self.server_info.as_ref().unwrap().supports_feature(Extension::PlainAuthentication).is_some() { + let credentials = self.credentials.clone().unwrap(); + let result = self.auth_plain(credentials.username.as_slice(), + credentials.password.as_slice()); + try_smtp!(result, self); + } else { + debug!("No supported authentication mecanisms available"); + } + } + self.state.current_message = Some(Uuid::new_v4()); email.set_message_id(format!("<{}@{}>", self.state.current_message.as_ref().unwrap(), self.configuration.hello_name.clone())); @@ -373,6 +403,12 @@ impl Client { self.command(format!("EXPN {}", list).as_slice(), [250, 252].iter()) } + /// Sends an AUTH command with PLAIN mecanism + pub fn auth_plain(&mut self, username: &str, password: &str) -> SmtpResult { + let auth_string = format!("{}{}{}{}{}", "", NUL, username, NUL, password); + self.command(format!("AUTH PLAIN {}", auth_string.as_bytes().to_base64(base64::STANDARD)).as_slice(), [235].iter()) + } + /// Sends the message content and close pub fn message(&mut self, message_content: &str) -> SmtpResult { let result = self.send_server(message_content, MESSAGE_ENDING, [250].iter()); //250 diff --git a/src/lib.rs b/src/lib.rs index 489acb7..b8139e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,9 +128,10 @@ #![deny(missing_docs)] -#![feature(plugin,core,old_io,io,collections)] +#![feature(plugin, core, old_io, io, collections)] #[macro_use] extern crate log; +extern crate "rustc-serialize" as serialize; extern crate time; extern crate uuid;