diff --git a/content/blog/pgp_encrypting_all_incoming_emails.md b/content/blog/pgp_encrypting_all_incoming_emails.md
new file mode 100644
index 0000000..d7bdeb9
--- /dev/null
+++ b/content/blog/pgp_encrypting_all_incoming_emails.md
@@ -0,0 +1,257 @@
++++
+date = '2024-12-13T16:06:23Z'
+draft = false
+title = 'PGP Encrypting All (Incoming) Emails'
+tags = ['mail server', 'alpine linux', 'dovecot', 'sieve']
++++
+
+Let's say we want emails on our mail server to be encrypted at rest, such that only the user has the key. Luckily,
+there already exists a popular solution for encrypting emails such that only the recipient can read them: OpenPGP.
+
+Using [Dovecot Sieve scripts](https://doc.dovecot.org/main/core/plugins/sieve.html), we can easily PGP-encrypt all
+incoming email for a user.
+
+A lot of people have done this before, and I didn't come up with the idea. Please see the [Further
+reading](#further-reading) section for some recommended articles I referred to.
+
+The only prerequisite is an existing Dovecot server set up and running. This guide will be 100% compatible with [my
+mail server guide](/blog/mail_server_alpine_postfix_dovecot_tutorial/). This guide assumes you are using system users
+as mail users, and may require changes if you are using virtual users.
+
+This instructions should be distro-agnostic, though it was written for an Alpine Linux server. I think the only
+Alpine-specific part should be how to install the required packages, which can just be replaced by the relevant command
+for your distro's package manager.[^alpine_searchability]
+
+This is compatible with Dovecot's mail\_crypt plugin, because mail\_crypt's encryption is transparent to the user.
+
+Finally, this only encrypts incoming mail, because Sieve scripts aren't applied to outgoing mail.
+
+# Admin guide
+
+Install Pigeonhole (a Sieve implementation for Dovecot):
+
+ # apk add dovecot-pigeonhole-plugin
+
+Set Dovecot to use Pigeonhole. Edit `/etc/dovecot/conf.d/20-lmtp.conf`:
+
+```conf
+protocol lmtp {
+ mail_plugins = $mail_plugins sieve
+}
+```
+
+If you use LDA, you should do:
+
+```conf
+protocol lda {
+ mail_plugins = $mail_plugins sieve
+}
+```
+
+Now set Dovecot to use the `sieve_extprograms` Sieve plugin. This allows Sieve to run external executables. Don't
+worry; it won't allow users to execute arbitrary executables, but only executables you specify.
+
+`sieve_extprograms` may come installed with Pigeonhole, or you may have to install it separately. For me, my
+`/etc/dovecot/conf.d/90-sieve.conf` contains a comment that states:
+
+> The sieve\_extprograms plugin is included in this release.
+
+To enable `sieve_extprograms`, anywhere in your Dovecot config (I put it in `/etc/dovecot/conf.d/90-sieve.conf`):
+
+```conf
+plugin {
+ sieve_plugins = sieve_extprograms
+ sieve_extensions = +vnd.dovecot.filter
+ sieve_filter_bin_dir = /etc/dovecot/sieve-filters
+}
+```
+
+We add `vnd.dovecot.filter` to the list of Sieve extensions, to allow users to use the `filter` Sieve command. A filter
+is an executable that takes an email from stdin, performs an action on it, and outputs the modified email to stdout.
+
+By specifying `sieve_filter_bin_dir`, we are saying that we will place any Sieve filters in
+`/etc/dovecot/sieve-filters`.
+
+> WARNING!
+>
+> Users can execute *any executable* you place in `/etc/dovecot/sieve-filters`. Only put executables you trust in
+> there!
+
+Sieve filters will be executed with the following environment variables, and *only* the following environment
+variables:
+
+* `HOME`
+* `USER`
+* `SENDER`
+* `RECIPIENT`
+* `ORIG_RECIPIENT`
+
+They can take one argument specified by the user in their Sieve script.
+
+Now let's add the Sieve filter itself. This can be any executable that takes an email from stdin and outputs the
+PGP-encrypted email from stdout.
+
+[Here's one written in Perl](https://gitlab.com/mikecardwell/gpgit). I had trouble with installing the Perl
+dependencies on Alpine, so I ended up using [a Python script by Julian Andres
+Klode](https://github.com/julian-klode/ansible.jak-linux.org/blob/dovecot/roles/mailserver/files/usr/local/lib/dovecot-sieve-filters/gpgmymail).
+
+On Alpine Linux, to use this script, you should install [python-gnupg](https://gnupg.readthedocs.io/en/latest/) as:
+
+ # apk add py3-gnupg
+
+Now place the executable you want to use in `/etc/dovecot/sieve-filters` and make it executable (`chmod +x`). You can
+see the version of Julian Andres Klode's script I use on my server
+[here](https://git.revsuine.xyz/revsuine/gpgmymail).
+
+You also need to make sure that users can set their own personal Sieve scripts. You can set:
+
+```conf
+plugin {
+ sieve = ~/.dovecot.sieve
+}
+```
+
+to make it so that a user's Sieve script would be at `~/.dovecot.sieve`. You can also set
+
+```conf
+plugin {
+ sieve = file:~/sieve;active=~/.dovecot.sieve
+}
+```
+
+so that `~/sieve/` is a directory full of sieve scripts, and the active one is symlinked at `~/.dovecot.sieve`. See
+[Dovecot docs on Sieve script locations](https://doc.dovecot.org/main/core/plugins/sieve.html#script-locations), or [my
+further explanation of the `sieve = file:~/sieve;active=~/.dovecot.sieve` example on my previous blog
+post](/blog/mail_server_alpine_postfix_dovecot_tutorial/#installing-and-setting-up-pigeonhole).
+
+Restart Dovecot for your changes to take effect:
+
+ # rc-service dovecot restart
+
+You are now done from the admin side of things.
+
+# User guide
+
+In order for gpgmymail (the script linked above) to have the user's public PGP key, they need to import it to their
+system GnuPG keyring. If they have shell access,
+
+ user@localhost $ gpg --export --armor user@revsuine.xyz > public.asc
+ user@localhost $ scp public.asc user@revsuine.xyz:~/public.asc
+ user@localhost $ ssh user@revsuine.xyz
+ user@revsuine.xyz $ gpg --import ~/public.asc
+
+Or you could copy and paste the ASCII armored public key into an SSH shell, etc.
+
+> ==***DO NOT PUT YOUR PRIVATE KEY ON THE SERVER!***==
+>
+> Not only is this a security hole, but this also entirely defeats the point of this setup, which is designed to
+> protect against an attacker who gains full disk access to the mail server from reading your emails. If the private
+> key is stored on the server, this attacker with full disk access will have the key to decrypt and read your emails.
+
+Because `$HOME` and `$USER` are included in a Sieve filter's environment, python-gnupg can see the public keys in the
+user's personal GPG keyring.
+
+If the user doesn't have shell access, they need to send their public key to a server admin who can run `gpg --import`
+as their user (e.g. with `doas -u`).
+
+You also need to mark the public key as trusted so that GPG doesn't refuse to encrypt data with the key:
+
+ user@revsuine.xyz $ gpg --edit-key user@revsuine.xyz
+
+Then enter `trust`, select `5`, enter `y`, then enter `save`:
+
+ gpg> trust
+ 1 = I don't know or won't say
+ 2 = I do NOT trust
+ 3 = I trust marginally
+ 4 = I trust fully
+ 5 = I trust ultimately
+ m = back to the main menu
+ Your decision? 5
+ Do you really want to set this key to ultimate trust? (y/N) y
+ gpg> save
+
+Now the user needs to create or amend their Sieve script. A minimal Sieve script could be
+
+```sieve
+require "vnd.dovecot.filter";
+
+filter "gpgmymail" "user@revsuine.xyz";
+```
+
+Note that `filter` commands need to go *before* `fileinto` commands for them to take effect.
+
+If your Sieve filter is named something else, replace `gpgmymail` with the name of your script (relative to
+`/etc/dovecot/sieve-filters/`).
+
+Your Sieve filter does not need to implement behaviour such as "don't encrypt emails from `domain.com`", because this
+is exactly what Sieve scripting is for. If you want to apply conditions to encrypting mail, do it with Sieve, e.g.
+
+```sieve
+require "vnd.dovecot.filter";
+
+if not address :is :domain "from" ["revsuine.xyz", "gmail.com"] {
+ filter "gpgmymail" "user@revsuine.xyz";
+}
+```
+
+# Assessment
+
+Given a trusted server admin to implement this, and not spy on emails prior to them passing through the Sieve filter,
+this solution protects against an attacker who can read the full disk of the server, as stated previously. Potential
+threats this defends against include seizure of the server by law enforcement who bypass full disk encryption, or a VPS
+host who reads the FDE key from RAM and reads the disk contents; essentially,
+any instance of a third party who gains full disk access.
+
+This does not do much to protect against a server admin who is intent upon reading their users' emails, because the
+email is unencrypted the whole time it moves through the Postfix queue. At the end of the day, there is really nothing
+at all that can be done to stop a server admin from reading something that arrives at the server unencrypted, such as
+an unencrypted email.
+
+This solution is an improvement over services such as Protonmail or Tuta, because unlike with Protonmail, users of a
+mail server with this Sieve filter do not have to have their private keys stored on the server. Protonmail, assuming an
+entirely non-technical user, manages PGP keys for the user, and therefore generates and stores them server-side.
+However, with our solution, emails become encrypted on the server, but only get decrypted on the user's local machine.
+Their public key is stored on the server, but not their private key. Also, unlike Protonmail and Tuta, this solution
+works out-of-the-box with IMAP or POP3, not requiring a bridge like Protonmail does.
+
+As mentioned at the start, outgoing mail is still stored unencrypted on the server, unless the user has encrypted it
+themselves (e.g. with PGP).
+
+This solution will also make it impossible to search for message contents, as they are all encrypted. If you also
+encrypt subject lines, you can essentially only search for emails by sender or date.
+
+This solution shouldn't break spam filters if they are [integrated with your MTA](https://cwiki.apache.org/confluence/display/SPAMASSASSIN/IntegratedInMta),
+but if your spam filter happens after Sieve filtering for some reason (likely only if your spam filter is client-side),
+it obviously won't work because the message contents are encrypted and unreadable to a spam filter. Modifying the email
+in this way also renders DKIM signing invalid, but DKIM validation should be integrated with your MTA, in which case
+you'll still have an email header indicating DKIM status prior to encryption.
+
+## Compatibility
+
+[OpenKeychain](https://www.openkeychain.org/) fails to decrypt emails encrypted this way. As far as I can tell, the
+only way these emails can be read on Android is by using Termux to decrypt emails with GnuPG, i.e. not with any
+conventional Android IMAP client.
+
+[Desktop Thunderbird](https://www.thunderbird.net/) seems to have an issue rendering quoted-printable or base64-encoded
+emails encrypted this way, however I've had no problem with [GNOME Evolution](https://wiki.gnome.org/Apps/Evolution/).
+I haven't tested with other desktop email clients.
+
+Because all gpgmymail does is, essentially, encrypt emails with a Python wrapper for GnuPG, any desktop email client
+that uses GPG to decrypt PGP-encrypted emails should be able to read gpgmymail-encrypted emails. When you run
+`gpg --decrypt` on a gpgmymail'ed email, you will see the email headers twice, and then the email as it was prior to
+being gpgmymail'ed. You could easily not have a client render your email, and just read the raw decrypted email with
+`gpg --decrypt`.
+
+# Further reading
+
+In order:
+
+1. https://www.grepular.com/Automatically_Encrypting_all_Incoming_Email
+2. https://perot.me/encrypt-specific-incoming-emails-using-dovecot-and-sieve
+3. https://blog.jak-linux.org/2019/06/13/encrypted-email-storage/
+4. https://github.com/julian-klode/ansible.jak-linux.org/blob/dovecot/roles/mailserver/files/usr/local/lib/dovecot-sieve-filters/gpgmymail
+
+[^alpine_searchability]: Tagged [#alpine linux](/tags/alpine-linux/) for findability when searching.
+