Implement imap-flow #34

Merged
quentin merged 18 commits from refactor/imap-flow into main 2024-01-02 22:44:29 +00:00
6 changed files with 178 additions and 36 deletions
Showing only changes of commit 771c4eac79 - Show all commits

View file

@ -450,10 +450,7 @@ impl K2vWatch {
) { ) {
let mut row = match Weak::upgrade(&self_weak) { let mut row = match Weak::upgrade(&self_weak) {
Some(this) => this.target.clone(), Some(this) => this.target.clone(),
None => { None => return,
error!("can't start loop");
return;
}
}; };
while let Some(this) = Weak::upgrade(&self_weak) { while let Some(this) = Weak::upgrade(&self_weak) {

View file

@ -21,7 +21,10 @@ pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response, flow::Tran
CommandBody::Capability => ctx.capability().await, CommandBody::Capability => ctx.capability().await,
CommandBody::Logout => ctx.logout().await, CommandBody::Logout => ctx.logout().await,
CommandBody::Login { username, password } => ctx.login(username, password).await, CommandBody::Login { username, password } => ctx.login(username, password).await,
_ => Ok((Response::no("Command unavailable")?, flow::Transition::None)), cmd => {
tracing::warn!("Unknown command {:?}", cmd);
Ok((Response::no("Command unavailable")?, flow::Transition::None))
}
} }
} }

View file

@ -43,6 +43,9 @@ impl LoginProvider for DemoLoginProvider {
let storage = self.in_memory_store.builder("alice").await; let storage = self.in_memory_store.builder("alice").await;
let public_key = self.keys.public.clone(); let public_key = self.keys.public.clone();
Ok(PublicCredentials { storage, public_key }) Ok(PublicCredentials {
storage,
public_key,
})
} }
} }

View file

@ -1,6 +1,6 @@
pub mod demo_provider;
pub mod ldap_provider; pub mod ldap_provider;
pub mod static_provider; pub mod static_provider;
pub mod demo_provider;
use base64::Engine; use base64::Engine;
use std::sync::Arc; use std::sync::Arc;

View file

@ -11,7 +11,7 @@ use crate::config::*;
use crate::imap; use crate::imap;
use crate::lmtp::*; use crate::lmtp::*;
use crate::login::ArcLoginProvider; use crate::login::ArcLoginProvider;
use crate::login::{ldap_provider::*, static_provider::*, demo_provider::*}; use crate::login::{demo_provider::*, ldap_provider::*, static_provider::*};
pub struct Server { pub struct Server {
lmtp_server: Option<Arc<LmtpServer>>, lmtp_server: Option<Arc<LmtpServer>>,

View file

@ -1,8 +1,8 @@
use std::process::Command; use anyhow::{bail, Context, Result};
use std::io::{Read, Write};
use std::net::{Shutdown, TcpStream}; use std::net::{Shutdown, TcpStream};
use std::process::Command;
use std::{thread, time}; use std::{thread, time};
use anyhow::{bail, Result};
use std::io::{Write, Read};
static SMALL_DELAY: time::Duration = time::Duration::from_millis(200); static SMALL_DELAY: time::Duration = time::Duration::from_millis(200);
static EMAIL: &[u8] = b"Date: Sat, 8 Jul 2023 07:14:29 +0200\r static EMAIL: &[u8] = b"Date: Sat, 8 Jul 2023 07:14:29 +0200\r
@ -49,7 +49,6 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />\r
--b1_e376dc71bafc953c0b0fdeb9983a9956--\r --b1_e376dc71bafc953c0b0fdeb9983a9956--\r
"; ";
fn main() { fn main() {
let mut daemon = Command::new(env!("CARGO_BIN_EXE_aerogramme")) let mut daemon = Command::new(env!("CARGO_BIN_EXE_aerogramme"))
.arg("--dev") .arg("--dev")
@ -65,7 +64,7 @@ fn main() {
(Err(e), 0) => panic!("no more retry, last error is: {}", e), (Err(e), 0) => panic!("no more retry, last error is: {}", e),
(Err(e), _) => { (Err(e), _) => {
println!("unable to connect: {} ; will retry in 1 sec", e); println!("unable to connect: {} ; will retry in 1 sec", e);
}, }
(Ok(v), _) => break v, (Ok(v), _) => break v,
} }
thread::sleep(SMALL_DELAY); thread::sleep(SMALL_DELAY);
@ -74,26 +73,66 @@ fn main() {
let mut lmtp_socket = TcpStream::connect("[::1]:1025").expect("lmtp socket must be connected"); let mut lmtp_socket = TcpStream::connect("[::1]:1025").expect("lmtp socket must be connected");
println!("-- ready to test imap features --"); println!("-- ready to test imap features --");
login(&mut imap_socket).expect("login test"); let result = generic_test(&mut imap_socket, &mut lmtp_socket);
select_inbox(&mut imap_socket).expect("select inbox");
lmtp_handshake(&mut lmtp_socket).expect("handshake lmtp done");
lmtp_deliver_email(&mut lmtp_socket, EMAIL).expect("mail delivered successfully");
noop_exists(&mut imap_socket).expect("noop loop must detect a new email");
fetch_rfc822(&mut imap_socket, EMAIL).expect("fecth rfc822 message");
println!("-- test teardown --"); println!("-- test teardown --");
imap_socket.shutdown(Shutdown::Both).expect("closing imap socket at the end of the test"); imap_socket
lmtp_socket.shutdown(Shutdown::Both).expect("closing lmtp socket at the end of the test"); .shutdown(Shutdown::Both)
.expect("closing imap socket at the end of the test");
lmtp_socket
.shutdown(Shutdown::Both)
.expect("closing lmtp socket at the end of the test");
daemon.kill().expect("daemon should be killed"); daemon.kill().expect("daemon should be killed");
result.expect("all tests passed");
} }
fn login(imap: &mut TcpStream) -> Result<()> { fn generic_test(imap_socket: &mut TcpStream, lmtp_socket: &mut TcpStream) -> Result<()> {
connect(imap_socket).context("server says hello")?;
capability(imap_socket).context("check server capabilities")?;
login(imap_socket).context("login test")?;
create_mailbox(imap_socket).context("created mailbox archive")?;
// UNSUBSCRIBE IS NOT IMPLEMENTED YET
//unsubscribe_mailbox(imap_socket).context("unsubscribe from archive")?;
select_inbox(imap_socket).context("select inbox")?;
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
lmtp_deliver_email(lmtp_socket, EMAIL).context("mail delivered successfully")?;
noop_exists(imap_socket).context("noop loop must detect a new email")?;
fetch_rfc822(imap_socket, EMAIL).context("fetch rfc822 message")?;
copy_email(imap_socket).context("copy message to the archive mailbox")?;
// SEARCH IS NOT IMPLEMENTED YET
//search(imap_socket).expect("search should return something");
add_flags_email(imap_socket).context("should add delete and important flags to the email")?;
expunge(imap_socket).context("expunge emails")?;
rename_mailbox(imap_socket).context("archive mailbox is renamed my-archives")?;
delete_mailbox(imap_socket).context("my-archives mailbox is deleted")?;
Ok(())
}
fn connect(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 1500] = [0; 1500]; let mut buffer: [u8; 1500] = [0; 1500];
let read = read_lines(imap, &mut buffer, None)?; let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..4], &b"* OK"[..]); assert_eq!(&read[..4], &b"* OK"[..]);
Ok(())
}
fn capability(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"5 capability\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let read = read_lines(imap, &mut buffer, Some(&b"5 OK"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
assert!(srv_msg.contains("IMAP4REV1"));
assert!(srv_msg.contains("IDLE"));
Ok(())
}
fn login(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 1500] = [0; 1500];
imap.write(&b"10 login alice hunter2\r\n"[..])?; imap.write(&b"10 login alice hunter2\r\n"[..])?;
let read = read_lines(imap, &mut buffer, None)?; let read = read_lines(imap, &mut buffer, None)?;
@ -102,6 +141,39 @@ fn login(imap: &mut TcpStream) -> Result<()> {
Ok(()) Ok(())
} }
fn create_mailbox(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 1500] = [0; 1500];
imap.write(&b"15 create archive\r\n"[..])?;
let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..12], &b"15 OK CREATE"[..]);
Ok(())
}
#[allow(dead_code)]
fn unsubscribe_mailbox(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 6000] = [0; 6000];
imap.write(&b"16 lsub \"\" *\r\n"[..])?;
let read = read_lines(imap, &mut buffer, Some(&b"16 OK LSUB"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
assert!(srv_msg.contains(" INBOX\r\n"));
assert!(srv_msg.contains(" archive\r\n"));
imap.write(&b"17 unsubscribe archive\r\n"[..])?;
let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..5], &b"17 OK"[..]);
imap.write(&b"18 lsub \"\" *\r\n"[..])?;
let read = read_lines(imap, &mut buffer, Some(&b"18 OK LSUB"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
assert!(srv_msg.contains(" INBOX\r\n"));
assert!(!srv_msg.contains(" archive\r\n"));
Ok(())
}
fn select_inbox(imap: &mut TcpStream) -> Result<()> { fn select_inbox(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 6000] = [0; 6000]; let mut buffer: [u8; 6000] = [0; 6000];
@ -159,7 +231,7 @@ fn noop_exists(imap: &mut TcpStream) -> Result<()> {
} }
thread::sleep(SMALL_DELAY); thread::sleep(SMALL_DELAY);
}; }
Ok(()) Ok(())
} }
@ -176,19 +248,86 @@ fn fetch_rfc822(imap: &mut TcpStream, ref_mail: &[u8]) -> Result<()> {
Ok(()) Ok(())
} }
fn copy_email(imap: &mut TcpStream) -> Result<()> {
let mut buffer: [u8; 65535] = [0; 65535];
imap.write(&b"45 copy 1 archive\r\n"[..])?;
let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..5], &b"45 OK"[..]);
fn read_lines<'a, F: Read>(reader: &mut F, buffer: &'a mut [u8], stop_marker: Option<&[u8]>) -> Result<&'a [u8]> { Ok(())
}
fn add_flags_email(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"50 store 1 +FLAGS (\\Deleted \\Important)\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let _read = read_lines(imap, &mut buffer, Some(&b"50 OK STORE"[..]))?;
Ok(())
}
#[allow(dead_code)]
/// Not yet implemented
fn search(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"55 search text \"OoOoO\"\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let _read = read_lines(imap, &mut buffer, Some(&b"55 OK SEARCH"[..]))?;
Ok(())
}
fn expunge(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"60 expunge\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let _read = read_lines(imap, &mut buffer, Some(&b"60 OK EXPUNGE"[..]))?;
Ok(())
}
fn rename_mailbox(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"70 rename archive my-archives\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..5], &b"70 OK"[..]);
imap.write(&b"71 list \"\" *\r\n"[..])?;
let read = read_lines(imap, &mut buffer, Some(&b"71 OK LIST"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
assert!(!srv_msg.contains(" archive\r\n"));
assert!(srv_msg.contains(" INBOX\r\n"));
assert!(srv_msg.contains(" my-archives\r\n"));
Ok(())
}
fn delete_mailbox(imap: &mut TcpStream) -> Result<()> {
imap.write(&b"80 delete my-archives\r\n"[..])?;
let mut buffer: [u8; 1500] = [0; 1500];
let read = read_lines(imap, &mut buffer, None)?;
assert_eq!(&read[..5], &b"80 OK"[..]);
imap.write(&b"81 list \"\" *\r\n"[..])?;
let read = read_lines(imap, &mut buffer, Some(&b"81 OK LIST"[..]))?;
let srv_msg = std::str::from_utf8(read)?;
assert!(!srv_msg.contains(" archive\r\n"));
assert!(!srv_msg.contains(" my-archives\r\n"));
assert!(srv_msg.contains(" INBOX\r\n"));
Ok(())
}
fn read_lines<'a, F: Read>(
reader: &mut F,
buffer: &'a mut [u8],
stop_marker: Option<&[u8]>,
) -> Result<&'a [u8]> {
let mut nbytes = 0; let mut nbytes = 0;
loop { loop {
nbytes += reader.read(&mut buffer[nbytes..])?; nbytes += reader.read(&mut buffer[nbytes..])?;
let pre_condition = match stop_marker { let pre_condition = match stop_marker {
None => true, None => true,
Some(mark) => buffer[..nbytes] Some(mark) => buffer[..nbytes].windows(mark.len()).any(|w| w == mark),
.windows(mark.len())
.any(|w| w == mark)
}; };
if pre_condition && &buffer[nbytes - 2..nbytes] == &b"\r\n"[..] { if pre_condition && &buffer[nbytes - 2..nbytes] == &b"\r\n"[..] {
break break;
} }
} }
println!("read: {}", std::str::from_utf8(&buffer[..nbytes])?); println!("read: {}", std::str::from_utf8(&buffer[..nbytes])?);