#![crate_name = "pop3"]
#![crate_type = "lib"]
extern crate openssl;
extern crate regex;
use POP3StreamTypes::{Basic, Ssl};
use POP3Command::{Greet, User, Pass, Stat, UidlAll, UidlOne, ListAll, ListOne, Retr, Dele, Noop, Rset, Quit};
use std::string::String;
use std::io::prelude::*;
use std::io::{Error, ErrorKind, Result};
use std::net::{ToSocketAddrs,TcpStream};
use openssl::ssl::{SslConnector, SslStream};
use std::str::FromStr;
use regex::Regex;
#[derive(Debug)]
enum POP3StreamTypes {
Basic(TcpStream),
Ssl(SslStream<TcpStream>)
}
#[derive(Debug)]
pub struct POP3Stream {
stream: POP3StreamTypes,
pub is_authenticated: bool
}
#[derive(Clone)]
enum POP3Command {
Greet,
User,
Pass,
Stat,
UidlAll,
UidlOne,
ListAll,
ListOne,
Retr,
Dele,
Noop,
Rset,
Quit
}
impl POP3Stream {
pub fn connect<A:ToSocketAddrs>(addr: A,ssl_context: Option<SslConnector>,domain: &str) -> Result<POP3Stream> {
let tcp_stream = TcpStream::connect(addr)?;
let mut socket = match ssl_context {
Some(context) => POP3Stream {
stream: Ssl(SslConnector::connect(&context, domain,tcp_stream).unwrap()),
is_authenticated: false},
None => POP3Stream {
stream: Basic(tcp_stream),
is_authenticated: false},
};
match socket.read_response(Greet) {
Ok(_) => (),
Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read greet response"))
}
Ok(socket)
}
fn write_str(&mut self, s: &str) -> Result<()> {
match self.stream {
Ssl(ref mut stream) => stream.write_fmt(format_args!("{}", s)),
Basic(ref mut stream) => stream.write_fmt(format_args!("{}", s)),
}
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self.stream {
Ssl(ref mut stream) => stream.read(buf),
Basic(ref mut stream) => stream.read(buf),
}
}
pub fn login(&mut self, username: &str, password: &str) -> POP3Result {
let user_command = format!("USER {}\r\n", username);
let pass_command = format!("PASS {}\r\n", password);
match self.write_str(&user_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(User) {
Ok(_) => {
match self.write_str(&pass_command) {
Ok(_) => self.is_authenticated = true,
Err(_) => panic!("Error writing"),
}
match self.read_response(Pass) {
Ok(_) => {
POP3Result::POP3Ok
},
Err(_) => panic!("Failure to use PASS")
}
},
Err(_) => panic!("Failure to use USER")
}
}
pub fn stat(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let stat_command = "STAT\r\n";
match self.write_str(&stat_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Stat) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn uidl(&mut self, message_number: Option<i32>) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let uidl_command = match message_number {
Some(i) => format!("UIDL {}\r\n", i),
None => format!("UIDL\r\n"),
};
let command_type = match message_number {
Some(_) => UidlOne,
None => UidlAll,
};
match self.write_str(&uidl_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(command_type) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn list(&mut self, message_number: Option<i32>) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let list_command = match message_number {
Some(i) => format!("LIST {}\r\n", i),
None => format!("LIST\r\n"),
};
let command_type = match message_number {
Some(_) => ListOne,
None => ListAll,
};
match self.write_str(&list_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(command_type) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn retr(&mut self, message_id: i32) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let retr_command = format!("RETR {}\r\n", message_id);
match self.write_str(&retr_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Retr) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn dele(&mut self, message_id: i32) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let dele_command = format!("DELE {}\r\n", message_id);
match self.write_str(&dele_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Dele) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn rset(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("Not Logged In");
}
let retr_command = format!("RETR\r\n");
match self.write_str(&retr_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Rset) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn quit(&mut self) -> POP3Result {
let quit_command = "QUIT\r\n";
match self.write_str(&quit_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Quit) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub fn noop(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("Not Logged In");
}
let noop_command = "noop\r\n";
match self.write_str(noop_command) {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Noop) {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => panic!("Error noop")
}
}
fn read_response(&mut self, command: POP3Command) -> Result<Box<POP3Response>> {
let mut response = Box::new(POP3Response::new());
let cr = 0x0d;
let lf = 0x0a;
let mut line_buffer: Vec<u8> = Vec::new();
while !response.complete {
while line_buffer.len() < 2 || (line_buffer[line_buffer.len()-1] != lf && line_buffer[line_buffer.len()-2] != cr) {
let byte_buffer: &mut [u8] = &mut [0];
match self.read(byte_buffer) {
Ok(_) => {},
Err(_) => println!("Error Reading!"),
}
line_buffer.push(byte_buffer[0]);
}
match String::from_utf8(line_buffer.clone()) {
Ok(res) => {
response.add_line(res, command.clone());
line_buffer = Vec::new();
},
Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read the response"))
}
}
Ok(response)
}
}
#[derive(Clone,Copy,Debug)]
pub struct POP3EmailMetadata {
pub message_id: i32,
pub message_size: i32
}
#[derive(Clone,Debug)]
pub struct POP3EmailUidldata {
pub message_id: i32,
pub message_uid: String
}
#[derive(Debug)]
pub enum POP3Result {
POP3Ok,
POP3Err,
POP3Stat {
num_email: i32,
mailbox_size: i32
},
POP3Uidl {
emails_metadata: Vec<POP3EmailUidldata>,
},
POP3List {
emails_metadata: Vec<POP3EmailMetadata>,
},
POP3Message {
raw: Vec<String>,
},
}
#[derive(Default)]
struct POP3Response {
complete: bool,
lines: Vec<String>,
result: Option<POP3Result>
}
impl POP3Response {
fn new() -> POP3Response {
POP3Response {
complete: false,
lines: Vec::new(),
result: None
}
}
fn add_line(&mut self, line: String, command: POP3Command) {
let l = line.clone();
let ending_regex = match Regex::new(r"^\.\r\n$") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
if self.lines.len() == 0 {
let ok_regex = match Regex::new(r"\+OK(.*)") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let err_regex = match Regex::new(r"-ERR(.*)") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
if ok_regex.is_match(&l) {
self.lines.push(l);
match command {
Greet|User|Pass|Quit|Dele|Rset => {
self.result = Some(POP3Result::POP3Ok);
self.complete = true;
},
Stat => {
self.complete = true;
self.parse_stat()
},
UidlAll => {
},
UidlOne => {
self.complete = true;
self.parse_uidl_one();
},
ListAll => {
},
ListOne => {
self.complete = true;
self.parse_list_one();
},
Retr => {
},
_ => self.complete = true,
}
} else if err_regex.is_match(&l) {
self.lines.push(l);
self.result = Some(POP3Result::POP3Err);
self.complete = true;
}
} else {
if ending_regex.is_match(&l) {
self.lines.push(l);
match command {
UidlAll => {
self.complete = true;
self.parse_uidl_all();
},
ListAll => {
self.complete = true;
self.parse_list_all();
},
Retr => {
self.complete = true;
self.parse_message();
},
_ => self.complete = true,
}
} else {
self.lines.push(l);
}
}
}
fn parse_stat(&mut self) {
let stat_regex = match Regex::new(r"\+OK (\d+) (\d+)\r\n") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let caps = stat_regex.captures(&self.lines[0]).unwrap();
let num_emails = FromStr::from_str(caps.get(1).unwrap().as_str());
let total_email_size = FromStr::from_str(caps.get(2).unwrap().as_str());
self.result = Some(POP3Result::POP3Stat {
num_email: num_emails.unwrap(),
mailbox_size: total_email_size.unwrap()
})
}
fn parse_uidl_all(&mut self) {
let message_data_regex = match Regex::new(r"(\d+) ([\x21-\x7e]+)\r\n") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let mut metadata = Vec::new();
for i in 1 .. self.lines.len() - 1 {
let caps = message_data_regex.captures(&self.lines[i]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_uid = caps.get(2).unwrap().as_str();
metadata.push(POP3EmailUidldata {
message_id: message_id.unwrap(),
message_uid: message_uid.to_owned()
});
}
self.result = Some(POP3Result::POP3Uidl {
emails_metadata: metadata
});
}
fn parse_uidl_one(&mut self) {
let message_data_regex = match Regex::new(r"\+OK (\d+) ([\x21-\x7e]+)\r\n") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let caps = message_data_regex.captures(&self.lines[0]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_uid = caps.get(2).unwrap().as_str();
self.result = Some(POP3Result::POP3Uidl {
emails_metadata: vec![POP3EmailUidldata{
message_id: message_id.unwrap(),
message_uid: message_uid.to_owned()
}]
});
}
fn parse_list_all(&mut self) {
let message_data_regex = match Regex::new(r"(\d+) (\d+)\r\n") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let mut metadata = Vec::new();
for i in 1 .. self.lines.len()-1 {
let caps = message_data_regex.captures(&self.lines[i]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
metadata.push(POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()});
}
self.result = Some(POP3Result::POP3List {
emails_metadata: metadata
});
}
fn parse_list_one(&mut self) {
let stat_regex = match Regex::new(r"\+OK (\d+) (\d+)\r\n") {
Ok(re) => re,
Err(_) => panic!("Invalid Regex!!"),
};
let caps = stat_regex.captures(&self.lines[0]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
self.result = Some(POP3Result::POP3List {
emails_metadata: vec![POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()}]
});
}
fn parse_message(&mut self) {
let mut raw = Vec::new();
for i in 1 .. self.lines.len()-1 {
raw.push(self.lines[i].clone());
}
self.result = Some(POP3Result::POP3Message{
raw: raw
});
}
}