add some test-related things
This commit is contained in:
parent
4ddcaf19c1
commit
c1e770e050
10 changed files with 149 additions and 11 deletions
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
mail:
|
||||||
|
image: greenmail/standalone
|
||||||
|
ports:
|
||||||
|
- "3025:3025" # SMTP
|
||||||
|
- "3143:3143" # IMAP
|
||||||
|
- "3465:3465" # SMTPS
|
||||||
|
- "3993:3993" # IMAPS
|
||||||
|
- "3080:8080" # Web
|
||||||
|
environment:
|
||||||
|
- "GREENMAIL_OPTS=-Dgreenmail.setup.test.all -Dgreenmail.users=user:pass -Dgreenmail.verbose"
|
|
@ -3,7 +3,12 @@ name = "imap"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "greenmail-test"
|
||||||
|
path = "bin/greenmail_test.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["rfc2177-idle"]
|
||||||
rfc2177-idle = []
|
rfc2177-idle = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
4
imap/bin/greenmail_test.rs
Normal file
4
imap/bin/greenmail_test.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,33 @@
|
||||||
pub trait AuthMethod {}
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
use crate::proto::command::{Command, CommandLogin};
|
||||||
|
use crate::client::inner::Inner;
|
||||||
|
|
||||||
|
pub trait Client: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AuthMethod {
|
||||||
|
async fn perform_auth<C>(&self, inner: &mut Inner<C>)
|
||||||
|
where
|
||||||
|
C: Client;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Login {
|
pub struct Login {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthMethod for Login {}
|
#[async_trait]
|
||||||
|
impl AuthMethod for Login {
|
||||||
|
async fn perform_auth<C>(&self, inner: &mut Inner<C>)
|
||||||
|
where
|
||||||
|
C: Client,
|
||||||
|
{
|
||||||
|
let command = Command::Login(CommandLogin {
|
||||||
|
username: &self.username,
|
||||||
|
password: &self.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = inner.execute(command).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{split, AsyncRead, AsyncWrite, ReadHalf},
|
io::{split, AsyncRead, AsyncWrite, ReadHalf, WriteHalf},
|
||||||
sync::oneshot,
|
sync::oneshot,
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
use tokio_rustls::client::TlsStream;
|
||||||
use tokio_util::codec::FramedRead;
|
use tokio_util::codec::FramedRead;
|
||||||
|
|
||||||
use crate::codec::ImapCodec;
|
use crate::codec::ImapCodec;
|
||||||
|
use crate::proto::command::Command;
|
||||||
|
|
||||||
|
use super::upgrade::upgrade;
|
||||||
|
|
||||||
type ExitSender = oneshot::Sender<()>;
|
type ExitSender = oneshot::Sender<()>;
|
||||||
type ExitListener = oneshot::Receiver<()>;
|
type ExitListener = oneshot::Receiver<()>;
|
||||||
|
@ -15,11 +19,13 @@ type ExitListener = oneshot::Receiver<()>;
|
||||||
pub struct Inner<C> {
|
pub struct Inner<C> {
|
||||||
read_exit: ExitSender,
|
read_exit: ExitSender,
|
||||||
read_handle: JoinHandle<ReadHalf<C>>,
|
read_handle: JoinHandle<ReadHalf<C>>,
|
||||||
|
|
||||||
|
write_half: WriteHalf<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Inner<C>
|
impl<C> Inner<C>
|
||||||
where
|
where
|
||||||
C: AsyncRead + AsyncWrite + Send + 'static,
|
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
pub async fn open(c: C) -> Result<Self> {
|
pub async fn open(c: C) -> Result<Self> {
|
||||||
// break the stream of bytes into a reader and a writer
|
// break the stream of bytes into a reader and a writer
|
||||||
|
@ -34,8 +40,25 @@ where
|
||||||
Ok(Inner {
|
Ok(Inner {
|
||||||
read_exit,
|
read_exit,
|
||||||
read_handle,
|
read_handle,
|
||||||
|
write_half,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn execute<'a>(&mut self, command: Command<'a>) {}
|
||||||
|
|
||||||
|
pub async fn upgrade(self) -> Result<Inner<TlsStream<C>>> {
|
||||||
|
// TODO: check that this capability exists??
|
||||||
|
// issue exit to the read loop and retrieve the read half
|
||||||
|
let _ = self.read_exit.send(());
|
||||||
|
let read_half = self.read_handle.await?;
|
||||||
|
let write_half = self.write_half;
|
||||||
|
|
||||||
|
// put the read half and write half back together
|
||||||
|
let stream = read_half.unsplit(write_half);
|
||||||
|
let tls_stream = upgrade(stream, "hellosu").await?;
|
||||||
|
|
||||||
|
Inner::open(tls_stream).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit is a channel that will notify this loop when some external
|
// exit is a channel that will notify this loop when some external
|
||||||
|
@ -57,5 +80,6 @@ where
|
||||||
_ = exit => break,
|
_ = exit => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
todo!()
|
|
||||||
|
framed.into_inner()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,24 @@
|
||||||
use anyhow::Result;
|
use std::sync::Arc;
|
||||||
use tokio_rustls::TlsStream;
|
|
||||||
|
|
||||||
pub fn upgrade<C>(c: C) -> Result<TlsStream<C>> { todo!() }
|
use anyhow::Result;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio_rustls::{
|
||||||
|
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn upgrade<C>(c: C, hostname: impl AsRef<str>) -> Result<TlsStream<C>>
|
||||||
|
where
|
||||||
|
C: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
let server_name = hostname.as_ref();
|
||||||
|
|
||||||
|
let mut tls_config = RustlsConfig::new();
|
||||||
|
tls_config
|
||||||
|
.root_store
|
||||||
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
|
let tls_config = TlsConnector::from(Arc::new(tls_config));
|
||||||
|
let dnsname = DNSNameRef::try_from_ascii_str(server_name).unwrap();
|
||||||
|
let stream = tls_config.connect(dnsname, c).await?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,43 @@
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
Todo(&'a ()),
|
// Any state
|
||||||
|
Capability,
|
||||||
|
Noop,
|
||||||
|
Logout,
|
||||||
|
|
||||||
|
// Not authenticated
|
||||||
|
Login(CommandLogin<'a>),
|
||||||
|
Starttls,
|
||||||
|
Authenticate,
|
||||||
|
|
||||||
|
// Authenticated
|
||||||
|
Select,
|
||||||
|
Examine,
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
Rename,
|
||||||
|
Subscribe,
|
||||||
|
Unsubscribe,
|
||||||
|
List,
|
||||||
|
Lsub,
|
||||||
|
Status,
|
||||||
|
Append,
|
||||||
|
|
||||||
|
// Selected
|
||||||
|
Check,
|
||||||
|
Close,
|
||||||
|
Expunge,
|
||||||
|
Search,
|
||||||
|
Fetch,
|
||||||
|
Store,
|
||||||
|
Copy,
|
||||||
|
Uid,
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
#[cfg(feature = "rfc2177-idle")]
|
||||||
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CommandLogin<'a> {
|
||||||
|
pub username: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
}
|
|
@ -11,3 +11,6 @@ pub mod response;
|
||||||
pub mod parsers;
|
pub mod parsers;
|
||||||
pub mod rfc2234;
|
pub mod rfc2234;
|
||||||
pub mod rfc3501;
|
pub mod rfc3501;
|
||||||
|
|
||||||
|
#[cfg(feature = "rfc2177-idle")]
|
||||||
|
pub mod rfc2177;
|
1
imap/src/proto/rfc2177.rs
Normal file
1
imap/src/proto/rfc2177.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//! Grammar from https://datatracker.ietf.org/doc/html/rfc2177#section-4
|
|
@ -58,8 +58,11 @@ rule!(pub capability_data : Vec<Capability> => preceded(tag_no_case("CAPABILITY"
|
||||||
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
|
pub(crate) fn is_list_wildcards(c: u8) -> bool { c == b'%' || c == b'*' }
|
||||||
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
||||||
|
|
||||||
/// literal = "{" number "}" CRLF *CHAR8
|
// literal = "{" number "}" CRLF *CHAR8
|
||||||
/// ; Number represents the number of CHAR8s
|
// ; Number represents the number of CHAR8s
|
||||||
|
// TODO: Future work, could possibly initialize writing to file if the length is
|
||||||
|
// determined to exceed a certain threshold so we don't have insane amounts of
|
||||||
|
// data in memory
|
||||||
pub fn literal(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
pub fn literal(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
|
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
|
||||||
let (i, length) = length_of(i)?;
|
let (i, length) = length_of(i)?;
|
||||||
|
|
Loading…
Reference in a new issue