Skip to content
This repository has been archived by the owner on Dec 25, 2021. It is now read-only.

Bug fixes for broken html #3

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bb4d267
Update gpg-mailgate.py
TheGreatGooo Apr 21, 2016
ba0953a
Update gpg-mailgate.py
TheGreatGooo Apr 21, 2016
29f902b
Fix bug
TheGreatGooo Sep 9, 2017
6406d90
Install: automatic pub key retrieval
svenseeberg Mar 10, 2019
0b92307
Added starttls support for SMTP
mukesh610 May 24, 2019
e1c610c
Merge pull request #3 from mukesh610/master
TheGreatGooo May 27, 2019
ff0871b
Merge pull request #1 from svenseeberg/feature/automatic-key-download
TheGreatGooo May 27, 2019
59566d1
Update readme to include info on new name and nlnet funding
muppeth Apr 19, 2021
8095f15
Few typos fixed.
fede-github Apr 20, 2021
0fb623c
Merge pull request 'README file: Few typos fixed.' (#33) from fix_01 …
Apr 21, 2021
a950680
Write a test mail relay
Nov 4, 2021
f3f56a4
Implement an E2E testing script
Nov 8, 2021
7a063a9
Polish E2E testing script and make it configurable
Nov 9, 2021
27d7481
Set up ground for E2E tests
Nov 11, 2021
f41adc0
Configure git to ignore temporary files and Emacs files
Nov 11, 2021
f1a799d
Adjust E2E tests to work with all scenarios
Jan 6, 2022
ff3f6aa
Add a test overview, extract constants
Jan 7, 2022
01377f4
Keep test/certs directory
Jan 7, 2022
e90a29c
Ignore temporary test directory
Jan 7, 2022
98c4580
Document E2E tests
Jan 7, 2022
fc2779e
Improve test code structure
Jan 8, 2022
2cf60de
Add unit tests for GnuPG command-line generator
Jan 9, 2022
1002da7
Clean up test messages
Jan 9, 2022
3b9f714
Ignore random_seed
Jan 10, 2022
ee0d65f
Merge pull request 'Add tests for Python code' (#51) from 39-add-test…
Jan 10, 2022
5f02223
Perform automatic migration to Python 3.x
Jan 8, 2022
b2a01c1
Fix auto-migrated code
Jan 9, 2022
1e7d33c
Handle bytes properly
Jan 9, 2022
24f0c86
Tidy up tests
Jan 10, 2022
435528d
Add an E2E test case with an already encrypted message
Jan 10, 2022
5a8d2c0
Add E2E cases: signed cleartext and multipart/encrypted
Jan 10, 2022
c81c6e6
Remove hardcoded python3.8 path
Jan 10, 2022
a201265
Rework how E2E tests are executed
Jan 19, 2022
03fc3d1
Update testing documentation
Jan 19, 2022
67a938c
GnuPG.add_key: Use build_command
Jan 25, 2022
9e17726
Use f-strings for formatting
Feb 5, 2022
c4927d2
Avoid unnecessary list creation
Feb 5, 2022
968677f
Merge pull request 'Migrate to Python 3.x' (#58) from py3-migration i…
Mar 8, 2022
47d8f1e
Merge pull request #4 from pefimo/master
TheGreatGooo Mar 15, 2022
a1c5d3d
we need to convert to string to concatenate
TheGreatGooo Dec 3, 2024
db209b3
Merge pull request #7 from TheGreatGooo/fix-text-only
TheGreatGooo Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ pip-log.txt
.tox
nosetests.xml

# GPG-Mailgate test files
test/logs
test/tmp
test/gpg-mailgate.conf
test/keyhome/random_seed

# Emacs files
*~
TAGS
TAGS-Python
TAGS-PHP

# Translations
*.mo

Expand Down
45 changes: 31 additions & 14 deletions GnuPG/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,20 @@
import shutil
import random
import string
import sys


LINE_FINGERPRINT = 'fpr'
LINE_USER_ID = 'uid'

POS_FINGERPRINT = 9

def build_command(key_home, *args, **kwargs):
cmd = ["gpg", '--homedir', key_home] + list(args)
return cmd

def private_keys( keyhome ):
cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-secret-keys', '--with-colons']
cmd = build_command(keyhome, '--list-secret-keys', '--with-colons')
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()
keys = dict()
Expand All @@ -39,17 +50,25 @@ def private_keys( keyhome ):
return keys

def public_keys( keyhome ):
cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-keys', '--with-colons']
cmd = build_command(keyhome, '--list-keys', '--with-colons')
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()

keys = dict()
fingerprint = None
email = None
for line in p.stdout.readlines():
if line[0:3] == 'uid' or line[0:3] == 'pub':
line = line.decode('utf-8')
if line[0:3] == LINE_FINGERPRINT:
fingerprint = line.split(':')[POS_FINGERPRINT]
if line[0:3] == LINE_USER_ID:
if ('<' not in line or '>' not in line):
continue
email = line.split('<')[1].split('>')[0]
fingerprint = line.split(':')[4]
if not (fingerprint is None or email is None):
keys[fingerprint] = email
fingerprint = None
email = None
return keys

# confirms a key has a given email address
Expand All @@ -64,7 +83,7 @@ def confirm_key( content, email ):
os.mkdir(tmpkeyhome)
localized_env = os.environ.copy()
localized_env["LANG"] = "C"
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', tmpkeyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=localized_env )
p = subprocess.Popen( build_command(tmpkeyhome, '--import', '--batch'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=localized_env )
result = p.communicate(input=content)[1]
confirmed = False

Expand All @@ -83,7 +102,7 @@ def confirm_key( content, email ):

# adds a key and ensures it has the given email address
def add_key( keyhome, content ):
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE )
p = subprocess.Popen( build_command(keyhome, '--import', '--batch'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.communicate(input=content)
p.wait()

Expand All @@ -93,7 +112,7 @@ def delete_key( keyhome, email ):

if result[1]:
# delete all keys matching this email address
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--delete-key', '--batch', '--yes', result[1]], stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p = subprocess.Popen( build_command(keyhome, '--delete-key', '--batch', '--yes', result[1]), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()
return True

Expand All @@ -102,7 +121,7 @@ def delete_key( keyhome, email ):
class GPGEncryptor:
def __init__(self, keyhome, recipients = None, charset = None):
self._keyhome = keyhome
self._message = ''
self._message = b''
self._recipients = list()
self._charset = charset
if recipients != None:
Expand All @@ -112,12 +131,12 @@ def update(self, message):
self._message += message

def encrypt(self):
p = subprocess.Popen( self._command(), stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE )
p = subprocess.Popen( self._command(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
encdata = p.communicate(input=self._message)[0]
return (encdata, p.returncode)

def _command(self):
cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--pgp7", "--no-secmem-warning", "-a", "-e"]
cmd = build_command(self._keyhome, "--trust-model", "always", "--batch", "--yes", "--pgp7", "--no-secmem-warning", "-a", "-e")

# add recipients
for recipient in self._recipients:
Expand All @@ -140,11 +159,9 @@ def update(self, message):
self._message += message

def decrypt(self):
p = subprocess.Popen( self._command(), stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE )
p = subprocess.Popen( self._command(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
decdata = p.communicate(input=self._message)[0]
return (decdata, p.returncode)

def _command(self):
cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--no-secmem-warning", "-a", "-d"]

return cmd
return build_command(self._keyhome, "--trust-model", "always", "--batch", "--yes", "--no-secmem-warning", "-a", "-d")
8 changes: 6 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ These instructions are based on an installation on an Ubuntu 14.04 LTS virtual m
8. Add the following line to `/etc/postfix/main.cf`

content_filter = gpg-mailgate

9. Restart Postfix

9. Optional: GPG can automatically download new public keys for automatic signature verification. To enable automatic create the file `/var/gpgmailgate/.gnupg/gpg.conf`. Add the following line to the file:

keyserver-options auto-key-retrieve

10. Restart Postfix

You are now ready to go. To add a public key for encryption just use the following command:

Expand Down
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.POSIX:
.PHONY: test unittest pre-clean clean

#
# On systems where Python 3.x binary has a different name, just
# overwrite the name/path on the command line, like:
#
# make test PYTHON=/usr/local/bin/python3.8
#
# This marco is passed via environment to test/e2e_test.py, where it's
# used to compute further commands.
#
PYTHON = python3

#
# Run a set of end-to-end tests.
#
# Test scenarios are described and configured by the test/e2e.ini
# file. Basically this is just a script that feeds GPG Mailgate with
# known input and checks whether output meets expectations.
#
test: test/tmp test/logs pre-clean
$(PYTHON) test/e2e_test.py

#
# Run unit tests
#
unittest:
$(PYTHON) -m unittest discover -s test

pre-clean:
rm -fv test/gpg-mailgate.conf
rm -f test/logs/*.log

test/tmp:
mkdir test/tmp

test/logs:
mkdir test/logs

clean: pre-clean
rm -rfv test/tmp test/logs
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
# gpg-mailgate
# GPG Lacre Project

gpg-mailgate is a content filter for Postfix that automatically encrypts unencrypted incoming email using PGP or S/MIME for select recipients. It is also able to decrypt incoming PGP mails.
Fork and continuation of original work of gpg-mailgate project: https://github.com/fkrone/gpg-mailgate

For installation instructions, please refer to the included INSTALL file.

**GPG Lacre** (wax seal in Portuguese) is a content filter for Postfix that automatically encrypts unencrypted incoming email using PGP or S/MIME for select recipients.
This project is the continuation of the work of "gpg-mailgate" on providing open source, GnuPG based email encryption for emails at rest. All incoming emails are automatically encrypted with user's public key before they are saved on the server. It is a server side encryption solution while the control of the encryption keys are fully at the hands of the end-user and private keys are never stored on the server.

The scope of the project is to improve on the already existing code, provide easy to use key upload system (standalone as well as Roundcube plugin) and key discoverability. Beside providing a solution that is easy to use we will also provide easy to digest material about encryption, how it works and how to make use of it in situations other the just mailbox encryption. Understanding how encryption works is the key to self-determination and is therefore an important part of the project.

GPG Lacre will be battle tested on the email infrastructure of https://disroot.org (an ethical non-profit service provider).

---

The work on this project in 2021 is funded by https://nlnet.nl/thema/NGIZeroPET.html for which we are very thankful.

The scope of the work for 2021 is:
- Rewrite code to python3
- Improve standalone key upload website
- Provide Roundcube plugin for key management
- Improve key server features
- Provide webiste with information and tutorials on how to use GPG in general and also **Lacre**
- (Optional) provide Autocrypt support

Made possible thanks to:<br>
![](https://nlnet.nl/logo/banner.png)

---
For installation instructions, please refer to the included **INSTALL** file.

---

# Features
- Correctly displays attachments and general email content; currently will only display first part of multipart messages
Expand All @@ -19,7 +45,7 @@ This is forked from the original project at http://code.google.com/p/gpg-mailgat

# Authors

This is a combined work of many developers and contributors:
This is a combined work of many developers and contributors. We would like to pay honours to original gpg mailbox developers for making this project happen, and providing solid solution for encryption emails at rest:

* mcmaster <[email protected]>
* Igor Rzegocki <[email protected]> - [GitHub](https://github.com/ajgon/gpg-mailgate)
Expand All @@ -32,9 +58,3 @@ This is a combined work of many developers and contributors:
* Remko Tronçon - [GitHub](https://github.com/remko/phkp/)
* Kiritan Flux [GitHub](https://github.com/kflux)
* Fabian Krone [GitHub] (https://github.com/fkrone/gpg-mailgate)

# To Do

* rename from gpg-mailgate to openpgp-s-mime-mailgate or something.....
* find a better solution for an own user instead of the user `nobody`
* make PGP/INLINE decryption more reliable
40 changes: 40 additions & 0 deletions doc/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Testing

First tests have been set up to cover GPG Mailgate with at least basic test
that would be easy to run. The tests are called "end-to-end", meaning that we
feed some input to GPG Mailgate and inspect the output.

## Running tests

To run tests, use command `make test` or `make unittest`.

Tests produce some helpful logs, so inspect contents of `test/logs` directory
if something goes wrong.

If your system's Python binary isn't found in your `$PATH` or you want to use
a specific binary, use make's macro overriding: `make test
PYTHON=/path/to/python`.

## Key building blocks

- *Test Script* (`test/e2e_test.py`) that orchestrates the other components.
It performs test cases described in the *Test Configuration*. It spawns
*Test Mail Relay* and *GPG Mailgate* in appropriate order.
- *Test Mail Relay* (`test/relay.py`), a simplistic mail daemon that only
supports the happy path. It accepts a mail message and prints it to
stdandard output.
- *Test Configuration* (`test/e2e.ini`) specifies test cases: their input,
expected results and helpful documentation. It also specifies the port that
the *Test Mail Relay* should listen on.

## Limitations

Currently tests only check if the message has been encrypted, without
verifying that the correct key has been used. That's because we don't know
(yet) how to have a reproducible encrypted message. Option
`--faked-system-time` wasn't enough to produce identical output.

## Troubleshooting

When things go wrong, be sure to study `test/logs/e2e.log` and
`test/logs/gpg-mailgate.log` files -- they contain some useful information.
12 changes: 6 additions & 6 deletions gpg-mailgate-web/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
#

from ConfigParser import RawConfigParser
from configparser import RawConfigParser
import GnuPG
import MySQLdb
import smtplib
Expand Down Expand Up @@ -64,7 +64,7 @@ def send_msg( mailsubject, messagefile, recipients = None ):
for (name, value) in _cfg.items(sect):
cfg[sect][name] = value

if cfg.has_key('database') and cfg['database'].has_key('enabled') and cfg['database']['enabled'] == 'yes' and cfg['database'].has_key('name') and cfg['database'].has_key('host') and cfg['database'].has_key('username') and cfg['database'].has_key('password'):
if 'database' in cfg and 'enabled' in cfg['database'] and cfg['database']['enabled'] == 'yes' and 'name' in cfg['database'] and 'host' in cfg['database'] and 'username' in cfg['database'] and 'password' in cfg['database']:
connection = MySQLdb.connect(host = cfg['database']['host'], user = cfg['database']['username'], passwd = cfg['database']['password'], db = cfg['database']['name'], port = 3306)
cursor = connection.cursor()

Expand All @@ -83,17 +83,17 @@ def send_msg( mailsubject, messagefile, recipients = None ):
GnuPG.add_key(cfg['gpg']['keyhome'], row[0]) # import the key to gpg
cursor.execute("UPDATE gpgmw_keys SET status = 1 WHERE id = %s", (row[1],)) # mark key as accepted
appendLog('Imported key from <' + row[2] + '>')
if cfg['cron'].has_key('send_email') and cfg['cron']['send_email'] == 'yes':
if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes':
send_msg( "PGP key registration successful", "registrationSuccess.md", row[2] )
else:
cursor.execute("DELETE FROM gpgmw_keys WHERE id = %s", (row[1],)) # delete key
appendLog('Import confirmation failed for <' + row[2] + '>')
if cfg['cron'].has_key('send_email') and cfg['cron']['send_email'] == 'yes':
if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes':
send_msg( "PGP key registration failed", "registrationError.md", row[2] )
else:
# delete key so we don't continue processing it
cursor.execute("DELETE FROM gpgmw_keys WHERE id = %s", (row[1],))
if cfg['cron'].has_key('send_email') and cfg['cron']['send_email'] == 'yes':
if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes':
send_msg( "PGP key deleted", "keyDeleted.md", row[2])

connection.commit()
Expand All @@ -108,4 +108,4 @@ def send_msg( mailsubject, messagefile, recipients = None ):
appendLog('Deleted key for <' + row[0] + '>')
connection.commit()
else:
print "Warning: doing nothing since database settings are not configured!"
print("Warning: doing nothing since database settings are not configured!")
3 changes: 3 additions & 0 deletions gpg-mailgate.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ port = 10028
# mails through the GPG-Mailgate so they are encrypted
enc_port = 25

# Set this option to yes to use TLS for SMTP Servers which require TLS.
starttls = no

[database]
# uncomment the settings below if you want
# to read keys from a gpg-mailgate-web database
Expand Down
Loading