Commit graph

14 commits

Author SHA1 Message Date
Shautvast
e2badaa170 update dependencies 2026-03-02 14:58:44 +01:00
Shautvast
e858236f51 Fix IMAP search tag mismatch and clean up compiler warnings
Remove UIDVALIDITY/UIDNEXT from SELECT response to prevent imap-proto
from leaving the tagged SELECT line unconsumed in the buffer, which caused
the subsequent SEARCH command to assert on a stale tag (a122 vs a123).
Also fix empty SEARCH response to omit the trailing space before CRLF.

Remove dead diagnostic code and unused functions throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 17:35:58 +01:00
Shautvast
a709a3a946 implemented safe cache 2026-02-25 16:48:40 +01:00
Shautvast
facb44d561 Store credentials in OS keychain via keyring crate
Passwords are no longer stored in config.toml. Instead:
- New setup wizard (--configure) prompts for credentials on first run
  and stores them in the OS keychain (macOS Keychain, GNOME Keyring /
  KWallet on Linux, Windows Credential Manager)
- Env-var fallback: TUIMAIL_<KEY> for headless environments
- ProtonMail session token moves from session.json to the keychain
- Config file path moves to {config_dir}/tuimail/config.toml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 10:50:23 +01:00
Shautvast
a8ea1fb5bb replaced println's with tracing calls 2026-02-25 09:04:11 +01:00
Shautvast
2287b08cb5 Integrate proton-bridge in-process and silence verbose logging
- Add proton-bridge as optional dep behind `proton` feature flag
- New proton-bridge/src/lib.rs: pub fn start() spins a background Tokio
  thread, pre-binds ports, and signals readiness via mpsc before returning
- src/main.rs: conditionally starts bridge before TUI enters raw mode;
  derives effective IMAP/SMTP config via Provider enum
- src/config.rs: add Provider enum, optional imap/smtp, ProtonConfig/
  BridgeConfig mirrors, effective_imap/smtp() helpers
- Remove all per-operation eprintln!/println! from imap_server, smtp_server,
  and api.rs that fired during TUI operation and corrupted the display
- config.toml.example: unified format covering both imap and proton providers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 22:51:30 +01:00
Shautvast
2ef6a0fd24 Add SMTP server (step 7) with PGP/MIME external send
Implements a minimal ESMTP listener (AUTH LOGIN/PLAIN, MAIL FROM, RCPT TO,
DATA, QUIT) that sends via the ProtonMail v4 API (create draft → send).

- ProtonMail internal recipients: Type 1 (encrypt to recipient's ECDH key)
- External recipients: Type 32 PGP/MIME — detached signature in a separate
  application/pgp-signature MIME part so the body arrives clean in Gmail.
  The MIME entity is wrapped in an OpenPGP-signed message (Signature=1) to
  satisfy ProtonMail's mandatory signature check on all external sends.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 16:36:38 +01:00
Shautvast
fa1d1a6cb7 Add IMAP server (step 6) — all 9 commands implemented
Plain-TCP listener on 0.0.0.0 (handles both localhost and 127.0.0.1).
LOGIN, NOOP, SELECT (reloads inbox), FETCH header+body, SEARCH, STORE,
EXPUNGE (deletes on ProtonMail), LOGOUT.

FETCH body decrypts messages on demand: brief lock for ID lookup, API call
without lock, brief lock again for crypto. RFC 3501 literal format with
exact byte counts for imap-crate compatibility.

Also: update store.expunge() to return (ids, seqs) in descending order for
correct IMAP EXPUNGE response ordering; add chrono for RFC 2822 dates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 19:13:20 +01:00
Shautvast
7916365816 Add message store and refactor main into bridge startup
Step 5: in-memory MessageStore mapping IMAP seq numbers to ProtonMail IDs.
Oldest-first ordering, mark/expunge, range queries, subject/sender search.

Refactor main.rs: extract unlock_key_pool(), build SharedState (Arc<Mutex>),
load initial inbox, then wait for Ctrl-C. Ready to plug in IMAP/SMTP servers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 18:55:32 +01:00
Shautvast
70b2b0348e Decrypt ProtonMail messages end-to-end via key passphrase fix
The key insight from go-proton-api SaltForKey: ProtonMail uses only the
last 31 chars of the bcrypt output as the key passphrase — not the full
60-char string. One line fix, two days of debugging.

Also adds the full crypto layer (crypto.rs): user key unlock, address key
token decryption, and message body decryption via rpgp. Includes SRP auth,
session caching with locked-scope handling, TOTP, and the ProtonMail API
client for inbox listing and message fetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:55:18 +01:00
Shautvast
fc0eef0c31 Add ProtonMail API client (Step 3)
Implements typed async wrappers for the four endpoints tuimail needs:
- list_messages: GET /mail/v4/messages (paged inbox listing)
- get_message: GET /mail/v4/messages/{id} (full message with encrypted body)
- delete_messages: PUT /mail/v4/messages/delete (soft-delete to Trash)
- get_public_keys: GET /core/v4/keys (recipient keys for outbound mail)

All responses decoded through Envelope<T> with Code 1000 check.
main.rs smoke-tests the inbox listing after authentication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 16:23:42 +01:00
Shautvast
6956fa8c01 manual refactor 2026-02-20 15:29:16 +01:00
Shautvast
dd9651a946 Add session expiry tracking and token refresh to proton-bridge
- Store expires_at (Unix timestamp) in session.json from ExpiresIn response field
- Add is_expired() with 5-minute refresh margin
- Implement POST /auth/v4/refresh flow: tries refresh before falling back to SRP login
- authenticate() now: use cached → refresh if expired → full login if all else fails

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:27:47 +01:00
Shautvast
05b6aac692 Add proton-bridge crate: workspace setup and SRP authentication (Step 2)
- Convert tuimail repo to Cargo workspace with tuimail and proton-bridge members
- Add proton-bridge binary crate with config, SRP 6a, and auth modules
- Implement ProtonMail SRP 6a exactly matching go-srp:
  - Little-endian bigints throughout
  - expandHash = SHA512(data||0..3) producing 256 bytes
  - k, u, M1, M2 all via expandHash with 256-byte normalised inputs
  - Password hashing v3/v4: bcrypt($2y$, salt+proton) + expandHash(output||N)
- Authenticate against Proton API (auth/info → auth/v4), verify server proof
- Persist session (UID, access/refresh tokens) to session.json
- Add bridge.toml and session.json to .gitignore (contain credentials/tokens)
- Add PROTON.md with full build plan for the mini-bridge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 21:31:10 +01:00