Unhardcode server names and add config file
This commit is contained in:
parent
042c68cd95
commit
148510db2f
6 changed files with 107 additions and 26 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/.env
|
/.env
|
||||||
/output.log
|
/output.log
|
||||||
|
/config.toml
|
||||||
|
|
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -736,9 +736,11 @@ dependencies = [
|
||||||
"notify",
|
"notify",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rustls-connector",
|
"rustls-connector",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"toml",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
@ -1033,6 +1035,9 @@ name = "serde"
|
||||||
version = "1.0.123"
|
version = "1.0.123"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
|
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
|
@ -1205,6 +1210,15 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
|
@ -27,3 +27,5 @@ webpki-roots = "0.21.0"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
|
|
||||||
imap = { path = "imap" }
|
imap = { path = "imap" }
|
||||||
|
toml = "0.5.8"
|
||||||
|
serde = { version = "1.0.123", features = ["derive"] }
|
||||||
|
|
8
src/config.rs
Normal file
8
src/config.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub server: String,
|
||||||
|
pub port: u16,
|
||||||
|
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
86
src/mail.rs
86
src/mail.rs
|
@ -14,41 +14,49 @@ use imap::{
|
||||||
parser::parse_response,
|
parser::parse_response,
|
||||||
types::{Capability, RequestId, Response, ResponseCode, State, Status},
|
types::{Capability, RequestId, Response, ResponseCode, State, Status},
|
||||||
};
|
};
|
||||||
use tokio::{net::TcpStream, sync::mpsc};
|
use tokio::{
|
||||||
|
net::TcpStream,
|
||||||
|
sync::mpsc::{self, UnboundedReceiver},
|
||||||
|
};
|
||||||
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
||||||
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
pub enum MailCommand {
|
pub enum MailCommand {
|
||||||
|
Refresh,
|
||||||
Raw(Command),
|
Raw(Command),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_mail(server: impl AsRef<str>, port: u16) -> Result<()> {
|
pub async fn run_mail(config: Config, cmd_in: UnboundedReceiver<MailCommand>) -> Result<()> {
|
||||||
let server = server.as_ref();
|
let server = config.server.as_str();
|
||||||
|
let port = config.port;
|
||||||
|
|
||||||
let client = TcpStream::connect((server, port)).await?;
|
let client = TcpStream::connect((server, port)).await?;
|
||||||
let codec = LinesCodec::new();
|
let codec = LinesCodec::new();
|
||||||
let framed = codec.framed(client);
|
let framed = codec.framed(client);
|
||||||
let mut state = State::NotAuthenticated;
|
let mut state = State::NotAuthenticated;
|
||||||
let (sink, stream) = framed.split::<String>();
|
let (sink, stream) = framed.split::<String>();
|
||||||
|
|
||||||
let result = listen_loop(&mut state, sink, stream).await?;
|
let result = listen_loop(config.clone(), &mut state, sink, stream, false).await?;
|
||||||
if let LoopExit::NegotiateTls(stream, sink) = result {
|
if let LoopExit::NegotiateTls(stream, sink) = result {
|
||||||
debug!("negotiating tls");
|
debug!("negotiating tls");
|
||||||
let mut config = ClientConfig::new();
|
let mut tls_config = ClientConfig::new();
|
||||||
config
|
tls_config
|
||||||
.root_store
|
.root_store
|
||||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
let config = TlsConnector::from(Arc::new(config));
|
let tls_config = TlsConnector::from(Arc::new(tls_config));
|
||||||
let dnsname = DNSNameRef::try_from_ascii_str(server).unwrap();
|
let dnsname = DNSNameRef::try_from_ascii_str(server).unwrap();
|
||||||
|
|
||||||
// reconstruct the original stream
|
// reconstruct the original stream
|
||||||
let stream = stream.reunite(sink)?.into_inner();
|
let stream = stream.reunite(sink)?.into_inner();
|
||||||
// let stream = TcpStream::connect((server, port)).await?;
|
// let stream = TcpStream::connect((server, port)).await?;
|
||||||
let stream = config.connect(dnsname, stream).await?;
|
let stream = tls_config.connect(dnsname, stream).await?;
|
||||||
|
|
||||||
let codec = LinesCodec::new();
|
let codec = LinesCodec::new();
|
||||||
let framed = codec.framed(stream);
|
let framed = codec.framed(stream);
|
||||||
let (sink, stream) = framed.split::<String>();
|
let (sink, stream) = framed.split::<String>();
|
||||||
listen_loop(&mut state, sink, stream).await?;
|
listen_loop(config.clone(), &mut state, sink, stream, true).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -59,7 +67,13 @@ enum LoopExit<S, S2> {
|
||||||
Closed,
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn listen_loop<S, S2>(st: &mut State, sink: S2, mut stream: S) -> Result<LoopExit<S, S2>>
|
async fn listen_loop<S, S2>(
|
||||||
|
config: Config,
|
||||||
|
st: &mut State,
|
||||||
|
sink: S2,
|
||||||
|
mut stream: S,
|
||||||
|
with_ssl: bool,
|
||||||
|
) -> Result<LoopExit<S, S2>>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
||||||
S2: Sink<String> + Unpin,
|
S2: Sink<String> + Unpin,
|
||||||
|
@ -68,6 +82,14 @@ where
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
|
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
|
||||||
let mut cmd_mgr = CommandManager::new(sink);
|
let mut cmd_mgr = CommandManager::new(sink);
|
||||||
|
|
||||||
|
if with_ssl {
|
||||||
|
let cmd = Command {
|
||||||
|
args: b"CAPABILITY".to_vec(),
|
||||||
|
next_state: Some(State::Authenticated),
|
||||||
|
};
|
||||||
|
cmd_mgr.send(cmd, |_| {}).await?;
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let fut1 = stream.next();
|
let fut1 = stream.next();
|
||||||
let fut2 = rx.recv();
|
let fut2 = rx.recv();
|
||||||
|
@ -95,23 +117,39 @@ where
|
||||||
code: Some(ResponseCode::Capabilities(caps)),
|
code: Some(ResponseCode::Capabilities(caps)),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut has_starttls = false;
|
if !with_ssl {
|
||||||
for cap in caps {
|
// prepare to do TLS negotiation
|
||||||
if let Capability::Atom("STARTTLS") = cap {
|
let mut has_starttls = false;
|
||||||
has_starttls = true;
|
for cap in caps {
|
||||||
|
if let Capability::Atom("STARTTLS") = cap {
|
||||||
|
has_starttls = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_starttls {
|
||||||
|
let cmd = Command {
|
||||||
|
args: b"STARTTLS".to_vec(),
|
||||||
|
next_state: None,
|
||||||
|
};
|
||||||
|
let tx = tx.clone();
|
||||||
|
cmd_mgr
|
||||||
|
.send(cmd, move |_| {
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if has_starttls {
|
}
|
||||||
|
|
||||||
|
Response::Capabilities(caps) => {
|
||||||
|
if with_ssl {
|
||||||
|
// send authentication information
|
||||||
let cmd = Command {
|
let cmd = Command {
|
||||||
args: b"STARTTLS".to_vec(),
|
args: format!("LOGIN {} {}", config.username, config.password)
|
||||||
next_state: None,
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
next_state: Some(State::Authenticated),
|
||||||
};
|
};
|
||||||
let tx = tx.clone();
|
cmd_mgr.send(cmd, |_| {}).await?;
|
||||||
cmd_mgr
|
|
||||||
.send(cmd, move |_| {
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +207,8 @@ where
|
||||||
let cmd_str = std::str::from_utf8(&cmd.args)?;
|
let cmd_str = std::str::from_utf8(&cmd.args)?;
|
||||||
let full_str = format!("{} {}", tag_str, cmd_str);
|
let full_str = format!("{} {}", tag_str, cmd_str);
|
||||||
self.in_flight.insert(tag_str.clone(), cb);
|
self.in_flight.insert(tag_str.clone(), cb);
|
||||||
|
|
||||||
|
debug!(">>> {:?}", full_str);
|
||||||
self.sink
|
self.sink
|
||||||
.send(full_str)
|
.send(full_str)
|
||||||
.await
|
.await
|
||||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -4,13 +4,21 @@ extern crate anyhow;
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
mod config;
|
||||||
mod mail;
|
mod mail;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::future::TryFutureExt;
|
use futures::future::TryFutureExt;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
type ExitSender = oneshot::Sender<()>;
|
type ExitSender = oneshot::Sender<()>;
|
||||||
|
|
||||||
|
@ -18,9 +26,17 @@ type ExitSender = oneshot::Sender<()>;
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
setup_logger()?;
|
setup_logger()?;
|
||||||
|
|
||||||
let (exit_tx, exit_rx) = oneshot::channel::<()>();
|
let config: Config = {
|
||||||
|
let mut config_file = File::open("config.toml")?;
|
||||||
|
let mut contents = Vec::new();
|
||||||
|
config_file.read_to_end(&mut contents)?;
|
||||||
|
toml::from_slice(&contents)?
|
||||||
|
};
|
||||||
|
|
||||||
tokio::spawn(mail::run_mail("mzhang.io", 143).unwrap_or_else(report_err));
|
let (exit_tx, exit_rx) = oneshot::channel::<()>();
|
||||||
|
let (mail_tx, mail_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
tokio::spawn(mail::run_mail(config.clone(), mail_rx).unwrap_or_else(report_err));
|
||||||
let mut stdout = std::io::stdout();
|
let mut stdout = std::io::stdout();
|
||||||
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
|
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue