Wire up SMTP and HTTP
This commit is contained in:
parent
0e2736f2b4
commit
e0fd858c2f
12
src/http.rs
12
src/http.rs
|
@ -17,20 +17,24 @@ pub fn http_handler(request: &Request, state: &State) -> Response {
|
||||||
domain: String
|
domain: String
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let email = format!("{}@{}", get_name(), data.domain);
|
let email = format!("{}@{}", get_name().to_lowercase(), data.domain);
|
||||||
|
|
||||||
println!("{}", email);
|
println!("{}", email);
|
||||||
|
|
||||||
state.lock().unwrap().insert(email.clone(), Mailbox::new(email));
|
state.lock().unwrap().insert(email.clone(), Mailbox::new(email.clone()));
|
||||||
|
|
||||||
rouille::Response::text("ok")
|
rouille::Response::text(email)
|
||||||
},
|
},
|
||||||
|
|
||||||
(GET) (/ffff/{email: String}) => {
|
(GET) (/ffff/{email: String}) => {
|
||||||
println!("{}", email);
|
println!("{}", email);
|
||||||
|
|
||||||
if let Some(mailbox) = state.lock().unwrap().get(&email) {
|
if let Some(mailbox) = state.lock().unwrap().get(&email) {
|
||||||
rouille::Response::text(format!("{}: {}", &mailbox.email, &mailbox.messages.len()))
|
let body: &Vec<String> = &mailbox.messages.iter().map(|msg| {
|
||||||
|
return format!("<h3>From {}<h3><br><pre>{}</pre><br>", msg.sender, msg.data)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
rouille::Response::html(body.join(""))
|
||||||
} else {
|
} else {
|
||||||
rouille::Response::text("Mailbox not found")
|
rouille::Response::text("Mailbox not found")
|
||||||
}
|
}
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate rouille;
|
extern crate rouille;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
mod http;
|
mod http;
|
||||||
|
@ -13,14 +14,16 @@ fn main() {
|
||||||
|
|
||||||
let state = state::fresh_state();
|
let state = state::fresh_state();
|
||||||
|
|
||||||
let http_thread = thread::spawn(|| {
|
let http_state = Arc::clone(&state);
|
||||||
|
let http_thread = thread::spawn(move || {
|
||||||
rouille::start_server("localhost:8005", move |request| {
|
rouille::start_server("localhost:8005", move |request| {
|
||||||
http::http_handler(request, &state)
|
http::http_handler(request, &http_state)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let smtp_thread = thread::spawn(|| {
|
let smtp_state = Arc::clone(&state);
|
||||||
smtp::start_server();
|
let smtp_thread = thread::spawn(move || {
|
||||||
|
smtp::start_server(smtp_state);
|
||||||
});
|
});
|
||||||
|
|
||||||
http_thread.join().unwrap();
|
http_thread.join().unwrap();
|
||||||
|
|
58
src/smtp.rs
58
src/smtp.rs
|
@ -1,8 +1,9 @@
|
||||||
use std::io::{BufRead, BufReader, Write};
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
|
||||||
|
use crate::state::{Message, State};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
pub fn start_server() {
|
pub fn start_server(state: State) {
|
||||||
let listener = TcpListener::bind("localhost:8025").unwrap();
|
let listener = TcpListener::bind("localhost:8025").unwrap();
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
|
@ -13,16 +14,23 @@ pub fn start_server() {
|
||||||
let reader = BufReader::new(tcp_stream.try_clone().unwrap());
|
let reader = BufReader::new(tcp_stream.try_clone().unwrap());
|
||||||
|
|
||||||
let mut connection = Connection::new();
|
let mut connection = Connection::new();
|
||||||
|
|
||||||
if let Err(e) = connection.handle(tcp_stream, reader) {
|
if let Err(e) = connection.handle(tcp_stream, reader) {
|
||||||
eprintln!("System error: {}", e)
|
eprintln!("System error: {}", e)
|
||||||
} else {
|
} else if let Ok(mut state_inner) = state.lock() {
|
||||||
for mail in connection.messages {
|
for mail in connection.messages {
|
||||||
println!(
|
println!(
|
||||||
"Mail from {} for {}\n{}",
|
"Mail from {} for {}\n{}",
|
||||||
mail.sender,
|
mail.sender,
|
||||||
mail.recipients.join(", "),
|
mail.recipients.join(", "),
|
||||||
mail.data.unwrap_or("<empty>".to_string())
|
mail.data
|
||||||
)
|
);
|
||||||
|
|
||||||
|
for r in &mail.recipients {
|
||||||
|
if let Some(mailbox) = state_inner.get_mut(&r.to_lowercase()) {
|
||||||
|
mailbox.messages.push(mail.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +58,10 @@ const MSG_BYE: &str = "221 Bye";
|
||||||
const ERR_WRITE_FAILED: &str = "Failed to write";
|
const ERR_WRITE_FAILED: &str = "Failed to write";
|
||||||
const ERR_READ_FAILED: &str = "Failed to read";
|
const ERR_READ_FAILED: &str = "Failed to read";
|
||||||
|
|
||||||
|
// <a@b> => 5 chars
|
||||||
|
const RCPT_MIN_LEN: usize = CMD_RCPT_TO.len() + 5;
|
||||||
|
const MAIL_MIN_LEN: usize = CMD_MAIL_FROM.len() + 5;
|
||||||
|
|
||||||
enum ConnectionState {
|
enum ConnectionState {
|
||||||
PreHello,
|
PreHello,
|
||||||
Hello,
|
Hello,
|
||||||
|
@ -59,7 +71,7 @@ enum ConnectionState {
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Message {
|
struct IncomingMessage {
|
||||||
sender: String,
|
sender: String,
|
||||||
recipients: Vec<String>,
|
recipients: Vec<String>,
|
||||||
data: Option<String>,
|
data: Option<String>,
|
||||||
|
@ -68,17 +80,25 @@ struct Message {
|
||||||
struct Connection {
|
struct Connection {
|
||||||
state: ConnectionState,
|
state: ConnectionState,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
current_message: Option<Message>,
|
current_message: Option<IncomingMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl IncomingMessage {
|
||||||
fn new(sender: String) -> Self {
|
fn new(sender: String) -> Self {
|
||||||
Message {
|
IncomingMessage {
|
||||||
sender,
|
sender,
|
||||||
recipients: Vec::new(),
|
recipients: Vec::new(),
|
||||||
data: None,
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize(self) -> Message {
|
||||||
|
Message {
|
||||||
|
sender: self.sender,
|
||||||
|
recipients: self.recipients,
|
||||||
|
data: self.data.unwrap_or("<empty>".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
@ -135,20 +155,22 @@ impl Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConnectionState::Hello => {
|
ConnectionState::Hello => {
|
||||||
if line.starts_with(CMD_MAIL_FROM) {
|
if line.starts_with(CMD_MAIL_FROM) && line.len() >= MAIL_MIN_LEN {
|
||||||
self.current_message = Some(Message::new(line.to_owned()));
|
self.current_message = Some(IncomingMessage::new(
|
||||||
|
line[CMD_MAIL_FROM.len() + 1..line.len() - 3].to_owned(),
|
||||||
|
));
|
||||||
self.state = ConnectionState::Mail;
|
self.state = ConnectionState::Mail;
|
||||||
return Ok(Some(MSG_OK));
|
return Ok(Some(MSG_OK));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConnectionState::Mail => {
|
ConnectionState::Mail => {
|
||||||
if line.starts_with(CMD_RCPT_TO) {
|
if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN {
|
||||||
self.add_recipient(line)?;
|
self.add_recipient(line)?;
|
||||||
return Ok(Some(MSG_OK));
|
return Ok(Some(MSG_OK));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConnectionState::PreData => {
|
ConnectionState::PreData => {
|
||||||
if line.starts_with(CMD_RCPT_TO) {
|
if line.starts_with(CMD_RCPT_TO) && line.len() >= RCPT_MIN_LEN {
|
||||||
self.add_recipient(line)?;
|
self.add_recipient(line)?;
|
||||||
return Ok(Some(MSG_OK));
|
return Ok(Some(MSG_OK));
|
||||||
}
|
}
|
||||||
|
@ -166,10 +188,10 @@ impl Connection {
|
||||||
|
|
||||||
return match self.current_message {
|
return match self.current_message {
|
||||||
Some(ref mut _message) => {
|
Some(ref mut _message) => {
|
||||||
if let Some(ref mut d) = _message.data {
|
if let Some(data) = _message.data.as_mut() {
|
||||||
*d += line.trim_end()
|
data.push_str(line);
|
||||||
} else {
|
} else {
|
||||||
_message.data = Some(String::from(line.trim_end()))
|
_message.data = Some(String::from(line))
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -197,14 +219,16 @@ impl Connection {
|
||||||
let current_message = self.current_message.take();
|
let current_message = self.current_message.take();
|
||||||
|
|
||||||
if let Some(mail) = current_message {
|
if let Some(mail) = current_message {
|
||||||
self.messages.push(mail)
|
self.messages.push(mail.finalize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> {
|
fn add_recipient(&mut self, line: &str) -> Result<(), &'static str> {
|
||||||
match self.current_message {
|
match self.current_message {
|
||||||
Some(ref mut message) => {
|
Some(ref mut message) => {
|
||||||
message.recipients.push(line.to_owned());
|
message
|
||||||
|
.recipients
|
||||||
|
.push(line[CMD_RCPT_TO.len() + 1..line.len() - 3].to_string());
|
||||||
self.state = ConnectionState::PreData
|
self.state = ConnectionState::PreData
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
20
src/state.rs
20
src/state.rs
|
@ -1,18 +1,11 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub type State = Mutex<HashMap<String, Mailbox>>;
|
pub type State = Arc<Mutex<HashMap<String, Mailbox>>>;
|
||||||
|
|
||||||
pub fn fresh_state() -> State {
|
pub fn fresh_state() -> State {
|
||||||
Mutex::new(HashMap::new())
|
Arc::new(Mutex::new(HashMap::new()))
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Message {
|
|
||||||
pub from: String,
|
|
||||||
pub received: DateTime<Utc>,
|
|
||||||
pub subject: String,
|
|
||||||
pub body: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mailbox {
|
pub struct Mailbox {
|
||||||
|
@ -21,6 +14,13 @@ pub struct Mailbox {
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Message {
|
||||||
|
pub sender: String,
|
||||||
|
pub recipients: Vec<String>,
|
||||||
|
pub data: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Mailbox {
|
impl Mailbox {
|
||||||
pub fn new(email: String) -> Self {
|
pub fn new(email: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
Loading…
Reference in New Issue