initial import
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
# POP3 Source Server Configuration
|
||||||
|
POP3_HOST=pop.example.com
|
||||||
|
POP3_PORT=995
|
||||||
|
POP3_USERNAME=your_pop3_username
|
||||||
|
POP3_PASSWORD=your_pop3_password
|
||||||
|
|
||||||
|
# IMAP Destination Server Configuration
|
||||||
|
IMAP_HOST=imap.example.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
IMAP_USERNAME=your_imap_username
|
||||||
|
IMAP_PASSWORD=your_imap_password
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.env
|
||||||
Generated
+1051
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "rs_pop_imap_importer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
imap = "2.4.1"
|
||||||
|
imap-proto = "0.16.6"
|
||||||
|
native-tls = "0.2.14"
|
||||||
|
rust-pop3-client = "0.2.2"
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM rust:1.91 AS builder
|
||||||
|
|
||||||
|
WORKDIR /usr/src/pop_imap_importer
|
||||||
|
|
||||||
|
# Copy manifests first to leverage Docker layer caching
|
||||||
|
COPY src/ Cargo.* ./
|
||||||
|
|
||||||
|
# Build the actual application
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
RUN cargo install --path .
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# Install CA certificates and OpenSSL runtime libraries
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y ca-certificates libssl3 && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=builder /usr/local/cargo/bin/pop_imap_importer /usr/local/bin/pop_imap_importer
|
||||||
|
|
||||||
|
CMD ["pop_imap_importer"]
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Makefile for building and running the POP3 to IMAP Importer Docker image
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
IMAGE_NAME = pop-imap-importer
|
||||||
|
DOCKER_PLATFORM = linux/amd64
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Show this help message
|
||||||
|
@echo "Usage: make [target]"
|
||||||
|
@echo ""
|
||||||
|
@echo "Targets:"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: ## Build the Docker image for linux/amd64 platform
|
||||||
|
docker build --platform=$(DOCKER_PLATFORM) -t $(IMAGE_NAME) .
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run: ## Run the Docker container
|
||||||
|
docker run --platform=$(DOCKER_PLATFORM) --rm -it $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: run-with-env
|
||||||
|
run-with-env: ## Run the Docker container with a custom .env file
|
||||||
|
docker run --platform=$(DOCKER_PLATFORM) --rm -it -v $(PWD)/.env:/app/.env $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean: ## Remove the Docker image
|
||||||
|
docker rmi $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: push
|
||||||
|
push: ## Push the Docker image to a registry (requires REPOSITORY variable)
|
||||||
|
ifndef REPOSITORY
|
||||||
|
$(error REPOSITORY is not set. Please set it with: make push REPOSITORY=your-repo/image-name)
|
||||||
|
endif
|
||||||
|
docker tag $(IMAGE_NAME) $(REPOSITORY):latest
|
||||||
|
docker push $(REPOSITORY):latest
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell: ## Run a shell in the Docker container
|
||||||
|
docker run --platform=$(DOCKER_PLATFORM) --rm -it $(IMAGE_NAME) sh
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# POP to IMAP Importer
|
||||||
|
|
||||||
|
A Rust application that downloads emails from a POP3 server and imports them into an IMAP server's INBOX.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Downloads all emails from a POP3 server
|
||||||
|
- Imports emails to the INBOX of an IMAP server
|
||||||
|
- Secure TLS connections
|
||||||
|
- Environment-based configuration
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Copy `.env.example` to `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `.env` with your server credentials:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# POP3 Source Server Configuration
|
||||||
|
POP3_HOST=pop.example.com
|
||||||
|
POP3_PORT=995
|
||||||
|
POP3_USERNAME=your_pop3_username
|
||||||
|
POP3_PASSWORD=your_pop3_password
|
||||||
|
|
||||||
|
# IMAP Destination Server Configuration
|
||||||
|
IMAP_HOST=imap.example.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
IMAP_USERNAME=your_imap_username
|
||||||
|
IMAP_PASSWORD=your_imap_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod settings;
|
||||||
|
|
||||||
|
pub use settings::Settings;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use dotenvy::dotenv;
|
||||||
|
use dotenvy::from_filename;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Pop3Config {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ImapConfig {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub pop3: Pop3Config,
|
||||||
|
pub imap: ImapConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn from_env_file(filename: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
if filename == ".env" {
|
||||||
|
dotenv().ok();
|
||||||
|
} else {
|
||||||
|
from_filename(filename).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pop3 = Pop3Config {
|
||||||
|
host: env::var("POP3_HOST")?,
|
||||||
|
port: env::var("POP3_PORT")?.parse()?,
|
||||||
|
username: env::var("POP3_USERNAME")?,
|
||||||
|
password: env::var("POP3_PASSWORD")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let imap = ImapConfig {
|
||||||
|
host: env::var("IMAP_HOST")?,
|
||||||
|
port: env::var("IMAP_PORT")?.parse()?,
|
||||||
|
username: env::var("IMAP_USERNAME")?,
|
||||||
|
password: env::var("IMAP_PASSWORD")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Settings { pop3, imap })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::config::settings::ImapConfig;
|
||||||
|
use native_tls::TlsConnector;
|
||||||
|
|
||||||
|
pub struct ImapClient {
|
||||||
|
session: Option<imap::Session<native_tls::TlsStream<std::net::TcpStream>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImapClient {
|
||||||
|
pub fn new(_config: &ImapConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
Ok(ImapClient {
|
||||||
|
session: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(&mut self, config: &ImapConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let tls = TlsConnector::builder().build()?;
|
||||||
|
let client = imap::connect((config.host.as_str(), config.port), &config.host, &tls)?;
|
||||||
|
let session = client.login(&config.username, &config.password).map_err(|e| e.0)?;
|
||||||
|
self.session = Some(session);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_inbox(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if let Some(ref mut session) = self.session {
|
||||||
|
session.select("INBOX")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_message(&mut self, message: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if let Some(ref mut session) = self.session {
|
||||||
|
session.append("INBOX", message.as_bytes())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logout(self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if let Some(mut session) = self.session {
|
||||||
|
session.logout()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
mod config;
|
||||||
|
mod pop3_client;
|
||||||
|
mod imap_client;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use config::Settings;
|
||||||
|
use pop3_client::Pop3Client;
|
||||||
|
use imap_client::ImapClient;
|
||||||
|
|
||||||
|
/// POP3 to IMAP Email Importer
|
||||||
|
///
|
||||||
|
/// This utility migrates emails from a POP3 server to an IMAP server.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Path to the .env file containing server configurations
|
||||||
|
#[clap(short, long, default_value = ".env")]
|
||||||
|
env_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
println!("Starting POP3 to IMAP email importer...");
|
||||||
|
|
||||||
|
// Load configuration from specified .env file
|
||||||
|
let settings = Settings::from_env_file(&args.env_file)?;
|
||||||
|
|
||||||
|
// Connect to POP3 server
|
||||||
|
println!("Connecting to POP3 server at {}:{}...", settings.pop3.host, settings.pop3.port);
|
||||||
|
let mut pop3_client = Pop3Client::new(&settings.pop3)?;
|
||||||
|
pop3_client.login(&settings.pop3)?;
|
||||||
|
println!("Successfully connected to POP3 server");
|
||||||
|
|
||||||
|
// Connect to IMAP server
|
||||||
|
println!("Connecting to IMAP server at {}:{}...", settings.imap.host, settings.imap.port);
|
||||||
|
let mut imap_client = ImapClient::new(&settings.imap)?;
|
||||||
|
imap_client.login(&settings.imap)?;
|
||||||
|
imap_client.select_inbox()?;
|
||||||
|
println!("Successfully connected to IMAP server");
|
||||||
|
|
||||||
|
// List messages in POP3 inbox
|
||||||
|
let messages = pop3_client.list_messages()?;
|
||||||
|
println!("Found {} messages in POP3 inbox", messages.len());
|
||||||
|
|
||||||
|
// Process each message
|
||||||
|
for (msg_id, _) in messages {
|
||||||
|
println!("Processing message ID: {}", msg_id);
|
||||||
|
|
||||||
|
// Retrieve message content
|
||||||
|
let message_content = pop3_client.retrieve_message(msg_id)?;
|
||||||
|
|
||||||
|
// Append message to IMAP inbox
|
||||||
|
imap_client.append_message(&message_content)?;
|
||||||
|
println!("Message {} imported successfully", msg_id);
|
||||||
|
|
||||||
|
// Optionally delete message from POP3 server after successful import
|
||||||
|
// pop3_client.delete_message(msg_id)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up connections
|
||||||
|
pop3_client.quit()?;
|
||||||
|
imap_client.logout()?;
|
||||||
|
|
||||||
|
println!("Email import completed successfully!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::config::settings::Pop3Config;
|
||||||
|
use rust_pop3_client::Pop3Connection;
|
||||||
|
|
||||||
|
pub struct Pop3Client {
|
||||||
|
connection: Pop3Connection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pop3Client {
|
||||||
|
pub fn new(config: &Pop3Config) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let connection = Pop3Connection::new(&config.host, config.port)?;
|
||||||
|
Ok(Pop3Client { connection })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(&mut self, config: &Pop3Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.connection.login(&config.username, &config.password)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_messages(&mut self) -> Result<Vec<(u32, u32)>, Box<dyn std::error::Error>> {
|
||||||
|
let infos = self.connection.list()?;
|
||||||
|
let list = infos.into_iter().map(|info| (info.message_id, info.message_size)).collect();
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retrieve_message(&mut self, msg_id: u32) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
self.connection.retrieve(msg_id, &mut buffer)?;
|
||||||
|
let message = String::from_utf8(buffer)?;
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn delete_message(&mut self, msg_id: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.connection.delete(msg_id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The rust-pop3-client doesn't seem to have an explicit quit method
|
||||||
|
// The connection should be closed when the object is dropped
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user