mirror of
https://github.com/spartanz51/tutabridge.git
synced 2026-06-24 10:54:32 +02:00
124 lines
3.8 KiB
Rust
124 lines
3.8 KiB
Rust
mod session;
|
|
mod utf7;
|
|
|
|
use log::{debug, error, info};
|
|
use std::sync::Arc;
|
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|
use tokio::net::TcpListener;
|
|
use tokio::sync::watch;
|
|
use tokio_rustls::TlsAcceptor;
|
|
|
|
use crate::sync::MailStore;
|
|
use crate::tuta::MailBackend;
|
|
use session::ImapSession;
|
|
|
|
pub async fn serve(
|
|
port: u16,
|
|
store: Arc<MailStore>,
|
|
backend: Arc<dyn MailBackend>,
|
|
tls: TlsAcceptor,
|
|
password_hash: Option<String>,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
|
|
info!("IMAP server listening on 127.0.0.1:{} (TLS)", port);
|
|
|
|
loop {
|
|
let (stream, addr) = listener.accept().await?;
|
|
debug!("IMAP connection from {}", addr);
|
|
let store = store.clone();
|
|
let backend = backend.clone();
|
|
let tls = tls.clone();
|
|
let pw_hash = password_hash.clone();
|
|
|
|
tokio::spawn(async move {
|
|
match tls.accept(stream).await {
|
|
Ok(tls_stream) => {
|
|
if let Err(e) = handle_connection(tls_stream, store, backend, pw_hash).await {
|
|
error!("IMAP connection error: {}", e);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("IMAP TLS handshake failed: {}", e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async fn handle_connection(
|
|
stream: tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
|
|
store: Arc<MailStore>,
|
|
backend: Arc<dyn MailBackend>,
|
|
password_hash: Option<String>,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let (reader, mut writer) = tokio::io::split(stream);
|
|
let mut reader = BufReader::new(reader);
|
|
let mut store_watch: watch::Receiver<u64> = store.subscribe();
|
|
let mut session = ImapSession::new(store, backend, password_hash);
|
|
|
|
writer
|
|
.write_all(b"* OK TutaBridge IMAP4rev1 ready\r\n")
|
|
.await?;
|
|
writer.flush().await?;
|
|
|
|
let mut line = String::new();
|
|
loop {
|
|
if session.is_idle() {
|
|
line.clear();
|
|
tokio::select! {
|
|
result = reader.read_line(&mut line) => {
|
|
let n = result?;
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
let trimmed = line.trim_end();
|
|
debug!("IMAP C (idle): {}", trimmed);
|
|
if trimmed.eq_ignore_ascii_case("DONE") {
|
|
let responses = session.end_idle();
|
|
for resp in &responses {
|
|
debug!("IMAP S: {}", resp.trim_end());
|
|
writer.write_all(resp.as_bytes()).await?;
|
|
}
|
|
writer.flush().await?;
|
|
}
|
|
}
|
|
_ = store_watch.changed() => {
|
|
let updates = session.check_new_mail().await;
|
|
for resp in &updates {
|
|
debug!("IMAP S (store update): {}", resp.trim_end());
|
|
writer.write_all(resp.as_bytes()).await?;
|
|
}
|
|
writer.flush().await?;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
line.clear();
|
|
let n = reader.read_line(&mut line).await?;
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
|
|
let trimmed = line.trim_end();
|
|
debug!("IMAP C: {}", trimmed);
|
|
|
|
let responses = if session.is_awaiting_auth() {
|
|
session.handle_auth_response(trimmed)
|
|
} else {
|
|
session.handle_command(trimmed).await
|
|
};
|
|
for resp in &responses {
|
|
debug!("IMAP S: {}", resp.trim_end());
|
|
writer.write_all(resp.as_bytes()).await?;
|
|
}
|
|
writer.flush().await?;
|
|
|
|
if session.is_logout() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|