Compare commits

...

5 Commits

Author SHA1 Message Date
Florian Stecker
b9c8cc02a8 Add Cargo.lock to .gitignore 2024-07-29 13:23:42 -04:00
Florian Stecker
257d95528a handle TimedOut error and use write_all 2024-07-29 13:21:24 -04:00
Florian Stecker
caf3912af1 reresolve address at every reconnect, to avoid problems switching from IPv6 to IPv4-only networks 2024-01-02 22:47:52 -05:00
Florian Stecker
002393b18e clarify readme 2023-02-20 18:59:01 -05:00
Florian Stecker
26cdccee3c only look up hostname once in the beginning 2023-02-20 18:51:38 -05:00
4 changed files with 33 additions and 400 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target /target
README.html README.html
Cargo.lock

385
Cargo.lock generated
View File

@@ -1,385 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "cc"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "imapidle"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"rustls",
"webpki-roots",
]
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "os_str_bytes"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
dependencies = [
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -6,7 +6,7 @@ Although this is quite minimal and only implements a tiny subset of the IMAP pro
## Installation ## ## Installation ##
Get [rust] and run Get [Rust] and run
cargo build --release cargo build --release
@@ -33,13 +33,15 @@ The output of `imapidle --help` does a good job at explaining how to use it:
Note that it only supports TLS encrypted IMAP and plain password authentication. Also, it currently reads the password from the command line, which isn't a great thing to do. I might change that eventually. Note that it only supports TLS encrypted IMAP and plain password authentication. Also, it currently reads the password from the command line, which isn't a great thing to do. I might change that eventually.
## Why? ## ## Goals ##
Rust might not be the canonical programming language to do something like this in. And also, this probably already exists somewhere in a more complete and polished form. So this might not useful for anyone else. But I had three goals in making this: I made this for the following reasons:
1. I wanted my emails to arrive faster and without having to manually hit the refresh button. 1. I wanted my emails to arrive faster and without having to manually hit the refresh button.
2. I wanted to find out how IMAP works and why it's often so slow (I'm still not sure about the latter). 2. I wanted to find out how IMAP works and why it's often so slow (I'm still not really sure).
3. I wanted to try using rust for something practical and see how well it works. The result is it worked, and I would do it again. 3. I wanted to try using Rust for something practical and see how well it works. It worked pretty well.
In terms of actual usability, this works fine for me, but I'm sure there are better alternatives out there.
[Rust]: https://www.rust-lang.org/ [Rust]: https://www.rust-lang.org/
[IMAP]: https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol [IMAP]: https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol

View File

@@ -2,12 +2,13 @@
use rustls::{OwnedTrustAnchor, ClientConfig, RootCertStore, ClientConnection}; use rustls::{OwnedTrustAnchor, ClientConfig, RootCertStore, ClientConnection};
use anyhow::{Result as AResult, bail}; use anyhow::{Result as AResult, bail};
use std::net::{TcpStream, ToSocketAddrs}; use std::net::{TcpStream, ToSocketAddrs, SocketAddr};
use std::io::{self, ErrorKind, Read, Write, Error as IOError}; use std::io::{self, ErrorKind, Read, Write, Error as IOError};
use std::process::Command; use std::process::Command;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::cell::RefCell;
use std::thread; use std::thread;
use clap::Parser; use clap::Parser;
@@ -22,6 +23,10 @@ pub struct Cli {
#[arg(long, default_value_t = 993)] #[arg(long, default_value_t = 993)]
port: u16, port: u16,
// the resolved address(es)
#[arg(skip)]
addrs: RefCell<Vec<SocketAddr>>,
/// IMAP user name /// IMAP user name
#[arg(short, long)] #[arg(short, long)]
username: String, username: String,
@@ -46,11 +51,12 @@ pub struct Cli {
#[derive(Debug)] #[derive(Debug)]
struct Status { struct Status {
connected: bool, connected: bool,
last_run: SystemTime last_run: SystemTime,
} }
const CONNECTION_LOST_ERRORS: &[ErrorKind] = &[ const CONNECTION_LOST_ERRORS: &[ErrorKind] = &[
ErrorKind::Interrupted, ErrorKind::Interrupted,
ErrorKind::TimedOut,
ErrorKind::WouldBlock, // when a read times out ErrorKind::WouldBlock, // when a read times out
]; ];
@@ -162,7 +168,11 @@ pub fn run() -> AResult<()> {
time_to_reconnect = u64::min(time_to_reconnect*2, 1800); time_to_reconnect = u64::min(time_to_reconnect*2, 1800);
if cli.verbose > 0 {
println!("Error: {:?}", err);
}
println!("Cannot connect currently, retrying in {time_to_reconnect} seconds"); println!("Cannot connect currently, retrying in {time_to_reconnect} seconds");
thread::sleep(Duration::from_secs(time_to_reconnect)); thread::sleep(Duration::from_secs(time_to_reconnect));
continue; continue;
@@ -203,10 +213,15 @@ pub fn connect_and_idle<F: Fn(), G: Fn()>(cli: &Cli, connected_callback: F, mail
let mut tls_client = ClientConnection::new( let mut tls_client = ClientConnection::new(
Arc::new(tls_config), Arc::new(tls_config),
cli.server.as_str().try_into().unwrap())?; cli.server.as_str().try_into().unwrap())?;
let addrs = (cli.server.as_str(), cli.port)
.to_socket_addrs() let mut addrs = cli.addrs.borrow_mut();
.map_err(|e|io::Error::new(ErrorKind::NotConnected, e.to_string()))? addrs.clear();
.collect::<Vec<_>>(); addrs.extend(
(cli.server.as_str(), cli.port)
.to_socket_addrs()
.map_err(|e|io::Error::new(ErrorKind::NotConnected, e.to_string()))?
);
let mut socket = TcpStream::connect(addrs.as_slice())?; let mut socket = TcpStream::connect(addrs.as_slice())?;
let mut state = ImapState::Unauthenticated; let mut state = ImapState::Unauthenticated;
@@ -228,7 +243,7 @@ pub fn connect_and_idle<F: Fn(), G: Fn()>(cli: &Cli, connected_callback: F, mail
let responses = buffer[0..len] let responses = buffer[0..len]
.split(|&x|x == b'\r' || x == b'\n') .split(|&x|x == b'\r' || x == b'\n')
.filter(|&x|x.len() != 0); .filter(|&x|!x.is_empty());
for response in responses { for response in responses {
if cli.verbose > 0 { if cli.verbose > 0 {
@@ -244,17 +259,17 @@ pub fn connect_and_idle<F: Fn(), G: Fn()>(cli: &Cli, connected_callback: F, mail
match state { match state {
ImapState::Unauthenticated => if response.starts_with(b"* OK") { ImapState::Unauthenticated => if response.starts_with(b"* OK") {
let request = format!("A001 login {} {}\r\n", cli.username, cli.password); let request = format!("A001 login {} {}\r\n", cli.username, cli.password);
tls_client.writer().write(request.as_bytes())?; tls_client.writer().write_all(request.as_bytes())?;
state = ImapState::Authenticated; state = ImapState::Authenticated;
}, },
ImapState::Authenticated => if response.starts_with(b"A001 OK") { ImapState::Authenticated => if response.starts_with(b"A001 OK") {
tls_client.writer().write(b"A002 select inbox\r\n")?; tls_client.writer().write_all(b"A002 select inbox\r\n")?;
state = ImapState::Inbox; state = ImapState::Inbox;
} else if response.starts_with(b"A001") { } else if response.starts_with(b"A001") {
bail!("The server rejected authentication"); bail!("The server rejected authentication");
}, },
ImapState::Inbox => if response.starts_with(b"A002 OK") { ImapState::Inbox => if response.starts_with(b"A002 OK") {
tls_client.writer().write(b"A003 idle\r\n")?; tls_client.writer().write_all(b"A003 idle\r\n")?;
state = ImapState::Idling; state = ImapState::Idling;
connected_callback(); connected_callback();
// notify timer thread that we're live // notify timer thread that we're live