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
/.env
/output.log
/config.toml

14
Cargo.lock generated
View file

@ -736,9 +736,11 @@ dependencies = [
"notify",
"pin-project",
"rustls-connector",
"serde",
"tokio",
"tokio-rustls",
"tokio-util",
"toml",
"webpki-roots",
"xdg",
]
@ -1033,6 +1035,9 @@ name = "serde"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
@ -1205,6 +1210,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"

View file

@ -27,3 +27,5 @@ webpki-roots = "0.21.0"
xdg = "2.2.0"
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,
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_util::codec::{Decoder, LinesCodec, LinesCodecError};
use crate::config::Config;
pub enum MailCommand {
Refresh,
Raw(Command),
}
pub async fn run_mail(server: impl AsRef<str>, port: u16) -> Result<()> {
let server = server.as_ref();
pub async fn run_mail(config: Config, cmd_in: UnboundedReceiver<MailCommand>) -> Result<()> {
let server = config.server.as_str();
let port = config.port;
let client = TcpStream::connect((server, port)).await?;
let codec = LinesCodec::new();
let framed = codec.framed(client);
let mut state = State::NotAuthenticated;
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 {
debug!("negotiating tls");
let mut config = ClientConfig::new();
config
let mut tls_config = ClientConfig::new();
tls_config
.root_store
.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();
// reconstruct the original stream
let stream = stream.reunite(sink)?.into_inner();
// 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 framed = codec.framed(stream);
let (sink, stream) = framed.split::<String>();
listen_loop(&mut state, sink, stream).await?;
listen_loop(config.clone(), &mut state, sink, stream, true).await?;
}
Ok(())
@ -59,7 +67,13 @@ enum LoopExit<S, S2> {
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
S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
S2: Sink<String> + Unpin,
@ -68,6 +82,14 @@ where
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
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 {
let fut1 = stream.next();
let fut2 = rx.recv();
@ -95,6 +117,8 @@ where
code: Some(ResponseCode::Capabilities(caps)),
..
} => {
if !with_ssl {
// prepare to do TLS negotiation
let mut has_starttls = false;
for cap in caps {
if let Capability::Atom("STARTTLS") = cap {
@ -114,6 +138,20 @@ where
.await?;
}
}
}
Response::Capabilities(caps) => {
if with_ssl {
// send authentication information
let cmd = Command {
args: format!("LOGIN {} {}", config.username, config.password)
.as_bytes()
.to_vec(),
next_state: Some(State::Authenticated),
};
cmd_mgr.send(cmd, |_| {}).await?;
}
}
Response::Done { tag, code, .. } => {
cmd_mgr.process_done(tag, code)?;
@ -169,6 +207,8 @@ where
let cmd_str = std::str::from_utf8(&cmd.args)?;
let full_str = format!("{} {}", tag_str, cmd_str);
self.in_flight.insert(tag_str.clone(), cb);
debug!(">>> {:?}", full_str);
self.sink
.send(full_str)
.await

View file

@ -4,13 +4,21 @@ extern crate anyhow;
extern crate crossterm;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde;
mod config;
mod mail;
mod ui;
use std::fs::File;
use std::io::Read;
use anyhow::Result;
use futures::future::TryFutureExt;
use tokio::sync::oneshot;
use tokio::sync::{mpsc, oneshot};
use crate::config::Config;
type ExitSender = oneshot::Sender<()>;
@ -18,9 +26,17 @@ type ExitSender = oneshot::Sender<()>;
async fn main() -> Result<()> {
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();
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));