feat: add TLS support to Rust implementation
- Add async-native-tls dependency for secure IMAP connections - Implement ImapStream enum supporting both TLS and plain connections - Add automatic TLS detection based on port (993=TLS, 143=plain, 3143=test) - Add comprehensive Read/Write trait implementations for stream wrapper - Add debug logging for connection type verification - Create example configurations for Gmail, Outlook, and other providers - Add TLS_SUPPORT.md documentation with security guidelines - Test with existing test environment and TLS detection logic - Maintain backward compatibility with plain IMAP for testing The Rust implementation now supports secure connections to production email providers while maintaining compatibility with test environments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4835df070e
commit
35c3c8657a
5 changed files with 279 additions and 4 deletions
|
|
@ -33,6 +33,9 @@ config = "0.13"
|
|||
async-imap = "0.9"
|
||||
async-std = { version = "1.12", features = ["attributes"] }
|
||||
|
||||
# TLS support for secure IMAP connections
|
||||
async-native-tls = "0.5"
|
||||
|
||||
# Logging
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
|
|
|
|||
98
rust/TLS_SUPPORT.md
Normal file
98
rust/TLS_SUPPORT.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# TLS Support in mail2couch Rust Implementation
|
||||
|
||||
The Rust implementation of mail2couch now includes full TLS support for secure IMAP connections.
|
||||
|
||||
## Automatic TLS Detection
|
||||
|
||||
The client automatically determines whether to use TLS based on the configured port:
|
||||
|
||||
- **Port 993** (IMAPS): Uses TLS encryption (default for Gmail, Outlook, etc.)
|
||||
- **Port 143** (IMAP): Uses plain text connection (insecure, typically for testing)
|
||||
- **Port 3143**: Uses plain text (test environment default)
|
||||
- **Other ports**: Defaults to TLS with a warning message
|
||||
|
||||
## Example Configurations
|
||||
|
||||
### Gmail with TLS (Recommended)
|
||||
```json
|
||||
{
|
||||
"name": "Personal Gmail",
|
||||
"host": "imap.gmail.com",
|
||||
"port": 993,
|
||||
"user": "your-email@gmail.com",
|
||||
"password": "your-app-password"
|
||||
}
|
||||
```
|
||||
|
||||
### Outlook with TLS
|
||||
```json
|
||||
{
|
||||
"name": "Work Outlook",
|
||||
"host": "outlook.office365.com",
|
||||
"port": 993,
|
||||
"user": "you@company.com",
|
||||
"password": "your-app-password"
|
||||
}
|
||||
```
|
||||
|
||||
### Test Environment (Plain)
|
||||
```json
|
||||
{
|
||||
"name": "Test Server",
|
||||
"host": "localhost",
|
||||
"port": 3143,
|
||||
"user": "testuser",
|
||||
"password": "testpass"
|
||||
}
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Always use port 993** for production email providers
|
||||
2. **Never use port 143** with real email accounts (credentials sent in plain text)
|
||||
3. **Use app passwords** instead of account passwords for Gmail/Outlook
|
||||
4. **Port 3143** is only for local testing environments
|
||||
|
||||
## Provider-Specific Settings
|
||||
|
||||
### Gmail
|
||||
- Host: `imap.gmail.com`
|
||||
- Port: `993` (TLS)
|
||||
- Requires app password (not regular password)
|
||||
- Enable 2FA and generate app password in Google Account settings
|
||||
|
||||
### Microsoft Outlook/Office 365
|
||||
- Host: `outlook.office365.com`
|
||||
- Port: `993` (TLS)
|
||||
- May require app password depending on organization settings
|
||||
|
||||
### Yahoo Mail
|
||||
- Host: `imap.mail.yahoo.com`
|
||||
- Port: `993` (TLS)
|
||||
- Requires app password
|
||||
|
||||
## Testing TLS Functionality
|
||||
|
||||
1. **Test with local environment**: Port 3143 (plain)
|
||||
```bash
|
||||
./mail2couch -c config-test.json
|
||||
```
|
||||
|
||||
2. **Test with Gmail**: Port 993 (TLS)
|
||||
```bash
|
||||
./mail2couch -c config-gmail.json
|
||||
```
|
||||
|
||||
3. **Verify TLS detection**: Check logs for connection type
|
||||
- TLS connections will show successful handshake
|
||||
- Plain connections will connect directly
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The TLS support is implemented using:
|
||||
- `async-native-tls` for TLS connections
|
||||
- `async-std` for plain TCP connections
|
||||
- Custom `ImapStream` enum that wraps both connection types
|
||||
- Automatic port-based detection logic
|
||||
|
||||
This ensures compatibility with both secure production environments and insecure test setups.
|
||||
26
rust/config-gmail-example.json
Normal file
26
rust/config-gmail-example.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"couchDb": {
|
||||
"url": "http://localhost:5984",
|
||||
"user": "admin",
|
||||
"password": "password"
|
||||
},
|
||||
"mailSources": [
|
||||
{
|
||||
"name": "Personal Gmail",
|
||||
"enabled": true,
|
||||
"protocol": "imap",
|
||||
"host": "imap.gmail.com",
|
||||
"port": 993,
|
||||
"user": "your-email@gmail.com",
|
||||
"password": "your-app-password",
|
||||
"mode": "archive",
|
||||
"folderFilter": {
|
||||
"include": ["INBOX", "[Gmail]/Sent Mail"],
|
||||
"exclude": ["[Gmail]/Trash", "[Gmail]/Spam"]
|
||||
},
|
||||
"messageFilter": {
|
||||
"since": "2024-01-01"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
rust/config-tls-test.json
Normal file
48
rust/config-tls-test.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"couchDb": {
|
||||
"url": "http://localhost:5984",
|
||||
"user": "admin",
|
||||
"password": "password"
|
||||
},
|
||||
"mailSources": [
|
||||
{
|
||||
"name": "TLS Test Port 993",
|
||||
"enabled": true,
|
||||
"protocol": "imap",
|
||||
"host": "imap.gmail.com",
|
||||
"port": 993,
|
||||
"user": "test@example.com",
|
||||
"password": "dummy",
|
||||
"mode": "archive",
|
||||
"folderFilter": {
|
||||
"include": ["INBOX"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Plain Test Port 143",
|
||||
"enabled": true,
|
||||
"protocol": "imap",
|
||||
"host": "localhost",
|
||||
"port": 143,
|
||||
"user": "test",
|
||||
"password": "dummy",
|
||||
"mode": "archive",
|
||||
"folderFilter": {
|
||||
"include": ["INBOX"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Unknown Port Test",
|
||||
"enabled": true,
|
||||
"protocol": "imap",
|
||||
"host": "example.com",
|
||||
"port": 9999,
|
||||
"user": "test",
|
||||
"password": "dummy",
|
||||
"mode": "archive",
|
||||
"folderFilter": {
|
||||
"include": ["INBOX"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
108
rust/src/imap.rs
108
rust/src/imap.rs
|
|
@ -8,10 +8,14 @@ use crate::schemas::MailDocument;
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_imap::types::Fetch;
|
||||
use async_imap::{Client, Session};
|
||||
use async_native_tls::{TlsConnector, TlsStream};
|
||||
use async_std::io::{Read, Write};
|
||||
use async_std::net::TcpStream;
|
||||
use async_std::stream::StreamExt;
|
||||
use async_std::task::{Context, Poll};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
@ -26,7 +30,62 @@ pub enum ImapError {
|
|||
Parsing(String),
|
||||
}
|
||||
|
||||
pub type ImapSession = Session<TcpStream>;
|
||||
/// Wrapper for both TLS and plain TCP streams
|
||||
pub enum ImapStream {
|
||||
Plain(TcpStream),
|
||||
Tls(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl Read for ImapStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
match self.get_mut() {
|
||||
ImapStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||
ImapStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for ImapStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
match self.get_mut() {
|
||||
ImapStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||
ImapStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
ImapStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
|
||||
ImapStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
ImapStream::Plain(stream) => Pin::new(stream).poll_close(cx),
|
||||
ImapStream::Tls(stream) => Pin::new(stream).poll_close(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ImapStream {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ImapStream::Plain(_) => write!(f, "ImapStream::Plain(_)"),
|
||||
ImapStream::Tls(_) => write!(f, "ImapStream::Tls(_)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ImapSession = Session<ImapStream>;
|
||||
|
||||
/// IMAP client for mail operations
|
||||
pub struct ImapClient {
|
||||
|
|
@ -63,9 +122,29 @@ impl ImapClient {
|
|||
let tcp_stream = TcpStream::connect(&addr).await
|
||||
.map_err(|e| ImapError::Connection(format!("Failed to connect to {}: {}", addr, e)))?;
|
||||
|
||||
// For now, use unsecured connection (this should be made configurable)
|
||||
// In production, you'd want to use TLS
|
||||
let client = Client::new(tcp_stream);
|
||||
// Determine if we should use TLS based on port
|
||||
let imap_stream = if self.should_use_tls() {
|
||||
// Use TLS for secure connection (typically port 993)
|
||||
let tls_connector = TlsConnector::new();
|
||||
let tls_stream = tls_connector.connect(&self.source.host, tcp_stream).await
|
||||
.map_err(|e| ImapError::Connection(format!("TLS connection failed: {}", e)))?;
|
||||
ImapStream::Tls(tls_stream)
|
||||
} else {
|
||||
// Use plain connection (typically port 143 or test environments)
|
||||
ImapStream::Plain(tcp_stream)
|
||||
};
|
||||
|
||||
// Log connection type for debugging
|
||||
let connection_type = if self.should_use_tls() { "TLS" } else { "Plain" };
|
||||
log::debug!(
|
||||
"Connecting to {}:{} using {} connection",
|
||||
self.source.host,
|
||||
self.source.port,
|
||||
connection_type
|
||||
);
|
||||
|
||||
// Create IMAP client
|
||||
let client = Client::new(imap_stream);
|
||||
|
||||
// Perform IMAP login
|
||||
let session = client
|
||||
|
|
@ -77,6 +156,27 @@ impl ImapClient {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine if TLS should be used based on port and configuration
|
||||
fn should_use_tls(&self) -> bool {
|
||||
// Standard IMAPS port (993) uses TLS
|
||||
// Port 143 is typically plain IMAP
|
||||
// Port 3143 is used in test environments (plain)
|
||||
match self.source.port {
|
||||
993 => true, // Standard IMAPS port
|
||||
143 => false, // Standard IMAP port
|
||||
3143 => false, // Test environment port
|
||||
_ => {
|
||||
// For other ports, default to TLS for security
|
||||
// but log a warning
|
||||
log::warn!(
|
||||
"Unknown IMAP port {}, defaulting to TLS. Use port 143 for plain IMAP or 993 for TLS",
|
||||
self.source.port
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List all mailboxes
|
||||
pub async fn list_mailboxes(&mut self) -> Result<Vec<String>> {
|
||||
let session = self.session.as_mut()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue