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"
|
||||
edition = "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "greenmail-test"
|
||||
path = "bin/greenmail_test.rs"
|
||||
|
||||
[features]
|
||||
default = ["rfc2177-idle"]
|
||||
rfc2177-idle = []
|
||||
|
||||
[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 username: 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 futures::future::FutureExt;
|
||||
use tokio::{
|
||||
io::{split, AsyncRead, AsyncWrite, ReadHalf},
|
||||
io::{split, AsyncRead, AsyncWrite, ReadHalf, WriteHalf},
|
||||
sync::oneshot,
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_rustls::client::TlsStream;
|
||||
use tokio_util::codec::FramedRead;
|
||||
|
||||
use crate::codec::ImapCodec;
|
||||
use crate::proto::command::Command;
|
||||
|
||||
use super::upgrade::upgrade;
|
||||
|
||||
type ExitSender = oneshot::Sender<()>;
|
||||
type ExitListener = oneshot::Receiver<()>;
|
||||
|
@ -15,11 +19,13 @@ type ExitListener = oneshot::Receiver<()>;
|
|||
pub struct Inner<C> {
|
||||
read_exit: ExitSender,
|
||||
read_handle: JoinHandle<ReadHalf<C>>,
|
||||
|
||||
write_half: WriteHalf<C>,
|
||||
}
|
||||
|
||||
impl<C> Inner<C>
|
||||
where
|
||||
C: AsyncRead + AsyncWrite + Send + 'static,
|
||||
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
pub async fn open(c: C) -> Result<Self> {
|
||||
// break the stream of bytes into a reader and a writer
|
||||
|
@ -34,8 +40,25 @@ where
|
|||
Ok(Inner {
|
||||
read_exit,
|
||||
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
|
||||
|
@ -57,5 +80,6 @@ where
|
|||
_ = exit => break,
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
|
||||
framed.into_inner()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use tokio_rustls::TlsStream;
|
||||
use std::sync::Arc;
|
||||
|
||||
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> {
|
||||
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 rfc2234;
|
||||
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'*' }
|
||||
rule!(pub list_wildcards : u8 => satisfy(is_list_wildcards));
|
||||
|
||||
/// literal = "{" number "}" CRLF *CHAR8
|
||||
/// ; Number represents the number of CHAR8s
|
||||
// literal = "{" number "}" CRLF *CHAR8
|
||||
// ; 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>> {
|
||||
let mut length_of = terminated(delimited(char('{'), number, char('}')), CRLF);
|
||||
let (i, length) = length_of(i)?;
|
||||
|
|
Loading…
Reference in a new issue