├── .gitignore ├── 2.0-DESIGN.org ├── CHANGELOG.md ├── COPYING ├── Cargo.lock ├── Cargo.toml ├── HISTORICAL-DESIGN.org ├── IMAP-SYNTAX.org ├── README.md ├── book ├── .gitignore ├── book.toml ├── src │ ├── SUMMARY.md │ ├── admin-guide │ │ ├── backups.md │ │ ├── config.md │ │ ├── filesystem.md │ │ ├── index.md │ │ ├── migration.md │ │ ├── tuning.md │ │ └── users.md │ ├── encryption.md │ ├── imap.md │ ├── index.md │ ├── installation-guide.md │ ├── smtp.md │ └── user-guide.md └── theme │ ├── book.js │ ├── css │ ├── chrome.css │ ├── general.css │ ├── print.css │ └── variables.css │ ├── favicon.png │ ├── highlight.css │ ├── highlight.js │ └── index.hbs ├── gen-readme.sh ├── proptest-regressions ├── account │ └── mailbox_state.txt ├── crypt │ └── item_stream.txt ├── mime │ └── utf7.txt ├── smtp │ └── inbound │ │ └── server.txt ├── support │ └── un64.txt └── userbox │ ├── mailbox_state.txt │ └── model.txt ├── readme-antelogue.md ├── readme-prologue.md ├── rustfmt.toml └── src ├── account ├── key_store.rs ├── message_format.rs ├── mod.rs ├── model.rs ├── search_backend.rs ├── v1 │ ├── account.rs │ ├── hier_id_scheme.rs │ ├── mailbox │ │ ├── change_tx.rs │ │ ├── defs.rs │ │ ├── expunge.rs │ │ ├── fetch.rs │ │ ├── flags.rs │ │ ├── idle.rs │ │ ├── messages.rs │ │ ├── mod.rs │ │ ├── poll.rs │ │ ├── search.rs │ │ ├── select.rs │ │ └── zstd_train.rs │ ├── mailbox_path.rs │ ├── mailbox_state.rs │ ├── mod.rs │ ├── model.rs │ └── recency_token.rs └── v2 │ ├── mod.rs │ ├── state │ ├── defs.rs │ ├── delivery.rs │ ├── expunge.rs │ ├── fetch.rs │ ├── flags.rs │ ├── idle.rs │ ├── init.rs │ ├── mailboxes.rs │ ├── maintenance.rs │ ├── messages.rs │ ├── migration.rs │ ├── mod.rs │ ├── poll.rs │ ├── search.rs │ ├── select.rs │ ├── spool.rs │ ├── user_config.rs │ └── zstd_train.rs │ └── storage │ ├── db_migrations.rs │ ├── deliverydb.rs │ ├── deliverydb.v1.sql │ ├── messages.rs │ ├── metadb.rs │ ├── metadb.v1.sql │ ├── mod.rs │ ├── sqlite_xex_vfs.rs │ └── types.rs ├── cli ├── deliver.rs ├── imap_test.rs ├── main.rs ├── mod.rs ├── remote.rs ├── sanity.rs ├── serve.rs └── user.rs ├── crypt ├── data_stream.rs ├── master_key.rs ├── mod.rs ├── naked.rs ├── test_keys.rs └── xex.rs ├── file-preamble ├── imap ├── client.rs ├── command_processor │ ├── auth.rs │ ├── commands.rs │ ├── defs.rs │ ├── fetch.rs │ ├── flags.rs │ ├── mailboxes.rs │ ├── messages.rs │ ├── mod.rs │ ├── search.rs │ ├── smtp_out.rs │ └── user_config.rs ├── integration_tests │ ├── defs.rs │ ├── imap4rev2.rs │ ├── mod.rs │ ├── rfc2177.rs │ ├── rfc2342.rs │ ├── rfc2971.rs │ ├── rfc3348.rs │ ├── rfc3501 │ │ ├── auth.rs │ │ ├── bad_commands.rs │ │ ├── fetch.rs │ │ ├── first_contact.rs │ │ ├── flags.rs │ │ ├── literal.rs │ │ ├── mailboxes.rs │ │ ├── messages.rs │ │ ├── mod.rs │ │ ├── search.rs │ │ └── select.rs │ ├── rfc3502.rs │ ├── rfc3516.rs │ ├── rfc3691.rs │ ├── rfc4315.rs │ ├── rfc4731.rs │ ├── rfc4959.rs │ ├── rfc4978.rs │ ├── rfc5161.rs │ ├── rfc5182.rs │ ├── rfc5258.rs │ ├── rfc5819.rs │ ├── rfc6154.rs │ ├── rfc6851.rs │ ├── rfc6855.rs │ ├── rfc7162 │ │ ├── bad.rs │ │ ├── condstore_basics.rs │ │ ├── condstore_enable.rs │ │ ├── condstore_fetch.rs │ │ ├── condstore_flags.rs │ │ ├── condstore_search.rs │ │ ├── condstore_status.rs │ │ ├── mod.rs │ │ └── qresync.rs │ ├── rfc7888.rs │ ├── rfc8438.rs │ ├── rfc8474.rs │ ├── rfc8514.rs │ ├── xcry.rs │ └── xlist.rs ├── lex.rs ├── literal_source.rs ├── mailbox_name.rs ├── mod.rs ├── request_reader.rs ├── response_writer.rs ├── server.rs ├── syntax-macros.rs └── syntax.rs ├── main.rs ├── mime ├── address-list-corpus-enron.txt ├── address-list-corpus-jlingle.txt ├── content_encoding.rs ├── date-corpus-jlingle.txt ├── dkim │ ├── canonicalisation.rs │ ├── error.rs │ ├── hash.rs │ ├── header.rs │ ├── mod.rs │ ├── sign.rs │ ├── test-data │ │ ├── 20230601._domainkey.gmail.com.dat │ │ ├── amazon-b.dat │ │ ├── amazon-bh.dat │ │ ├── dkimproxy-b.dat │ │ ├── dkimproxy-bh.dat │ │ ├── google-b.dat │ │ ├── google-bh.dat │ │ ├── s2048._domainkey.yahoo.com.dat │ │ ├── selector1._domainkey.lin.gl.dat │ │ ├── yahoo-b.dat │ │ ├── yahoo-bh.dat │ │ └── yg4mwqurec7fkhzutopddd3ytuaqrvuz._domainkey.amazon.com.dat │ ├── test_domain_keys.rs │ └── verify.rs ├── encoded_word.rs ├── fetch │ ├── bodystructure.rs │ ├── envelope.rs │ ├── mod.rs │ ├── multi.rs │ ├── search.rs │ ├── section.rs │ ├── simple.rs │ ├── strings.rs │ └── zstd_train.rs ├── grovel.rs ├── header.rs ├── mod.rs ├── quoted_printable.rs └── utf7.rs ├── smtp ├── codes.rs ├── dmarc │ ├── mod.rs │ ├── psl.rs │ ├── psl.txt │ └── syntax.rs ├── inbound │ ├── bridge.rs │ ├── delivery.rs │ ├── integration_test_common.rs │ ├── lmtp.rs │ ├── lmtp_integration_tests.rs │ ├── mod.rs │ ├── server.rs │ ├── smtpin.rs │ ├── smtpin_integration_tests.rs │ ├── smtpsub.rs │ └── smtpsub_integration_tests.rs ├── mod.rs ├── outbound │ ├── mod.rs │ ├── send.rs │ ├── serverseq.rs │ ├── transact.rs │ └── transcript.rs ├── spf │ ├── driver.rs │ ├── eval.rs │ ├── mod.rs │ └── syntax.rs └── syntax.rs ├── support ├── append_limit.rs ├── async_io.rs ├── buffer.rs ├── chronox.rs ├── compression.rs ├── diagnostic.rs ├── dns.rs ├── error.rs ├── file_ops.rs ├── log_prefix.rs ├── mailbox_paths.rs ├── mod.rs ├── rcio.rs ├── safe_name.rs ├── small_bitset.rs ├── sysexits.rs ├── system_config.rs ├── un64.rs ├── unix_privileges.rs ├── user_config.rs └── zstd-dict-20200725.dat └── test_data ├── christmas-tree.eml ├── dkim-amazoncojp-2x-rsa-sha256.eml ├── dkim-lingl-rsa-sha1.eml ├── dkim-yahoo-rsa-sha256.eml ├── dovecot-prefer-standalone-daemons.eml ├── enron_dasovich-j_all_documents_11634.eml ├── enron_dasovich-j_notes_inbox_3944.eml ├── enron_farmer-d_all_documents_3128.eml ├── enron_farmer-d_discussion_threads_4965.eml ├── enron_farmer-d_entex_1.eml ├── enron_farmer-d_entex_106.eml ├── enron_keavey-p_deleted_items_127.eml ├── enron_keavey-p_deleted_items_144.eml ├── enron_keavey-p_deleted_items_163.eml ├── enron_keavey-p_deleted_items_21.eml ├── enron_keavey-p_deleted_items_258.eml ├── enron_keavey-p_deleted_items_269.eml ├── enron_keavey-p_deleted_items_314.eml ├── enron_keavey-p_deleted_items_55.eml ├── enron_keavey-p_deleted_items_6.eml ├── enron_keavey-p_deleted_items_63.eml ├── enron_keavey-p_deleted_items_82.eml ├── enron_keavey-p_inbox_551.eml ├── enron_scholtes-d_transmission_29.eml ├── enron_scholtes-d_transmission_35.eml ├── mod.rs ├── multi-part-base64.eml ├── rfc-8463.eml ├── rfc3501_p56.eml ├── single-part-base64.eml ├── torture-test.eml ├── unknown-cte.eml └── with-obsolete-routing.eml /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | target 3 | test-root 4 | core.* 5 | local-scripts 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 2 | 3 | - Major overhaul. 4 | - Entirely new data model for accounts, giving better performance and 5 | reliability. 6 | - The SAVEDATE IMAP extension is now supported. 7 | - Crymap can now take inbound SMTP directly. 8 | - Crymap can now perform outbound SMTP (albeit the workflow is a bit 9 | unconventional). 10 | - Various bugfixes. 11 | 12 | ## Breaking changes 13 | 14 | - `--create` is no longer an option to `crymap deliver`. 15 | 16 | # 1.0.1 17 | 18 | - Rust 1.66.0 is now the earliest officially supported Rust version. 19 | - IMAP4rev2 is now officially supported. 20 | - Update OpenSSL bindings and other crate versions to support latest Rust 21 | version. 22 | - Added option to redirect standard error to a file so that fatal errors do not 23 | get sent over the wire to the client. 24 | - The literal string "NIL" is no longer sent on the wire as an atom to prevent 25 | issues with bad parsers that would interpret it as the sentinel `NIL`. 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crymap" 3 | version = "2.0.0" 4 | authors = ["Jason Lingle "] 5 | license = "GPL-3.0" 6 | edition = "2021" 7 | readme = "README.md" 8 | repository = "https://github.com/altsysrq/crymap" 9 | homepage = "https://altsysrq.github.io/crymap/index.html" 10 | keywords = ["imap", "lmtp"] 11 | exclude = ["/gen-*.sh", "/readme-*.md", "book", "*.org"] 12 | description = "A simple, secure IMAP server with encrypted data at rest" 13 | 14 | [dependencies] 15 | # Newer versions of base64 have a substantially inferior API in exchange for no 16 | # improvements useful to crymap. 17 | base64 = "0.12" 18 | bitflags = "2.4.1" 19 | byteorder = "1.5.0" 20 | clap = { version = "2.33", default-features = false } 21 | crossbeam = "0.7" 22 | encoding_rs = "0.8.33" 23 | # Using the zlib backend and not miniz_oxide is a hard requirement currently 24 | # because with miniz_oxide, it sometimes blocks for more data when a full frame 25 | # is available. This is reproduceable by connecting thunderbird and doing a 26 | # bulk copy from another IMAP server. 27 | # TODO Investigate more so we can file a bug report with whichever repo is 28 | # causing the problem here. 29 | flate2 = { version = "1.0", default-features = false, features = ["zlib"] } 30 | futures = "0.3.29" 31 | hickory-resolver = "0.24.0" 32 | itertools = "0.12.0" 33 | lazy_static = "1.4" 34 | log = "0.4.8" 35 | memchr = "2.6.4" 36 | nom = { version = "7.1.3" } 37 | num_cpus = "1.13" 38 | openssl = "0.10.60" 39 | rand = "0.8.5" 40 | regex = "1.10.2" 41 | rpassword = "7.3.1" 42 | rust-argon2 = "0.8" 43 | secstr = "0.5.1" 44 | serde = { version = "<=1.0.171", features = ["derive"] } 45 | serde_bytes = "0.11" 46 | serde_cbor = "0.11" 47 | serde_repr = "0.1" 48 | structopt = { version = "0.3.15", default-features = false } 49 | syslog = "5.0" 50 | tempfile = "3.8.1" 51 | thiserror = "1.0" 52 | tiny-keccak = { version = "2.0", features = ["sha3", "kmac"] } 53 | toml = "0.5" 54 | walkdir = "2.4.0" 55 | zstd = "0.13.0" 56 | 57 | libsqlite3-sys = { version = "0.27.0", features = ["bundled"] } 58 | rusqlite = { version = "0.30.0", features = ["bundled"] } 59 | 60 | [dependencies.chrono] 61 | version = "0.4.31" 62 | default-features = false 63 | features = [ "std", "clock", "serde" ] 64 | 65 | [dependencies.log4rs] 66 | version = "1.2.0" 67 | default-features = false 68 | features = [ 69 | "console_appender", 70 | "file_appender", 71 | "rolling_file_appender", 72 | "compound_policy", 73 | "delete_roller", 74 | "fixed_window_roller", 75 | "size_trigger", 76 | "threshold_filter", 77 | "toml_format", 78 | "config_parsing", 79 | ] 80 | 81 | [dependencies.nix] 82 | version = "0.27.1" 83 | features = [ "event", "fs", "hostname", "inotify", "net", "poll", "process", "user" ] 84 | 85 | [dependencies.tokio] 86 | version = "1.35.1" 87 | default-features = false 88 | # We deliberately exclude things like `fs` and `io-std` which cause tokio to 89 | # spin up a thread pool. Filesystem operations are done synchronously, even 90 | # within the async context. stdio operations used for networking are done by 91 | # manually implementing the async interfaces by hand on the raw file 92 | # descriptors. 93 | features = [ "rt", "net", "time", "macros", "sync", "io-util" ] 94 | 95 | [dev-dependencies] 96 | proptest = "0.10" 97 | rayon = "1.3" 98 | 99 | # rust-argon2 and openssl can be quite slow without optimisations. We don't 100 | # need to debug them, so optimise even in dev/test builds. 101 | [profile.dev.package.rust-argon2] 102 | opt-level = 3 103 | 104 | [profile.dev.package.openssl] 105 | opt-level = 3 106 | 107 | [profile.dev.package.tiny-keccak] 108 | opt-level = 3 109 | 110 | [profile.release] 111 | panic = "abort" 112 | 113 | [features] 114 | # Enable tooling which is mainly useful for the development of Crymap. 115 | dev-tools = [] 116 | # Build and run tests that require a live network. This does not change the 117 | # non-test build. 118 | live-network-tests = [] 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crymap IMAP and SMTP Server 2 | 3 | [![](http://meritbadge.herokuapp.com/crymap)](https://crates.io/crates/crymap) 4 | 5 | # Introduction 6 | 7 | Crymap is an IMAP and SMTP server implementation for FreeBSD and Linux with a 8 | strong focus on security and simplicity of administration. Its spotlight 9 | feature is transparent encryption of data at rest — it is not possible to read 10 | any user's mail without knowing their password, while a regular IMAP experience 11 | is provided and mail can be received while the user is offline. 12 | 13 | If using the Crymap SMTP server, none of any user's mail will ever be stored on 14 | disk in the clear (on your server, anyway), though note that Crymap SMTP has 15 | serious caveats. 16 | 17 | Crymap supports both traditional UNIX-style deployments, where each user 18 | corresponds to a UNIX account and owns their own mail, and "black box" 19 | deployments, where users do not have shell access and all mail is owned by a 20 | single system UNIX account. 21 | 22 | ## Features 23 | 24 | - Fully compliant with the IMAP4rev1 and IMAP4rev2 specifications. 25 | - Secure by default. IMAPS only. 26 | - Minimal configuration. 27 | - Messages and metadata transparently encrypted at rest. 28 | - Automatic key rotation. 29 | - Transparent file and over-the-wire compression. 30 | - All normal mailboxes are "dual-use" (allow both messages and sub-mailboxes). 31 | - Instant mail delivery notifications. 32 | - QRESYNC support. 33 | - "Special-use" mailbox support. 34 | - A decent number of additional IMAP extensions. 35 | - Supports messages with 8-bit and binary content. 36 | - Mail delivery as an MDA. 37 | - Mail delivery via SMTP or LMTP. 38 | - Simple but secure sending of mail via SMTP with built-in DKIM support. 39 | - Interoperates well with filesystem-based backup systems. 40 | 41 | ## Status and Support 42 | 43 | The author uses Crymap for all personal email. It is known to work well in this 44 | use case. But this is naturally a fairly small amount of experience; in 45 | particular, Crymap has only seen day-to-day use in conjunction with Thunderbird 46 | and FairEmail. 47 | 48 | Crymap is currently maintained by its author alone. I am motivated to address 49 | bugs, but feature requests are unlikely to be accepted unless they offer 50 | substantial benefits to security or very common use cases. I will try to answer 51 | questions but cannot make commitments. 52 | 53 | ## Caveats 54 | 55 | - If a password is forgotten, all data owned by that user is lost forever. 56 | There is no way for an administrator to reset a user's password, as that 57 | would defeat Crymap's purpose. 58 | 59 | - Crymap has no ability to integrate into the host authentication system. I.e., 60 | Crymap user accounts are fully independent of host user accounts in terms of 61 | password, enabled/disabled status, etc. This is because Crymap needs full 62 | control of the password change process for password changes to happen without 63 | destroying the user's data. It would be technologically feasible to make, 64 | e.g., a PAM module that delegates to Crymap, but this is not implemented nor 65 | are there any plans to ever do so. 66 | 67 | - The maximum size of an email is currently hard-coded to 64MB. 68 | 69 | - Crymap's outbound SMTP experience is unusual and somewhat cumbersome. If 70 | sending an email experiences a temporary failure, it cannot be automatically 71 | retried since all access to messages is cryptographically locked behind user 72 | authentication. Retrying must be done manually via an IMAP extension, which 73 | is currently only implemented by the Crymap CLI utility. For a more 74 | conventional experience, you can use something like OpenSMTPD to handle 75 | outbound messages instead. 76 | ## Documentation 77 | 78 | Refer to the [Crymap mdbook](https://altsysrq.github.io/crymap/index.html) for 79 | full documentation. 80 | 81 | ## License 82 | 83 | Crymap is licensed under the [GPL version 3 or later](COPYING). 84 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jason Lingle"] 3 | multilingual = false 4 | src = "src" 5 | title = "Crymap" 6 | description = "Usage documentation for the Crymap IMAP server" 7 | 8 | [output.html] 9 | curly-quotes = true 10 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](index.md) 4 | 5 | - [How Crymap's Encryption Works](encryption.md) 6 | - [Installation Guide](installation-guide.md) 7 | - [Administration Guide](admin-guide/index.md) 8 | - [Configuration](admin-guide/config.md) 9 | - [Managing Users](admin-guide/users.md) 10 | - [Backups and Recovery](admin-guide/backups.md) 11 | - [Understanding the Filesystem](admin-guide/filesystem.md) 12 | - [Tuning](admin-guide/tuning.md) 13 | - [Migration from Crymap 1.x](admin-guide/migration.md) 14 | - [User Guide](user-guide.md) 15 | - [IMAP Characteristics](imap.md) 16 | - [SMTP/LMTP Characteristics](smtp.md) 17 | -------------------------------------------------------------------------------- /book/src/admin-guide/backups.md: -------------------------------------------------------------------------------- 1 | # Backups and Recovery 2 | 3 | ## Backups 4 | 5 | Starting with Crymap 2.0.0, each message and private key is stored in its own 6 | file, with all other data tracked in a couple SQLite databases. 7 | 8 | Backup tooling does not need to support any exotic filesystem features, and 9 | even Windows filesystems will be able to carry the backup in tact. 10 | 11 | ## Restoring from Backup 12 | 13 | In most cases, simply restoring from backup should do the trick. 14 | 15 | There are a few cases where a backup could be torn across an update: 16 | 17 | 1. Messages on the filesystem but not in the database. At least once a day, 18 | Crymap automatically checks for such messages. They will automatically 19 | appear in the user's inbox. 20 | 21 | 2. Messages in the database but not in the filesystem. These will appear to the 22 | user as stub messages indicating the problem. The user must delete the 23 | entries themselves. 24 | 25 | 3. Corrupt SQLite database. You can remove `delivery.sqlite*` and 26 | `meta.sqlite.xex*` (**important**: ensure you remove the `-journal` file 27 | too!), then copy one of the `meta.sqlite.xex.*` backups from the user's 28 | `backup` directory to `meta.sqlite.xex` in the user's directory. In the 29 | worst case, if you can't get anything to work, you can remove 30 | `meta.sqlite.xex*` entirely, which will result in all the user's messages 31 | being in unread, in `INBOX`, and in no particular order, but at least their 32 | mail will be there. 33 | 34 | Objects from a user account are readable from only that user account. For 35 | example, in case of a system failure, you cannot set up a new system, create 36 | user accounts in it, and then expect to be able to drop data from the backups 37 | into those new user accounts. The user accounts themselves must be restored 38 | from backup. 39 | -------------------------------------------------------------------------------- /book/src/admin-guide/index.md: -------------------------------------------------------------------------------- 1 | # Administration Guide 2 | 3 | These sections cover (hopefully) everything you need to know about operating 4 | and maintaining a Crymap installation beyond the initial setup. 5 | -------------------------------------------------------------------------------- /book/src/admin-guide/migration.md: -------------------------------------------------------------------------------- 1 | # Migration from Crymap 1.x 2 | 3 | ## System 4 | 5 | To upgrade from Crymap 1.x to 2.x, no configuration changes or user action is 6 | required. The recommended upgrade procedure is as follows: 7 | 8 | 1. Disable all ways for Crymap server processes to be created. 9 | 2. Terminate any remaining Crymap server processes. 10 | 3. Upgrade the Crymap binary to 2.x. 11 | 4. Reenable Crymap. 12 | 13 | ## User 14 | 15 | Crymap 2.x uses an entirely different data model than 1.x. When a user next 16 | logs in, they will automatically be migrated from the 1.x data model to the 2.x 17 | data model. This can take a few seconds to a few minutes, depending on the size 18 | of the account and the speed of the server. 19 | 20 | The data migration process preserves all messages, message flags, and the 21 | mailbox hierarchy. It does not preserve message IDs of any kind or 22 | synchronisation states. The user's email client will thus effectively start 23 | from a blank slate once migration completes and will need to 24 | resynchronise/redownload everything it wants to keep local. 25 | 26 | There is no way to migrate users in advance, as the migration process requires 27 | the user's credentials to proceed. 28 | 29 | A user whose account is still on the 1.x data model can still receive mail from 30 | Crymap 2.x, though this mail will be added to the account under the 2.x data 31 | model. 32 | 33 | ## Rollback 34 | 35 | If you decide you need to roll back, the recommended procedure is as follows: 36 | 37 | 1. Disable all ways for Crymap server processes to be created. 38 | 2. Terminate any remaining Crymap server processes. 39 | 3. Manually roll back any user accounts that had been upgraded. 40 | 4. Downgrade the Crymap binary to 1.x. 41 | 5. Reenable Crymap. 42 | 43 | A user account that was migrated from the 1.x data model to the 2.x data model 44 | can be identified by the presence of a `crymap-v1-files` directory under the 45 | user directory. A user can be rolled back to the 1.x model by running the 46 | following commands in the user directory: 47 | 48 | ```sh 49 | mv crymap-v1-files/* . 50 | rmdir crymap-v1-files 51 | rm -rf messages delivery.sqlite* meta.sqlite.xex* 52 | ``` 53 | 54 | This will reset the account to the state it was in before the migration, except 55 | for changes to the `user.toml` file (which would include password changes). If 56 | the `user.toml` file now has an `[smtp_out]` section, you will need to remove 57 | that manually with a text editor, or restore the `user.toml` file from a backup 58 | in `tmp` if there is one or from another backup you had made. 59 | 60 | ## Finishing touches 61 | 62 | Once you are sure you won't need to roll back, you can entirely remove the 63 | `crymap-v1-files` within each user directory. 64 | -------------------------------------------------------------------------------- /book/src/admin-guide/tuning.md: -------------------------------------------------------------------------------- 1 | # Tuning 2 | 3 | Crymap's performance tuning options are very limited; in fact, Crymap itself 4 | does not provide any at all. 5 | 6 | ## Thread Count 7 | 8 | As of Crymap 2.0.0, Crymap is always entirely single-threaded. 9 | 10 | ## Memory Allocator 11 | 12 | Crymap uses the host system's `malloc()`, so exactly how it gets tuned depends 13 | on your system. Often, you can refer to `malloc(3)` for information on how to 14 | configure the allocator. 15 | 16 | Crymap is very conservative about allocating memory and ensures that memory not 17 | needed is freed expediently. The memory allocator itself may not be. In 18 | particular, glibc's `malloc()` (used on most Linux installations) and jemalloc 19 | (used on FreeBSD) will switch to a strategy optimised for multi-core 20 | performance when running on a multi-core system. This can cause Crymap to use 21 | dramatically more memory than it actually needs, potentially by a factor of as 22 | much as 10. If running Crymap on a multi-core system with memory constraints, 23 | it is useful to disable the multi-core allocation strategies. 24 | 25 | With glibc `malloc()`, this can be done by setting the environment variable 26 | `M_ARENA_MAX` to `1`. 27 | 28 | With jemalloc, a similar thing can be done by setting `MALLOC_CONF` to 29 | `narenas:1`. 30 | -------------------------------------------------------------------------------- /book/src/admin-guide/users.md: -------------------------------------------------------------------------------- 1 | # Managing Users 2 | 3 | ## Crymap's notion of a user 4 | 5 | A "user" in Crymap simply refers to an entry within the `users` directory. Each 6 | entry may either be a directory, or be a symlink to a directory. Whichever it 7 | is, that directory is the _user data directory_. 8 | 9 | In traditional UNIX-style deployments, Crymap uses the owner of the user data 10 | directory to determine which UNIX account to assume for operations on that 11 | user. In black box deployments, the exact ownership of the user data directory 12 | is less important, but it is still necessarily something that the Crymap user 13 | has access to and which doesn't let unauthorised users access it. 14 | 15 | ## Creating users 16 | 17 | Creation of a user is (internally) a non-trivial operation since it needs to 18 | generate a master key for the user and make it derivable from the user's 19 | initial password. The process also must set up the user's basic directory 20 | structure and mailboxes, most importantly INBOX. 21 | 22 | User creation is done through the `crymap user add` command. This command 23 | should be run as `root` for traditional UNIX-style deployments and as the 24 | Crymap user for black box deployments. 25 | 26 | The first argument of the command is the name of the user to create, e.g., 27 | `jsmith`. If the command is run as `root`, Crymap will by default assume that 28 | that name also refers to the name of the UNIX account that will own the mail 29 | data, and will fail if no such account exists. The `--uid` option can be passed 30 | to provide the UID that should own the user data directory. 31 | 32 | The optional second argument of the command gives the path to the user data 33 | directory. If this argument is not given, the user data directory is simply a 34 | directory within `users`. The command will fail if this would cause the user 35 | data to be stored inside `/etc` or `/usr/local/etc`; in this case, you need to 36 | explicitly give a path for the user data. 37 | 38 | By default, a password for the user is randomly generated. You can pass 39 | `--prompt-password` to input your own. 40 | 41 | ## Renaming, aliasing, and deleting users 42 | 43 | Crymap does not currently have special commands for these operations. Once 44 | created, users can be treated as regular file system objects. In particular: 45 | 46 | - A user can be renamed by simply renaming the entry under `users`. 47 | 48 | - User aliases can be created by symlinking the additional alias to the user 49 | data directory. These symlinks are understood to allow the user to use all of 50 | them equivalently. For example, if `bob` is a symlink to `robert`, the user 51 | can log in as `robert` and then send mail as `bob`. 52 | 53 | - Users can be deleted by removing their entry from `users`. 54 | 55 | - Users can be disabled by renaming them to an illegal user name. The simplest 56 | way is to just prefix their name with `%`. You do need to deal with any 57 | aliases as well, though. 58 | 59 | - A user can be imported from another Crymap installation by simply moving the 60 | user data directory (or a symlink thereto) into `users`. 61 | 62 | ## Password Resets 63 | 64 | When a user changes their password, a backup of the user configuration is 65 | created in the `tmp` directory within the user data directory, with a name of 66 | the format `config-backup-DATETIME.toml`. If necessary (for example, because 67 | the user mistyped their new password), the password change can be undone by 68 | replacing the `user.toml` file at the top of the user data directory with the 69 | backup file. Note that these backup files are automatically deleted after a 70 | successful login 24 hours after the change was made. 71 | 72 | If a user forgets their password, there is no recourse. Their data is gone 73 | forever. The best thing to do is to move their user data directory to somewhere 74 | else in case they remember the password later and create a new account for 75 | them. 76 | -------------------------------------------------------------------------------- /book/src/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Crymap is an IMAP and SMTP server implementation for FreeBSD and Linux with a 4 | strong focus on security and simplicity of administration. Its spotlight 5 | feature is transparent encryption of data at rest — it is not possible to read 6 | any user's mail without knowing their password, while a regular IMAP experience 7 | is provided and mail can be received while the user is offline. 8 | 9 | If using the Crymap SMTP server, none of any user's mail will ever be stored on 10 | disk in the clear (on your server, anyway), though note that Crymap SMTP has 11 | serious caveats. 12 | 13 | Crymap supports both traditional UNIX-style deployments, where each user 14 | corresponds to a UNIX account and owns their own mail, and "black box" 15 | deployments, where users do not have shell access and all mail is owned by a 16 | single system UNIX account. 17 | 18 | ## Features 19 | 20 | - Fully compliant with the IMAP4rev1 and IMAP4rev2 specifications. 21 | - Secure by default. IMAPS only. 22 | - Minimal configuration. 23 | - Messages and metadata transparently encrypted at rest. 24 | - Automatic key rotation. 25 | - Transparent file and over-the-wire compression. 26 | - All normal mailboxes are "dual-use" (allow both messages and sub-mailboxes). 27 | - Instant mail delivery notifications. 28 | - QRESYNC support. 29 | - "Special-use" mailbox support. 30 | - A decent number of additional IMAP extensions. 31 | - Supports messages with 8-bit and binary content. 32 | - Mail delivery as an MDA. 33 | - Mail delivery via SMTP or LMTP. 34 | - Simple but secure sending of mail via SMTP with built-in DKIM support. 35 | - Interoperates well with filesystem-based backup systems. 36 | 37 | ## Status and Support 38 | 39 | The author uses Crymap for all personal email. It is known to work well in this 40 | use case. But this is naturally a fairly small amount of experience; in 41 | particular, Crymap has only seen day-to-day use in conjunction with Thunderbird 42 | and FairEmail. 43 | 44 | Crymap is currently maintained by its author alone. I am motivated to address 45 | bugs, but feature requests are unlikely to be accepted unless they offer 46 | substantial benefits to security or very common use cases. I will try to answer 47 | questions but cannot make commitments. 48 | 49 | ## Caveats 50 | 51 | - If a password is forgotten, all data owned by that user is lost forever. 52 | There is no way for an administrator to reset a user's password, as that 53 | would defeat Crymap's purpose. 54 | 55 | - Crymap has no ability to integrate into the host authentication system. I.e., 56 | Crymap user accounts are fully independent of host user accounts in terms of 57 | password, enabled/disabled status, etc. This is because Crymap needs full 58 | control of the password change process for password changes to happen without 59 | destroying the user's data. It would be technologically feasible to make, 60 | e.g., a PAM module that delegates to Crymap, but this is not implemented nor 61 | are there any plans to ever do so. 62 | 63 | - The maximum size of an email is currently hard-coded to 64MB. 64 | 65 | - Crymap's outbound SMTP experience is unusual and somewhat cumbersome. If 66 | sending an email experiences a temporary failure, it cannot be automatically 67 | retried since all access to messages is cryptographically locked behind user 68 | authentication. Retrying must be done manually via an IMAP extension, which 69 | is currently only implemented by the Crymap CLI utility. For a more 70 | conventional experience, you can use something like OpenSMTPD to handle 71 | outbound messages instead. 72 | -------------------------------------------------------------------------------- /book/theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | html { 6 | font-family: serif; 7 | color: var(--fg); 8 | background-color: var(--bg); 9 | text-size-adjust: none; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | font-size: 1rem; 15 | overflow-x: hidden; 16 | } 17 | 18 | code { 19 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; 20 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 21 | } 22 | 23 | .left { float: left; } 24 | .right { float: right; } 25 | .hidden { display: none; } 26 | .play-button.hidden { display: none; } 27 | 28 | h2, h3 { margin-top: 2.5em; } 29 | h4, h5 { margin-top: 2em; } 30 | 31 | .header + .header h3, 32 | .header + .header h4, 33 | .header + .header h5 { 34 | margin-top: 1em; 35 | } 36 | 37 | a.header:target h1:before, 38 | a.header:target h2:before, 39 | a.header:target h3:before, 40 | a.header:target h4:before { 41 | display: inline-block; 42 | content: "»"; 43 | margin-left: -30px; 44 | width: 30px; 45 | } 46 | 47 | .page { 48 | outline: 0; 49 | padding: 0 var(--page-padding); 50 | } 51 | .page-wrapper { 52 | box-sizing: border-box; 53 | } 54 | .js .page-wrapper { 55 | transition: none; 56 | } 57 | 58 | .content { 59 | overflow-y: auto; 60 | padding: 0 15px; 61 | padding-bottom: 50px; 62 | } 63 | .content main { 64 | margin-left: auto; 65 | margin-right: auto; 66 | max-width: var(--content-max-width); 67 | } 68 | .content a { text-decoration: none; } 69 | .content a:hover { text-decoration: underline; } 70 | .content img { max-width: 100%; } 71 | .content .header:link, 72 | .content .header:visited { 73 | color: var(--fg); 74 | } 75 | .content .header:link, 76 | .content .header:visited:hover { 77 | text-decoration: none; 78 | } 79 | 80 | table { 81 | margin: 0 auto; 82 | border-collapse: collapse; 83 | } 84 | table td { 85 | padding: 3px 20px; 86 | border: 1px var(--table-border-color) solid; 87 | } 88 | table thead { 89 | background: var(--table-header-bg); 90 | } 91 | table thead td { 92 | font-weight: 700; 93 | border: none; 94 | } 95 | table thead tr { 96 | border: 1px var(--table-header-bg) solid; 97 | } 98 | /* Alternate background colors for rows */ 99 | table tbody tr:nth-child(2n) { 100 | background: var(--table-alternate-bg); 101 | } 102 | 103 | 104 | blockquote { 105 | margin: 20px 0; 106 | padding: 0 20px; 107 | color: var(--fg); 108 | background-color: var(--quote-bg); 109 | border-top: .1em solid var(--quote-border); 110 | border-bottom: .1em solid var(--quote-border); 111 | } 112 | 113 | 114 | :not(.footnote-definition) + .footnote-definition, 115 | .footnote-definition + :not(.footnote-definition) { 116 | margin-top: 2em; 117 | } 118 | .footnote-definition { 119 | font-size: 0.9em; 120 | margin: 0.5em 0; 121 | } 122 | .footnote-definition p { 123 | display: inline; 124 | } 125 | 126 | .tooltiptext { 127 | position: absolute; 128 | visibility: hidden; 129 | color: #fff; 130 | background-color: #333; 131 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 132 | left: -8px; /* Half of the width of the icon */ 133 | top: -35px; 134 | font-size: 0.8em; 135 | text-align: center; 136 | border-radius: 6px; 137 | padding: 5px 8px; 138 | margin: 5px; 139 | z-index: 1000; 140 | } 141 | .tooltipped .tooltiptext { 142 | visibility: visible; 143 | } 144 | 145 | p { 146 | text-align: justify; 147 | } 148 | -------------------------------------------------------------------------------- /book/theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /book/theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/book/theme/favicon.png -------------------------------------------------------------------------------- /book/theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* Base16 Atelier Dune Light - Theme */ 2 | /* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ 3 | /* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ 4 | 5 | /* Atelier-Dune Comment */ 6 | .hljs-comment, 7 | .hljs-quote { 8 | color: #AAA; 9 | } 10 | 11 | /* Atelier-Dune Red */ 12 | .hljs-variable, 13 | .hljs-template-variable, 14 | .hljs-attribute, 15 | .hljs-tag, 16 | .hljs-name, 17 | .hljs-regexp, 18 | .hljs-link, 19 | .hljs-name, 20 | .hljs-selector-id, 21 | .hljs-selector-class { 22 | color: #d73737; 23 | } 24 | 25 | /* Atelier-Dune Orange */ 26 | .hljs-number, 27 | .hljs-meta, 28 | .hljs-built_in, 29 | .hljs-builtin-name, 30 | .hljs-literal, 31 | .hljs-type, 32 | .hljs-params { 33 | color: #b65611; 34 | } 35 | 36 | /* Atelier-Dune Green */ 37 | .hljs-string, 38 | .hljs-symbol, 39 | .hljs-bullet { 40 | color: #60ac39; 41 | } 42 | 43 | /* Atelier-Dune Blue */ 44 | .hljs-title, 45 | .hljs-section { 46 | color: #6684e1; 47 | } 48 | 49 | /* Atelier-Dune Purple */ 50 | .hljs-keyword, 51 | .hljs-selector-tag { 52 | color: #b854d4; 53 | } 54 | 55 | .hljs { 56 | display: block; 57 | overflow-x: auto; 58 | background: #f1f1f1; 59 | color: #6e6b5e; 60 | padding: 0.5em; 61 | } 62 | 63 | .hljs-emphasis { 64 | font-style: italic; 65 | } 66 | 67 | .hljs-strong { 68 | font-weight: bold; 69 | } 70 | -------------------------------------------------------------------------------- /gen-readme.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | cat readme-prologue.md book/src/index.md readme-antelogue.md >README.md 4 | -------------------------------------------------------------------------------- /proptest-regressions/account/mailbox_state.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 87681c07184faddebeeb237e99529709e351afa1241643cb64dc446e444056f1 # shrinks to expunge_before = [], expunge_after = [1] 8 | -------------------------------------------------------------------------------- /proptest-regressions/crypt/item_stream.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc fb07c47c87a51e45512a94ca777ee7a0b757246d4a53fdbcfd46d8de8a8a9704 # shrinks to strings = ["𐀀𐀀a𐀀A0"] 8 | -------------------------------------------------------------------------------- /proptest-regressions/mime/utf7.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 5d1e4faee794c0ea03ac7a95f7a901339cfbdb2e5e28b336aed68a606875f98b # shrinks to s = "\u{0}𐀀¢¡¡" 8 | cc 88550810522e33b6e1f76f3619825ee3b2af50c35e22ee71c356a34da1ba46ee # shrinks to s = "&AA¡" 9 | -------------------------------------------------------------------------------- /proptest-regressions/smtp/inbound/server.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 6b77151c030c61fa30b4f99838663579dace0fee7247d08c9495713d05b0e57d # shrinks to content = ".\r.\rxx\n\n\nx..\n.\nx\rx\r\n\n\r.\r\rx.\r\n\nx..x\r\nx\n..\r\n..\nx\n\n\nxx\n\rx.\r..x\r\n", buffer_size = 13 8 | cc 8f1deb527176c546b2a6f04235d93bfa8b89a62ef5840ebe82bb832d66aa608b # shrinks to content = "\n\r\r\r.x.\rx\rx\r\n.\r\r\r\n...\r\n", buffer_size = 4 9 | cc 813059613f949109b5b5186fe2522c540ccb0f3347b99991907eb6a5b7d7ff1b # shrinks to content = ".\n.\r\n", buffer_size = 1 10 | cc df807c014f6697efa26665a94bb21e2777e338958348bf21802fb15b5314f023 # shrinks to content = "\r\n\rx\r\n.\n\n\n\n\n....\r\r\n.\n.\r\n", buffer_size = 3 11 | -------------------------------------------------------------------------------- /proptest-regressions/userbox/mailbox_state.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc e7a94dd9a051bfb52f825dc8e8dafbdee8a0fba569223da8d2c7ceadcbebc36d # shrinks to expunge_before = [1], expunge_after = [76, 2] 8 | -------------------------------------------------------------------------------- /proptest-regressions/userbox/model.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 0285fa977ed5560dca8748da5604214d4b05153bbe41b76a2f75623fe966561c # shrinks to ranges = [(1, 1), (4, 1)] 8 | -------------------------------------------------------------------------------- /readme-antelogue.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | Refer to the [Crymap mdbook](https://altsysrq.github.io/crymap/index.html) for 4 | full documentation. 5 | 6 | ## License 7 | 8 | Crymap is licensed under the [GPL version 3 or later](COPYING). 9 | -------------------------------------------------------------------------------- /readme-prologue.md: -------------------------------------------------------------------------------- 1 | # Crymap IMAP and SMTP Server 2 | 3 | [![](http://meritbadge.herokuapp.com/crymap)](https://crates.io/crates/crymap) 4 | 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | newline_style = "Unix" 3 | 4 | # Not having the trailing commas is too confusing and irritating. 5 | # 6 | # match x { 7 | # 0 => { 8 | # do_something() 9 | # } // no comma here because it's a block 10 | # 1 => foo! { 11 | # do_something() 12 | # }, // comma here because it's not a block even though it looks like one 13 | # 2 => Struct { 14 | # foo: bar, 15 | # }, // comma here because it's an expression 16 | # } 17 | match_block_trailing_comma = true 18 | -------------------------------------------------------------------------------- /src/account/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! This module contains everything to do with a single user's data: their 20 | //! mailboxes, their settings, their keys. 21 | //! 22 | //! Unversioned submodules are not storage-implementation-specific. 23 | 24 | pub mod key_store; 25 | mod message_format; 26 | pub mod model; 27 | mod search_backend; 28 | 29 | mod v1; 30 | pub mod v2; 31 | -------------------------------------------------------------------------------- /src/account/v1/mailbox/zstd_train.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::io; 20 | 21 | use super::defs::*; 22 | use crate::mime::fetch::zstd_train::ZstdTrainFetcher; 23 | use crate::mime::grovel::grovel; 24 | use crate::support::error::Error; 25 | 26 | impl StatefulMailbox { 27 | pub fn zstd_train(&mut self) -> Result, Error> { 28 | let samples = self 29 | .state 30 | .uids() 31 | .map(|uid| { 32 | let mut accessor = self.access_message(uid)?; 33 | grovel(&mut accessor, ZstdTrainFetcher::default()) 34 | }) 35 | .collect::, Error>>()?; 36 | 37 | zstd::dict::from_samples(&samples, 4096) 38 | .map_err(|e| Error::Io(io::Error::new(io::ErrorKind::Other, e))) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/account/v1/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! The storage system that was used in Crymap before version 2.0. 20 | //! 21 | //! V1 storage uses a complex and delicate "filesystem as a database" model, 22 | //! described in more detail in each module. 23 | //! 24 | //! The code here is the entire storage layer from Crymap 1.x. In the binary 25 | //! build, it is used in a read-only manner to migrate to the V2 model. Tests 26 | //! do use the write support to generate scenarios to test the migration path. 27 | #![allow(dead_code)] 28 | 29 | pub mod account; 30 | mod hier_id_scheme; 31 | pub mod mailbox; 32 | pub mod mailbox_path; 33 | mod mailbox_state; 34 | mod model; 35 | mod recency_token; 36 | -------------------------------------------------------------------------------- /src/account/v2/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! The V2 storage and state system was introduced with Crymap 2.0.0. 20 | 21 | mod state; 22 | mod storage; 23 | 24 | pub use super::v1::account::account_config_file; 25 | pub use state::{ 26 | Account, DeliveryAccount, FetchReceiver, LogInError, Mailbox, 27 | SpooledMessage, SpooledMessageId, 28 | }; 29 | pub use storage::SmtpTransfer; 30 | -------------------------------------------------------------------------------- /src/account/v2/state/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! The in-memory state for the V2 state and storage system. This is the API 20 | //! used by the IMAP protocol layer and the delivery systems. 21 | //! 22 | //! This module tree should be thought of as one large module, as it is very 23 | //! tightly cross-coupled; the main types are `Account` and `ExternAccount`, 24 | //! whose very large implementation is split across multiple files for 25 | //! manageability. 26 | 27 | mod defs; 28 | mod delivery; 29 | mod expunge; 30 | mod fetch; 31 | mod flags; 32 | mod idle; 33 | mod init; 34 | mod mailboxes; 35 | mod maintenance; 36 | mod messages; 37 | mod migration; 38 | mod poll; 39 | mod search; 40 | mod select; 41 | mod spool; 42 | mod user_config; 43 | 44 | #[cfg(feature = "dev-tools")] 45 | mod zstd_train; 46 | 47 | pub use defs::{Account, Mailbox}; 48 | pub use delivery::DeliveryAccount; 49 | pub use fetch::FetchReceiver; 50 | pub use init::LogInError; 51 | pub use spool::{SpooledMessage, SpooledMessageId}; 52 | -------------------------------------------------------------------------------- /src/account/v2/state/select.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | use crate::{account::model::*, support::error::Error}; 21 | 22 | impl Account { 23 | /// Probe for the given mailbox, returning the error `select` would if 24 | /// anything would obviously go wrong. 25 | pub fn probe_mailbox(&mut self, mailbox: &str) -> Result<(), Error> { 26 | let mailbox_id = self.metadb.find_mailbox(mailbox)?; 27 | if !self.metadb.fetch_mailbox(mailbox_id)?.selectable { 28 | return Err(Error::MailboxUnselectable); 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | /// Perform a `SELECT` or `EXAMINE` of the given mailbox, with an optional 35 | /// fused `QRESYNC` operation. 36 | /// 37 | /// Use `Mailbox::select_response()` on the returned `Mailbox` to get the 38 | /// actual `SelectResponse`. 39 | pub fn select( 40 | &mut self, 41 | mailbox: &str, 42 | writable: bool, 43 | qresync: Option<&QresyncRequest>, 44 | ) -> Result<(Mailbox, Option), Error> { 45 | // Implicitly drain deliveries before selecting to ensure new messages 46 | // show up immediately. 47 | self.drain_deliveries(); 48 | 49 | let mailbox_id = self.metadb.find_mailbox(mailbox)?; 50 | let snapshot = self.metadb.select(mailbox_id, writable, qresync)?; 51 | let mailbox = Mailbox { 52 | id: mailbox_id, 53 | writable, 54 | messages: snapshot 55 | .messages 56 | .into_iter() 57 | .map(MessageStatus::from) 58 | .collect(), 59 | max_client_known_flag_id: snapshot 60 | .flags 61 | .last() 62 | .expect("there is always at least one flag") 63 | .0, 64 | flags: snapshot.flags, 65 | snapshot_modseq: snapshot.max_modseq, 66 | polled_snapshot_modseq: snapshot.max_modseq, 67 | has_pending_expunge: false, 68 | next_uid: snapshot.next_uid, 69 | changed_flags_uids: Vec::new(), 70 | fetch_loopbreaker: Default::default(), 71 | }; 72 | 73 | Ok((mailbox, snapshot.qresync)) 74 | } 75 | } 76 | 77 | impl Mailbox { 78 | /// Generates the `SelectResponse` to be produced in response to selecting 79 | /// the mailbox in this state. 80 | pub fn select_response(&self) -> Result { 81 | let seen_flag = 82 | self.flag_id(&Flag::Seen).expect("\\Seen is always defined"); 83 | 84 | Ok(SelectResponse { 85 | flags: self 86 | .flags 87 | .iter() 88 | .map(|&(_, ref flag)| flag.clone()) 89 | .collect(), 90 | exists: self.messages.len(), 91 | recent: self.messages.iter().filter(|m| m.recent).count(), 92 | unseen: self 93 | .messages 94 | .iter() 95 | .position(|m| !m.flags.contains(seen_flag.0)) 96 | .map(Seqnum::from_index), 97 | uidnext: self.next_uid, 98 | uidvalidity: self.id.as_uid_validity()?, 99 | read_only: !self.writable, 100 | max_modseq: self.snapshot_modseq, 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/account/v2/state/zstd_train.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::io; 20 | 21 | use super::defs::*; 22 | use crate::mime::fetch::zstd_train::ZstdTrainFetcher; 23 | use crate::mime::grovel::grovel; 24 | use crate::support::error::Error; 25 | 26 | impl Account { 27 | pub fn zstd_train(&mut self, mb: &Mailbox) -> Result, Error> { 28 | let samples = mb 29 | .messages 30 | .iter() 31 | .map(|m| { 32 | let mut accessor = self.access_message(mb, m.uid)?; 33 | grovel(&mut accessor, ZstdTrainFetcher::default()) 34 | }) 35 | .collect::, Error>>()?; 36 | 37 | zstd::dict::from_samples(&samples, 4096) 38 | .map_err(|e| Error::Io(io::Error::new(io::ErrorKind::Other, e))) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/account/v2/storage/db_migrations.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use log::info; 20 | 21 | use super::types::*; 22 | use crate::support::{error::Error, log_prefix::LogPrefix}; 23 | 24 | pub fn apply_migrations( 25 | log_prefix: &LogPrefix, 26 | cxn: &mut rusqlite::Connection, 27 | db_name: &str, 28 | migrations: &[&str], 29 | ) -> Result<(), Error> { 30 | let latest_version = migrations.len(); 31 | 32 | if Ok(latest_version) 33 | == cxn.query_row( 34 | "SELECT MAX(`version`) FROM `migration`", 35 | (), 36 | from_single::, 37 | ) 38 | { 39 | return Ok(()); 40 | } 41 | 42 | let txn = cxn 43 | .transaction_with_behavior(rusqlite::TransactionBehavior::Exclusive)?; 44 | txn.execute( 45 | "CREATE TABLE IF NOT EXISTS `migration` (\ 46 | `version` INTEGER NOT NULL PRIMARY KEY, \ 47 | `applied_at` INTEGER NOT NULL\ 48 | ) STRICT", 49 | (), 50 | )?; 51 | 52 | let current_version = txn 53 | .query_row( 54 | "SELECT MAX(`version`) FROM `migration`", 55 | (), 56 | from_single::>, 57 | )? 58 | .unwrap_or(0); 59 | 60 | for (version, migration) in migrations 61 | .iter() 62 | .copied() 63 | .enumerate() 64 | .map(|(ix, migration)| (ix + 1, migration)) 65 | .skip(current_version) 66 | { 67 | info!("{log_prefix} Applying #{version} migration to {db_name} DB"); 68 | txn.execute_batch(migration)?; 69 | txn.execute( 70 | "INSERT INTO `migration` (`version`, `applied_at`) \ 71 | VALUES (1, ?)", 72 | (UnixTimestamp::now(),), 73 | )?; 74 | } 75 | 76 | txn.commit()?; 77 | 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /src/account/v2/storage/deliverydb.v1.sql: -------------------------------------------------------------------------------- 1 | --- 2 | -- Copyright (c) 2023, Jason Lingle 3 | -- 4 | -- This file is part of Crymap. 5 | -- 6 | -- Crymap is free software: you can redistribute it and/or modify it under the 7 | -- terms of the GNU General Public License as published by the Free Software 8 | -- Foundation, either version 3 of the License, or (at your option) any later 9 | -- version. 10 | -- 11 | -- Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | -- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | -- details. 15 | -- 16 | -- You should have received a copy of the GNU General Public License along with 17 | -- Crymap. If not, see . 18 | 19 | -- Provides information about messages delivered to the user by not-logged-in 20 | -- processes. 21 | -- 22 | -- When a message is delivered externally, the message file is first written to 23 | -- the key store, then an entry added to this table. This ensures that if the 24 | -- second step fails, the message will at least eventually be recovered into 25 | -- the inbox and allowing entries in `delivery` to be processed immediately 26 | -- without needing to consider the possibility of the file not yet having been 27 | -- written. 28 | CREATE TABLE `delivery` ( 29 | -- The path to the message relative to the message store. 30 | `path` TEXT NOT NULL, 31 | -- The path to the mailbox into which to deliver the message. 32 | -- 33 | -- If such a delivery cannot be made, the message will instead be delivered 34 | -- into the inbox. 35 | `mailbox` TEXT NOT NULL, 36 | -- The initial flags to set on the message, separated by spaces. Invalid 37 | -- flags will be ignored. 38 | `flags` TEXT NOT NULL, 39 | -- The SAVEDATE value to set on the message when added to the destination 40 | -- mailbox. 41 | `savedate` INTEGER NOT NULL, 42 | -- If not NULL, some logged in process has attempted delivery of this entry. 43 | -- The entry is being kept around so that message recovery can see that the 44 | -- delivery is in-flight even if the file is not currently represented in the 45 | -- main database. 46 | -- 47 | -- Delivery entries are dropped once this indicates they are too old, opening 48 | -- messages that were not delivered successfully to unaccounted message 49 | -- recovery. 50 | `delivered` INTEGER 51 | ) STRICT; 52 | 53 | CREATE INDEX `delivery_path` ON `delivery` (`path`); 54 | CREATE INDEX `delivery_delivered` ON `delivery` (`delivered`); 55 | -------------------------------------------------------------------------------- /src/account/v2/storage/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! The storage layer for the V2 system. 20 | //! 21 | //! The storage layer is stateless (aside from SQLite connections themselves 22 | //! and the key cache) and provides the fundamental building blocks used to 23 | //! implement the state layer. The general guidelines are: 24 | //! 25 | //! 1. Every operation is atomic unless otherwise noted. 26 | //! 2. The concept of a database transaction does not escape the storage layer. 27 | 28 | mod db_migrations; 29 | mod deliverydb; 30 | mod messages; 31 | mod metadb; 32 | mod sqlite_xex_vfs; 33 | mod types; 34 | 35 | pub use deliverydb::Connection as DeliveryDb; 36 | pub use messages::MessageStore; 37 | pub use metadb::{message_summary_values, Connection as MetaDb}; 38 | pub use sqlite_xex_vfs::XexVfs; 39 | pub use types::*; 40 | -------------------------------------------------------------------------------- /src/cli/imap_test.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::fs; 20 | use std::os::unix::fs::DirBuilderExt; 21 | use std::path::PathBuf; 22 | use std::sync::Arc; 23 | 24 | use log::info; 25 | use tokio::net::TcpListener; 26 | 27 | use crate::account::v2::Account; 28 | use crate::crypt::master_key::MasterKey; 29 | use crate::imap::command_processor::CommandProcessor; 30 | use crate::support::{ 31 | async_io::ServerIo, log_prefix::LogPrefix, system_config::*, 32 | }; 33 | 34 | #[tokio::main(flavor = "current_thread")] 35 | pub async fn imap_test() { 36 | let local = tokio::task::LocalSet::new(); 37 | local.run_until(imap_test_impl()).await 38 | } 39 | 40 | async fn imap_test_impl() { 41 | crate::init_simple_log(); 42 | 43 | let system_root: PathBuf = 44 | format!("/tmp/crymaptest.{}", nix::unistd::getpid()).into(); 45 | let user_dir = system_root.join("user"); 46 | 47 | let system_config = Arc::new(SystemConfig { 48 | security: SecurityConfig::default(), 49 | ..SystemConfig::default() 50 | }); 51 | 52 | fs::DirBuilder::new() 53 | .mode(0o700) 54 | .create(&system_root) 55 | .expect(&format!("Failed to create {}", system_root.display())); 56 | fs::DirBuilder::new() 57 | .mode(0o700) 58 | .create(&user_dir) 59 | .expect(&format!("Failed to create {}", user_dir.display())); 60 | 61 | { 62 | let mut account = Account::new( 63 | LogPrefix::new("initial-setup".to_owned()), 64 | user_dir, 65 | Arc::new(MasterKey::new()), 66 | ) 67 | .expect("failed to open account"); 68 | account 69 | .provision(b"hunter2") 70 | .expect("Failed to set user account up"); 71 | } 72 | 73 | let listener = TcpListener::bind("127.0.0.1:14143") 74 | .await 75 | .expect("Failed to bind listener socket"); 76 | 77 | info!("Initialised successfully."); 78 | info!("Connect to: localhost:14143, username 'user', password 'hunter2'"); 79 | 80 | loop { 81 | let (tcp_sock, origin) = listener 82 | .accept() 83 | .await 84 | .expect("Failed to listen for connections"); 85 | // Convert to the std type so that the FD is deregistered from the 86 | // tokio runtime. 87 | let tcp_sock = tcp_sock.into_std().unwrap(); 88 | let io = ServerIo::new_owned_socket(tcp_sock) 89 | .expect("Failed to make socket non-blocking"); 90 | 91 | let processor = CommandProcessor::new( 92 | LogPrefix::new(origin.to_string()), 93 | Arc::clone(&system_config), 94 | system_root.clone(), 95 | None, 96 | ); 97 | 98 | tokio::task::spawn_local(crate::imap::server::run(io, processor)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2024 Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | macro_rules! die { 20 | ($ex:ident, $($stuff:tt)*) => {{ 21 | eprintln!($($stuff)*); 22 | crate::support::sysexits::$ex.exit() 23 | }} 24 | } 25 | 26 | pub mod main; 27 | 28 | #[cfg(feature = "dev-tools")] 29 | mod imap_test; 30 | 31 | mod deliver; 32 | mod remote; 33 | mod sanity; 34 | mod serve; 35 | mod user; 36 | -------------------------------------------------------------------------------- /src/crypt/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | pub const AES_BLOCK: usize = 16; 20 | pub const AES_BLOCK64: u64 = AES_BLOCK as u64; 21 | 22 | pub mod data_stream; 23 | pub mod master_key; 24 | pub mod naked; 25 | pub mod xex; 26 | 27 | #[cfg(test)] 28 | pub mod test_keys; 29 | -------------------------------------------------------------------------------- /src/crypt/naked.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use rand::{rngs::OsRng, Rng}; 20 | 21 | use super::AES_BLOCK; 22 | 23 | /// A context for reading and writing "naked" encrypted streams. 24 | /// 25 | /// Naked encrypted streams have no associated metadata, so they are unreadable 26 | /// without this context object. They are used for temporary buffers that must 27 | /// be spilled to disk. Internally, this operates with AES-128-CTR, allowing it 28 | /// to operate on a 1:1 basis. 29 | /// 30 | /// Each context has a unique key and IV. 31 | #[derive(Clone, Copy)] 32 | pub struct NakedCryptContext { 33 | key: [u8; AES_BLOCK], 34 | iv: [u8; AES_BLOCK], 35 | } 36 | 37 | impl NakedCryptContext { 38 | pub fn new() -> Self { 39 | NakedCryptContext { 40 | key: OsRng.gen(), 41 | iv: OsRng.gen(), 42 | } 43 | } 44 | 45 | pub fn encryptor(&self) -> openssl::symm::Crypter { 46 | let mut c = openssl::symm::Crypter::new( 47 | openssl::symm::Cipher::aes_128_ctr(), 48 | openssl::symm::Mode::Encrypt, 49 | &self.key, 50 | Some(&self.iv), 51 | ) 52 | .unwrap(); 53 | c.pad(false); 54 | c 55 | } 56 | 57 | pub fn decryptor(&self) -> openssl::symm::Crypter { 58 | let mut c = openssl::symm::Crypter::new( 59 | openssl::symm::Cipher::aes_128_ctr(), 60 | openssl::symm::Mode::Decrypt, 61 | &self.key, 62 | Some(&self.iv), 63 | ) 64 | .unwrap(); 65 | c.pad(false); 66 | c 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/crypt/test_keys.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use lazy_static::lazy_static; 4 | use openssl::pkey::Private; 5 | use openssl::rsa::Rsa; 6 | 7 | lazy_static! { 8 | pub static ref RSA1024A: Arc> = 9 | Arc::new(Rsa::generate(1024).unwrap()); 10 | } 11 | -------------------------------------------------------------------------------- /src/file-preamble: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | -------------------------------------------------------------------------------- /src/imap/command_processor/flags.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | use std::fmt; 21 | 22 | use super::defs::*; 23 | use crate::account::{ 24 | model::*, 25 | v2::{Account, Mailbox}, 26 | }; 27 | use crate::support::error::Error; 28 | 29 | impl CommandProcessor { 30 | pub(crate) async fn cmd_store( 31 | &mut self, 32 | cmd: s::StoreCommand<'_>, 33 | sender: &mut SendResponse, 34 | ) -> CmdResult { 35 | let ids = self.parse_seqnum_range(&cmd.messages)?; 36 | self.store(ids, cmd, sender, Account::seqnum_store).await 37 | } 38 | 39 | pub(crate) async fn cmd_uid_store( 40 | &mut self, 41 | cmd: s::StoreCommand<'_>, 42 | sender: &mut SendResponse, 43 | ) -> CmdResult { 44 | let ids = self.parse_uid_range(&cmd.messages)?; 45 | self.store(ids, cmd, sender, Account::store).await 46 | } 47 | 48 | async fn store( 49 | &mut self, 50 | ids: SeqRange, 51 | cmd: s::StoreCommand<'_>, 52 | sender: &mut SendResponse, 53 | f: impl FnOnce( 54 | &mut Account, 55 | &mut Mailbox, 56 | &StoreRequest, 57 | ) -> Result, Error>, 58 | ) -> CmdResult 59 | where 60 | SeqRange: fmt::Debug, 61 | { 62 | if cmd.unchanged_since.is_some() { 63 | self.enable_condstore(sender, true).await; 64 | } 65 | 66 | let request = StoreRequest { 67 | ids: &ids, 68 | flags: &cmd.flags, 69 | remove_listed: s::StoreCommandType::Minus == cmd.typ, 70 | remove_unlisted: s::StoreCommandType::Eq == cmd.typ, 71 | loud: !cmd.silent, 72 | unchanged_since: cmd.unchanged_since.map(Modseq::of), 73 | }; 74 | 75 | let resp = f(account!(self)?, selected!(self)?, &request).map_err(map_error! { 76 | self, 77 | MailboxFull => (No, Some(s::RespTextCode::Limit(()))), 78 | NxMessage => (No, Some(s::RespTextCode::Nonexistent(()))), 79 | ExpungedMessage => (No, Some(s::RespTextCode::ExpungeIssued(()))), 80 | MailboxReadOnly => (No, Some(s::RespTextCode::Cannot(()))), 81 | UnaddressableMessage => (No, Some(s::RespTextCode::ClientBug(()))), 82 | GaveUpInsertion => (No, Some(s::RespTextCode::Unavailable(()))), 83 | })?; 84 | 85 | if !resp.modified.is_empty() { 86 | Ok(s::Response::Cond(s::CondResponse { 87 | cond: if resp.ok { 88 | s::RespCondType::Ok 89 | } else { 90 | s::RespCondType::No 91 | }, 92 | code: Some(s::RespTextCode::Modified(Cow::Owned( 93 | resp.modified.to_string(), 94 | ))), 95 | quip: None, 96 | })) 97 | } else if resp.ok { 98 | success() 99 | } else { 100 | Ok(s::Response::Cond(s::CondResponse { 101 | cond: s::RespCondType::No, 102 | code: None, 103 | quip: Some(Cow::Borrowed("No messages matched")), 104 | })) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/imap/command_processor/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Implements most of the IMAP protocol, specifically that which is not 20 | //! sensitive to the actual wire format. 21 | //! 22 | //! As with `account::v2::state`, this module is split into several submodules 23 | //! for manageability, but is best thought of as one single module. 24 | 25 | macro_rules! map_error { 26 | ($this:expr) => {{ 27 | let log_prefix = &$this.log_prefix; 28 | let account = $this.account.as_mut(); 29 | let selected = $this.selected.as_ref(); 30 | move |e| { 31 | let selected_ok = account.map_or( 32 | true, |account| selected.map_or( 33 | true, |s| account.is_usable_mailbox(s))); 34 | catch_all_error_handling(selected_ok, log_prefix, e) 35 | } 36 | }}; 37 | 38 | (log_prefix = $log_prefix:expr, 39 | $($($kind:ident)|+ => ($cond:ident, $code:expr),)+) => {{ 40 | let log_prefix = $log_prefix; 41 | move |e| match e { 42 | $($(Error::$kind)|* => s::Response::Cond(s::CondResponse { 43 | cond: s::RespCondType::$cond, 44 | code: $code, 45 | quip: Some(Cow::Owned(e.to_string())), 46 | }),)* 47 | e => { 48 | catch_all_error_handling(true, log_prefix, e) 49 | } 50 | } 51 | }}; 52 | 53 | ($this:expr, $($($kind:ident)|+ => ($cond:ident, $code:expr),)+) => {{ 54 | let log_prefix = &$this.log_prefix; 55 | let account = $this.account.as_mut(); 56 | let selected = $this.selected.as_ref(); 57 | move |e| match e { 58 | $($(Error::$kind)|* => s::Response::Cond(s::CondResponse { 59 | cond: s::RespCondType::$cond, 60 | code: $code, 61 | quip: Some(Cow::Owned(e.to_string())), 62 | }),)* 63 | e => { 64 | let selected_ok = account.map_or( 65 | true, |account| selected.map_or( 66 | true, |s| account.is_usable_mailbox(s))); 67 | catch_all_error_handling(selected_ok, log_prefix, e) 68 | } 69 | } 70 | }}; 71 | } 72 | 73 | // account! and selected! are macros instead of methods on CommandProcessor 74 | // since there is no way to express that they borrow only one field --- as a 75 | // method, the returned value is considered to borrow the whole 76 | // `CommandProcessor`. 77 | macro_rules! account { 78 | ($this:expr) => { 79 | $this.account.as_mut().ok_or_else(|| { 80 | s::Response::Cond(s::CondResponse { 81 | cond: s::RespCondType::Bad, 82 | code: None, 83 | quip: Some(Cow::Borrowed("Not logged in")), 84 | }) 85 | }) 86 | }; 87 | } 88 | 89 | macro_rules! selected { 90 | ($this:expr) => { 91 | $this.selected.as_mut().ok_or_else(|| { 92 | s::Response::Cond(s::CondResponse { 93 | cond: s::RespCondType::Bad, 94 | code: None, 95 | quip: Some(Cow::Borrowed("No mailbox selected")), 96 | }) 97 | }) 98 | }; 99 | } 100 | 101 | mod auth; 102 | mod commands; 103 | mod defs; 104 | mod fetch; 105 | mod flags; 106 | mod mailboxes; 107 | mod messages; 108 | mod search; 109 | mod smtp_out; 110 | mod user_config; 111 | 112 | pub use self::defs::CommandProcessor; 113 | -------------------------------------------------------------------------------- /src/imap/integration_tests/imap4rev2.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | // Current as of IMAP4rev2 2020-07 draft 20 | 21 | use super::defs::*; 22 | 23 | #[test] 24 | fn capability_declared() { 25 | test_require_capability("i4r2capa", "IMAP4rev2"); 26 | } 27 | 28 | #[test] 29 | fn status_deleted() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("i4r2stde"); 32 | quick_log_in(&mut client); 33 | ok_command!(client, c("ENABLE IMAP4rev2")); 34 | quick_create(&mut client, "i4r2stde"); 35 | quick_append_enron(&mut client, "i4r2stde", 3); 36 | quick_select(&mut client, "i4r2stde"); 37 | 38 | ok_command!(client, c("STORE 2:3 +FLAGS \\Deleted")); 39 | 40 | command!(mut responses = client, c("STATUS i4r2stde (DELETED)")); 41 | assert_eq!(2, responses.len()); 42 | assert_tagged_ok_any(responses.pop().unwrap()); 43 | has_untagged_response_matching! { 44 | s::Response::Status(ref sr) in responses => { 45 | assert_eq!(1, sr.atts.len()); 46 | match sr.atts[0] { 47 | s::StatusResponseAtt::Deleted(n) => { 48 | assert_eq!(2, n); 49 | } 50 | ref a => panic!("Unexpected attribute: {:?}", a), 51 | } 52 | } 53 | }; 54 | } 55 | 56 | #[test] 57 | fn unicode_enabled() { 58 | let setup = set_up(); 59 | let mut client = setup.connect("i4r2unic"); 60 | quick_log_in(&mut client); 61 | ok_command!(client, c("ENABLE IMAP4rev2")); 62 | quick_create(&mut client, "i4r2unic/ünicöde"); 63 | 64 | command!(mut responses = client, c("LIST \"\" i4r2unic/%")); 65 | assert_eq!(2, responses.len()); 66 | assert_tagged_ok_any(responses.pop().unwrap()); 67 | has_untagged_response_matching! { 68 | s::Response::List(ref ml) in responses => { 69 | assert_eq!("i4r2unic/ünicöde", ml.name.raw); 70 | } 71 | }; 72 | } 73 | 74 | #[test] 75 | fn esearch_replaces_search() { 76 | let setup = set_up(); 77 | let mut client = setup.connect("i4r2srch"); 78 | quick_log_in(&mut client); 79 | ok_command!(client, c("ENABLE IMAP4rev2")); 80 | examine_shared(&mut client); 81 | 82 | command!(mut responses = client, c("SEARCH HEADER x-origin dasovich-j")); 83 | assert_eq!(2, responses.len()); 84 | assert_tagged_ok(responses.pop().unwrap()); 85 | has_untagged_response_matching! { 86 | s::Response::Esearch(s::EsearchResponse { 87 | uid: false, 88 | all: Some(_), 89 | .. 90 | }) in responses 91 | }; 92 | 93 | command!(mut responses = client, c("UID SEARCH HEADER x-origin dasovich-j")); 94 | assert_eq!(2, responses.len()); 95 | assert_tagged_ok(responses.pop().unwrap()); 96 | has_untagged_response_matching! { 97 | s::Response::Esearch(s::EsearchResponse { 98 | uid: true, 99 | all: Some(_), 100 | .. 101 | }) in responses 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc2342.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | #[test] 22 | fn capability_declared() { 23 | test_require_capability("2342capa", "NAMESPACE"); 24 | } 25 | 26 | #[test] 27 | fn command_works() { 28 | let setup = set_up(); 29 | let mut client = setup.connect("2342test"); 30 | 31 | command!(mut responses = client, c("NAMESPACE")); 32 | assert_tagged_ok(responses.pop().unwrap()); 33 | has_untagged_response_matching! { 34 | s::Response::Namespace(()) in responses 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc2971.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use super::defs::*; 22 | 23 | #[test] 24 | fn capability_declared() { 25 | test_require_capability("2971capa", "ID"); 26 | } 27 | 28 | #[test] 29 | fn command_works() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("2971test"); 32 | 33 | command!(mut responses = client, 34 | c(r#"ID ("name" "test" "version" "1.0")"#)); 35 | assert_tagged_ok(responses.pop().unwrap()); 36 | has_untagged_response_matching! { 37 | s::Response::Id(ref ids) in responses => { 38 | assert!(ids.contains(&Some(Cow::Borrowed("name")))); 39 | assert!(ids.contains(&Some(Cow::Borrowed(env!("CARGO_PKG_NAME"))))); 40 | } 41 | }; 42 | 43 | // Check some corner cases 44 | // New connection each time to ensure we go through the full logic 45 | client = setup.connect("2971test"); 46 | ok_command!(client, c("ID NIL")); 47 | 48 | client = setup.connect("2971test"); 49 | ok_command!(client, c(r#"ID ("name" NIL "version" "0.1")"#)); 50 | 51 | client = setup.connect("2971test"); 52 | ok_command!(client, c(r#"ID ("name" "foo" "version" NIL)"#)); 53 | 54 | client = setup.connect("2971test"); 55 | ok_command!(client, c(r#"ID ("name" "foo" "x-foo" "bar")"#)); 56 | } 57 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc3348.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | #[test] 22 | fn capability_declared() { 23 | test_require_capability("3348capa", "CHILDREN"); 24 | } 25 | 26 | #[test] 27 | fn children_attributes_returned() { 28 | let setup = set_up(); 29 | let mut client = setup.connect("3348attr"); 30 | quick_log_in(&mut client); 31 | quick_create(&mut client, "3348attr/foo"); 32 | quick_create(&mut client, "3348attr/bar"); 33 | quick_create(&mut client, "3348attr/bar/baz"); 34 | 35 | command!(mut responses = client, c("LIST \"\" 3348attr*")); 36 | assert_tagged_ok(responses.pop().unwrap()); 37 | assert_eq!( 38 | "3348attr \\HasChildren\n\ 39 | 3348attr/bar \\HasChildren\n\ 40 | 3348attr/bar/baz \\HasNoChildren\n\ 41 | 3348attr/foo \\HasNoChildren\n", 42 | list_results_to_str(responses) 43 | ); 44 | 45 | command!(mut responses = client, c("LIST \"\" INBOX")); 46 | assert_tagged_ok(responses.pop().unwrap()); 47 | // RFC 3348 does not explicitly say that clients need to infer 48 | // \HasNoChildren from \Noinferiors (while RFC 5258 does require it). 49 | // However, RFC 3348 allows us to return neither child marker, and does 50 | // weakly imply that clients should infer the absence of children from 51 | // \Noinferiors. 52 | assert_eq!("INBOX \\Noinferiors\n", list_results_to_str(responses)); 53 | } 54 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc3501/first_contact.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use super::super::defs::*; 22 | 23 | #[test] 24 | fn greeting_goodbye() { 25 | let setup = set_up(); 26 | let mut client = setup.connect("3501fcgg"); 27 | 28 | let mut buffer = Vec::new(); 29 | let greeting = client.read_one_response(&mut buffer).unwrap(); 30 | match greeting { 31 | s::ResponseLine { 32 | tag: None, 33 | response: 34 | s::Response::Cond(s::CondResponse { 35 | cond: s::RespCondType::Ok, 36 | code: Some(s::RespTextCode::Capability(caps)), 37 | quip: _, 38 | }), 39 | } => { 40 | assert!(caps.capabilities.contains(&Cow::Borrowed("IMAP4rev1"))); 41 | assert!(caps.capabilities.contains(&Cow::Borrowed("LITERAL+"))); 42 | }, 43 | g => panic!("Unexpected greeting: {:?}", g), 44 | } 45 | 46 | client.write_raw(b"1 LOGOUT\r\n").unwrap(); 47 | receive_line_like(&mut client, r#"^* BYE BYE\r\n$"#); 48 | receive_line_like(&mut client, r#"^1 OK"#); 49 | } 50 | 51 | #[test] 52 | fn request_capabilities() { 53 | let setup = set_up(); 54 | let mut client = setup.connect("3501fcrc"); 55 | 56 | skip_greeting(&mut client); 57 | 58 | let mut buffer = Vec::new(); 59 | let responses = client 60 | .command( 61 | s::Command::Simple(s::SimpleCommand::Capability), 62 | &mut buffer, 63 | ) 64 | .unwrap(); 65 | assert_eq!(2, responses.len()); 66 | 67 | let mut responses = responses.into_iter(); 68 | match responses.next().unwrap() { 69 | s::ResponseLine { 70 | tag: None, 71 | response: s::Response::Capability(caps), 72 | } => { 73 | assert!(caps.capabilities.contains(&Cow::Borrowed("IMAP4rev1"))); 74 | assert!(caps.capabilities.contains(&Cow::Borrowed("LITERAL+"))); 75 | }, 76 | r => panic!("Unexpected response: {:?}", r), 77 | } 78 | assert_tagged_ok(responses.next().unwrap()); 79 | } 80 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc3501/literal.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020 Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | 21 | // This doesn't test APPEND with synchronising literals since all the tests 22 | // that append messages already use it. 23 | #[test] 24 | fn command_synchronising_literals() { 25 | let setup = set_up(); 26 | let mut client = setup.connect("3501lics"); 27 | quick_log_in(&mut client); 28 | quick_select(&mut client, "INBOX"); 29 | 30 | client.write_raw(b"A1 SEARCH TEXT {5}\r\n").unwrap(); 31 | 32 | let mut buffer = Vec::new(); 33 | client.read_logical_line(&mut buffer).unwrap(); 34 | assert!(buffer.starts_with(b"+ ")); 35 | 36 | client.write_raw(b"enron TEXT {5}\r\n").unwrap(); 37 | buffer.clear(); 38 | client.read_logical_line(&mut buffer).unwrap(); 39 | assert!(buffer.starts_with(b"+ ")); 40 | 41 | client.write_raw(b"plugh\r\n").unwrap(); 42 | 43 | buffer.clear(); 44 | let mut responses = 45 | client.read_responses_until_tagged(&mut buffer).unwrap(); 46 | 47 | assert_tagged_ok(responses.pop().unwrap()); 48 | 49 | // Server must not get confused by literal text itself ending with 50 | // something that looks like a literal. 51 | client.write_raw(b"A2 SEARCH TEXT {3}\r\n").unwrap(); 52 | buffer.clear(); 53 | client.read_logical_line(&mut buffer).unwrap(); 54 | assert!(buffer.starts_with(b"+ ")); 55 | 56 | client.write_raw(b"{3} TEXT {5}\r\n").unwrap(); 57 | buffer.clear(); 58 | client.read_logical_line(&mut buffer).unwrap(); 59 | assert!(buffer.starts_with(b"+ ")); 60 | 61 | client.write_raw(b"{3}\r\n\r\n").unwrap(); 62 | 63 | buffer.clear(); 64 | let mut responses = 65 | client.read_responses_until_tagged(&mut buffer).unwrap(); 66 | 67 | assert_tagged_ok(responses.pop().unwrap()); 68 | 69 | // Ensure the connection state is still consistent 70 | ok_command!(client, c("NOOP")); 71 | } 72 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc3501/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod auth; 20 | mod bad_commands; 21 | mod fetch; 22 | mod first_contact; 23 | mod flags; 24 | mod literal; 25 | mod mailboxes; 26 | mod messages; 27 | mod search; 28 | mod select; 29 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc3691.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | #[test] 22 | fn capability_declared() { 23 | test_require_capability("3691capa", "UNSELECT"); 24 | } 25 | 26 | #[test] 27 | fn unselect() { 28 | let setup = set_up(); 29 | let mut client = setup.connect("3691unsl"); 30 | quick_log_in(&mut client); 31 | quick_create(&mut client, "3691unsl"); 32 | quick_append_enron(&mut client, "3691unsl", 2); 33 | quick_select(&mut client, "3691unsl"); 34 | 35 | ok_command!(client, c("STORE 1:* +FLAGS (\\Deleted)")); 36 | ok_command!(client, c("UNSELECT")); 37 | 38 | // Ensure we really did unselect 39 | command!([response] = client, c("UNSELECT")); 40 | unpack_cond_response! { 41 | (Some(_), s::RespCondType::Bad, _, _) = response 42 | }; 43 | 44 | command!([response] = client, c("EXPUNGE")); 45 | unpack_cond_response! { 46 | (Some(_), s::RespCondType::Bad, _, _) = response 47 | }; 48 | 49 | // When we SELECT again, the two messages should still exist 50 | command!(mut responses = client, c("SELECT 3691unsl")); 51 | assert_tagged_ok_any(responses.pop().unwrap()); 52 | has_untagged_response_matching! { 53 | s::Response::Exists(2) in responses 54 | }; 55 | 56 | // CLOSE will then expunge those two messages 57 | ok_command!(client, c("CLOSE")); 58 | 59 | command!(mut responses = client, c("SELECT 3691unsl")); 60 | assert_tagged_ok_any(responses.pop().unwrap()); 61 | has_untagged_response_matching! { 62 | s::Response::Exists(0) in responses 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc4959.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | #[test] 22 | fn capability_declared() { 23 | test_require_capability("4959capa", "SASL-IR"); 24 | } 25 | 26 | #[test] 27 | fn sasl_ir() { 28 | let setup = set_up(); 29 | let mut client = setup.connect("4959sair"); 30 | skip_greeting(&mut client); 31 | 32 | // azure\0azure\0hunter2 33 | client 34 | .write_raw(b"A1 AUTHENTICATE PLAIN YXp1cmUAYXp1cmUAaHVudGVyMg==\r\n") 35 | .unwrap(); 36 | 37 | let mut buffer = Vec::new(); 38 | let response = client.read_one_response(&mut buffer).unwrap(); 39 | unpack_cond_response! { 40 | (Some(_), s::RespCondType::Ok, _, _) = response => () 41 | }; 42 | 43 | // Make sure we actually logged in 44 | ok_command!(client, c("EXAMINE INBOX")); 45 | } 46 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc5161.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use super::defs::*; 22 | 23 | #[test] 24 | fn capability_declared() { 25 | test_require_capability("5161capa", "ENABLE"); 26 | } 27 | 28 | #[test] 29 | fn command_works() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("5161test"); 32 | 33 | // Try to enable non-existent extension 34 | command!(mut responses = client, c("ENABLE RANDOMLY-LOSE")); 35 | assert_tagged_ok(responses.pop().unwrap()); 36 | has_untagged_response_matching! { 37 | s::Response::Enabled(ref exts) in responses => { 38 | assert!(exts.is_empty()); 39 | } 40 | } 41 | 42 | // Try to enable extension which cannot be enabled 43 | command!(mut responses = client, c("ENABLE ENABLE")); 44 | assert_tagged_ok(responses.pop().unwrap()); 45 | has_untagged_response_matching! { 46 | s::Response::Enabled(ref exts) in responses => { 47 | assert!(exts.is_empty()); 48 | } 49 | } 50 | 51 | // Enable an extension which can be enabled 52 | command!(mut responses = client, c("ENABLE XYZZY")); 53 | assert_tagged_ok(responses.pop().unwrap()); 54 | has_untagged_response_matching! { 55 | s::Response::Enabled(ref exts) in responses => { 56 | assert_eq!(1, exts.len()); 57 | assert!(exts.contains(&Cow::Borrowed("XYZZY"))); 58 | } 59 | } 60 | 61 | // Mix valid and invalid extensions 62 | command!(mut responses = client, c("ENABLE XYZZY ENABLE RANDOMLY-LOSE")); 63 | assert_tagged_ok(responses.pop().unwrap()); 64 | has_untagged_response_matching! { 65 | s::Response::Enabled(ref exts) in responses => { 66 | assert_eq!(1, exts.len()); 67 | assert!(exts.contains(&Cow::Borrowed("XYZZY"))); 68 | } 69 | } 70 | 71 | command!(mut responses = client, c("ENABLE ENABLE XYZZY RANDOMLY-LOSE")); 72 | assert_tagged_ok(responses.pop().unwrap()); 73 | has_untagged_response_matching! { 74 | s::Response::Enabled(ref exts) in responses => { 75 | assert_eq!(1, exts.len()); 76 | assert!(exts.contains(&Cow::Borrowed("XYZZY"))); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc5819.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use super::defs::*; 22 | 23 | #[test] 24 | fn capability_declared() { 25 | test_require_capability("5819capa", "LIST-STATUS"); 26 | } 27 | 28 | #[test] 29 | fn test_list_status() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("5819lsst"); 32 | quick_log_in(&mut client); 33 | quick_create(&mut client, "5819lsst/foo"); 34 | quick_create(&mut client, "5819lsst/noselect/bar"); 35 | quick_append_enron(&mut client, "5819lsst/foo", 2); 36 | quick_append_enron(&mut client, "5819lsst/noselect/bar", 3); 37 | 38 | ok_command!(client, c("DELETE 5819lsst/noselect")); 39 | 40 | command!( 41 | responses = client, 42 | c("LIST 5819lsst/ * RETURN (STATUS (RECENT UIDNEXT))") 43 | ); 44 | assert_eq!(6, responses.len()); 45 | let mut responses = responses.into_iter(); 46 | 47 | // RETURN STATUS is highly order-sensitive, so manually verify each 48 | // response in sequence. 49 | match responses.next().unwrap() { 50 | s::ResponseLine { 51 | tag: None, 52 | response: s::Response::List(lr), 53 | } => assert_eq!("5819lsst/foo", lr.name.raw), 54 | r => panic!("Unexpected response: {:?}", r), 55 | } 56 | 57 | match responses.next().unwrap() { 58 | s::ResponseLine { 59 | tag: None, 60 | response: s::Response::Status(sr), 61 | } => { 62 | assert_eq!("5819lsst/foo", sr.mailbox.raw); 63 | assert!(sr.atts.contains(&s::StatusResponseAtt::Recent(2))); 64 | assert!(sr.atts.contains(&s::StatusResponseAtt::UidNext(3))); 65 | }, 66 | r => panic!("Unexpected response: {:?}", r), 67 | } 68 | 69 | match responses.next().unwrap() { 70 | s::ResponseLine { 71 | tag: None, 72 | response: s::Response::List(lr), 73 | } => { 74 | assert_eq!("5819lsst/noselect", lr.name.raw); 75 | assert!(lr.flags.contains(&Cow::Borrowed("\\Noselect"))); 76 | }, 77 | r => panic!("Unexpected response: {:?}", r), 78 | } 79 | 80 | match responses.next().unwrap() { 81 | s::ResponseLine { 82 | tag: None, 83 | response: s::Response::List(lr), 84 | } => assert_eq!("5819lsst/noselect/bar", lr.name.raw), 85 | r => panic!("Unexpected response: {:?}", r), 86 | } 87 | 88 | match responses.next().unwrap() { 89 | s::ResponseLine { 90 | tag: None, 91 | response: s::Response::Status(sr), 92 | } => { 93 | assert_eq!("5819lsst/noselect/bar", sr.mailbox.raw); 94 | assert!(sr.atts.contains(&s::StatusResponseAtt::Recent(3))); 95 | assert!(sr.atts.contains(&s::StatusResponseAtt::UidNext(4))); 96 | }, 97 | r => panic!("Unexpected response: {:?}", r), 98 | } 99 | 100 | assert_tagged_ok(responses.next().unwrap()); 101 | 102 | assert_bad_command( 103 | &mut client, 104 | Some(s::RespTextCode::ClientBug(())), 105 | "LIST \"\" * RETURN (STATUS (RECENT) STATUS (UNSEEN))", 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc6154.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | use crate::support::error::Error; 21 | 22 | #[test] 23 | fn capabilities_declared() { 24 | test_require_capability("6154capa", "SPECIAL-USE"); 25 | test_require_capability("6514capa", "CREATE-SPECIAL-USE"); 26 | } 27 | 28 | #[test] 29 | fn list_special_use() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("6154list"); 32 | quick_log_in(&mut client); 33 | quick_create(&mut client, "Spam2"); 34 | 35 | command!(mut responses = client, c("LIST (SPECIAL-USE) \"\" %")); 36 | assert_tagged_ok(responses.pop().unwrap()); 37 | assert_eq!( 38 | "Archive \\Archive\n\ 39 | Drafts \\Drafts\n\ 40 | Sent \\Sent\n\ 41 | Spam \\Junk\n\ 42 | Trash \\Trash\n", 43 | list_results_to_str(responses) 44 | ); 45 | 46 | command!(mut responses = client, c("LIST \"\" Spa% RETURN (SPECIAL-USE)")); 47 | assert_tagged_ok(responses.pop().unwrap()); 48 | assert_eq!( 49 | "Spam \\Junk\n\ 50 | Spam2\n", 51 | list_results_to_str(responses) 52 | ); 53 | } 54 | 55 | #[test] 56 | fn create_special_use() { 57 | let setup = set_up(); 58 | let mut client = setup.connect("6154crea"); 59 | quick_log_in(&mut client); 60 | 61 | ok_command!(client, c("CREATE 6154crea/flagged USE (\\Flagged)")); 62 | ok_command!(client, c("CREATE 6154crea/sub USE ()")); 63 | ok_command!(client, c("CREATE 6154crea/sub/spam USE (\\Junk)")); 64 | 65 | command!(mut responses = client, 66 | c("LIST \"\" 6154crea* RETURN (SPECIAL-USE)")); 67 | assert_tagged_ok(responses.pop().unwrap()); 68 | assert_eq!( 69 | "6154crea\n\ 70 | 6154crea/flagged \\Flagged\n\ 71 | 6154crea/sub\n\ 72 | 6154crea/sub/spam \\Junk\n", 73 | list_results_to_str(responses) 74 | ); 75 | 76 | command!(mut responses = client, 77 | c("LIST (SPECIAL-USE) \"\" 6154crea/%")); 78 | assert_tagged_ok(responses.pop().unwrap()); 79 | assert_eq!( 80 | "6154crea/flagged \\Flagged\n", 81 | list_results_to_str(responses) 82 | ); 83 | 84 | command!(mut responses = client, 85 | c("LIST (SPECIAL-USE RECURSIVEMATCH) \"\" 6154crea/%")); 86 | assert_tagged_ok(responses.pop().unwrap()); 87 | assert_eq!( 88 | "6154crea/flagged \\Flagged\n\ 89 | 6154crea/sub CHILDINFO SPECIAL-USE\n", 90 | list_results_to_str(responses) 91 | ); 92 | 93 | command!( 94 | [response] = client, 95 | c("CREATE 6154crea/bad USE (\\Unknown)") 96 | ); 97 | assert_error_response( 98 | response, 99 | Some(s::RespTextCode::UseAttr(())), 100 | Error::UnsupportedSpecialUse, 101 | ); 102 | 103 | command!( 104 | [response] = client, 105 | c("CREATE 6154crea/bad USE (\\Junk \\Sent)") 106 | ); 107 | assert_error_response( 108 | response, 109 | Some(s::RespTextCode::UseAttr(())), 110 | Error::UnsupportedSpecialUse, 111 | ); 112 | 113 | command!( 114 | [response] = client, 115 | c("CREATE 6154crea/bad USE (\\Subscribed)") 116 | ); 117 | assert_error_response( 118 | response, 119 | Some(s::RespTextCode::UseAttr(())), 120 | Error::UnsupportedSpecialUse, 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/bad.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | 21 | #[test] 22 | fn condstore_bad_usage() { 23 | let setup = set_up(); 24 | let mut client = setup.connect("7162bacs"); 25 | quick_log_in(&mut client); 26 | 27 | assert_bad_command( 28 | &mut client, 29 | Some(s::RespTextCode::ClientBug(())), 30 | "SELECT INBOX (CONDSTORE CONDSTORE)", 31 | ); 32 | 33 | quick_select(&mut client, "INBOX"); 34 | assert_bad_command( 35 | &mut client, 36 | Some(s::RespTextCode::ClientBug(())), 37 | "FETCH 1 UID (CHANGEDSINCE 1 CHANGEDSINCE 2)", 38 | ); 39 | } 40 | 41 | #[test] 42 | fn qresync_bad_usage() { 43 | let setup = set_up(); 44 | let mut client = setup.connect("7162baqr"); 45 | quick_log_in(&mut client); 46 | 47 | // Using QRESYNC without enabling it is forbidden 48 | assert_bad_command( 49 | &mut client, 50 | Some(s::RespTextCode::ClientBug(())), 51 | "SELECT INBOX (QRESYNC (1 2))", 52 | ); 53 | quick_select(&mut client, "INBOX"); 54 | assert_bad_command( 55 | &mut client, 56 | Some(s::RespTextCode::ClientBug(())), 57 | "UID FETCH 1 UID (CHANGEDSINCE 1 VANISHED)", 58 | ); 59 | 60 | // New client to get to known state and since, strictly speaking, ENABLE is 61 | // not allowed after selecting something 62 | let mut client = setup.connect("7162baqr"); 63 | quick_log_in(&mut client); 64 | ok_command!(client, c("ENABLE QRESYNC")); 65 | 66 | assert_bad_command( 67 | &mut client, 68 | Some(s::RespTextCode::Parse(())), 69 | "SELECT INBOX (QRESYNC (1 2 1:*))", 70 | ); 71 | assert_bad_command( 72 | &mut client, 73 | Some(s::RespTextCode::Parse(())), 74 | "SELECT INBOX (QRESYNC (1 2 :1))", 75 | ); 76 | assert_bad_command( 77 | &mut client, 78 | Some(s::RespTextCode::ClientBug(())), 79 | "SELECT INBOX (QRESYNC (1 2 (1:3 1:2)))", 80 | ); 81 | assert_bad_command( 82 | &mut client, 83 | Some(s::RespTextCode::ClientBug(())), 84 | "SELECT INBOX (QRESYNC (1 2) QRESYNC (1 2))", 85 | ); 86 | 87 | quick_select(&mut client, "INBOX"); 88 | 89 | assert_bad_command( 90 | &mut client, 91 | Some(s::RespTextCode::ClientBug(())), 92 | "UID FETCH 1 UID (VANISHED)", 93 | ); 94 | assert_bad_command( 95 | &mut client, 96 | Some(s::RespTextCode::ClientBug(())), 97 | "UID FETCH 1 UID (CHANGEDSINCE 1 VANISHED VANISHED)", 98 | ); 99 | assert_bad_command( 100 | &mut client, 101 | Some(s::RespTextCode::ClientBug(())), 102 | "FETCH 1 UID (CHANGEDSINCE 1 VANISHED)", 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/condstore_basics.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use super::super::defs::*; 22 | use super::extract_highest_modseq; 23 | 24 | #[test] 25 | fn capability_declared() { 26 | test_require_capability("7162cbcd", "CONDSTORE"); 27 | } 28 | 29 | #[test] 30 | fn condstore_enable() { 31 | let setup = set_up(); 32 | let mut client = setup.connect("7162cbce"); 33 | skip_greeting(&mut client); 34 | 35 | command!(mut responses = client, c("ENABLE CONDSTORE")); 36 | assert_eq!(2, responses.len()); 37 | assert_tagged_ok(responses.pop().unwrap()); 38 | has_untagged_response_matching! { 39 | s::Response::Enabled(ref what) in responses => { 40 | assert!(what.contains(&Cow::Borrowed("CONDSTORE"))); 41 | } 42 | }; 43 | } 44 | 45 | // Test that we get the primordial modseq of 1 when selecting an empty mailbox 46 | #[test] 47 | fn select_primordial() { 48 | let setup = set_up(); 49 | let mut client = setup.connect("7162cbsp"); 50 | quick_log_in(&mut client); 51 | quick_create(&mut client, "7162cbsp"); 52 | 53 | command!(mut responses = client, c("SELECT 7162cbsp (CONDSTORE)")); 54 | assert_tagged_ok_any(responses.pop().unwrap()); 55 | assert_eq!(1, extract_highest_modseq(&responses)); 56 | } 57 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/condstore_enable.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | use super::extract_highest_modseq; 21 | 22 | #[test] 23 | fn condstore_enable() { 24 | let setup = set_up(); 25 | let mut client = setup.connect("7162cece"); 26 | quick_log_in(&mut client); 27 | quick_create(&mut client, "7162cece"); 28 | quick_append_enron(&mut client, "7162cece", 3); 29 | 30 | // Follows the order of "CONDSTORE enabling commands" from page 7 of RFC 31 | // 7162. 32 | assert_enabled_by(&setup, "SELECT 7162cece (CONDSTORE)"); 33 | assert_enabled_by(&setup, "EXAMINE 7162cece (CONDSTORE)"); 34 | assert_enabled_by(&setup, "STATUS INBOX (HIGHESTMODSEQ)"); 35 | assert_enabled_by(&setup, "FETCH 1:* MODSEQ"); 36 | assert_enabled_by(&setup, "SEARCH MODSEQ 0"); 37 | assert_enabled_by(&setup, "FETCH 1:* UID (CHANGEDSINCE 0)"); 38 | assert_enabled_by( 39 | &setup, 40 | "STORE 1:* (UNCHANGEDSINCE 0) \ 41 | -FLAGS.SILENT (keyword)", 42 | ); 43 | assert_enabled_by(&setup, "ENABLE CONDSTORE"); 44 | } 45 | 46 | fn assert_enabled_by(setup: &Setup, command: &'static str) { 47 | let mut client = setup.connect("7162cece"); 48 | quick_log_in(&mut client); 49 | quick_select(&mut client, "7162cece"); 50 | 51 | command!(responses = client, c(command)); 52 | has_untagged_response_matching! { 53 | s::Response::Cond(s::CondResponse { 54 | code: Some(s::RespTextCode::HighestModseq(_)), 55 | .. 56 | }) in responses 57 | }; 58 | 59 | command!(responses = client, c("EXAMINE 7162cece")); 60 | has_untagged_response_matching! { 61 | s::Response::Cond(s::CondResponse { 62 | code: Some(s::RespTextCode::HighestModseq(_)), 63 | .. 64 | }) in responses 65 | }; 66 | } 67 | 68 | #[test] 69 | fn enable_primordial() { 70 | let setup = set_up(); 71 | let mut client = setup.connect("7162cesp"); 72 | quick_log_in(&mut client); 73 | quick_create(&mut client, "7162cesp"); 74 | quick_select(&mut client, "7162cesp"); 75 | 76 | command!(mut responses = client, c("ENABLE CONDSTORE")); 77 | assert_tagged_ok_any(responses.pop().unwrap()); 78 | assert_eq!(1, extract_highest_modseq(&responses)); 79 | } 80 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/condstore_fetch.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | use super::extract_highest_modseq; 21 | 22 | #[test] 23 | fn condstore_fetch() { 24 | let setup = set_up(); 25 | let mut client = setup.connect("7162cfcf"); 26 | quick_log_in(&mut client); 27 | quick_create(&mut client, "7162cfcf"); 28 | quick_append_enron(&mut client, "7162cfcf", 2); 29 | 30 | command!(mut responses = client, c("SELECT 7162cfcf (CONDSTORE)")); 31 | assert_tagged_ok_any(responses.pop().unwrap()); 32 | let max_modseq = extract_highest_modseq(&responses); 33 | 34 | command!(mut responses = client, c("FETCH 1:* UID (CHANGEDSINCE 0)")); 35 | assert_eq!(3, responses.len()); 36 | assert_tagged_ok(responses.pop().unwrap()); 37 | 38 | // CHANGEDSINCE implicitly includes MODSEQ 39 | has_untagged_response_matching! { 40 | s::Response::Fetch(ref fr @ s::FetchResponse { 41 | seqnum: 1, 42 | .. 43 | }) in responses => { 44 | has_msgatt_matching! { 45 | s::MsgAtt::Modseq(m) in fr => { 46 | assert!(m < max_modseq); 47 | } 48 | }; 49 | } 50 | }; 51 | has_untagged_response_matching! { 52 | s::Response::Fetch(ref fr @ s::FetchResponse { 53 | seqnum: 2, 54 | .. 55 | }) in responses => { 56 | has_msgatt_matching! { 57 | s::MsgAtt::Modseq(m) in fr => { 58 | assert_eq!(max_modseq, m); 59 | } 60 | }; 61 | } 62 | }; 63 | 64 | command!(mut responses = client, c("FETCH 1 MODSEQ")); 65 | assert_eq!(2, responses.len()); 66 | assert_tagged_ok(responses.pop().unwrap()); 67 | has_untagged_response_matching! { 68 | s::Response::Fetch(ref fr @ s::FetchResponse { 69 | seqnum: 1, 70 | .. 71 | }) in responses => { 72 | has_msgatt_matching! { 73 | s::MsgAtt::Modseq(m) in fr => { 74 | assert!(m < max_modseq); 75 | } 76 | }; 77 | } 78 | }; 79 | 80 | // CHANGEDSINCE equal to max_modseq excludes everything 81 | command!(mut responses = client, cb(&format!( 82 | "FETCH 1:* UID (CHANGEDSINCE {})", max_modseq 83 | ))); 84 | assert_eq!(1, responses.len()); 85 | assert_tagged_ok(responses.pop().unwrap()); 86 | 87 | ok_command!(client, c("STORE 1 +FLAGS (\\deleted)")); 88 | 89 | // Now that we changed message 1, it is returned with the same CHANGEDSINCE 90 | // filter 91 | command!(mut responses = client, cb(&format!( 92 | "FETCH 1:* UID (CHANGEDSINCE {})", max_modseq 93 | ))); 94 | assert_eq!(2, responses.len()); 95 | assert_tagged_ok(responses.pop().unwrap()); 96 | has_untagged_response_matching! { 97 | s::Response::Fetch(ref fr @ s::FetchResponse { 98 | seqnum: 1, 99 | .. 100 | }) in responses => { 101 | has_msgatt_matching! { 102 | s::MsgAtt::Modseq(m) in fr => { 103 | assert!(m > max_modseq); 104 | } 105 | }; 106 | } 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/condstore_search.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | use super::extract_highest_modseq; 21 | 22 | #[test] 23 | fn condstore_search() { 24 | let setup = set_up(); 25 | let mut client = setup.connect("7162cscs"); 26 | quick_log_in(&mut client); 27 | quick_create(&mut client, "7162cscs"); 28 | quick_append_enron(&mut client, "7162cscs", 2); 29 | 30 | command!(mut responses = client, c("SELECT 7162cscs (CONDSTORE)")); 31 | assert_tagged_ok_any(responses.pop().unwrap()); 32 | let max_modseq = extract_highest_modseq(&responses); 33 | 34 | // STORE doesn't emit an up-to-date HIGHESTMODSEQ since it isn't allowed to 35 | // send EXPUNGE responses 36 | ok_command!(client, c("STORE 1 +FLAGS (\\deleted)")); 37 | 38 | // NOOP lets us get the latest HIGHESTMODSEQ 39 | command!(responses = client, c("NOOP")); 40 | let new_modseq = extract_highest_modseq(&responses); 41 | 42 | fn search_test( 43 | client: &mut PipeClient, 44 | command: &str, 45 | expected_hits: &[u32], 46 | expected_max_modseq: Option, 47 | ) { 48 | command!(mut responses = client, cb(command)); 49 | assert_eq!(2, responses.len()); 50 | assert_tagged_ok(responses.pop().unwrap()); 51 | has_untagged_response_matching! { 52 | s::Response::Search(ref sr) in responses => { 53 | assert_eq!(expected_hits, &sr.hits[..]); 54 | assert_eq!(expected_max_modseq, sr.max_modseq); 55 | } 56 | }; 57 | } 58 | 59 | // Everything matches MODSEQ 0, and we implicitly get the max_modseq value. 60 | search_test(&mut client, "SEARCH MODSEQ 0", &[1, 2], Some(new_modseq)); 61 | 62 | // This also matches everything, since the modseq of 2 is equal to 63 | // max_modseq, and of 1 is greater than max_modseq 64 | search_test( 65 | &mut client, 66 | &format!("SEARCH MODSEQ {}", max_modseq), 67 | &[1, 2], 68 | Some(new_modseq), 69 | ); 70 | 71 | // Only message 1 has a modseq >= new_modseq 72 | search_test( 73 | &mut client, 74 | &format!("SEARCH MODSEQ {}", new_modseq), 75 | &[1], 76 | Some(new_modseq), 77 | ); 78 | 79 | // Nothing had a modseq > new_modseq 80 | search_test( 81 | &mut client, 82 | &format!("SEARCH MODSEQ {}", new_modseq + 1), 83 | &[], 84 | None, 85 | ); 86 | 87 | // The max_modseq return is taken from the returned messages, not the full 88 | // state 89 | search_test( 90 | &mut client, 91 | &format!("SEARCH NOT MODSEQ {}", new_modseq), 92 | &[2], 93 | Some(max_modseq), 94 | ); 95 | 96 | // The weird extension thing is parsed but ignored 97 | // We use "keyword" rather than "\Flagged" because otherwise the client 98 | // mis-formats the content (since using literals in place of this one 99 | // particular string is forbidden). 100 | search_test( 101 | &mut client, 102 | &format!(r#"SEARCH MODSEQ "/flags/keyword" all {}"#, new_modseq), 103 | &[1], 104 | Some(new_modseq), 105 | ); 106 | 107 | // MODSEQ is not returned if the query doesn't use it 108 | search_test(&mut client, "SEARCH DELETED", &[1], None); 109 | } 110 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/condstore_status.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::super::defs::*; 20 | 21 | #[test] 22 | fn condstore_status_highest_modseq() { 23 | let setup = set_up(); 24 | let mut client = setup.connect("7162cshm"); 25 | quick_log_in(&mut client); 26 | quick_create(&mut client, "7162cshm"); 27 | 28 | command!(mut responses = client, c("STATUS 7162cshm (HIGHESTMODSEQ)")); 29 | assert_tagged_ok_any(responses.pop().unwrap()); 30 | has_untagged_response_matching! { 31 | s::Response::Status(ref sr) in responses => { 32 | assert_eq!(1, sr.atts.len()); 33 | assert_eq!(s::StatusResponseAtt::HighestModseq(1), sr.atts[0]); 34 | } 35 | }; 36 | 37 | quick_append_enron(&mut client, "7162cshm", 1); 38 | 39 | command!(mut responses = client, c("STATUS 7162cshm (HIGHESTMODSEQ)")); 40 | assert_tagged_ok_any(responses.pop().unwrap()); 41 | has_untagged_response_matching! { 42 | s::Response::Status(ref sr) in responses => { 43 | assert_eq!(1, sr.atts.len()); 44 | match sr.atts[0] { 45 | s::StatusResponseAtt::HighestModseq(v) => assert!(v > 1), 46 | _ => panic!("Unexpected attribute"), 47 | } 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7162/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod bad; 20 | mod condstore_basics; 21 | mod condstore_enable; 22 | mod condstore_fetch; 23 | mod condstore_flags; 24 | mod condstore_search; 25 | mod condstore_status; 26 | mod qresync; 27 | 28 | use super::defs::*; 29 | 30 | fn extract_highest_modseq(responses: &[s::ResponseLine<'_>]) -> u64 { 31 | has_untagged_response_matching! { 32 | s::Response::Cond(s::CondResponse { 33 | code: Some(s::RespTextCode::HighestModseq(mm)), 34 | .. 35 | }) in responses => mm 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc7888.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Integration tests for the happy paths of RFC 7888. 20 | //! 21 | //! Note that some tests involving LITERAL+ handling in unusual situations is 22 | //! handled by `rfc3501::bad_commands` due to its lower-level nature. 23 | 24 | use super::defs::*; 25 | use crate::test_data; 26 | 27 | #[test] 28 | fn capability_declared() { 29 | test_require_capability("7888capa", "LITERAL+"); 30 | } 31 | 32 | #[test] 33 | fn command_non_synchronising_literal() { 34 | let setup = set_up(); 35 | let mut client = setup.connect("7888cnsl"); 36 | quick_log_in(&mut client); 37 | quick_select(&mut client, "INBOX"); 38 | 39 | client 40 | .write_raw( 41 | b"A1 SEARCH TEXT {5+}\r\n\ 42 | enron TEXT {5+}\r\n\ 43 | plugh\r\n", 44 | ) 45 | .unwrap(); 46 | let mut buffer = Vec::new(); 47 | let mut responses = 48 | client.read_responses_until_tagged(&mut buffer).unwrap(); 49 | assert_tagged_ok(responses.pop().unwrap()); 50 | 51 | // Server must not get confused by literal text itself ending with 52 | // something that looks like a literal. 53 | client 54 | .write_raw( 55 | b"A2 SEARCH TEXT {4+}\r\n\ 56 | {3+} TEXT {6+}\r\n\ 57 | {3+}\r\n\r\n", 58 | ) 59 | .unwrap(); 60 | buffer.clear(); 61 | let mut responses = 62 | client.read_responses_until_tagged(&mut buffer).unwrap(); 63 | assert_tagged_ok(responses.pop().unwrap()); 64 | 65 | // Ensure connection is still consistent 66 | ok_command!(client, c("NOOP")); 67 | } 68 | 69 | #[test] 70 | fn append_non_synchronising_literal() { 71 | let setup = set_up(); 72 | let mut client = setup.connect("7888ansl"); 73 | quick_log_in(&mut client); 74 | quick_create(&mut client, "7888ansl"); 75 | 76 | client 77 | .write_raw( 78 | format!( 79 | "A1 APPEND 7888ansl {{{}+}}\r\n", 80 | test_data::CHRISTMAS_TREE.len() 81 | ) 82 | .as_bytes(), 83 | ) 84 | .unwrap(); 85 | client.write_raw(test_data::CHRISTMAS_TREE).unwrap(); 86 | client.write_raw(b"\r\n").unwrap(); 87 | 88 | let mut buffer = Vec::new(); 89 | let mut responses = 90 | client.read_responses_until_tagged(&mut buffer).unwrap(); 91 | assert_tagged_ok_any(responses.pop().unwrap()); 92 | 93 | // Ensure connection is still consistent 94 | ok_command!(client, c("NOOP")); 95 | } 96 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc8438.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | #[test] 22 | fn capability_declared() { 23 | test_require_capability("8438capa", "STATUS=SIZE"); 24 | } 25 | 26 | #[test] 27 | fn size_is_reported_and_correct_lower_bound() { 28 | let setup = set_up(); 29 | let mut client = setup.connect("8438size"); 30 | quick_log_in(&mut client); 31 | examine_shared(&mut client); 32 | 33 | command!(mut responses = client, c("STATUS shared (SIZE)")); 34 | assert_tagged_ok_any(responses.pop().unwrap()); 35 | let approx_size = has_untagged_response_matching! { 36 | s::Response::Status(ref sr) in responses => { 37 | assert_eq!(1, sr.atts.len()); 38 | match sr.atts[0] { 39 | s::StatusResponseAtt::Size(size) => size, 40 | ref a => panic!("Unexpected attribute: {:?}", a), 41 | } 42 | } 43 | }; 44 | 45 | command!(mut responses = client, c("FETCH 1:* RFC822.SIZE")); 46 | assert_tagged_ok_any(responses.pop().unwrap()); 47 | let actual_size = responses 48 | .into_iter() 49 | .filter_map(|r| { 50 | if let s::ResponseLine { 51 | tag: None, 52 | response: s::Response::Fetch(fr), 53 | } = r 54 | { 55 | has_msgatt_matching! { 56 | s::MsgAtt::Rfc822Size(sz) in fr => Some(sz as u64) 57 | } 58 | } else { 59 | None 60 | } 61 | }) 62 | .sum::(); 63 | 64 | assert!(actual_size <= approx_size); 65 | } 66 | -------------------------------------------------------------------------------- /src/imap/integration_tests/rfc8514.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use chrono::prelude::*; 20 | 21 | use super::defs::*; 22 | 23 | #[test] 24 | fn capability_declared() { 25 | test_require_capability("8514capa", "SAVEDATE"); 26 | } 27 | 28 | #[test] 29 | fn fetch() { 30 | let setup = set_up(); 31 | let mut client = setup.connect("8514ftch"); 32 | quick_log_in(&mut client); 33 | quick_create(&mut client, "8514ftch"); 34 | quick_select(&mut client, "8514ftch"); 35 | quick_append_enron(&mut client, "8514ftch", 1); 36 | 37 | fetch_single!(client, c("FETCH 1 SAVEDATE"), ref fr => { 38 | has_msgatt_matching! { 39 | s::MsgAtt::SaveDate(Some(_)) in fr 40 | }; 41 | }); 42 | } 43 | 44 | #[test] 45 | fn search() { 46 | let setup = set_up(); 47 | let mut client = setup.connect("8514srch"); 48 | quick_log_in(&mut client); 49 | quick_create(&mut client, "8514srch"); 50 | quick_select(&mut client, "8514srch"); 51 | quick_append_enron(&mut client, "8514srch", 1); 52 | 53 | let today = Utc::now().date_naive(); 54 | let yesterday = today 55 | .checked_sub_days(chrono::Days::new(1)) 56 | .unwrap() 57 | .format("%d-%b-%Y") 58 | .to_string(); 59 | let tomorrow = today 60 | .checked_add_days(chrono::Days::new(1)) 61 | .unwrap() 62 | .format("%d-%b-%Y") 63 | .to_string(); 64 | let today = today.format("%d-%b-%Y").to_string(); 65 | 66 | let mut check = |expect: bool, s: &str| { 67 | command!(mut responses = client, cb(s)); 68 | assert_eq!(2, responses.len()); 69 | assert_tagged_ok(responses.pop().unwrap()); 70 | 71 | let hits = match responses.pop().unwrap() { 72 | s::ResponseLine { 73 | tag: None, 74 | response: s::Response::Search(v), 75 | } => v.hits, 76 | r => panic!("Unexpected response: {:?}", r), 77 | }; 78 | 79 | assert_eq!(expect, !hits.is_empty()); 80 | }; 81 | 82 | check(false, &format!("UID SEARCH SAVEDON {yesterday}")); 83 | check(true, &format!("UID SEARCH SAVEDON {today}")); 84 | check(false, &format!("UID SEARCH SAVEDON {tomorrow}")); 85 | 86 | check(false, &format!("UID SEARCH SAVEDBEFORE {yesterday}")); 87 | check(false, &format!("UID SEARCH SAVEDBEFORE {today}")); 88 | check(true, &format!("UID SEARCH SAVEDBEFORE {tomorrow}")); 89 | 90 | check(true, &format!("UID SEARCH SAVEDSINCE {yesterday}")); 91 | check(false, &format!("UID SEARCH SAVEDSINCE {today}")); 92 | check(false, &format!("UID SEARCH SAVEDSINCE {tomorrow}")); 93 | 94 | check(true, &format!("UID SEARCH SAVEDATESUPPORTED")); 95 | } 96 | -------------------------------------------------------------------------------- /src/imap/integration_tests/xlist.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use super::defs::*; 20 | 21 | fn xlist_results_to_str(lines: Vec>) -> String { 22 | let mut ret = String::new(); 23 | for line in lines { 24 | match line { 25 | s::ResponseLine { 26 | tag: None, 27 | response: 28 | s::Response::Xlist(s::MailboxList { 29 | mut flags, name, .. 30 | }), 31 | } => { 32 | flags.sort(); 33 | ret.push_str(&name.raw); 34 | for flag in flags { 35 | ret.push(' '); 36 | ret.push_str(&flag); 37 | } 38 | ret.push('\n'); 39 | }, 40 | 41 | line => panic!("Unexpected response line: {:?}", line), 42 | } 43 | } 44 | 45 | ret 46 | } 47 | 48 | #[test] 49 | fn capability_declared() { 50 | test_require_capability("XLSTcapa", "XLIST"); 51 | } 52 | 53 | #[test] 54 | fn xlist_results() { 55 | let setup = set_up(); 56 | let mut client = setup.connect("XLSTlist"); 57 | quick_log_in(&mut client); 58 | 59 | ok_command!(client, c("CREATE XLSTlist/flagged USE (\\Flagged)")); 60 | ok_command!(client, c("CREATE XLSTlist/flagged/child")); 61 | ok_command!(client, c("CREATE XLSTlist/sub/trash USE (\\Trash)")); 62 | 63 | command!(mut responses = client, c("XLIST \"\" XLSTlist/*")); 64 | assert_tagged_ok(responses.pop().unwrap()); 65 | assert_eq!( 66 | "XLSTlist/flagged \\Flagged \\HasChildren\n\ 67 | XLSTlist/flagged/child \\HasNoChildren\n\ 68 | XLSTlist/sub \\HasChildren\n\ 69 | XLSTlist/sub/trash \\HasNoChildren \\Trash\n", 70 | xlist_results_to_str(responses) 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/imap/literal_source.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::fmt; 20 | use std::io; 21 | 22 | /// A data source for literal items that aren't strings. 23 | /// 24 | /// This is often backed by a `BufferReader` but can take any `io::Read`. 25 | /// 26 | /// This struct has a `Clone` implementation so that it fits in with the stuff 27 | /// the macros in `syntax` generates, but the implementation always panics. 28 | /// 29 | /// For similar reasons, it has `PartialEq` and `Eq` implementations. These 30 | /// only compare the non-data fields. 31 | pub struct LiteralSource { 32 | /// The data for the literal. 33 | pub data: Box, 34 | /// The actual length of the literal. 35 | pub len: u64, 36 | /// Whether to use the binary syntax for the literal. 37 | pub binary: bool, 38 | } 39 | 40 | impl Clone for LiteralSource { 41 | fn clone(&self) -> Self { 42 | panic!("LiteralSource::clone") 43 | } 44 | } 45 | 46 | impl PartialEq for LiteralSource { 47 | fn eq(&self, other: &Self) -> bool { 48 | self.len == other.len && self.binary == other.binary 49 | } 50 | } 51 | 52 | impl Eq for LiteralSource {} 53 | 54 | impl fmt::Debug for LiteralSource { 55 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 56 | f.debug_struct("LiteralSource") 57 | .field("data", &"") 58 | .field("len", &self.len) 59 | .field("binary", &self.binary) 60 | .finish() 61 | } 62 | } 63 | 64 | impl LiteralSource { 65 | #[cfg(test)] 66 | pub fn of_data(data: &'static [u8], binary: bool) -> Self { 67 | LiteralSource { 68 | data: Box::new(data), 69 | len: data.len() as u64, 70 | binary, 71 | } 72 | } 73 | 74 | pub fn of_reader( 75 | reader: impl io::Read + 'static, 76 | len: u64, 77 | binary: bool, 78 | ) -> Self { 79 | LiteralSource { 80 | data: Box::new(reader), 81 | len, 82 | binary, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/imap/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | pub mod client; 20 | pub mod command_processor; 21 | mod lex; 22 | mod literal_source; 23 | mod mailbox_name; 24 | mod request_reader; 25 | mod response_writer; 26 | pub mod server; 27 | pub mod syntax; 28 | 29 | #[cfg(test)] 30 | mod integration_tests; 31 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | #![allow( 20 | clippy::collapsible_if, 21 | clippy::module_inception, 22 | clippy::type_complexity, 23 | clippy::too_many_arguments, 24 | clippy::needless_range_loop, 25 | clippy::needless_borrowed_reference, 26 | clippy::precedence 27 | )] 28 | #![deny(clippy::pattern_type_mismatch)] 29 | 30 | #[cfg(test)] 31 | macro_rules! assert_matches { 32 | ($expected:pat, $actual:expr $(,)*) => { 33 | match $actual { 34 | $expected => (), 35 | unexpected => panic!( 36 | "Expected {} matches {}, got {:?}", 37 | stringify!($expected), 38 | stringify!($actual), 39 | unexpected 40 | ), 41 | } 42 | }; 43 | } 44 | 45 | #[macro_use] 46 | mod support; 47 | 48 | mod account; 49 | mod cli; 50 | mod crypt; 51 | mod imap; 52 | mod mime; 53 | mod smtp; 54 | 55 | #[cfg(test)] 56 | mod test_data; 57 | 58 | fn main() { 59 | cli::main::main(); 60 | } 61 | 62 | #[cfg(test)] 63 | static INIT_TEST_LOG: std::sync::Once = std::sync::Once::new(); 64 | 65 | #[cfg(test)] 66 | fn init_test_log() { 67 | INIT_TEST_LOG.call_once(|| { 68 | if !std::env::var("TEST_LOG").ok().map_or(false, |v| "1" == v) { 69 | return; 70 | } 71 | 72 | init_simple_log(); 73 | }) 74 | } 75 | 76 | fn init_simple_log() { 77 | let stderr = log4rs::append::console::ConsoleAppender::builder() 78 | .target(log4rs::append::console::Target::Stderr) 79 | .encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new( 80 | "{d(%H:%M:%S%.3f)} [{l}][{t}] {m}{n}", 81 | ))) 82 | .build(); 83 | let log_config = log4rs::config::Config::builder() 84 | .appender( 85 | log4rs::config::Appender::builder() 86 | .build("stderr", Box::new(stderr)), 87 | ) 88 | .build( 89 | log4rs::config::Root::builder() 90 | .appender("stderr") 91 | .build(log::LevelFilter::Trace), 92 | ) 93 | .unwrap(); 94 | log4rs::init_config(log_config).unwrap(); 95 | } 96 | -------------------------------------------------------------------------------- /src/mime/dkim/error.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use thiserror::Error; 20 | 21 | /// Reasons a DKIM signature could not be validated. 22 | #[derive(Error, Debug)] 23 | pub enum Error { 24 | #[error("unexpected OpenSSL error: {0}")] 25 | Ssl(openssl::error::ErrorStack), 26 | #[error("unexpected I/O error: {0}")] 27 | Io(std::io::Error), 28 | #[error(transparent)] 29 | Fail(#[from] Failure), 30 | } 31 | 32 | impl std::cmp::PartialEq for Error { 33 | fn eq(&self, rhs: &Self) -> bool { 34 | match (self, rhs) { 35 | (&Self::Ssl(..) | &Self::Io(..), _) => false, 36 | (&Self::Fail(ref l), &Self::Fail(ref r)) => l == r, 37 | (&Self::Fail(..), _) => false, 38 | } 39 | } 40 | } 41 | 42 | #[derive(Clone, Debug, Error, PartialEq)] 43 | pub enum Failure { 44 | #[error("can't parse DKIM-Signature header: {0}")] 45 | HeaderParse(String), 46 | #[error("can't parse TXT record {0}: {1}")] 47 | DnsTxtParse(String, String), 48 | #[error("can't find TXT record {0}, or it is not DKIM1")] 49 | DnsTxtNotFound(String), 50 | #[error("DNS error fetching TXT record {0}")] 51 | DnsTxtError(String), 52 | #[error("unsupported DKIM version")] 53 | UnsupportedVersion, 54 | #[error("RSA key is too big to validate")] 55 | RsaKeyTooBig, 56 | #[error("valid signature, but hash function is weak")] 57 | WeakHashFunction, 58 | #[error("valid signature, but signing key is weak")] 59 | WeakKey, 60 | #[error("verification failed, but the selector is in test mode: {0}")] 61 | TestMode(Box), 62 | #[error("body is shorter than the l= tag indicates")] 63 | BodyTruncated, 64 | #[error("the computed body hash does not match the bh= tag")] 65 | BodyHashMismatch, 66 | #[error("the computed message hash does not match the signature")] 67 | SignatureMismatch, 68 | #[error("the public key was revoked")] 69 | PublicKeyRevoked, 70 | #[error("'From' field not signed")] 71 | FromFieldUnsigned, 72 | #[error("DKIM-Signature hash algorithm not allowed by TXT record")] 73 | UnacceptableHashAlgorithm, 74 | #[error("DKIM-Signature signature algorithm disagrees with TXT record")] 75 | SignatureAlgorithmMismatch, 76 | #[error("valid signature, but it has expired")] 77 | ExpiredSignature, 78 | #[error("valid signature, but the timestamp is in the future")] 79 | FutureSignature, 80 | #[error("public key is invalid")] 81 | InvalidPublicKey, 82 | #[error("invalid hash + signature algorithm combination")] 83 | InvalidHashSignatureCombination, 84 | #[error("SDID is not a valid domain")] 85 | InvalidSdid, 86 | #[error("AUID carries an invalid domain")] 87 | InvalidAuid, 88 | #[error("AUID is not within SDID zone")] 89 | AuidOutsideSdid, 90 | #[error("AUID is not the same as SDID, but the strict flag is set")] 91 | AuidSdidMismatch, 92 | } 93 | -------------------------------------------------------------------------------- /src/mime/dkim/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod canonicalisation; 20 | mod error; 21 | mod hash; 22 | mod header; 23 | mod sign; 24 | mod verify; 25 | 26 | #[cfg(test)] 27 | mod test_domain_keys; 28 | 29 | pub use canonicalisation::{ 30 | BodyCanonicalisation, BodyCanonicaliser, Canonicalisation, 31 | HeaderCanonicalisation, 32 | }; 33 | pub use error::*; 34 | #[allow(unused_imports)] 35 | pub use header::{ 36 | Algorithm, HashAlgorithm, Header, SignatureAlgorithm, TxtFlags, TxtRecord, 37 | HEADER_NAME, 38 | }; 39 | #[allow(unused_imports)] 40 | pub use sign::{KeyPair, Signer}; 41 | #[allow(unused_imports)] 42 | pub use verify::{Outcome, TxtRecordEntry, VerificationEnvironment, Verifier}; 43 | 44 | #[cfg(test)] 45 | fn split_message(message: &[u8]) -> (&[u8], &[u8]) { 46 | let blank_line = memchr::memmem::find(message, b"\r\n\r\n") 47 | .expect("no CRLF-CRLF in message"); 48 | (&message[..blank_line], &message[blank_line + 4..]) 49 | } 50 | -------------------------------------------------------------------------------- /src/mime/dkim/test-data/20230601._domainkey.gmail.com.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/20230601._domainkey.gmail.com.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/amazon-b.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/amazon-b.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/amazon-bh.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/amazon-bh.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/dkimproxy-b.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/dkimproxy-b.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/dkimproxy-bh.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/dkimproxy-bh.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/google-b.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/google-b.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/google-bh.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/google-bh.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/s2048._domainkey.yahoo.com.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/s2048._domainkey.yahoo.com.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/selector1._domainkey.lin.gl.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/selector1._domainkey.lin.gl.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/yahoo-b.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/yahoo-b.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/yahoo-bh.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/yahoo-bh.dat -------------------------------------------------------------------------------- /src/mime/dkim/test-data/yg4mwqurec7fkhzutopddd3ytuaqrvuz._domainkey.amazon.com.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/mime/dkim/test-data/yg4mwqurec7fkhzutopddd3ytuaqrvuz._domainkey.amazon.com.dat -------------------------------------------------------------------------------- /src/mime/dkim/test_domain_keys.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | /// selector1._domainkey.lin.gl, 2023-12-27 20 | pub static SELECTOR1_LIN_GL: &str = 21 | "v=DKIM1;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxCRVe\ 22 | M0ctOIvf0NRKs2bcYE3gXjfE9G0s+IY1Iw8cE/XAhisgUraQg5Vzv0d4La+\ 23 | SgQIJEm5XtkTeHFUgWIJM7ZXCI+WOi33+BRn9lwNe9TvoX+zYMCvTLFkEUF\ 24 | /tXihfg/8VcKMC1pc2Ik9bMh020XQUpPJkA/tduYJpq762n1gML0XhxaXHW\ 25 | 41Qzkxh2TlATzbBv4V0Lcm4/JXFS9psUB8Sm6TB8N5G5g1zpCQbsA9jFyt3\ 26 | G8VkzUJ4gFJpAqE9czME7BPtVEKHDOSVqA+sztfrUsVjxHoqRXEQR6nj99/\ 27 | uIPprEvjdJ1PyZQKaj9mWqnX7XZor0nGl1tNW+rmfKgIhSh+cRvt2hRbtTF\ 28 | nXL+q6efqK+CwfN5j8pyLkox+S7WITdGrTTXoqPiPSDkjfaJhNi9Uhd/Mbk\ 29 | xF854vDeAm8ZYIIsjwt1p+XIscDP8X7niUOrRuWcpElX+CRtqc2qi2atqAJ\ 30 | hMySZQbh8NW8XVI+EPDYbWA5/JFA5lrf16TuCoyN5uwfaiYTBzTXxlQHWUm\ 31 | sZN/tXkpbO6fHAmc7bvBZfKGMYpmDvKhNZMhmeQjDLkOaSb47AEQf7+weMi\ 32 | qsZEIUhKoQf0En6KNhVWBjezH8022dy7GkxP3Hek+ESxvbwSJHH5mby+TGS\ 33 | U6a+mRausK4Ji72JhXH4PvnEvtimECAwEAAQ==;s=email;t=s"; 34 | 35 | /// s2048._domainkey.yahoo.com, 2023-12-27 36 | pub static S2048_YAHOO_COM: &str = 37 | "k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoWufg\ 38 | bWw58MczUGbMv176RaxdZGOMkQmn8OOJ/HGoQ6dalSMWiLaj8IMcHC1cubJ\ 39 | x2gziAPQHVPtFYayyLA4ayJUSNk10/uqfByiU8qiPCE4JSFrpxflhMIKV4b\ 40 | t+g1uHw7wLzguCf4YAoR6XxUKRsAoHuoF7M+v6bMZ/X1G+viWHkBl4UfgJQ\ 41 | 6O8F1ckKKoZ5KqUkJH5pDaqbgs+F3PpyiAUQfB6EEzOA1KMPRWJGpzgPtKo\ 42 | ukDcQuKUw9GAul7kSIyEcizqrbaUKNLGAmz0elkqRnzIsVpz6jdT1/YV5Ri\ 43 | 6YUOQ5sN5bqNzZ8TxoQlkbVRy6eKOjUnoSSTmSAhwIDAQAB;"; 44 | 45 | /// yg4mwqurec7fkhzutopddd3ytuaqrvuz._domainkey.amazon.com, 2023-12-27 46 | pub static YG4_AMAZON_COM: &str = 47 | "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5bK96ORNNFosbAaVNZ\ 48 | U/gVzhANHyd00o1O7qbEeMNLKPNpS8/TYwdlrVnQ7JtJHjIR9EPj61jgtS6\ 49 | 04XpAltDMYvic2I40AaKgSfr4dDlRcALRtlVqmG7U5MdLiMyabxXPl2s/oq\ 50 | kevALySg0sr/defHC+qAhmdot9Ii/ZQ3YcQIDAQAB"; 51 | 52 | /// hsbnp7p3ensaochzwyq5wwmceodymuwv._domainkey.amazonses.com, 2023-12-28 53 | pub static HSG_AMAZONSES_COM: &str = 54 | "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvt2uMyV5G8KQJlwmiu\ 55 | 54Z6crNCpYlzuj1DVcajAd7PvEEUXxiT1lejc+D5ELrKnB4jpNN+4xmkvJQ\ 56 | 0sO0RySXJNbbNKUFYav+A4HgLPlqcNSSP5YYaejvsfBQYmvpiMA8+M+NAjy\ 57 | yMvm+5/23YFF8st4gJD3C19VGjlAJf/AxgQIDAQAB"; 58 | 59 | /// 20230601._domainkey.gmail.com, 2023-12-27 60 | pub static K20230601_GMAIL_COM: &str = 61 | "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCA\ 62 | QEAntvSKT1hkqhKe0xcaZ0x+QbouDsJuBfby/S82jxsoC/SodmfmVs2D1KA\ 63 | H3mi1AqdMdU12h2VfETeOJkgGYq5ljd996AJ7ud2SyOLQmlhaNHH7Lx+Mda\ 64 | b8/zDN1SdxPARDgcM7AsRECHwQ15R20FaKUABGu4NTbR2fDKnYwiq5jQyBk\ 65 | LWP+LgGOgfUF4T4HZb2PY2bQtEP6QeqOtcW4rrsH24L7XhD+HSZb1hsitrE\ 66 | 0VPbhJzxDwI4JF815XMnSVjZgYUXP8CxI1Y0FONlqtQYgsorZ9apoW1KPQe\ 67 | 8brSSlRsi9sXB/tu56LmG7tEDNmrZ5XUwQYUUADBOu7t1niwXwIDAQAB"; 68 | 69 | pub static RFC8463_BRISBANE: &str = 70 | "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaa\ 71 | PcHURo="; 72 | 73 | pub static RFC8463_TEST: &str = 74 | "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkH\ 75 | lOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx\ 76 | /byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4\ 77 | hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB"; 78 | -------------------------------------------------------------------------------- /src/mime/fetch/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Everything needed to implement the IMAP `FETCH` operation. 20 | 21 | pub mod bodystructure; 22 | pub mod envelope; 23 | pub mod multi; 24 | pub mod search; 25 | pub mod section; 26 | pub mod simple; 27 | 28 | mod strings; 29 | 30 | #[cfg(feature = "dev-tools")] 31 | pub mod zstd_train; 32 | -------------------------------------------------------------------------------- /src/mime/fetch/strings.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::borrow::Cow; 20 | 21 | use crate::mime::encoded_word; 22 | 23 | fn to_utf8(cow: Cow<[u8]>) -> Cow { 24 | match cow { 25 | Cow::Owned(owned) => Cow::Owned(match String::from_utf8(owned) { 26 | Ok(s) => s, 27 | Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(), 28 | }), 29 | Cow::Borrowed(borrowed) => String::from_utf8_lossy(borrowed), 30 | } 31 | } 32 | 33 | pub fn decode_atom(atom: Cow<[u8]>) -> String { 34 | to_utf8(atom).into_owned() 35 | } 36 | 37 | pub fn decode_phrase(phrase: Vec>) -> String { 38 | decode_sequence(phrase, b' ') 39 | } 40 | 41 | pub fn decode_dotted(phrase: Vec>) -> String { 42 | decode_sequence(phrase, b'.') 43 | } 44 | 45 | pub fn decode_routing(routing: Vec>>) -> String { 46 | let mut s = String::new(); 47 | for route in routing { 48 | if !s.is_empty() { 49 | s.push(','); 50 | } 51 | 52 | s.push('@'); 53 | s.push_str(&decode_dotted(route)); 54 | } 55 | 56 | s 57 | } 58 | 59 | fn decode_sequence(phrase: Vec>, delim: u8) -> String { 60 | if 1 == phrase.len() { 61 | decode_atom(phrase.into_iter().next().unwrap()) 62 | } else { 63 | let mut accum = Vec::new(); 64 | let mut first = true; 65 | for word in phrase { 66 | if !first { 67 | accum.push(delim); 68 | } 69 | first = false; 70 | 71 | match word { 72 | Cow::Owned(mut owned) => accum.append(&mut owned), 73 | Cow::Borrowed(borrowed) => accum.extend_from_slice(borrowed), 74 | } 75 | } 76 | 77 | to_utf8(Cow::Owned(accum)).into_owned() 78 | } 79 | } 80 | 81 | pub fn decode_unstructured(mut s: Cow<[u8]>) -> String { 82 | // Remove folding 83 | if memchr::memchr(b'\n', &s).is_some() { 84 | let mut unfolded = Vec::with_capacity(s.len()); 85 | let mut is_unfolding = false; 86 | for ch in s.iter().copied() { 87 | if is_unfolding { 88 | if b' ' == ch || b'\t' == ch || b'\r' == ch || b'\n' == ch { 89 | continue; 90 | } else { 91 | is_unfolding = false; 92 | unfolded.push(ch); 93 | } 94 | } else if b'\r' == ch || b'\n' == ch { 95 | unfolded.push(b' '); 96 | is_unfolding = true; 97 | } else { 98 | unfolded.push(ch); 99 | } 100 | } 101 | 102 | *s.to_mut() = unfolded; 103 | } 104 | 105 | let s = to_utf8(s); 106 | 107 | encoded_word::ew_decode_unstructured(s.trim()).into_owned() 108 | } 109 | -------------------------------------------------------------------------------- /src/mime/fetch/zstd_train.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Internal utility for fetching inputs from header blocks that can be used to 20 | //! train zstd. 21 | //! 22 | //! Basically, this will accumulate all headers in the header block. With a 23 | //! couple exceptions, the header values are replaced with a few random 24 | //! characters, both to prevent personal data from ending up in the dictionary, 25 | //! and to ensure the dictionary is generally applicable. (This does cause zstd 26 | //! to "learn" some of the random values, but this is OK since the dictionary 27 | //! also matches substrings. The random values are still needed to prevent it 28 | //! from "learning" things like both `From: \r\nTo: \r\n` and 29 | //! `To: \r\nFrom: \r\n`. 30 | 31 | use std::io::Write; 32 | 33 | use rand::{rngs::OsRng, Rng}; 34 | 35 | use crate::{mime::grovel, support::un64}; 36 | 37 | #[derive(Debug, Clone, Default)] 38 | pub struct ZstdTrainFetcher(Vec); 39 | 40 | impl grovel::Visitor for ZstdTrainFetcher { 41 | type Output = Vec; 42 | 43 | fn header( 44 | &mut self, 45 | raw: &[u8], 46 | name: &str, 47 | _value: &[u8], 48 | ) -> Result<(), Self::Output> { 49 | if name.eq_ignore_ascii_case("Content-Type") 50 | || name.eq_ignore_ascii_case("Content-Transfer-Encoding") 51 | || name.eq_ignore_ascii_case("Date") 52 | || name.eq_ignore_ascii_case("Precedence") 53 | || name.eq_ignore_ascii_case("MIME-Version") 54 | { 55 | self.0.extend_from_slice(raw); 56 | } else { 57 | let rand: [u8; 3] = OsRng.gen(); 58 | 59 | self.0.extend_from_slice(name.as_bytes()); 60 | self.0.extend_from_slice(b": "); 61 | self.0.extend_from_slice(base64::encode(&rand).as_bytes()); 62 | self.0.extend_from_slice(b"\r\n"); 63 | } 64 | Ok(()) 65 | } 66 | 67 | fn start_content(&mut self) -> Result<(), Self::Output> { 68 | Err(self.end()) 69 | } 70 | 71 | fn end(&mut self) -> Self::Output { 72 | // We need to feed what we've accumulated into un64 compression since 73 | // that's what zstd will actually see, and some header names are longer 74 | // to be recognised as "base64" and "decoded" by un64. 75 | let mut compressed = Vec::::with_capacity(self.0.len()); 76 | { 77 | let mut writer = un64::Writer::new(&mut compressed); 78 | writer 79 | .write_all(&self.0) 80 | .expect("writing to vec never fails"); 81 | writer.flush().expect("writing to vec never fails"); 82 | } 83 | self.0.clear(); 84 | compressed 85 | } 86 | 87 | fn visit_default(&mut self) -> Result<(), Self::Output> { 88 | Ok(()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/mime/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod content_encoding; 20 | pub mod dkim; 21 | pub mod encoded_word; 22 | pub mod fetch; 23 | pub mod grovel; 24 | pub mod header; 25 | mod quoted_printable; 26 | pub mod utf7; 27 | -------------------------------------------------------------------------------- /src/smtp/dmarc/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod psl; 20 | mod syntax; 21 | 22 | #[allow(unused_imports)] 23 | pub use psl::organisational_domain; 24 | #[allow(unused_imports)] 25 | pub use syntax::*; 26 | 27 | #[cfg(feature = "dev-tools")] 28 | pub use psl::cli::compile_psl; 29 | -------------------------------------------------------------------------------- /src/smtp/inbound/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod bridge; 20 | mod delivery; 21 | mod lmtp; 22 | mod server; 23 | mod smtpin; 24 | mod smtpsub; 25 | 26 | #[cfg(test)] 27 | mod integration_test_common; 28 | #[cfg(test)] 29 | mod lmtp_integration_tests; 30 | #[cfg(test)] 31 | mod smtpin_integration_tests; 32 | #[cfg(test)] 33 | mod smtpsub_integration_tests; 34 | 35 | pub use lmtp::serve_lmtp; 36 | pub use smtpin::serve_smtpin; 37 | pub use smtpsub::serve_smtpsub; 38 | -------------------------------------------------------------------------------- /src/smtp/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod codes; 20 | mod syntax; 21 | 22 | pub mod dmarc; 23 | pub mod inbound; 24 | pub mod outbound; 25 | pub mod spf; 26 | 27 | #[cfg(feature = "dev-tools")] 28 | pub use dmarc::compile_psl; 29 | -------------------------------------------------------------------------------- /src/smtp/outbound/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod send; 20 | mod serverseq; 21 | mod transact; 22 | mod transcript; 23 | 24 | pub use send::send_message; 25 | -------------------------------------------------------------------------------- /src/smtp/outbound/transcript.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::fmt; 20 | use std::io::{self, Write}; 21 | use std::sync::Arc; 22 | 23 | use chrono::prelude::*; 24 | 25 | use crate::{ 26 | account::model::CommonPaths, 27 | support::buffer::{BufferReader, BufferWriter}, 28 | }; 29 | 30 | pub struct Transcript { 31 | buffer: BufferWriter, 32 | last_entry: Option>, 33 | } 34 | 35 | impl Transcript { 36 | pub fn new(common_paths: Arc) -> Self { 37 | Self { 38 | buffer: BufferWriter::new(common_paths), 39 | last_entry: None, 40 | } 41 | } 42 | 43 | pub fn line(&mut self, args: fmt::Arguments<'_>) { 44 | let now = Utc::now(); 45 | let now_fmt = now.format("%Y-%m-%d %H:%M:%S"); 46 | if let Some(last_entry) = self.last_entry { 47 | let delta = 48 | now.signed_duration_since(last_entry).num_milliseconds(); 49 | let _ = writeln!(self.buffer, "{now_fmt} ({delta:+5}ms) {args}\r"); 50 | } else { 51 | let _ = writeln!(self.buffer, "{now_fmt} {args}\r"); 52 | } 53 | 54 | self.last_entry = Some(now); 55 | } 56 | 57 | pub fn finish(self) -> io::Result { 58 | self.buffer.flip() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/smtp/spf/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | mod driver; 20 | mod eval; 21 | mod syntax; 22 | 23 | #[allow(unused_imports)] 24 | pub use driver::run; 25 | #[allow(unused_imports)] 26 | pub use eval::{Context, Explanation, SpfResult}; 27 | -------------------------------------------------------------------------------- /src/support/append_limit.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | pub const APPEND_SIZE_LIMIT: u32 = 67108864; // 64 MB 20 | #[macro_export] 21 | macro_rules! concat_appendlimit { 22 | ($prefix:tt) => { 23 | concat!($prefix, "67108864") 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/support/chronox.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Helper traits which restore non-deprecated panicking methods (with 'x' 20 | //! appended to disambiguate) and non-panicking wrappers for things that are 21 | //! obviously infallible, since Chrono decided to make everything super noisy 22 | //! instead. 23 | 24 | use chrono::prelude::*; 25 | 26 | pub trait FixedOffsetX { 27 | fn zero() -> Self; 28 | fn eastx(secs: i32) -> Self; 29 | } 30 | 31 | pub trait NaiveDateX { 32 | fn from_ymdx(y: i32, m: u32, d: u32) -> Self; 33 | fn and_hmsx(&self, h: u32, m: u32, s: u32) -> NaiveDateTime; 34 | fn and_hmsx_utc(&self, h: u32, m: u32, s: u32) -> DateTime; 35 | } 36 | 37 | pub trait OffsetX { 38 | type DateTime; 39 | 40 | fn ymd_hmsx( 41 | &self, 42 | y: i32, 43 | m: u32, 44 | d: u32, 45 | h: u32, 46 | min: u32, 47 | s: u32, 48 | ) -> Self::DateTime; 49 | fn timestamp0(&self) -> Self::DateTime; 50 | } 51 | 52 | impl FixedOffsetX for FixedOffset { 53 | fn zero() -> Self { 54 | Self::eastx(0) 55 | } 56 | 57 | fn eastx(secs: i32) -> Self { 58 | Self::east_opt(secs).unwrap() 59 | } 60 | } 61 | 62 | impl NaiveDateX for NaiveDate { 63 | fn from_ymdx(y: i32, m: u32, d: u32) -> Self { 64 | Self::from_ymd_opt(y, m, d).unwrap() 65 | } 66 | 67 | fn and_hmsx(&self, h: u32, m: u32, s: u32) -> NaiveDateTime { 68 | self.and_hms_opt(h, m, s).unwrap() 69 | } 70 | 71 | fn and_hmsx_utc(&self, h: u32, m: u32, s: u32) -> DateTime { 72 | self.and_hmsx(h, m, s).and_utc() 73 | } 74 | } 75 | 76 | impl OffsetX for T { 77 | type DateTime = DateTime; 78 | 79 | fn timestamp0(&self) -> Self::DateTime { 80 | self.timestamp_millis_opt(0).unwrap() 81 | } 82 | 83 | fn ymd_hmsx( 84 | &self, 85 | y: i32, 86 | m: u32, 87 | d: u32, 88 | h: u32, 89 | min: u32, 90 | s: u32, 91 | ) -> Self::DateTime { 92 | self.with_ymd_and_hms(y, m, d, h, min, s).unwrap() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/support/diagnostic.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2022, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::path::Path; 20 | 21 | use log::error; 22 | 23 | use super::sysexits::*; 24 | use super::system_config::DiagnosticConfig; 25 | 26 | /// Apply the diagnostic configuration. 27 | /// 28 | /// On failure, an error message has already been logged, and the appropriate 29 | /// exit code is returned. 30 | pub fn apply_diagnostics( 31 | root: &Path, 32 | config: &DiagnosticConfig, 33 | ) -> Result<(), Sysexit> { 34 | if let Some(ref stderr_path) = config.stderr { 35 | redirect_stderr(&root.join(stderr_path))?; 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | fn redirect_stderr(stderr_path: &Path) -> Result<(), Sysexit> { 42 | if let Err(e) = nix::unistd::close(2) { 43 | error!("failed to redirect stderr: close(stderr): {:?}", e); 44 | return Err(EX_IOERR); 45 | } 46 | 47 | match nix::fcntl::open( 48 | stderr_path, 49 | nix::fcntl::OFlag::O_APPEND 50 | | nix::fcntl::OFlag::O_WRONLY 51 | | nix::fcntl::OFlag::O_CREAT, 52 | nix::sys::stat::Mode::from_bits(0o640).unwrap(), 53 | ) { 54 | Err(e) => { 55 | error!( 56 | "failed to redirect stderr: open({}): {:?}", 57 | stderr_path.display(), 58 | e, 59 | ); 60 | Err(EX_CANTCREAT) 61 | }, 62 | 63 | Ok(2) => Ok(()), 64 | Ok(fd) => { 65 | error!("failed to redirect stderr: got fd {} instead of 2", fd); 66 | Err(EX_OSERR) 67 | }, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/support/error.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2023, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::io; 20 | 21 | use thiserror::Error; 22 | 23 | #[derive(Error, Debug)] 24 | pub enum Error { 25 | #[error("Unsafe key or mailbox name")] 26 | UnsafeName, 27 | #[error("Master key unavailable")] 28 | MasterKeyUnavailable, 29 | #[error("Named key not found")] 30 | NamedKeyNotFound, 31 | #[error("Encrypted key malformed")] 32 | BadEncryptedKey, 33 | #[error("Mailbox full")] 34 | MailboxFull, 35 | #[error("Mailbox read-only")] 36 | MailboxReadOnly, 37 | #[error("Mailbox already exists")] 38 | MailboxExists, 39 | #[error("Mailbox has inferiors")] 40 | MailboxHasInferiors, 41 | #[error("Mailbox is not selectable")] 42 | MailboxUnselectable, 43 | #[error("Operation not allowed for INBOX")] 44 | BadOperationOnInbox, 45 | #[error("No such mailbox")] 46 | NxMailbox, 47 | #[error("Internal error: Mailbox ID out of range")] 48 | MailboxIdOutOfRange, 49 | #[error("Message expunged")] 50 | ExpungedMessage, 51 | #[error("Message not addressable by sequence number")] 52 | UnaddressableMessage, 53 | #[error("Non-existent message")] 54 | NxMessage, 55 | #[error("Unsupported/unknown flag")] 56 | NxFlag, 57 | #[error("Gave up atomic insertion after too many retries")] 58 | GaveUpInsertion, 59 | #[error("File/directory layout is corrupt")] 60 | CorruptFileLayout, 61 | #[error("Unsupported special-use for CREATE")] 62 | UnsupportedSpecialUse, 63 | #[error("Unknown mailbox attribute")] 64 | UnknownMailboxAttribute, 65 | #[error("Rename source and destination are the same")] 66 | RenameToSelf, 67 | #[error("Rename destination is child of self")] 68 | RenameIntoSelf, 69 | #[error("Move source and destination are the same")] 70 | MoveIntoSelf, 71 | #[error("Too many items in batch operation")] 72 | BatchTooBig, 73 | #[error("Unknown Content-Transfer-Encoding")] 74 | UnknownCte, 75 | #[error(transparent)] 76 | Io(#[from] io::Error), 77 | #[error(transparent)] 78 | Nix(#[from] nix::Error), 79 | #[error(transparent)] 80 | Ssl(#[from] openssl::error::ErrorStack), 81 | #[error(transparent)] 82 | Cbor(#[from] serde_cbor::error::Error), 83 | #[error(transparent)] 84 | Toml(#[from] toml::de::Error), 85 | #[error(transparent)] 86 | Rusqlite(#[from] rusqlite::Error), 87 | #[error("unexpected SQLite error: {0}")] 88 | Sqlite(std::os::raw::c_int), 89 | } 90 | -------------------------------------------------------------------------------- /src/support/log_prefix.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::fmt; 20 | use std::mem; 21 | use std::sync::{Arc, Mutex}; 22 | 23 | /// Tracks text that should be included in at the start of every log statement. 24 | /// 25 | /// Clones of a `LogPrefix` share the same underlying data. 26 | #[derive(Clone)] 27 | pub struct LogPrefix { 28 | inner: Arc>, 29 | } 30 | 31 | #[derive(Clone)] 32 | struct Inner { 33 | protocol: String, 34 | user: Option, 35 | helo: Option, 36 | ua_name: Option, 37 | ua_version: Option, 38 | } 39 | 40 | impl LogPrefix { 41 | pub fn new(protocol: String) -> Self { 42 | Self { 43 | inner: Arc::new(Mutex::new(Inner { 44 | protocol, 45 | user: None, 46 | helo: None, 47 | ua_name: None, 48 | ua_version: None, 49 | })), 50 | } 51 | } 52 | 53 | pub fn deep_clone(&self) -> Self { 54 | let inner = self.inner.lock().unwrap(); 55 | Self { 56 | inner: Arc::new(Mutex::new(Inner::clone(&inner))), 57 | } 58 | } 59 | 60 | pub fn set_user(&self, user: String) { 61 | self.inner.lock().unwrap().user = Some(sanitise(user)); 62 | } 63 | 64 | pub fn set_helo(&self, helo: String) { 65 | self.inner.lock().unwrap().helo = Some(sanitise(helo)); 66 | } 67 | 68 | pub fn set_user_agent( 69 | &self, 70 | name: Option, 71 | version: Option, 72 | ) { 73 | let mut inner = self.inner.lock().unwrap(); 74 | inner.ua_name = name.map(sanitise); 75 | inner.ua_version = version.map(sanitise); 76 | } 77 | } 78 | 79 | impl fmt::Display for LogPrefix { 80 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 81 | let inner = self.inner.lock().unwrap(); 82 | write!(f, "{}", inner.protocol)?; 83 | if inner.user.is_some() 84 | || inner.helo.is_some() 85 | || inner.ua_name.is_some() 86 | || inner.ua_version.is_some() 87 | { 88 | write!(f, "[")?; 89 | let mut first = true; 90 | if let Some(ref user) = inner.user { 91 | write!(f, "{user}")?; 92 | first = false; 93 | } 94 | 95 | if let Some(ref helo) = inner.helo { 96 | if !mem::take(&mut first) { 97 | write!(f, " ")?; 98 | } 99 | write!(f, "helo={helo}")?; 100 | } 101 | 102 | if inner.ua_name.is_some() || inner.ua_version.is_some() { 103 | if !mem::take(&mut first) { 104 | write!(f, " ")?; 105 | } 106 | write!( 107 | f, 108 | "agent={}/{}", 109 | inner.ua_name.as_deref().unwrap_or("unknown"), 110 | inner.ua_version.as_deref().unwrap_or("unknown"), 111 | )?; 112 | } 113 | write!(f, "]")?; 114 | } 115 | 116 | Ok(()) 117 | } 118 | } 119 | 120 | fn sanitise(mut s: String) -> String { 121 | s.retain(|c| !c.is_control()); 122 | if let Some((truncate_len, _)) = s.char_indices().nth(64) { 123 | s.truncate(truncate_len); 124 | } 125 | 126 | s 127 | } 128 | -------------------------------------------------------------------------------- /src/support/mod.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2022, 2023, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | #[macro_use] 20 | pub mod append_limit; 21 | pub mod async_io; 22 | pub mod buffer; 23 | pub mod chronox; 24 | pub mod compression; 25 | pub mod diagnostic; 26 | pub mod dns; 27 | pub mod error; 28 | pub mod file_ops; 29 | pub mod log_prefix; 30 | pub mod mailbox_paths; 31 | pub mod rcio; 32 | pub mod safe_name; 33 | pub mod small_bitset; 34 | pub mod sysexits; 35 | pub mod system_config; 36 | pub mod un64; 37 | pub mod unix_privileges; 38 | pub mod user_config; 39 | -------------------------------------------------------------------------------- /src/support/rcio.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use std::cell::RefCell; 20 | use std::io::{self, Read, Write}; 21 | use std::rc::Rc; 22 | 23 | /// Wraps a type in a `Rc>` and provides the `std::io` traits for it 24 | /// through simple delegation. 25 | /// 26 | /// This is used to take a single bidirectional stream and split it into 27 | /// separate read and write parts (since, e.g., the read side often needs to be 28 | /// wrapped in a `BufReader`). 29 | #[derive(Debug)] 30 | pub struct RcIo(Rc>); 31 | 32 | impl RcIo { 33 | pub fn wrap(inner: T) -> Self { 34 | RcIo(Rc::new(RefCell::new(inner))) 35 | } 36 | } 37 | 38 | impl Clone for RcIo { 39 | fn clone(&self) -> Self { 40 | RcIo(Rc::clone(&self.0)) 41 | } 42 | } 43 | 44 | impl Read for RcIo { 45 | fn read(&mut self, dst: &mut [u8]) -> io::Result { 46 | self.0.borrow_mut().read(dst) 47 | } 48 | } 49 | 50 | impl Write for RcIo { 51 | fn write(&mut self, src: &[u8]) -> io::Result { 52 | self.0.borrow_mut().write(src) 53 | } 54 | 55 | fn flush(&mut self) -> io::Result<()> { 56 | self.0.borrow_mut().flush() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/support/safe_name.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | /// Determine whether the given name is "safe". 20 | /// 21 | /// This is used to validate the names of items which are used as file system 22 | /// elements. It excludes empty names and patterns that cause directory 23 | /// traversal or other unwanted behaviours, as well as things that have special 24 | /// meaning within IMAP. 25 | /// 26 | /// This does not care about whether the name is ultimately a valid file name; 27 | /// for that, we simply rely on the OS rejecting it. It also does not check for 28 | /// the MS-DOS device names; if we ever want first-class Windows support, 29 | /// something will need to be done there (probably involving use of `\\?\` or 30 | /// whatever it is to opt out of those weirdnesses, as well as things like ¥ 31 | /// being a path separator on Shift-JIS systems). 32 | pub fn is_safe_name(name: &str) -> bool { 33 | !name.is_empty() && 34 | // Block directory traversal through .. and creation of hidden files on 35 | // UNIX 36 | !name.starts_with('.') && 37 | // Names beginning with # have special meaning in IMAP 38 | !name.starts_with('#') && 39 | !name.chars().any(is_forbidden_char) 40 | } 41 | 42 | fn is_forbidden_char(ch: char) -> bool { 43 | match ch { 44 | // '/' is the hierarchy delimiter 45 | '/' | 46 | // Only a path separator on Windows, but always block since it has high 47 | // potential of causing problems 48 | '\\' | 49 | // Don't allow any ASCII control characters 50 | '\0'..='\x1F' | '\x7F' | 51 | // * and % are very special in *some* IMAP contexts, so forbid 52 | // everywhere 53 | '*' | '%' | 54 | // RFC 5198 forbids C1 control characters 55 | '\u{80}'..='\u{9F}' | 56 | // RFC 6855 forbids the Unicode LINE SEPARATOR and PARAGRAPH SEPARATOR 57 | // characters 58 | '\u{2028}' | '\u{2029}' => true, 59 | _ => false, 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod test { 65 | use super::is_safe_name; 66 | 67 | #[test] 68 | fn test_is_safe_name() { 69 | assert!(is_safe_name("foo")); 70 | assert!(is_safe_name("PRN")); 71 | assert!(is_safe_name("Entwürfe")); 72 | assert!(is_safe_name("郵便")); 73 | assert!(is_safe_name("foo.bar")); 74 | assert!(is_safe_name("folder #1")); 75 | assert!(!is_safe_name(".")); 76 | assert!(!is_safe_name("..")); 77 | assert!(!is_safe_name(".hidden")); 78 | assert!(!is_safe_name("foo/bar")); 79 | assert!(!is_safe_name("/foo")); 80 | assert!(!is_safe_name("foo/")); 81 | assert!(!is_safe_name("foo\\bar")); 82 | assert!(!is_safe_name("#news")); 83 | assert!(!is_safe_name("foo\0")); 84 | assert!(!is_safe_name("foo\r")); 85 | assert!(!is_safe_name("fo\x7Fo")); 86 | assert!(!is_safe_name("foo*bar")); 87 | assert!(!is_safe_name("foo%bar")); 88 | assert!(!is_safe_name("")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/support/sysexits.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | //! Constants from `sysexits.h` 20 | //! 21 | //! Relevant for things that use the sendmail/procmail/etc conventions for MTA 22 | //! exit codes. 23 | #![allow(dead_code)] 24 | 25 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 26 | pub struct Sysexit(pub i32); 27 | 28 | pub const EX_USAGE: Sysexit = Sysexit(64); 29 | pub const EX_DATAERR: Sysexit = Sysexit(65); 30 | pub const EX_NOINPUT: Sysexit = Sysexit(66); 31 | pub const EX_NOUSER: Sysexit = Sysexit(67); 32 | pub const EX_NOHOST: Sysexit = Sysexit(68); 33 | pub const EX_UNAVAILABLE: Sysexit = Sysexit(69); 34 | pub const EX_SOFTWARE: Sysexit = Sysexit(70); 35 | pub const EX_OSERR: Sysexit = Sysexit(71); 36 | pub const EX_OSFILE: Sysexit = Sysexit(72); 37 | pub const EX_CANTCREAT: Sysexit = Sysexit(73); 38 | pub const EX_IOERR: Sysexit = Sysexit(74); 39 | pub const EX_TEMPFAIL: Sysexit = Sysexit(75); 40 | pub const EX_PROTOCOL: Sysexit = Sysexit(76); 41 | pub const EX_NOPERM: Sysexit = Sysexit(77); 42 | pub const EX_CONFIG: Sysexit = Sysexit(78); 43 | 44 | impl Sysexit { 45 | pub fn exit(self) -> ! { 46 | std::process::exit(self.0) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/support/user_config.rs: -------------------------------------------------------------------------------- 1 | //- 2 | // Copyright (c) 2020, 2024, Jason Lingle 3 | // 4 | // This file is part of Crymap. 5 | // 6 | // Crymap is free software: you can redistribute it and/or modify it under the 7 | // terms of the GNU General Public License as published by the Free Software 8 | // Foundation, either version 3 of the License, or (at your option) any later 9 | // version. 10 | // 11 | // Crymap is distributed in the hope that it will be useful, but WITHOUT ANY 12 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | // details. 15 | // 16 | // You should have received a copy of the GNU General Public License along with 17 | // Crymap. If not, see . 18 | 19 | use serde::{Deserialize, Serialize}; 20 | 21 | use crate::account::key_store::KeyStoreConfig; 22 | use crate::crypt::master_key::MasterKeyConfig; 23 | 24 | #[allow(clippy::ptr_arg)] 25 | pub mod b64 { 26 | use serde::{Deserialize, Deserializer, Serializer}; 27 | 28 | pub fn serialize( 29 | bytes: &Vec, 30 | ser: S, 31 | ) -> Result { 32 | ser.serialize_str(&base64::encode(bytes)) 33 | } 34 | 35 | pub fn deserialize<'a, D: Deserializer<'a>>( 36 | de: D, 37 | ) -> Result, D::Error> { 38 | use serde::de::Error; 39 | String::deserialize(de).and_then(|s| { 40 | base64::decode(s).map_err(|err| Error::custom(err.to_string())) 41 | }) 42 | } 43 | } 44 | 45 | /// The user configuration. 46 | /// 47 | /// This is the root of the TOML file stored in "config.toml" at the root of 48 | /// the user directory. 49 | /// 50 | /// Everything inside here is assumed to be mutable by the user. 51 | #[derive(Serialize, Deserialize, Debug, Clone)] 52 | pub struct UserConfig { 53 | pub master_key: MasterKeyConfig, 54 | pub key_store: KeyStoreConfig, 55 | #[serde(default)] 56 | pub smtp_out: SmtpOutConfig, 57 | } 58 | 59 | #[derive(Serialize, Deserialize, Debug, Clone, Default)] 60 | pub struct SmtpOutConfig { 61 | /// If set, save messages sent through outbound SMTP in this mailbox. 62 | pub save: Option, 63 | /// If set, save receipts of successfully sent outbound messages in this 64 | /// mailbox. 65 | pub success_receipts: Option, 66 | /// If set, deliver receipts of unsuccessfully sent outbound messages in 67 | /// this mailbox instead of `INBOX`. 68 | pub failure_receipts: Option, 69 | } 70 | -------------------------------------------------------------------------------- /src/support/zstd-dict-20200725.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AltSysrq/crymap/e04d2824bb1f0924799c7021e9bbb8475c35a0f4/src/support/zstd-dict-20200725.dat -------------------------------------------------------------------------------- /src/test_data/christmas-tree.eml: -------------------------------------------------------------------------------- 1 | Remark: This is a message with every interesting header set. The headers are 2 | not necessarily set to *interesting* or unusual values, but they are 3 | distinct. 4 | From: from@example.com 5 | To: A Mäiling List: "Töm Orwell" ; 6 | Cc: cc@example.com 7 | Bcc: bcc@example.com 8 | Sender: sender@example.com 9 | Reply-To: reply-to@example.com 10 | Date: Fri, 21 Nov 1997 09:55:06 -0600 11 | Subject: This is the subject 12 | XYZZY: Nothing happens 13 | Message-ID: 14 | Content-ID: 15 | In-Reply-To: 16 | References: , 17 | Content-Type: text/rich; charset=utf-8; richness=low 18 | Content-Disposition: attachment; name="foo.txt" 19 | Content-Language: en 20 | Content-Location: /foo/bar 21 | Content-Description: A test message 22 | Content-Transfer-Encoding: 8bit 23 | 24 | That is not dead which can eternal lie. 25 | And with strange æons even death may die. 26 | -------------------------------------------------------------------------------- /src/test_data/dovecot-prefer-standalone-daemons.eml: -------------------------------------------------------------------------------- 1 | Received: with ECARTIS (v1.0.0; list dovecot); Wed, 26 Feb 2003 10:58:31 +0200 (EET) 2 | Return-Path: 3 | X-Original-To: dovecot@procontrol.fi 4 | Delivered-To: dovecot@procontrol.fi 5 | Received: from mail1.bppiac.hu (blue.bppiac.hu [212.108.197.67]) 6 | by danu.procontrol.fi (Postfix) with ESMTP id 75EC32384D 7 | for ; Wed, 26 Feb 2003 10:58:31 +0200 (EET) 8 | Received: from mail2.bppiac.hu (portal.bppiac.hu [194.143.224.170]) 9 | by mail1.bppiac.hu (Postfix) with ESMTP id 4E3AA75401D 10 | for ; Wed, 26 Feb 2003 09:59:03 +0100 (CET) 11 | Received: from portal.bppiac.hu (localhost [127.0.0.1]) 12 | by portal.bppiac.hu (Postfix) with SMTP id 4B1A63EF2 13 | for ; Wed, 26 Feb 2003 09:58:29 +0100 (CET) 14 | Message-ID: <3E5C81BC.6090100@bnap.hu> 15 | Date: Wed, 26 Feb 2003 09:58:36 +0100 16 | From: Farkas Levente 17 | User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2) Gecko/20021202 18 | X-Accept-Language: en-us, en 19 | MIME-Version: 1.0 20 | To: dovecot@procontrol.fi 21 | Subject: [dovecot] Re: inetd/xinetd/tcpserver support 22 | References: <1046224359.30815.36.camel@hurina> 23 | In-Reply-To: <1046224359.30815.36.camel@hurina> 24 | Content-Type: text/plain; charset=us-ascii; format=flowed 25 | Content-Transfer-Encoding: 7bit 26 | X-archive-position: 325 27 | X-ecartis-version: Ecartis v1.0.0 28 | Sender: dovecot-bounce@procontrol.fi 29 | Errors-to: dovecot-bounce@procontrol.fi 30 | X-original-sender: lfarkas@bnap.hu 31 | Precedence: bulk 32 | X-list: dovecot 33 | X-UID: 325 34 | Status: O 35 | Content-Length: 1392 36 | 37 | I always prefer standalone daemons, and as we see the tendency is that 38 | most server run as standalone (apache, vsftpd, ssh...). at the begining 39 | they has (x)inetd version later remove it... 40 | IMHO ip/tcp filtering should have done in a firewall or some fitering 41 | can be implemented in the standalone server too.. 42 | but this is just my 2c:-) 43 | 44 | Timo Sirainen wrote: 45 | > I was just thinking how they could be easily supported. This would work, 46 | > right? : 47 | > 48 | > imap stream tcp nowait root /usr/sbin/tcpd /usr/local/libexec/dovecot/imap-login 49 | > imaps stream tcp nowait root /usr/sbin/tcpd /usr/local/libexec/dovecot/imap-login --ssl 50 | > 51 | > imap-login would try to connect to master process using some named 52 | > socket. If it couldn't, it would create the master process itself. 53 | > Master process would work as usual (executes auth and imap processes), 54 | > except it wouldn't be executing login processes. 55 | > 56 | > This wouldn't require much code changing, and it would still be using 57 | > all the same privilege separations as the standalone version so it would 58 | > be just a secure. 59 | > 60 | > Only thing I'm wondering is if any of the TCP wrappers care about the 61 | > created child processes? The master process would have to stay alive 62 | > after the connection that created it dies. 63 | > 64 | > I guess I'll implement this soon and try if it works. 65 | > 66 | > 67 | > 68 | 69 | 70 | -- 71 | Levente "Si vis pacem para bellum!" 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/test_data/enron_dasovich-j_all_documents_11634.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <2078948.1075843446892.JavaMail.evans@thyme> 2 | Date: Wed, 25 Apr 2001 14:59:00 -0700 (PDT) 3 | From: asama@yahoo.com 4 | To: dasovic@enron.com, dasovich@haas.berkeley.edu 5 | Subject: Fwd: failure delivery 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=ANSI_X3.4-1968 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Anil Sama 10 | X-To: dasovic@enron.com, dasovich@haas.berkeley.edu 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Jeff_Dasovich_June2001\Notes Folders\All documents 14 | X-Origin: DASOVICH-J 15 | X-FileName: jdasovic.nsf 16 | 17 | ? MAILER-DAEMON@yahoo.com wrote: 18 | Date: 26 Apr 2001 04:45:38 -0000 19 | From: MAILER-DAEMON@yahoo.com 20 | To: asama@yahoo.com 21 | Subject: failure delivery 22 | 23 | Message from yahoo.com. 24 | Unable to deliver message to the following address(es). 25 | 26 | : 27 | 128.32.67.113 does not like recipient. 28 | Remote host said: 550 5.1.1 ... User unknown 29 | Giving up on 128.32.67.113. 30 | 31 | --- Original message follows. 32 | 33 | Return-Path: 34 | Message-ID: <20010426044537.10403.qmail@web11602.mail.yahoo.com> 35 | Received: from [132.233.247.7] by web11602.mail.yahoo.com; Wed, 25 Apr 2001 36 | 21:45:37 PDT 37 | Date: Wed, 25 Apr 2001 21:45:37 -0700 (PDT) 38 | From: Anil Sama 39 | Subject: Send me your files... 40 | To: vavrek@haas.berkeley.edu, dasovish@haas.berkeley.edu 41 | MIME-Version: 1.0 42 | Content-Type: multipart/alternative; boundary="0-28447961-988260! 337=:7976" 43 | 44 | --0-28447961-988260337=:7976 45 | Content-Type: text/plain; charset=us-ascii 46 | 47 | 48 | Jeff, Carolyn: 49 | 50 | Waiting on files from you as discussed this afternoon. I'll 51 | incorporate and send out the prezo to all later tonight... 52 | 53 | -Anil 54 | 55 | 56 | 57 | --------------------------------- 58 | Do You Yahoo!? 59 | Yahoo! Auctions - buy the things you want at great prices 60 | --0-28447961-988260337=:7976 61 | Content-Type: text/html; charset=us-ascii 62 | 63 | 64 | Jeff, Carolyn: 65 | 66 | 67 | Waiting on files from you as discussed this afternoon. I'll 68 | incorporate and send out the prezo to all later tonight... 69 | 70 | 71 | -Anil 72 | 73 | 74 | 75 | 76 | 77 | Do You Yahoo!? 78 | 79 | Yahoo! Auctions - buy the things you want at great prices 80 | --0-28447961-988260337=:7976-- 81 | 82 | 83 | 84 | Do You Yahoo!? 85 | Yahoo! Auctions - buy the things you want at great prices -------------------------------------------------------------------------------- /src/test_data/enron_dasovich-j_notes_inbox_3944.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <18202009.1075843686080.JavaMail.evans@thyme> 2 | Date: Wed, 25 Apr 2001 14:59:00 -0700 (PDT) 3 | From: asama@yahoo.com 4 | To: dasovic@enron.com, dasovich@haas.berkeley.edu 5 | Subject: Fwd: failure delivery 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=ANSI_X3.4-1968 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Anil Sama 10 | X-To: dasovic@enron.com, dasovich@haas.berkeley.edu 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Jeff_Dasovich_June2001\Notes Folders\Notes inbox 14 | X-Origin: DASOVICH-J 15 | X-FileName: jdasovic.nsf 16 | 17 | ? MAILER-DAEMON@yahoo.com wrote: 18 | Date: 26 Apr 2001 04:45:38 -0000 19 | From: MAILER-DAEMON@yahoo.com 20 | To: asama@yahoo.com 21 | Subject: failure delivery 22 | 23 | Message from yahoo.com. 24 | Unable to deliver message to the following address(es). 25 | 26 | : 27 | 128.32.67.113 does not like recipient. 28 | Remote host said: 550 5.1.1 ... User unknown 29 | Giving up on 128.32.67.113. 30 | 31 | --- Original message follows. 32 | 33 | Return-Path: 34 | Message-ID: <20010426044537.10403.qmail@web11602.mail.yahoo.com> 35 | Received: from [132.233.247.7] by web11602.mail.yahoo.com; Wed, 25 Apr 2001 36 | 21:45:37 PDT 37 | Date: Wed, 25 Apr 2001 21:45:37 -0700 (PDT) 38 | From: Anil Sama 39 | Subject: Send me your files... 40 | To: vavrek@haas.berkeley.edu, dasovish@haas.berkeley.edu 41 | MIME-Version: 1.0 42 | Content-Type: multipart/alternative; boundary="0-28447961-988260! 337=:7976" 43 | 44 | --0-28447961-988260337=:7976 45 | Content-Type: text/plain; charset=us-ascii 46 | 47 | 48 | Jeff, Carolyn: 49 | 50 | Waiting on files from you as discussed this afternoon. I'll 51 | incorporate and send out the prezo to all later tonight... 52 | 53 | -Anil 54 | 55 | 56 | 57 | --------------------------------- 58 | Do You Yahoo!? 59 | Yahoo! Auctions - buy the things you want at great prices 60 | --0-28447961-988260337=:7976 61 | Content-Type: text/html; charset=us-ascii 62 | 63 | 64 | Jeff, Carolyn: 65 | 66 | 67 | Waiting on files from you as discussed this afternoon. I'll 68 | incorporate and send out the prezo to all later tonight... 69 | 70 | 71 | -Anil 72 | 73 | 74 | 75 | 76 | 77 | Do You Yahoo!? 78 | 79 | Yahoo! Auctions - buy the things you want at great prices 80 | --0-28447961-988260337=:7976-- 81 | 82 | 83 | 84 | Do You Yahoo!? 85 | Yahoo! Auctions - buy the things you want at great prices -------------------------------------------------------------------------------- /src/test_data/enron_farmer-d_all_documents_3128.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <32990431.1075854190718.JavaMail.evans@thyme> 2 | Date: Mon, 2 Apr 2001 04:47:00 -0700 (PDT) 3 | From: tom.acton@enron.com 4 | To: daren.farmer@enron.com 5 | Subject: Entex apr 3 noms 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Tom Acton 10 | X-To: Daren J Farmer 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Darren_Farmer_Jun2001\Notes Folders\All documents 14 | X-Origin: Farmer-D 15 | X-FileName: dfarmer.nsf 16 | 17 | ---------------------- Forwarded by Tom Acton/Corp/Enron on 04/02/2001 11:45 18 | AM --------------------------- 19 | 20 | 21 | ronald_s_fancher@reliantenergy.com on 04/02/2001 11:32:33 AM 22 | To: tom.acton@enron.com, liz.bellamy@enron.com 23 | cc: 24 | 25 | Subject: apr 3 noms 26 | 27 | 28 | 29 | This is a multipart message in MIME format. 30 | --=_mixed 005ACF2486256A22_= 31 | Content-Type: multipart/alternative; boundary="=_alternative 32 | 005ACF2486256A22_=" 33 | 34 | 35 | --=_alternative 005ACF2486256A22_= 36 | Content-Type: text/plain; charset="us-ascii" 37 | 38 | 39 | --=_alternative 005ACF2486256A22_= 40 | Content-Type: text/html; charset="us-ascii" 41 | 42 | 43 | --=_alternative 005ACF2486256A22_=-- 44 | --=_mixed 005ACF2486256A22_= 45 | Content-Type: application/X-MS-Excel; name="HPL-Apr.xls" 46 | Content-Disposition: attachment; filename="HPL-Apr.xls" 47 | Content-Transfer-Encoding: binary 48 | 49 | - HPL-Apr.xls 50 | -------------------------------------------------------------------------------- /src/test_data/enron_farmer-d_discussion_threads_4965.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <25413344.1075854298941.JavaMail.evans@thyme> 2 | Date: Mon, 2 Apr 2001 04:47:00 -0700 (PDT) 3 | From: tom.acton@enron.com 4 | To: daren.farmer@enron.com 5 | Subject: Entex apr 3 noms 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Tom Acton 10 | X-To: Daren J Farmer 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Darren_Farmer_Jun2001\Notes Folders\Discussion threads 14 | X-Origin: Farmer-D 15 | X-FileName: dfarmer.nsf 16 | 17 | ---------------------- Forwarded by Tom Acton/Corp/Enron on 04/02/2001 11:45 18 | AM --------------------------- 19 | 20 | 21 | ronald_s_fancher@reliantenergy.com on 04/02/2001 11:32:33 AM 22 | To: tom.acton@enron.com, liz.bellamy@enron.com 23 | cc: 24 | 25 | Subject: apr 3 noms 26 | 27 | 28 | 29 | This is a multipart message in MIME format. 30 | --=_mixed 005ACF2486256A22_= 31 | Content-Type: multipart/alternative; boundary="=_alternative 32 | 005ACF2486256A22_=" 33 | 34 | 35 | --=_alternative 005ACF2486256A22_= 36 | Content-Type: text/plain; charset="us-ascii" 37 | 38 | 39 | --=_alternative 005ACF2486256A22_= 40 | Content-Type: text/html; charset="us-ascii" 41 | 42 | 43 | --=_alternative 005ACF2486256A22_=-- 44 | --=_mixed 005ACF2486256A22_= 45 | Content-Type: application/X-MS-Excel; name="HPL-Apr.xls" 46 | Content-Disposition: attachment; filename="HPL-Apr.xls" 47 | Content-Transfer-Encoding: binary 48 | 49 | - HPL-Apr.xls 50 | -------------------------------------------------------------------------------- /src/test_data/enron_farmer-d_entex_1.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <7832395.1075845109475.JavaMail.evans@thyme> 2 | Date: Mon, 2 Apr 2001 14:47:00 -0700 (PDT) 3 | From: tom.acton@enron.com 4 | To: daren.farmer@enron.com 5 | Subject: Entex apr 3 noms 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Tom Acton 10 | X-To: Daren J Farmer 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Farmer, Daren J.\Farmer, Daren J.\Entex 14 | X-Origin: FARMER-D 15 | X-FileName: Farmer, Daren J..pst 16 | 17 | 18 | ---------------------- Forwarded by Tom Acton/Corp/Enron on 04/02/2001 11:45 AM --------------------------- 19 | 20 | 21 | ronald_s_fancher@reliantenergy.com on 04/02/2001 11:32:33 AM 22 | To: tom.acton@enron.com, liz.bellamy@enron.com 23 | cc: 24 | 25 | Subject: apr 3 noms 26 | 27 | 28 | 29 | This is a multipart message in MIME format. 30 | --=_mixed 005ACF2486256A22_= 31 | Content-Type: multipart/alternative; boundary="=_alternative 32 | 005ACF2486256A22_=" 33 | 34 | 35 | --=_alternative 005ACF2486256A22_= 36 | Content-Type: text/plain; charset="us-ascii" 37 | 38 | 39 | --=_alternative 005ACF2486256A22_= 40 | Content-Type: text/html; charset="us-ascii" 41 | 42 | 43 | --=_alternative 005ACF2486256A22_=-- 44 | --=_mixed 005ACF2486256A22_= 45 | Content-Type: application/X-MS-Excel; name="HPL-Apr.xls" 46 | Content-Disposition: attachment; filename="HPL-Apr.xls" 47 | Content-Transfer-Encoding: binary 48 | 49 | - HPL-Apr.xls 50 | -------------------------------------------------------------------------------- /src/test_data/enron_farmer-d_entex_106.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <16613902.1075854301335.JavaMail.evans@thyme> 2 | Date: Mon, 2 Apr 2001 04:47:00 -0700 (PDT) 3 | From: tom.acton@enron.com 4 | To: daren.farmer@enron.com 5 | Subject: Entex apr 3 noms 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Tom Acton 10 | X-To: Daren J Farmer 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \Darren_Farmer_Jun2001\Notes Folders\Entex 14 | X-Origin: Farmer-D 15 | X-FileName: dfarmer.nsf 16 | 17 | ---------------------- Forwarded by Tom Acton/Corp/Enron on 04/02/2001 11:45 18 | AM --------------------------- 19 | 20 | 21 | ronald_s_fancher@reliantenergy.com on 04/02/2001 11:32:33 AM 22 | To: tom.acton@enron.com, liz.bellamy@enron.com 23 | cc: 24 | 25 | Subject: apr 3 noms 26 | 27 | 28 | 29 | This is a multipart message in MIME format. 30 | --=_mixed 005ACF2486256A22_= 31 | Content-Type: multipart/alternative; boundary="=_alternative 32 | 005ACF2486256A22_=" 33 | 34 | 35 | --=_alternative 005ACF2486256A22_= 36 | Content-Type: text/plain; charset="us-ascii" 37 | 38 | 39 | --=_alternative 005ACF2486256A22_= 40 | Content-Type: text/html; charset="us-ascii" 41 | 42 | 43 | --=_alternative 005ACF2486256A22_=-- 44 | --=_mixed 005ACF2486256A22_= 45 | Content-Type: application/X-MS-Excel; name="HPL-Apr.xls" 46 | Content-Disposition: attachment; filename="HPL-Apr.xls" 47 | Content-Transfer-Encoding: binary 48 | 49 | - HPL-Apr.xls 50 | -------------------------------------------------------------------------------- /src/test_data/enron_scholtes-d_transmission_29.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <15316155.1075840030891.JavaMail.evans@thyme> 2 | Date: Tue, 14 Aug 2001 19:09:32 -0700 (PDT) 3 | From: rlstone@bpa.gov 4 | To: oasispostings-l@list.transmission.bpa.gov 5 | Subject: OASIS Posting - EXTENSION OF NOTIFICATION BY CUSTOMERS FOR 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: rlstone@bpa.gov 10 | X-To: OASISPOSTINGS-L@LIST.TRANSMISSION.BPA.GOV 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \ExMerge - Scholtes, Diana\Transmission 14 | X-Origin: SCHOLTES-D 15 | X-FileName: 16 | 17 | REAL POWER LOSS PROVIDERS 18 | 19 | Mime-Version: 1.0 20 | Content-Type: multipart/mixed; boundary="763455949.997830572216.JavaMail.RLS4690@b22081" 21 | 22 | --763455949.997830572216.JavaMail.RLS4690@b22081 23 | Content-Type: text/plain; charset=us-ascii 24 | Content-Transfer-Encoding: 7bit 25 | 26 | Please click on the attached icon or the Web address below to go to the most recent OASIS Posting. 27 | 28 | If that doesn't work highlight and copy the address below, then paste it into the address line of your Web browser and hit "enter": 29 | 30 | Information on BPAT's implementation of tariff terms and conditions and/or transmission marketing policy has been posted to: 31 | 32 | http://www.transmission.bpa.gov/oasis/bpat/BusPractices/forum/messageview.cfm?catid=22&threadid=66 33 | 34 | For questions or comments, please contact your Transmission Account Executive. 35 | --763455949.997830572216.JavaMail.RLS4690@b22081 36 | Content-Type: text/html; name=posting.htm 37 | Content-Transfer-Encoding: 7bit 38 | Content-Disposition: attachment; filename=posting.htm 39 | 40 | 41 | 42 | 43 | 44 | --763455949.997830572216.JavaMail.RLS4690@b22081-- -------------------------------------------------------------------------------- /src/test_data/enron_scholtes-d_transmission_35.eml: -------------------------------------------------------------------------------- 1 | Message-ID: <31639999.1075840031035.JavaMail.evans@thyme> 2 | Date: Fri, 3 Aug 2001 17:32:30 -0700 (PDT) 3 | From: cjpauley@bpa.gov 4 | To: oasispostings-l@list.transmission.bpa.gov 5 | Subject: 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; charset=us-ascii 8 | Content-Transfer-Encoding: 7bit 9 | X-From: Claudia Pauley 10 | X-To: OASISPOSTINGS-L@LIST.TRANSMISSION.BPA.GOV 11 | X-cc: 12 | X-bcc: 13 | X-Folder: \ExMerge - Scholtes, Diana\Transmission 14 | X-Origin: SCHOLTES-D 15 | X-FileName: 16 | 17 | Load & Resource Forecast - Bonneville Responses to Customer Comments 18 | 19 | Mime-Version: 1.0 20 | Content-Type: multipart/mixed; boundary="343376168.996874350313.JavaMail.CJP9532@b18355" 21 | 22 | --343376168.996874350313.JavaMail.CJP9532@b18355 23 | Content-Type: text/plain; charset=us-ascii 24 | Content-Transfer-Encoding: 7bit 25 | 26 | Please click on the attached icon or the Web address below to go to the most recent OASIS Posting. 27 | 28 | If that doesn't work highlight and copy the address below, then paste it into the address line of your Web browser and hit "enter": 29 | 30 | Information on BPAT's implementation of tariff terms and conditions and/or transmission marketing policy has been posted to: 31 | 32 | http://www.transmission.bpa.gov/oasis/bpat/http://www.transmission.bpa.gov/oasis/bpat/BusPractices/forum/messageview.cfm?catid=13&threadid=144 33 | 34 | For questions or comments, please contact your Transmission Account Executive. 35 | --343376168.996874350313.JavaMail.CJP9532@b18355 36 | Content-Type: text/html; name=posting.htm 37 | Content-Transfer-Encoding: 7bit 38 | Content-Disposition: attachment; filename=posting.htm 39 | 40 | 41 | 42 | 43 | 44 | --343376168.996874350313.JavaMail.CJP9532@b18355-- -------------------------------------------------------------------------------- /src/test_data/multi-part-base64.eml: -------------------------------------------------------------------------------- 1 | From: from@example.com 2 | To: to@example.com 3 | Date: Fri, 21 Nov 1997 09:55:06 -0600 4 | Subject: This is the subject 5 | Content-Type: multipart/alternative; boundary=bound 6 | 7 | --bound 8 | Content-Transfer-Encoding: base64 9 | Remark: Content is "hello world" with no newline 10 | 11 | aGVsbG8gd29ybGQ= 12 | 13 | --bound 14 | Content-Transfer-Encoding: base64 15 | Remark: Content is "hell\0 w\0rld" with no newline 16 | 17 | aGVsbAAgdwBybGQ= 18 | 19 | --bound-- 20 | -------------------------------------------------------------------------------- /src/test_data/rfc-8463.eml: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=football.example.com; i=@football.example.com; 3 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 4 | subject : date : message-id : from : subject : date; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 7 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 8 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 9 | d=football.example.com; i=@football.example.com; 10 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 11 | date : message-id : from : subject : date; 12 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 13 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 14 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 15 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 16 | From: Joe SixPack 17 | To: Suzie Q 18 | Subject: Is dinner ready? 19 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 20 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 21 | 22 | Hi. 23 | 24 | We lost the game. Are you hungry yet? 25 | 26 | Joe. 27 | -------------------------------------------------------------------------------- /src/test_data/rfc3501_p56.eml: -------------------------------------------------------------------------------- 1 | Remark: This message is used to test examples from RFC 3501 page 56, the 2 | "example of a complex message". 3 | From: foo@bar.com 4 | Subject: RFC 3501 5 | Content-Type: multipart/mixed; boundary=toplevel 6 | 7 | --toplevel 8 | Content-Id: 1 9 | Content-Type: text/plain 10 | 11 | Part 1 12 | 13 | --toplevel 14 | Content-Id: 2 15 | Content-Type: application/octet-stream 16 | 17 | Part 2 18 | 19 | --toplevel 20 | Content-Id: 3 21 | Content-Type: message/rfc822 22 | 23 | Subject: Part 3 24 | Content-Type: multipart/mixed; boundary=part3 25 | 26 | --part3 27 | Content-Id: 3.1 28 | Content-Type: text/plain 29 | 30 | Part 3.1 31 | 32 | --part3 33 | Content-Id: 3.2 34 | Content-Type: application/octet-stream 35 | 36 | Part 3.2 37 | 38 | --part3-- 39 | --toplevel 40 | Content-Id: 4 41 | Content-Type: multipart/mixed; boundary=part4 42 | 43 | --part4 44 | Content-Id: 4.1 45 | Content-Type: image/gif 46 | 47 | Part 4.1 48 | 49 | --part4 50 | Content-Id: 4.2 51 | Content-Type: message/rfc822 52 | 53 | Subject: Part 4.2 54 | Content-Type: multipart/mixed; boundary=subpart42 55 | 56 | --subpart42 57 | Content-Id: 4.2.1 58 | Content-Type: text/plain 59 | 60 | Part 4.2.1 61 | 62 | --subpart42 63 | Content-Id: 4.2.2 64 | Content-Type: multipart/alternative; boundary=subsubpart422 65 | 66 | --subsubpart422 67 | Content-Id: 4.2.2.1 68 | Content-Type: text/plain 69 | 70 | Part 4.2.2.1 71 | 72 | --subsubpart422 73 | Content-Id: 4.2.2.2 74 | Content-Type: text/richtext 75 | 76 | Part 4.2.2.2 77 | 78 | --subsubpart422-- 79 | --subpart42-- 80 | --part4-- 81 | --toplevel-- 82 | -------------------------------------------------------------------------------- /src/test_data/single-part-base64.eml: -------------------------------------------------------------------------------- 1 | From: from@example.com 2 | To: to@example.com 3 | Date: Fri, 21 Nov 1997 09:55:06 -0600 4 | Subject: This is the subject 5 | Content-Transfer-Encoding: base64 6 | Remark: Content is "hello world" with no newline 7 | 8 | aGVsbG8gd29ybGQ= 9 | -------------------------------------------------------------------------------- /src/test_data/unknown-cte.eml: -------------------------------------------------------------------------------- 1 | From: from@example.com 2 | To: to@example.com 3 | Date: Fri, 21 Nov 1997 09:55:06 -0600 4 | Subject: This is the subject 5 | Content-Transfer-Encoding: chunked 6 | 7 | 5 8 | hello 9 | 6 10 | world 11 | 0 12 | 13 | -------------------------------------------------------------------------------- /src/test_data/with-obsolete-routing.eml: -------------------------------------------------------------------------------- 1 | From: <@route1.tld:foo@bar.com> 2 | To: <@route2.tld,@route3:baz@quux.com> 3 | 4 | hello 5 | --------------------------------------------------------------------------------