Table of Contents

Project website: lacre.io

Goal

The goal is to reanimate gpg-mailgate (of which Lacre is a fork) by porting it to Python 3.x, improve its documentation and in the future, maybe adapt to other services. We also plan to integrate it with Disroot’s webmail.

Source code notes

Before changing anything, I’ve spent some time reading the code.

The nobody workaround

I’ve asked two of the authors of the original gpg-mailgate project about their commits mentioning user nobody. One said they just didn’t want to make the system user’s name to reveal the purpose, the other didn’t remember the reasoning behind it.

My conclusion is that perhaps we can use a dedicated system user for GPG Lacre in the future.

Jail setup

Since I need to test my work somewhere, I’m setting up a FreeBSD jail to run a test instance Postfix server. I’ve created the jail following instructions from the handbook: 15.3. Creating and Controlling Jails.

Then, to install software inside the jail, I’ve used pkg -j $name from the host system, so I don’t have to connect the jail to the network.

Using GnuPG in a jail

To use GnuPG in a FreeBSD jail, one has to add the allow-loopback-pinentry option to ~/.gnupg/gpg-agent.conf and use --pinentry-mode loopback whenver calling gpg, as reported in this FreeBSD bug report: security/gnupg: pinentry-tty dumps core because of missing privelege (esp. see comment #11 and #12).

End–to–end tests

Before modifying GPG-Mailgate code, I wanted to cover it with at least some tests, because there were none and I would feel much more confidend modifying the code if I had a chance to verify it still works as expected.

I’ve started by writing an E2E test script that would perform the following steps:

However, GPG–encrypted messages include a packet with a timestamp, so we cannot just compare output against a message encrypted at another time. I’d like to avoid including private keys in the repository, so at the moment I’m looking for other options.

Automatic migration to Python 3.x

Having covered the project with at least these tests, which can be triggered using a simple command (see testing documentation for details), I’ve migrated the code to Python 3.x automatically. Implementing tests first proved to be the right approach, because auto–migrated code didn’t work out of the box. The change between Python 2.x and 3.x that caused most of the issues was the introduction of bytes type used by IPC. Thanks to the tests, it was very easy to fix the issues.

Pull-request to “the upstream”

Finally I’ve managed to open a pull-request to share my work with those who still maintain or use the original repository. It’s been merged soon after my submission.

Testing a cron-job

On our test environment, we’d found an issue with the cron-job. To fix it, I’ve written some tests for the cron-job too, but since this script reads keys from database, I needed a test database. To do that, I’ve replaced MySQLdb package with SQLAlchemy and started using SQLite for tests. This also makes it possible to use Lacre with other databases.

Implementing an Advanced Content Filter

Of course I've started by making sure I can even test the advanced filter, so I've set up end-to-end tests similar to those already used by the simple content filter. Eventually I've rewritten tests for both content filters to use unittest Python library. Since all these tests are defined as sets of parameters for the same test code, I've just used subTest generator to run them all.

Benefits:

Downsides:

While implementing advanced filter, I've also learned about event loops used by coroutines.

Next steps

In the near future I’d like to:

Asynchronous I/O

I'm having issues with integrating SQLAlchemy and aiosmtpd.

Stack Overflow entries: