melib/imap: introduce a conf flag for server timeout

timeout integer                       (optional) Timeout to use for server connections in seconds.  A timeout of 0 seconds means there's no timeout.  (16)
master
Manos Pitsidianakis 2020-09-12 22:05:48 +03:00
parent 96985c9c1f
commit 06a58a70bd
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 91 additions and 43 deletions

View File

@ -240,6 +240,12 @@ Use IDLE extension.
Use COMPRESS=DEFLATE extension (if built with DEFLATE support). Use COMPRESS=DEFLATE extension (if built with DEFLATE support).
.\" default value .\" default value
.Pq Em true .Pq Em true
.It Ic timeout Ar integer
.Pq Em optional
Timeout to use for server connections in seconds.
A timeout of 0 seconds means there's no timeout.
.\" default value
.Pq Em 16
.El .El
.Ss JMAP only .Ss JMAP only
JMAP specific options JMAP specific options

View File

@ -97,6 +97,7 @@ pub struct ImapServerConf {
pub use_tls: bool, pub use_tls: bool,
pub danger_accept_invalid_certs: bool, pub danger_accept_invalid_certs: bool,
pub protocol: ImapProtocol, pub protocol: ImapProtocol,
pub timeout: Option<Duration>,
} }
struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>); struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>);
@ -167,6 +168,7 @@ pub struct UIDStore {
mailboxes: Arc<FutureMutex<HashMap<MailboxHash, ImapMailbox>>>, mailboxes: Arc<FutureMutex<HashMap<MailboxHash, ImapMailbox>>>,
is_online: Arc<Mutex<(Instant, Result<()>)>>, is_online: Arc<Mutex<(Instant, Result<()>)>>,
event_consumer: BackendEventConsumer, event_consumer: BackendEventConsumer,
timeout: Option<Duration>,
} }
impl UIDStore { impl UIDStore {
@ -174,6 +176,7 @@ impl UIDStore {
account_hash: AccountHash, account_hash: AccountHash,
account_name: Arc<String>, account_name: Arc<String>,
event_consumer: BackendEventConsumer, event_consumer: BackendEventConsumer,
timeout: Option<Duration>,
) -> Self { ) -> Self {
UIDStore { UIDStore {
account_hash, account_hash,
@ -197,6 +200,7 @@ impl UIDStore {
Err(MeliError::new("Account is uninitialised.")), Err(MeliError::new("Account is uninitialised.")),
))), ))),
event_consumer, event_consumer,
timeout,
} }
} }
} }
@ -354,12 +358,12 @@ impl MailBackend for ImapType {
let main_conn = self.connection.clone(); let main_conn = self.connection.clone();
let uid_store = self.uid_store.clone(); let uid_store = self.uid_store.clone();
Ok(Box::pin(async move { Ok(Box::pin(async move {
let inbox = timeout(Duration::from_secs(3), uid_store.mailboxes.lock()) let inbox = timeout(uid_store.timeout, uid_store.mailboxes.lock())
.await? .await?
.get(&mailbox_hash) .get(&mailbox_hash)
.map(std::clone::Clone::clone) .map(std::clone::Clone::clone)
.unwrap(); .unwrap();
let mut conn = timeout(Duration::from_secs(3), main_conn.lock()).await?; let mut conn = timeout(uid_store.timeout, main_conn.lock()).await?;
watch::examine_updates(inbox, &mut conn, &uid_store).await?; watch::examine_updates(inbox, &mut conn, &uid_store).await?;
Ok(()) Ok(())
})) }))
@ -416,11 +420,12 @@ impl MailBackend for ImapType {
fn is_online(&self) -> ResultFuture<()> { fn is_online(&self) -> ResultFuture<()> {
let connection = self.connection.clone(); let connection = self.connection.clone();
let timeout_dur = self.server_conf.timeout;
Ok(Box::pin(async move { Ok(Box::pin(async move {
match timeout(std::time::Duration::from_secs(3), connection.lock()).await { match timeout(timeout_dur, connection.lock()).await {
Ok(mut conn) => { Ok(mut conn) => {
debug!("is_online"); debug!("is_online");
match debug!(timeout(std::time::Duration::from_secs(3), conn.connect()).await) { match debug!(timeout(timeout_dur, conn.connect()).await) {
Ok(Ok(())) => Ok(()), Ok(Ok(())) => Ok(()),
Err(err) | Ok(Err(err)) => { Err(err) | Ok(Err(err)) => {
conn.stream = Err(err.clone()); conn.stream = Err(err.clone());
@ -453,6 +458,7 @@ impl MailBackend for ImapType {
Ok(Box::pin(async move { Ok(Box::pin(async move {
debug!(has_idle); debug!(has_idle);
let main_conn2 = main_conn.clone(); let main_conn2 = main_conn.clone();
let timeout_dur = uid_store.timeout;
let kit = ImapWatchKit { let kit = ImapWatchKit {
conn, conn,
main_conn, main_conn,
@ -463,7 +469,7 @@ impl MailBackend for ImapType {
} else { } else {
poll_with_examine(kit).await poll_with_examine(kit).await
} { } {
let mut main_conn = timeout(Duration::from_secs(3), main_conn2.lock()).await?; let mut main_conn = timeout(timeout_dur, main_conn2.lock()).await?;
if err.kind.is_network() { if err.kind.is_network() {
main_conn.uid_store.is_online.lock().unwrap().1 = Err(err.clone()); main_conn.uid_store.is_online.lock().unwrap().1 = Err(err.clone());
} }
@ -1222,6 +1228,12 @@ impl ImapType {
s.name, s.name,
))); )));
} }
let timeout = get_conf_val!(s["timeout"], 16_u64)?;
let timeout = if timeout == 0 {
None
} else {
Some(Duration::from_secs(timeout))
};
let server_conf = ImapServerConf { let server_conf = ImapServerConf {
server_hostname: server_hostname.to_string(), server_hostname: server_hostname.to_string(),
server_username: server_username.to_string(), server_username: server_username.to_string(),
@ -1238,6 +1250,7 @@ impl ImapType {
deflate: get_conf_val!(s["use_deflate"], true)?, deflate: get_conf_val!(s["use_deflate"], true)?,
}, },
}, },
timeout,
}; };
let account_hash = { let account_hash = {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
@ -1247,7 +1260,12 @@ impl ImapType {
let account_name = Arc::new(s.name().to_string()); let account_name = Arc::new(s.name().to_string());
let uid_store: Arc<UIDStore> = Arc::new(UIDStore { let uid_store: Arc<UIDStore> = Arc::new(UIDStore {
keep_offline_cache, keep_offline_cache,
..UIDStore::new(account_hash, account_name, event_consumer) ..UIDStore::new(
account_hash,
account_name,
event_consumer,
server_conf.timeout,
)
}); });
let connection = ImapConnection::new_connection(&server_conf, uid_store.clone()); let connection = ImapConnection::new_connection(&server_conf, uid_store.clone());
@ -1262,15 +1280,18 @@ impl ImapType {
pub fn shell(&mut self) { pub fn shell(&mut self) {
let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone());
futures::executor::block_on(timeout(Duration::from_secs(3), conn.connect())) futures::executor::block_on(timeout(self.server_conf.timeout, conn.connect()))
.unwrap() .unwrap()
.unwrap(); .unwrap();
let mut res = String::with_capacity(8 * 1024); let mut res = String::with_capacity(8 * 1024);
futures::executor::block_on(timeout(Duration::from_secs(3), conn.send_command(b"NOOP")))
.unwrap()
.unwrap();
futures::executor::block_on(timeout( futures::executor::block_on(timeout(
Duration::from_secs(3), self.server_conf.timeout,
conn.send_command(b"NOOP"),
))
.unwrap()
.unwrap();
futures::executor::block_on(timeout(
self.server_conf.timeout,
conn.read_response(&mut res, RequiredResponses::empty()), conn.read_response(&mut res, RequiredResponses::empty()),
)) ))
.unwrap() .unwrap()
@ -1284,13 +1305,13 @@ impl ImapType {
match io::stdin().read_line(&mut input) { match io::stdin().read_line(&mut input) {
Ok(_) => { Ok(_) => {
futures::executor::block_on(timeout( futures::executor::block_on(timeout(
Duration::from_secs(3), self.server_conf.timeout,
conn.send_command(input.as_bytes()), conn.send_command(input.as_bytes()),
)) ))
.unwrap() .unwrap()
.unwrap(); .unwrap();
futures::executor::block_on(timeout( futures::executor::block_on(timeout(
Duration::from_secs(3), self.server_conf.timeout,
conn.read_lines(&mut res, String::new()), conn.read_lines(&mut res, String::new()),
)) ))
.unwrap() .unwrap()
@ -1460,6 +1481,7 @@ impl ImapType {
s.name.as_str(), s.name.as_str(),
))); )));
} }
let _timeout = get_conf_val!(s["timeout"], 16_u64)?;
Ok(()) Ok(())
} }

View File

@ -80,6 +80,7 @@ pub struct ImapStream {
pub stream: AsyncWrapper<Connection>, pub stream: AsyncWrapper<Connection>,
pub protocol: ImapProtocol, pub protocol: ImapProtocol,
pub current_mailbox: MailboxSelection, pub current_mailbox: MailboxSelection,
pub timeout: Option<Duration>,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
@ -134,8 +135,12 @@ impl ImapStream {
}; };
let mut socket = AsyncWrapper::new(Connection::Tcp( let mut socket = AsyncWrapper::new(Connection::Tcp(
TcpStream::connect_timeout(&addr, Duration::new(4, 0)) if let Some(timeout) = server_conf.timeout {
.chain_err_kind(crate::error::ErrorKind::Network)?, TcpStream::connect_timeout(&addr, timeout)
.chain_err_kind(crate::error::ErrorKind::Network)?
} else {
TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)?
},
)) ))
.chain_err_kind(crate::error::ErrorKind::Network)?; .chain_err_kind(crate::error::ErrorKind::Network)?;
if server_conf.use_starttls { if server_conf.use_starttls {
@ -239,8 +244,12 @@ impl ImapStream {
))); )));
}; };
AsyncWrapper::new(Connection::Tcp( AsyncWrapper::new(Connection::Tcp(
TcpStream::connect_timeout(&addr, Duration::new(4, 0)) if let Some(timeout) = server_conf.timeout {
.chain_err_kind(crate::error::ErrorKind::Network)?, TcpStream::connect_timeout(&addr, timeout)
.chain_err_kind(crate::error::ErrorKind::Network)?
} else {
TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)?
},
)) ))
.chain_err_kind(crate::error::ErrorKind::Network)? .chain_err_kind(crate::error::ErrorKind::Network)?
}; };
@ -250,6 +259,7 @@ impl ImapStream {
stream, stream,
protocol: server_conf.protocol, protocol: server_conf.protocol,
current_mailbox: MailboxSelection::None, current_mailbox: MailboxSelection::None,
timeout: server_conf.timeout,
}; };
if let ImapProtocol::ManageSieve = server_conf.protocol { if let ImapProtocol::ManageSieve = server_conf.protocol {
use data_encoding::BASE64; use data_encoding::BASE64;
@ -384,7 +394,7 @@ impl ImapStream {
ret.clear(); ret.clear();
let mut last_line_idx: usize = 0; let mut last_line_idx: usize = 0;
loop { loop {
match timeout(Duration::from_secs(16), self.stream.read(&mut buf)).await? { match timeout(self.timeout, self.stream.read(&mut buf)).await? {
Ok(0) => break, Ok(0) => break,
Ok(b) => { Ok(b) => {
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) }); ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
@ -432,7 +442,7 @@ impl ImapStream {
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
if let Err(err) = timeout( if let Err(err) = timeout(
Duration::from_secs(16), self.timeout,
try_await(async move { try_await(async move {
let command = command.trim(); let command = command.trim();
match self.protocol { match self.protocol {
@ -603,6 +613,7 @@ impl ImapConnection {
stream, stream,
protocol, protocol,
current_mailbox, current_mailbox,
timeout,
} = std::mem::replace(&mut self.stream, Err(MeliError::new("")))?; } = std::mem::replace(&mut self.stream, Err(MeliError::new("")))?;
let stream = stream.into_inner()?; let stream = stream.into_inner()?;
self.stream = Ok(ImapStream { self.stream = Ok(ImapStream {
@ -610,6 +621,7 @@ impl ImapConnection {
stream: AsyncWrapper::new(stream.deflate())?, stream: AsyncWrapper::new(stream.deflate())?,
protocol, protocol,
current_mailbox, current_mailbox,
timeout,
}); });
} }
} }

View File

@ -96,6 +96,12 @@ pub fn new_managesieve_connection(
let server_password = get_conf_val!(s["server_password"])?; let server_password = get_conf_val!(s["server_password"])?;
let server_port = get_conf_val!(s["server_port"], 4190)?; let server_port = get_conf_val!(s["server_port"], 4190)?;
let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?; let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?;
let timeout = get_conf_val!(s["timeout"], 16_u64)?;
let timeout = if timeout == 0 {
None
} else {
Some(std::time::Duration::from_secs(timeout))
};
let server_conf = ImapServerConf { let server_conf = ImapServerConf {
server_hostname: server_hostname.to_string(), server_hostname: server_hostname.to_string(),
server_username: server_username.to_string(), server_username: server_username.to_string(),
@ -105,13 +111,19 @@ pub fn new_managesieve_connection(
use_tls: true, use_tls: true,
danger_accept_invalid_certs, danger_accept_invalid_certs,
protocol: ImapProtocol::ManageSieve, protocol: ImapProtocol::ManageSieve,
timeout,
}; };
let uid_store = Arc::new(UIDStore { let uid_store = Arc::new(UIDStore {
is_online: Arc::new(Mutex::new(( is_online: Arc::new(Mutex::new((
Instant::now(), Instant::now(),
Err(MeliError::new("Account is uninitialised.")), Err(MeliError::new("Account is uninitialised.")),
))), ))),
..UIDStore::new(account_hash, Arc::new(account_name), event_consumer) ..UIDStore::new(
account_hash,
Arc::new(account_name),
event_consumer,
server_conf.timeout,
)
}); });
Ok(ImapConnection::new_connection(&server_conf, uid_store)) Ok(ImapConnection::new_connection(&server_conf, uid_store))
} }

View File

@ -66,8 +66,7 @@ impl BackendOp for ImapOp {
if !exists_in_cache { if !exists_in_cache {
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
{ {
let mut conn = let mut conn = timeout(uid_store.timeout, connection.lock()).await?;
timeout(std::time::Duration::from_secs(3), connection.lock()).await?;
conn.examine_mailbox(mailbox_hash, &mut response, false) conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?; .await?;
conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes()) conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes())

View File

@ -39,7 +39,7 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
conn.connect().await?; conn.connect().await?;
loop { loop {
let mailboxes: HashMap<MailboxHash, ImapMailbox> = { let mailboxes: HashMap<MailboxHash, ImapMailbox> = {
let mailboxes_lck = timeout(Duration::from_secs(3), uid_store.mailboxes.lock()).await?; let mailboxes_lck = timeout(uid_store.timeout, uid_store.mailboxes.lock()).await?;
mailboxes_lck.clone() mailboxes_lck.clone()
}; };
for (_, mailbox) in mailboxes { for (_, mailbox) in mailboxes {
@ -109,14 +109,16 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
let mut blockn = ImapBlockingConnection::from(conn); let mut blockn = ImapBlockingConnection::from(conn);
let mut beat = std::time::Instant::now(); let mut beat = std::time::Instant::now();
let mut watch = std::time::Instant::now(); let mut watch = std::time::Instant::now();
/* duration interval before IMAP timeouts */
const _35_MINS: std::time::Duration = std::time::Duration::from_secs(35 * 60);
/* duration interval to send heartbeat */ /* duration interval to send heartbeat */
const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60); const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60);
/* duration interval to check other mailboxes for changes */ /* duration interval to check other mailboxes for changes */
const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60); const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60);
while let Some(line) = timeout(Duration::from_secs(35 * 60), blockn.as_stream()).await? { while let Some(line) = timeout(Some(_35_MINS), blockn.as_stream()).await? {
let now = std::time::Instant::now(); let now = std::time::Instant::now();
if now.duration_since(beat) >= _26_MINS { if now.duration_since(beat) >= _26_MINS {
let mut main_conn_lck = timeout(Duration::from_secs(3), main_conn.lock()).await?; let mut main_conn_lck = timeout(uid_store.timeout, main_conn.lock()).await?;
blockn.conn.send_raw(b"DONE").await?; blockn.conn.send_raw(b"DONE").await?;
blockn blockn
.conn .conn
@ -131,10 +133,10 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
} }
if now.duration_since(watch) >= _5_MINS { if now.duration_since(watch) >= _5_MINS {
/* Time to poll all inboxes */ /* Time to poll all inboxes */
let mut conn = timeout(Duration::from_secs(3), main_conn.lock()).await?; let mut conn = timeout(uid_store.timeout, main_conn.lock()).await?;
let mailboxes: HashMap<MailboxHash, ImapMailbox> = { let mailboxes: HashMap<MailboxHash, ImapMailbox> = {
let mailboxes_lck = let mailboxes_lck =
timeout(Duration::from_secs(3), uid_store.mailboxes.lock()).await?; timeout(uid_store.timeout, uid_store.mailboxes.lock()).await?;
mailboxes_lck.clone() mailboxes_lck.clone()
}; };
for (h, mailbox) in mailboxes { for (h, mailbox) in mailboxes {
@ -183,16 +185,6 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
} }
debug!("IDLE connection dropped"); debug!("IDLE connection dropped");
let err: &str = blockn.err().unwrap_or("Unknown reason."); let err: &str = blockn.err().unwrap_or("Unknown reason.");
timeout(Duration::from_secs(3), main_conn.lock())
.await?
.add_refresh_event(RefreshEvent {
account_hash: uid_store.account_hash,
mailbox_hash,
kind: RefreshEventKind::Failure(MeliError::new(format!(
"IDLE connection dropped: {}",
&err
))),
});
Err(MeliError::new(format!("IDLE connection dropped: {}", err))) Err(MeliError::new(format!("IDLE connection dropped: {}", err)))
} }

View File

@ -191,13 +191,18 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result<std::net::SocketAddr>
use futures::future::{self, Either, Future}; use futures::future::{self, Either, Future};
pub async fn timeout<O>(dur: std::time::Duration, f: impl Future<Output = O>) -> crate::Result<O> { pub async fn timeout<O>(
dur: Option<std::time::Duration>,
f: impl Future<Output = O>,
) -> crate::Result<O> {
futures::pin_mut!(f); futures::pin_mut!(f);
match future::select(f, smol::Timer::after(dur)).await { if let Some(dur) = dur {
Either::Left((out, _)) => Ok(out), match future::select(f, smol::Timer::after(dur)).await {
Either::Right(_) => { Either::Left((out, _)) => Ok(out),
Err(crate::error::MeliError::new("Timed out.") Either::Right(_) => Err(crate::error::MeliError::new("Timed out.")
.set_kind(crate::error::ErrorKind::Timeout)) .set_kind(crate::error::ErrorKind::Timeout)),
} }
} else {
Ok(f.await)
} }
} }