authentication works for the most part
This commit is contained in:
parent
2b97aca995
commit
8be5d65435
12 changed files with 165 additions and 109 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -781,6 +781,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
|
"async-trait",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -13,6 +13,7 @@ maintenance = { status = "passively-maintained" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
async-trait = "0.1.42"
|
||||||
derive_builder = "0.9.0"
|
derive_builder = "0.9.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
|
48
imap/src/client/auth.rs
Normal file
48
imap/src/client/auth.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::command::Command;
|
||||||
|
use crate::response::{Response, Status};
|
||||||
|
|
||||||
|
use super::{ClientAuthenticated, ClientUnauthenticated};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Auth {
|
||||||
|
/// Performs authentication, consuming the client
|
||||||
|
// TODO: return the unauthed client if failed?
|
||||||
|
async fn perform_auth(self, client: ClientUnauthenticated) -> Result<ClientAuthenticated>;
|
||||||
|
|
||||||
|
fn convert_client(client: ClientUnauthenticated) -> ClientAuthenticated {
|
||||||
|
match client {
|
||||||
|
ClientUnauthenticated::Encrypted(e) => ClientAuthenticated::Encrypted(e),
|
||||||
|
ClientUnauthenticated::Unencrypted(e) => ClientAuthenticated::Unencrypted(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Plain {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Auth for Plain {
|
||||||
|
async fn perform_auth(self, mut client: ClientUnauthenticated) -> Result<ClientAuthenticated> {
|
||||||
|
let command = Command::Login {
|
||||||
|
username: self.username,
|
||||||
|
password: self.password,
|
||||||
|
};
|
||||||
|
let (result, _) = client.execute(command).await?;
|
||||||
|
|
||||||
|
if !matches!(
|
||||||
|
result,
|
||||||
|
Response::Done {
|
||||||
|
status: Status::Ok,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
bail!("unable to login: {:?}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(<Self as Auth>::convert_client(client))
|
||||||
|
}
|
||||||
|
}
|
|
@ -116,7 +116,7 @@ where
|
||||||
/// Executes the CAPABILITY command
|
/// Executes the CAPABILITY command
|
||||||
pub async fn capabilities(&mut self, force: bool) -> Result<()> {
|
pub async fn capabilities(&mut self, force: bool) -> Result<()> {
|
||||||
{
|
{
|
||||||
let caps = &*self.caps.read();
|
let caps = self.caps.read();
|
||||||
if caps.is_some() && !force {
|
if caps.is_some() && !force {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -134,13 +134,10 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.find(|resp| matches!(resp, Response::Capabilities(_)))
|
.find(|resp| matches!(resp, Response::Capabilities(_)))
|
||||||
{
|
{
|
||||||
let caps = &mut *self.caps.write();
|
let mut caps = self.caps.write();
|
||||||
*caps = Some(new_caps.iter().cloned().collect());
|
*caps = Some(new_caps.iter().cloned().collect());
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let Response::Capabilities(caps) = resp {
|
|
||||||
// debug!("capabilities: {:?}", caps);
|
|
||||||
// }
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +180,7 @@ where
|
||||||
let cap = parse_capability(cap)?;
|
let cap = parse_capability(cap)?;
|
||||||
|
|
||||||
self.capabilities(false).await?;
|
self.capabilities(false).await?;
|
||||||
let caps = &*self.caps.read();
|
let caps = self.caps.read();
|
||||||
// TODO: refresh caps
|
// TODO: refresh caps
|
||||||
|
|
||||||
let caps = caps.as_ref().unwrap();
|
let caps = caps.as_ref().unwrap();
|
||||||
|
@ -276,6 +273,7 @@ where
|
||||||
debug!("got a new line {:?}", next_line);
|
debug!("got a new line {:?}", next_line);
|
||||||
let resp = parse_response(next_line)?;
|
let resp = parse_response(next_line)?;
|
||||||
|
|
||||||
|
// if this is the very first message, treat it as a greeting
|
||||||
if let Some(greeting) = greeting.take() {
|
if let Some(greeting) = greeting.take() {
|
||||||
let (greeting, waker) = &mut *greeting.write();
|
let (greeting, waker) = &mut *greeting.write();
|
||||||
debug!("received greeting!");
|
debug!("received greeting!");
|
||||||
|
@ -285,19 +283,31 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match &resp {
|
// update capabilities list
|
||||||
// capabilities list
|
// TODO: probably not really necessary here (done somewhere else)?
|
||||||
Response::Capabilities(new_caps)
|
if let Response::Capabilities(new_caps)
|
||||||
| Response::Data {
|
| Response::Data {
|
||||||
status: Status::Ok,
|
status: Status::Ok,
|
||||||
code: Some(ResponseCode::Capabilities(new_caps)),
|
code: Some(ResponseCode::Capabilities(new_caps)),
|
||||||
..
|
..
|
||||||
} => {
|
} = &resp
|
||||||
|
{
|
||||||
let caps = &mut *caps.write();
|
let caps = &mut *caps.write();
|
||||||
*caps = Some(new_caps.iter().cloned().collect());
|
*caps = Some(new_caps.iter().cloned().collect());
|
||||||
debug!("new caps: {:?}", caps);
|
debug!("new caps: {:?}", caps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match &resp {
|
||||||
|
Response::Data {
|
||||||
|
status: Status::Ok, ..
|
||||||
|
} => {
|
||||||
|
let mut results = results.write();
|
||||||
|
if let Some((_, _, intermediate, _)) = results.iter_mut().next() {
|
||||||
|
debug!("pushed to intermediate: {:?}", resp);
|
||||||
|
intermediate.push(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bye
|
// bye
|
||||||
Response::Data {
|
Response::Data {
|
||||||
status: Status::Bye,
|
status: Status::Bye,
|
||||||
|
@ -308,7 +318,7 @@ where
|
||||||
|
|
||||||
Response::Done { tag, .. } => {
|
Response::Done { tag, .. } => {
|
||||||
if tag.starts_with(TAG_PREFIX) {
|
if tag.starts_with(TAG_PREFIX) {
|
||||||
let id = tag.trim_start_matches(TAG_PREFIX).parse::<usize>()?;
|
// let id = tag.trim_start_matches(TAG_PREFIX).parse::<usize>()?;
|
||||||
let mut results = results.write();
|
let mut results = results.write();
|
||||||
if let Some((_, opt, _, waker)) = results.iter_mut().next() {
|
if let Some((_, opt, _, waker)) = results.iter_mut().next() {
|
||||||
*opt = Some(resp);
|
*opt = Some(resp);
|
||||||
|
@ -321,38 +331,6 @@ where
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug!("parsed as: {:?}", resp);
|
|
||||||
// let next_line = next_line.trim_end_matches('\n').trim_end_matches('\r');
|
|
||||||
|
|
||||||
// let mut parts = next_line.split(" ");
|
|
||||||
// let tag = parts.next().unwrap();
|
|
||||||
// let rest = parts.collect::<Vec<_>>().join(" ");
|
|
||||||
|
|
||||||
// if tag == "*" {
|
|
||||||
// debug!("UNTAGGED {:?}", rest);
|
|
||||||
|
|
||||||
// // TODO: verify that the greeting is actually an OK
|
|
||||||
// if let Some(greeting) = greeting.take() {
|
|
||||||
// let (greeting, waker) = &mut *greeting.write();
|
|
||||||
// debug!("got greeting");
|
|
||||||
// *greeting = true;
|
|
||||||
// if let Some(waker) = waker.take() {
|
|
||||||
// waker.wake();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if tag.starts_with(TAG_PREFIX) {
|
|
||||||
// let id = tag.trim_start_matches(TAG_PREFIX).parse::<usize>()?;
|
|
||||||
// debug!("set {} to {:?}", id, rest);
|
|
||||||
// let mut results = results.write();
|
|
||||||
// if let Some((c, w)) = results.get_mut(&id) {
|
|
||||||
// // *c = Some(rest.to_string());
|
|
||||||
// *c = Some(resp);
|
|
||||||
// if let Some(waker) = w.take() {
|
|
||||||
// waker.wake();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Either::Right((_, _)) => {
|
Either::Right((_, _)) => {
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
mod inner;
|
mod inner;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -89,22 +90,18 @@ impl ClientConfig {
|
||||||
|
|
||||||
let inner = Client::new(conn, self);
|
let inner = Client::new(conn, self);
|
||||||
inner.wait_for_greeting().await;
|
inner.wait_for_greeting().await;
|
||||||
return Ok(ClientUnauthenticated::Encrypted(
|
return Ok(ClientUnauthenticated::Encrypted(inner));
|
||||||
ClientUnauthenticatedEncrypted { inner },
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
let inner = Client::new(conn, self);
|
let inner = Client::new(conn, self);
|
||||||
inner.wait_for_greeting().await;
|
inner.wait_for_greeting().await;
|
||||||
return Ok(ClientUnauthenticated::Unencrypted(
|
return Ok(ClientUnauthenticated::Unencrypted(inner));
|
||||||
ClientUnauthenticatedUnencrypted { inner },
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientUnauthenticated {
|
pub enum ClientUnauthenticated {
|
||||||
Encrypted(ClientUnauthenticatedEncrypted),
|
Encrypted(Client<TlsStream<TcpStream>>),
|
||||||
Unencrypted(ClientUnauthenticatedUnencrypted),
|
Unencrypted(Client<TcpStream>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientUnauthenticated {
|
impl ClientUnauthenticated {
|
||||||
|
@ -113,44 +110,46 @@ impl ClientUnauthenticated {
|
||||||
// this is a no-op, we don't need to upgrade
|
// this is a no-op, we don't need to upgrade
|
||||||
ClientUnauthenticated::Encrypted(_) => Ok(self),
|
ClientUnauthenticated::Encrypted(_) => Ok(self),
|
||||||
ClientUnauthenticated::Unencrypted(e) => {
|
ClientUnauthenticated::Unencrypted(e) => {
|
||||||
let client = ClientUnauthenticatedEncrypted {
|
Ok(ClientUnauthenticated::Encrypted(e.upgrade().await?))
|
||||||
inner: e.inner.upgrade().await?,
|
|
||||||
};
|
|
||||||
Ok(ClientUnauthenticated::Encrypted(client))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: Exposing low-level execute , shoudl remove later
|
/// Exposing low-level execute
|
||||||
pub async fn execute(&mut self, cmd: Command) -> Result<(Response, Vec<Response>)> {
|
async fn execute(&mut self, cmd: Command) -> Result<(Response, Vec<Response>)> {
|
||||||
match self {
|
match self {
|
||||||
ClientUnauthenticated::Encrypted(e) => e.inner.execute(cmd).await,
|
ClientUnauthenticated::Encrypted(e) => e.execute(cmd).await,
|
||||||
ClientUnauthenticated::Unencrypted(e) => e.inner.execute(cmd).await,
|
ClientUnauthenticated::Unencrypted(e) => e.execute(cmd).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the server that the client is talking to has support for the given capability.
|
/// Checks if the server that the client is talking to has support for the given capability.
|
||||||
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
pub async fn has_capability(&mut self, cap: impl AsRef<str>) -> Result<bool> {
|
||||||
match self {
|
match self {
|
||||||
ClientUnauthenticated::Encrypted(e) => e.inner.has_capability(cap).await,
|
ClientUnauthenticated::Encrypted(e) => e.has_capability(cap).await,
|
||||||
ClientUnauthenticated::Unencrypted(e) => e.inner.has_capability(cap).await,
|
ClientUnauthenticated::Unencrypted(e) => e.has_capability(cap).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientUnauthenticatedUnencrypted {
|
pub enum ClientAuthenticated {
|
||||||
/// Connection to the remote server
|
Encrypted(Client<TlsStream<TcpStream>>),
|
||||||
inner: Client<TcpStream>,
|
Unencrypted(Client<TcpStream>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientUnauthenticatedUnencrypted {
|
impl ClientAuthenticated {
|
||||||
pub async fn upgrade(&self) {}
|
/// Exposing low-level execute
|
||||||
|
async fn execute(&mut self, cmd: Command) -> Result<(Response, Vec<Response>)> {
|
||||||
|
match self {
|
||||||
|
ClientAuthenticated::Encrypted(e) => e.execute(cmd).await,
|
||||||
|
ClientAuthenticated::Unencrypted(e) => e.execute(cmd).await,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An IMAP client that isn't authenticated.
|
pub async fn list(&mut self) -> Result<()> {
|
||||||
pub struct ClientUnauthenticatedEncrypted {
|
let cmd = Command::List { reference: "".to_owned(), mailbox: "*".to_owned() };
|
||||||
/// Connection to the remote server
|
let resp = self.execute(cmd).await?;
|
||||||
inner: Client<TlsStream<TcpStream>>,
|
debug!("list response: {:?}", resp);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientUnauthenticatedEncrypted {}
|
|
||||||
|
|
|
@ -6,14 +6,19 @@ pub enum Command {
|
||||||
Capability,
|
Capability,
|
||||||
Starttls,
|
Starttls,
|
||||||
Login { username: String, password: String },
|
Login { username: String, password: String },
|
||||||
|
Select { mailbox: String },
|
||||||
|
List { reference: String, mailbox: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Command {
|
impl fmt::Display for Command {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use Command::*;
|
||||||
match self {
|
match self {
|
||||||
Command::Capability => write!(f, "CAPABILITY"),
|
Capability => write!(f, "CAPABILITY"),
|
||||||
Command::Starttls => write!(f, "STARTTLS"),
|
Starttls => write!(f, "STARTTLS"),
|
||||||
Command::Login { username, password } => write!(f, "LOGIN {} {}", username, password),
|
Login { username, password } => write!(f, "LOGIN {} {}", username, password),
|
||||||
|
Select { mailbox } => write!(f, "SELECT {}", mailbox),
|
||||||
|
List { reference, mailbox } => write!(f, "LIST {:?} {:?}", reference, mailbox),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
#[macro_use]
|
||||||
extern crate derive_builder;
|
extern crate derive_builder;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
|
|
@ -104,9 +104,6 @@ fn build_resp_code(pair: Pair<Rule>) -> Option<ResponseCode> {
|
||||||
unreachable!("{:#?}", pair);
|
unreachable!("{:#?}", pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
// panic!("pair: {:#?}", pair);
|
|
||||||
debug!("pair: {:#?}", pair);
|
|
||||||
|
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next()?;
|
let pair = pairs.next()?;
|
||||||
Some(match pair.as_rule() {
|
Some(match pair.as_rule() {
|
||||||
|
@ -155,7 +152,7 @@ fn build_status(pair: Pair<Rule>) -> Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_flag_list(pair: Pair<Rule>) -> Vec<Flag> {
|
fn build_flag_list(pair: Pair<Rule>) -> Vec<MailboxFlag> {
|
||||||
if !matches!(pair.as_rule(), Rule::flag_list) {
|
if !matches!(pair.as_rule(), Rule::flag_list) {
|
||||||
unreachable!("{:#?}", pair);
|
unreachable!("{:#?}", pair);
|
||||||
}
|
}
|
||||||
|
@ -163,18 +160,18 @@ fn build_flag_list(pair: Pair<Rule>) -> Vec<Flag> {
|
||||||
pair.into_inner().map(build_flag).collect()
|
pair.into_inner().map(build_flag).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_flag(pair: Pair<Rule>) -> Flag {
|
fn build_flag(pair: Pair<Rule>) -> MailboxFlag {
|
||||||
if !matches!(pair.as_rule(), Rule::flag) {
|
if !matches!(pair.as_rule(), Rule::flag) {
|
||||||
unreachable!("{:#?}", pair);
|
unreachable!("{:#?}", pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
match pair.as_str() {
|
match pair.as_str() {
|
||||||
"\\Answered" => Flag::Answered,
|
"\\Answered" => MailboxFlag::Answered,
|
||||||
"\\Flagged" => Flag::Flagged,
|
"\\Flagged" => MailboxFlag::Flagged,
|
||||||
"\\Deleted" => Flag::Deleted,
|
"\\Deleted" => MailboxFlag::Deleted,
|
||||||
"\\Seen" => Flag::Seen,
|
"\\Seen" => MailboxFlag::Seen,
|
||||||
"\\Draft" => Flag::Draft,
|
"\\Draft" => MailboxFlag::Draft,
|
||||||
s if s.starts_with("\\") => Flag::Ext(s.to_owned()),
|
s if s.starts_with("\\") => MailboxFlag::Ext(s.to_owned()),
|
||||||
_ => unreachable!("{:#?}", pair.as_str()),
|
_ => unreachable!("{:#?}", pair.as_str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,10 +192,28 @@ fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
|
||||||
MailboxData::Flags(flags)
|
MailboxData::Flags(flags)
|
||||||
}
|
}
|
||||||
Rule::mailbox_data_recent => MailboxData::Recent(build_number(pair)),
|
Rule::mailbox_data_recent => MailboxData::Recent(build_number(pair)),
|
||||||
|
Rule::mailbox_data_list => {
|
||||||
|
let mut pairs = pair.into_inner();
|
||||||
|
let pair = pairs.next().unwrap();
|
||||||
|
let (flags, delimiter, name) = build_mailbox_list(pair);
|
||||||
|
MailboxData::List { flags, delimiter, name }
|
||||||
|
},
|
||||||
_ => unreachable!("{:#?}", pair),
|
_ => unreachable!("{:#?}", pair),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_mailbox_list(pair: Pair<Rule>) -> (Vec<String>, Option<String>, String) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mbx_list_flags(pair: Pair<Rule>) -> Vec<String> {
|
||||||
|
if !matches!(pair.as_rule(), Rule::mbx_list_flags) {
|
||||||
|
unreachable!("{:#?}", pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
fn build_number<T>(pair: Pair<Rule>) -> T
|
fn build_number<T>(pair: Pair<Rule>) -> T
|
||||||
where
|
where
|
||||||
T: FromStr,
|
T: FromStr,
|
||||||
|
@ -266,11 +281,11 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
|
parse_response("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"),
|
||||||
Ok(Response::MailboxData(MailboxData::Flags(vec![
|
Ok(Response::MailboxData(MailboxData::Flags(vec![
|
||||||
Flag::Answered,
|
MailboxFlag::Answered,
|
||||||
Flag::Flagged,
|
MailboxFlag::Flagged,
|
||||||
Flag::Deleted,
|
MailboxFlag::Deleted,
|
||||||
Flag::Seen,
|
MailboxFlag::Seen,
|
||||||
Flag::Draft,
|
MailboxFlag::Draft,
|
||||||
])))
|
])))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -64,11 +64,13 @@ header_list = { "(" ~ header_fld_name ~ (sp ~ header_fld_name)* ~ ")" }
|
||||||
list_wildcards = @{ "%" | "*" }
|
list_wildcards = @{ "%" | "*" }
|
||||||
literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* }
|
literal = @{ "{" ~ number ~ "}" ~ crlf ~ char8* }
|
||||||
mailbox = { ^"INBOX" | astring }
|
mailbox = { ^"INBOX" | astring }
|
||||||
mailbox_data = { mailbox_data_flags | (^"LIST" ~ sp ~ mailbox_list) | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent }
|
mailbox_data = { mailbox_data_flags | mailbox_data_list | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent }
|
||||||
mailbox_data_exists = { number ~ sp ~ ^"EXISTS" }
|
mailbox_data_exists = { number ~ sp ~ ^"EXISTS" }
|
||||||
mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list }
|
mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list }
|
||||||
mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
|
mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
|
||||||
mailbox_list = { "(" ~ mbx_list_flags* ~ ")" ~ sp ~ (dquote ~ quoted_char ~ dquote | nil) ~ sp ~ mailbox }
|
mailbox_data_list = { ^"LIST" ~ sp ~ mailbox_list }
|
||||||
|
mailbox_list = { "(" ~ mbx_list_flags* ~ ")" ~ sp ~ mailbox_list_string ~ sp ~ mailbox }
|
||||||
|
mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil }
|
||||||
mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* }
|
mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* }
|
||||||
mbx_list_oflag = { "\\NoInferiors" | flag_extension }
|
mbx_list_oflag = { "\\NoInferiors" | flag_extension }
|
||||||
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }
|
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }
|
||||||
|
|
|
@ -65,7 +65,7 @@ pub enum AttributeValue {}
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MailboxData {
|
pub enum MailboxData {
|
||||||
Exists(u32),
|
Exists(u32),
|
||||||
Flags(Vec<Flag>),
|
Flags(Vec<MailboxFlag>),
|
||||||
List {
|
List {
|
||||||
flags: Vec<String>,
|
flags: Vec<String>,
|
||||||
delimiter: Option<String>,
|
delimiter: Option<String>,
|
||||||
|
@ -88,7 +88,7 @@ pub enum MailboxData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub enum Flag {
|
pub enum MailboxFlag {
|
||||||
Answered,
|
Answered,
|
||||||
Flagged,
|
Flagged,
|
||||||
Deleted,
|
Deleted,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{future::FutureExt, stream::StreamExt};
|
use futures::{future::FutureExt, stream::StreamExt};
|
||||||
use panorama_imap::{
|
use panorama_imap::{
|
||||||
client::{ClientBuilder, ClientConfig},
|
client::{ClientBuilder, ClientConfig, auth::{self, Auth}},
|
||||||
command::Command as ImapCommand,
|
command::Command as ImapCommand,
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
|
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
|
||||||
|
@ -78,7 +78,7 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
debug!("connecting to {}:{}", &acct.imap.server, acct.imap.port);
|
debug!("connecting to {}:{}", &acct.imap.server, acct.imap.port);
|
||||||
let unauth = builder.open().await?;
|
let unauth = builder.open().await?;
|
||||||
|
|
||||||
let mut unauth = if matches!(acct.imap.tls, TlsMethod::Starttls) {
|
let unauth = if matches!(acct.imap.tls, TlsMethod::Starttls) {
|
||||||
debug!("attempting to upgrade");
|
debug!("attempting to upgrade");
|
||||||
let client = unauth.upgrade().await?;
|
let client = unauth.upgrade().await?;
|
||||||
debug!("upgrade successful");
|
debug!("upgrade successful");
|
||||||
|
@ -89,18 +89,21 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
|
|
||||||
debug!("preparing to auth");
|
debug!("preparing to auth");
|
||||||
// check if the authentication method is supported
|
// check if the authentication method is supported
|
||||||
let authed = match acct.imap.auth {
|
let mut authed = match acct.imap.auth {
|
||||||
ImapAuth::Plain { username, password } => {
|
ImapAuth::Plain { username, password } => {
|
||||||
let ok = unauth.has_capability("AUTH=PLAIN").await?;
|
let auth = auth::Plain {username, password};
|
||||||
let res = unauth.execute(ImapCommand::Login { username, password }).await?;
|
auth.perform_auth(unauth).await?
|
||||||
debug!("res: {:?}", res);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!("authentication successful!");
|
||||||
|
|
||||||
// debug!("sending CAPABILITY");
|
// debug!("sending CAPABILITY");
|
||||||
// let result = unauth.capabilities().await?;
|
// let result = unauth.capabilities().await?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
debug!("listing all emails...");
|
||||||
|
authed.list().await?;
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
||||||
debug!("heartbeat");
|
debug!("heartbeat");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ struct Opt {
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
let now = chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]");
|
||||||
|
|
||||||
// parse command line arguments into options struct
|
// parse command line arguments into options struct
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
|
@ -33,8 +35,8 @@ async fn main() -> Result<()> {
|
||||||
let mut logger = fern::Dispatch::new()
|
let mut logger = fern::Dispatch::new()
|
||||||
.format(move |out, message, record| {
|
.format(move |out, message, record| {
|
||||||
out.finish(format_args!(
|
out.finish(format_args!(
|
||||||
"[{}][{}] {}",
|
"{}[{}][{}] {}",
|
||||||
// chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
now,
|
||||||
record.target(),
|
record.target(),
|
||||||
colors.color(record.level()),
|
colors.color(record.level()),
|
||||||
message
|
message
|
||||||
|
|
Loading…
Reference in a new issue