Unhardcode server names and add config file

This commit is contained in:
Michael Zhang 2021-02-12 07:44:08 -06:00
parent 042c68cd95
commit 148510db2f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
6 changed files with 107 additions and 26 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
/.env /.env
/output.log /output.log
/config.toml

14
Cargo.lock generated
View file

@ -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"

View file

@ -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
View 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,
}

View file

@ -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

View file

@ -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));