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
|
||||
/.env
|
||||
/output.log
|
||||
/config.toml
|
||||
|
|
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
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,
|
||||
}
|
60
src/mail.rs
60
src/mail.rs
|
@ -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
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue