Add PLAIN AUTH support

This commit is contained in:
Alexis Mousset
2015-03-01 23:18:35 +01:00
parent f9a22af531
commit 03aa25a699
5 changed files with 42 additions and 65 deletions

View File

@@ -18,3 +18,4 @@ time = "*"
uuid = "*"
log = "*"
env_logger = "*"
rustc-serialize = "*"

View File

@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<String>;
/// Response to the given challenge
fn response(&self, challenge: &str) -> String;
}

View File

@@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<String> {
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)
}
}

View File

@@ -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<Uuid>,
}
/// 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<S = TcpStream> {
@@ -75,6 +82,8 @@ pub struct Client<S = TcpStream> {
state: State,
/// Configuration of the client
configuration: Configuration,
/// Client credentials
credentials: Option<Credentials>,
}
macro_rules! try_smtp (
@@ -132,6 +141,7 @@ impl<S = TcpStream> Client<S> {
connection_reuse_count: 0,
current_message: None,
},
credentials: None,
}
}
@@ -156,6 +166,14 @@ impl<S = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
@@ -207,6 +225,18 @@ impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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

View File

@@ -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;