Initial commit
This commit is contained in:
41
src/http.rs
Normal file
41
src/http.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use rouille::{router, Request, Response, try_or_400, post_input};
|
||||
use crate::random_names::get_name;
|
||||
use crate::state::{Mailbox, State};
|
||||
|
||||
pub fn http_handler(request: &Request, state: &State) -> Response {
|
||||
router!(request,
|
||||
(GET) (/) => {
|
||||
rouille::Response::text("hello world")
|
||||
},
|
||||
|
||||
(GET) (/ffff) => {
|
||||
rouille::Response::html("<h1>email stuff</h1>")
|
||||
},
|
||||
|
||||
(POST) (/ffff) => {
|
||||
let data = try_or_400!(post_input!(request, {
|
||||
domain: String
|
||||
}));
|
||||
|
||||
let email = format!("{}@{}", get_name(), data.domain);
|
||||
|
||||
println!("{}", email);
|
||||
|
||||
state.lock().unwrap().insert(email.clone(), Mailbox::new(email));
|
||||
|
||||
rouille::Response::text("ok")
|
||||
},
|
||||
|
||||
(GET) (/ffff/{email: String}) => {
|
||||
println!("{}", email);
|
||||
|
||||
if let Some(mailbox) = state.lock().unwrap().get(&email) {
|
||||
rouille::Response::text(format!("{}: {}", &mailbox.email, &mailbox.messages.len()))
|
||||
} else {
|
||||
rouille::Response::text("Mailbox not found")
|
||||
}
|
||||
},
|
||||
|
||||
_ => rouille::Response::empty_404()
|
||||
)
|
||||
}
|
||||
28
src/main.rs
Normal file
28
src/main.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
extern crate rouille;
|
||||
extern crate rand;
|
||||
|
||||
use std::thread;
|
||||
|
||||
mod http;
|
||||
mod random_names;
|
||||
mod state;
|
||||
mod smtp;
|
||||
|
||||
fn main() {
|
||||
println!("Starting on localhost:8005");
|
||||
|
||||
let state = state::fresh_state();
|
||||
|
||||
let http_thread = thread::spawn(|| {
|
||||
rouille::start_server("localhost:8005", move |request| {
|
||||
http::http_handler(request, &state)
|
||||
})
|
||||
});
|
||||
|
||||
let smtp_thread = thread::spawn(|| {
|
||||
smtp::start_server();
|
||||
});
|
||||
|
||||
http_thread.join().unwrap();
|
||||
smtp_thread.join().unwrap();
|
||||
}
|
||||
7
src/random_names.rs
Normal file
7
src/random_names.rs
Normal file
File diff suppressed because one or more lines are too long
213
src/smtp.rs
Normal file
213
src/smtp.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
|
||||
pub fn start_server() {
|
||||
let listener = TcpListener::bind("localhost:8025").unwrap();
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(tcp_stream) => {
|
||||
println!("Incoming connection");
|
||||
|
||||
let reader = BufReader::new(tcp_stream.try_clone().unwrap());
|
||||
|
||||
let mut connection = Connection::new();
|
||||
let _ = connection.handle(tcp_stream, reader);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to accept connection: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CMD_MAIL_FROM: &str = "MAIL FROM:";
|
||||
const CMD_HELLO: &str = "HELO";
|
||||
const CMD_RCPT_TO: &str = "RCPT TO:";
|
||||
const CMD_DATA: &str = "DATA";
|
||||
const CMD_DOT: &str = ".";
|
||||
const CMD_QUIT: &str = "QUIT";
|
||||
|
||||
const MSG_BANNER: &str = "220 very good mailserver";
|
||||
const MSG_OK: &str = "250 ok";
|
||||
const MSG_DATA: &str = "354 End data with <CR><LF>.<CR><LF>";
|
||||
const MSG_UNEXPECTED: &str = "500 unexpected line";
|
||||
const MSG_FAIL: &str = "500 mailserver is broken";
|
||||
const MSG_BYE: &str = "221 Bye";
|
||||
|
||||
const ERR_WRITE_FAILED: &str = "Failed to write";
|
||||
const ERR_READ_FAILED: &str = "Failed to read";
|
||||
|
||||
enum ConnectionState {
|
||||
PreHello,
|
||||
Hello,
|
||||
Mail,
|
||||
PreData,
|
||||
Data,
|
||||
Done,
|
||||
}
|
||||
|
||||
struct Message {
|
||||
sender: String,
|
||||
recipients: Vec<String>,
|
||||
data: Option<String>,
|
||||
}
|
||||
|
||||
struct Connection {
|
||||
state: ConnectionState,
|
||||
messages: Vec<Message>,
|
||||
current_message: Option<Message>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn new(sender: String) -> Self {
|
||||
Message {
|
||||
sender,
|
||||
recipients: Vec::new(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
fn new() -> Self {
|
||||
Connection {
|
||||
state: ConnectionState::PreHello,
|
||||
messages: Vec::new(),
|
||||
current_message: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(&mut self, mut writer: TcpStream, mut reader: BufReader<TcpStream>) -> Result<(), &str> {
|
||||
send(&mut writer, MSG_BANNER)?;
|
||||
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
let read_result = reader.read_line(&mut line);
|
||||
|
||||
if read_result.is_err() || line.len() == 0 {
|
||||
return Err(ERR_READ_FAILED);
|
||||
}
|
||||
|
||||
println!("Read line: {}", line);
|
||||
|
||||
let response = self.handle_line(&line);
|
||||
|
||||
if let Err(_e) = response {
|
||||
return Err(_e); // _e
|
||||
}
|
||||
|
||||
if let Ok(Some(out)) = response {
|
||||
send(&mut writer, out)?;
|
||||
|
||||
if out == MSG_BYE {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
line.clear()
|
||||
}
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
fn handle_line(&mut self, line: &String) -> Result<Option<&str>, &'static str> {
|
||||
match self.state {
|
||||
ConnectionState::PreHello => {
|
||||
if line.starts_with(CMD_HELLO) {
|
||||
self.state = ConnectionState::Hello;
|
||||
return Ok(Some(MSG_OK));
|
||||
}
|
||||
}
|
||||
ConnectionState::Hello => {
|
||||
if line.starts_with(CMD_MAIL_FROM) {
|
||||
self.current_message = Some(Message::new(line.clone()));
|
||||
self.state = ConnectionState::Mail;
|
||||
return Ok(Some(MSG_OK));
|
||||
}
|
||||
}
|
||||
ConnectionState::Mail => {
|
||||
if line.starts_with(CMD_RCPT_TO) {
|
||||
self.add_recipient(line)?;
|
||||
return Ok(Some(MSG_OK));
|
||||
}
|
||||
}
|
||||
ConnectionState::PreData => {
|
||||
if line.starts_with(CMD_RCPT_TO) {
|
||||
self.add_recipient(line)?;
|
||||
return Ok(Some(MSG_OK));
|
||||
}
|
||||
|
||||
if line.starts_with(CMD_DATA) {
|
||||
self.state = ConnectionState::Data;
|
||||
return Ok(Some(MSG_DATA));
|
||||
}
|
||||
}
|
||||
ConnectionState::Data => {
|
||||
if line.trim_end() == CMD_DOT {
|
||||
self.state = ConnectionState::Done;
|
||||
return Ok(Some(MSG_OK))
|
||||
}
|
||||
|
||||
return match self.current_message {
|
||||
Some(ref mut _message) => {
|
||||
if let Some(ref mut d) = _message.data {
|
||||
*d += line.trim_end()
|
||||
} else {
|
||||
_message.data = Some(String::from(line.trim_end()))
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
_ => {
|
||||
Err(MSG_FAIL)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
ConnectionState::Done => {
|
||||
if line.starts_with(CMD_QUIT) {
|
||||
self.finalize_mail();
|
||||
return Ok(Some(MSG_BYE))
|
||||
}
|
||||
|
||||
if line.starts_with(CMD_MAIL_FROM) {
|
||||
self.finalize_mail();
|
||||
return Ok(Some(MSG_OK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(MSG_UNEXPECTED))
|
||||
}
|
||||
|
||||
fn finalize_mail(&mut self) {
|
||||
if let Some(mail) = &self.current_message {
|
||||
self.current_message = None;
|
||||
self.messages.push(*mail)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_recipient(&mut self, line: &String) -> Result<(), &'static str> {
|
||||
match self.current_message {
|
||||
Some(ref mut message) => {
|
||||
message.recipients.push(line.clone());
|
||||
self.state = ConnectionState::PreData
|
||||
}
|
||||
_ => {
|
||||
return Err(MSG_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn send(writer: &mut TcpStream, message: &str) -> Result<(), &'static str> {
|
||||
if writeln!(writer, "{}", message).is_err() {
|
||||
return Err(ERR_WRITE_FAILED);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
32
src/state.rs
Normal file
32
src/state.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub type State = Mutex<HashMap<String, Mailbox>>;
|
||||
|
||||
pub fn fresh_state() -> State {
|
||||
Mutex::new(HashMap::new())
|
||||
}
|
||||
|
||||
pub struct Message {
|
||||
pub from: String,
|
||||
pub received: DateTime<Utc>,
|
||||
pub subject: String,
|
||||
pub body: String
|
||||
}
|
||||
|
||||
pub struct Mailbox {
|
||||
pub email: String,
|
||||
pub messages: Vec<Message>,
|
||||
pub created_at: DateTime<Utc>
|
||||
}
|
||||
|
||||
impl Mailbox {
|
||||
pub fn new(email: String) -> Self {
|
||||
Self {
|
||||
email,
|
||||
messages: Vec::new(),
|
||||
created_at: Utc::now()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user