Support for decrypting PGP encrypted mails. However, it has some drawbacks and might cause some security issues. So before using it please read carefully through the installation instructions.

This commit is contained in:
fkrone 2015-05-25 20:24:37 +02:00
parent 6a7dda969b
commit 2c0e342e5e
5 changed files with 458 additions and 71 deletions

View File

@ -24,6 +24,20 @@ import shutil
import random
import string
def private_keys( keyhome ):
cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-secret-keys', '--with-colons']
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()
keys = dict()
for line in p.stdout.readlines():
if line[0:3] == 'uid' or line[0:3] == 'sec':
if ('<' not in line or '>' not in line):
continue
email = line.split('<')[1].split('>')[0]
fingerprint = line.split(':')[4]
keys[fingerprint] = email
return keys
def public_keys( keyhome ):
cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-keys', '--with-colons']
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
@ -116,3 +130,21 @@ class GPGEncryptor:
cmd.append('Charset: ' + self._charset)
return cmd
class GPGDecryptor:
def __init__(self, keyhome):
self._keyhome = keyhome
self._message = ''
def update(self, message):
self._message += message
def decrypt(self):
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

View File

@ -1,25 +1,49 @@
1. Ensure that GPG is installed and configured. Also make sure public keys for
all of your potential recipients are available in the GPG home directory
used for `keyhome` in step 2.
# Installation instructions
## Content
- General information
- Install GPG-Mailgate
- Install GPG-Mailgate-Web
- Install Register-handler
2. Configure `/etc/gpg-mailgate.conf` based on the provided
`gpg-mailgate.conf.sample`
## General information
GPG-Mailgate is divided in 3 main parts: GPG-Mailgate itself, GPG-Mailgate-Web and Register-handler. Some parts of the GPG-Mailgate project depend on other parts of the project. You will find information about these dependencies at the beginning of every installation part.
3. Install some python dependencies `apt-get install python-m2crypto python-markdown python-requests python-mysqldb` (for linux distributions based on Debian. If you have a non Debian based distribution, the install command might be different)
These instructions show you how to set up GPG-Mailgate in an easy way. If you are a more advanced user, feel free to experiment with the settings. For these instructions a home directory for the user `nobody` is set. Sadly this is an odd workaround but no better solution was found.
4. Place `gpg-mailgate.py` and `register-handler.py` in `/usr/local/bin/`
These instructions are based on an installation on an Ubuntu 14.04 LTS virtual machine. For other Linux distributions and other versions these instructions might need to be adapted to your distribution (e.g. installation of packages and used directories).
5. Make sure that `gpg-mailgate.py` and `register-handler.py` are executable
## Install GPG-Mailgate
### Requirements
- Python 2.X is already installed (GPG-Mailgate is not Python 3 compatible)
- Postfix is already installed and configured. It is recommended that you have already tested your configuration so we can exclude this as a main cause of problems
- GnuPG is already installed and configured
### Installation
1. Install the Python-M2Crypto module:
apt-get install python-m2crypto
2. Set the home directory for the user `nobody` (sadly this workaround is needed as there is no better solution at this point). If you get an error that the user is currently used by a process, you might need to kill the process manually.
usermod -d /var/gpgmailgate nobody
3. Create dedicated directories for storing PGP keys and S/MIME certificates and make the user `nobody` owner of these:
mkdir -p /var/gpgmailgate/.gnupg
mkdir -p /var/gpgmailgate/smime
chown -R nobody:nogroup /var/gpgmailgate/
4. Place the `gpg-mailgate.py` in `/usr/local/bin/`, make the user `nobody` owner of the file and make it executable:
chmod u+x /usr/local/bin/gpg-mailgate.py
chmod u+x /usr/local/bin/register-handler.py
chown nobody:nogroup /usr/local/bin/gpg-mailgate.py
chown nobody:nogroup /usr/local/bin/register-handler.py
6. Place the GnuPG directory in `/usr/local/lib/python2.7/dist-packages` (replace 2.7 with your
Python version)
7. Add the following to the end of `/etc/postfix/master.cf`
chmod u+x /usr/local/bin/gpg-mailgate.py
5. Place the `GnuPG` directory in `/usr/local/lib/python2.7/dist-packages` (replace 2.7 with your Python 2 version)
6. Configure `/etc/gpg-mailgate.conf` based on the provided `gpg-mailgate.conf.sample`. Change the settings according to your configuration. If you follow this guide and have a standard configuration for postfix, you don't need to change much.
7. Add the following to the end of `/etc/postfix/master.cf`
gpg-mailgate unix - n n - - pipe
flags= user=nobody argv=/usr/local/bin/gpg-mailgate.py ${recipient}
@ -34,41 +58,127 @@
-o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
8. Add the following to `/etc/postfix/main.cf`
If you use Postfix versions from 2.5 onwards, it is recommended to change `${recipient}` to `${original_recipient}` in line two of the lines above.
8. Add the following line to `/etc/postfix/main.cf`
content_filter = gpg-mailgate
9. Restart Postfix
You are now ready to go. To add a public key for encryption just use the following command:
9. Add `register: |/usr/local/bin/register-handler.py` to `/etc/aliases`
10. Update postfix's alias database with `postalias /etc/aliases`
sudo -u nobody /usr/bin/gpg --homedir=/var/gpgmailgate/.gnupg --import /some/public.key
11. Restart postfix.
- Replace `/some/public.key` with the location of a public key
- `/some/public.key` can be deleted after importation
- Confirm that it's working:
`sudo -u nobody /usr/bin/gpg --list-keys --homedir=/var/gpgmailgate/.gnupg`
12. Setup a place to store public keys and certificates with these example commands:
Please also test your installation before using it.
usermod -d /var/gpg nobody
GPG-Mailgate is also able to handle S/MIME certificates for encrypting mails. However, it is best to use it in combination with Register-Handler described later to add new certificates. If you try to add them manually it might fail. The certificates are stored in `/var/gpgmailgate/smime` in PKCS7 format and are named like `User@example.com` (the user part is case sensitive, the domain part should be in lower case).
If you encounter any errors when using this command you might need to kill active processes from nobody
####Additional settings
Most mail servers do not handle mail addresses case sensitive. If you know that all your recipient mail servers do not care about case sensitivity then you can set `mail_case_insensitive` in the settings to `yes` so looking up PGP keys or S/MIME certificates does also happen case insensitive.
If your recipients have problems to decrypt mails encrypted by GPG-Mailgate they might use a piece of software that does not support PGP/MIME encrypted mails. You can tell GPG-Mailgate to use the legacy PGP/INLINE format by adding the recipient to the `pgp_style` map in the following format:
`User@example.com=inline`
mkdir -p /var/gpg/.gnupg
mkdir -p /var/smime/certs
chown -R nobody /var/gpg
chown -R nobody /var/smime
chmod 700 /var/gpg/.gnupg
sudo -u nobody /usr/bin/gpg --homedir=/var/gpg/.gnupg --import /some/public.key
- Replace `/some/public.key` with the location of a public key
- `/some/public.key` can be deleted after importation
- Confirm that it's working: `sudo -u nobody /usr/bin/gpg --list-keys --homedir=/var/gpg/.gnupg`
### Mail decryption
GPG-Mailgate does not only feature encryption of mails but also decryption of PGP encrypted mails.
#### Important notice
**Read carefully before setting up and using this functionality!**
13. Create directories for storing email templates:
With this functionality you could use GPG-Mailgate to decrypt incoming PGP encrypted mails (it is also capable of decrypting outgoing mails if the necessary key is present). To use this, you need to store your private keys on the server. This means that anyone who is able to obtain admin rights on the server is able to get the private keys stored on the server and is able to decrypt any mail encrypted with the corresponding public key. **If the server gets compromised in any kind and the attacker may have gained access to the server's file system, the keys have to be regarded as compromised as well!** If this happens you have to revoke your keys, notify everyone who has your public key (key servers as well) not to use this key any longer. You also need to create a new key pair for encrypted communication.
#### Limitations
There are two main types of PGP encryption: PGP/MIME and PGP/INLINE. PGP/MIME is standardized while PGP/INLINE isn't completely (even though some people claim so). Decrypting PGP/MIME encrypted mails works in most cases while decrypting PGP/INLINE encrypted mails may fail more often. The reason is that most clients are implementing PGP/INLINE in their own way. GPG-Mailgate is able to decrypt mails which are encrypted PGP/INLINE by GPG-Mailgate on the sender's side. Furthermore it should be able to decrypt PGP/INLINE encrypted mails encrypted by Enigmail. For PGP/INLINE the mail's structure may not be preserved due to how PGP/INLINE is implemented on most clients. If you receive a PGP/INLINE encrypted mail that could not be decrypted by GPG-Mailgate you may ask the sender to use PGP/MIME instead. Furthermore file types might get lost when using PGP/INLINE.
#### Setting up decryption
You need the recipient's private key for whom you want to decrypt mails. To add the private key, use the following command:
`sudo -u nobody /usr/bin/gpg --homedir=/var/gpgmailgate/.gnupg --import /some/private.key`
From now on PGP encrypted mails will be decrypted for the recipients for whom the keys are imported.
You also can remove a private key by using the following command. Replace `user@example.com` with the user's address for whom you want to remove the key:
`sudo -u nobody /usr/bin/gpg --homedir=/var/gpgmailgate/.gnupg --delete-secret-keys user@example.com`
## Install GPG-Mailgate-Web
### Requirements
- A webserver is installed and reachable
- The webserver is able to handle PHP scripts
- MySQL is installed
- Python 2.X is already installed
### Installation
All files you need can be found in the [gpg-mailgate-web] (gpg-mailgate-web/) directory.
1. Install the Python-mysqldb and Python-markdown modules:
apt-get install python-mysqldb python-markdown
2. Create a new database for GPG-Mailgate-Web.
3. Import the schema file `schema.sql` into the newly created database.
4. Edit the config file located at `/etc/gpg-mailgate.conf`. Set `enabled = yes` in `[database]` and fill in the necessary settings for the database connection.
5. Copy the files located in the [public_html] (gpg-mailgate-web/public_html) directory onto your webserver. They can also be placed in a subdirectory on your webserver.
6. On your webserver move the `config.sample.php` file to `config.php` and edit the configuration file.
7. Create directories for storing email templates:
mkdir -p /var/gpgmailgate/cron_templates
8. Copy the templates found in the [cron_templates] (cron_templates/) directory into the newly created directory and transfer ownership:
chown -R nobody:nogroup /var/gpgmailgate/cron_templates
9. Copy `cron.py` to `/usr/local/bin/gpgmw-cron.py`. Make it executable and and transfer ownership to `nobody`:
chown nobody:nogroup /usr/local/bin/gpgmw-cron.py
chmod u+x /usr/local/bin/gpgmw-cron.py
10. Create `/etc/cron.d/gpgmw` with contents:
`*/3 * * * * nobody /usr/bin/python /usr/local/bin/gpgmw-cron.py > /dev/null`
for executing the cron job automatically.
11. Test your installation.
### GPG-Mailgate-Web as keyserver
GPG-Mailgate-Web can also be used as a keyserver. For more information have a look at GPG-Mailgate-Web's [readme] (gpg-mailgate-web/README).
## Install Register-handler
### Requirements
- Already set up and working GPG-Mailgate-Web. It should be reachable from the machine that will run register-handler
- Postfix is already installed and configured. It is recommended that you have already tested your configuration so we can exclude this as a main cause of problems. Your Postfix configuration should also support aliases
### Installation
1. Install the Python-requests module:
apt-get install python-requests
2. Create directories for storing email templates:
mkdir -p /var/gpgmailgate/register_templates
mkdir -p /var/gpgmailgate/cron_templates
chown -R nobody /var/gpgmailgate
3. Copy the templates found in the [register_templates] (register_templates/) directory into the newly created directory and transfer ownership:
- Place the corresponding directories from this project in the created ones
- Edit them if you want to
chown -R nobody:nogroup /var/gpgmailgate/register_templates
4. Copy `register-handler.py` to `/usr/local/bin/register-handler.py`. Make it executable and own it to `nobody`:
14. [Install gpg-mailgate-web] (gpg-mailgate-web/README)
chown nobody:nogroup /usr/local/bin/register-handler.py
chmod a+x /usr/local/bin/register-handler.py
5. Edit the config file located at `/etc/gpg-mailgate.conf`. Set the parameter `webpanel_url` in `[mailregister]` to the url of your GPG-Mailgate-Web panel (the URL should be the same as the one you use to access the panel with your web browser). Also set the parameter `register_email` to the email address you want the user to see when receiving mails from the register-handler (it does not have to be an existing address but it is recommended). Register-handler will send users mails when they are registering S/MIME certificates or when neither a S/MIME certificate nor a PGP key was found in a mail sent to the register-handler.
6. Add `register: |/usr/local/bin/register-handler.py` to `/etc/aliases`
7. Update postfix's alias database with `postalias /etc/aliases`
8. Restart postfix.
9. Test your installation.

View File

@ -2,7 +2,7 @@
**Please note: This fork is currently WIP. It is not recommended for use at the moment.**
gpg-mailgate is a content filter for Postfix that automatically encrypts unencrypted incoming email using PGP or S/MIME for select recipients.
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.
For installation instructions, please refer to the included INSTALL file.
@ -10,6 +10,7 @@ For installation instructions, please refer to the included INSTALL file.
- Correctly displays attachments and general email content; currently will only display first part of multipart messages
- Public keys are stored in a dedicated gpg-home-directory
- Encrypts both matching incoming and outgoing mail (this means gpg-mailgate can be used to encrypt outgoing mail for software that doesn't support PGP or S/MIME)
- Decrypt PGP encrypted mails for present private keys (but no signature check and it does not always work with PGP/INLINE encrypted mails)
- Easy installation
- gpg-mailgate-web extension is a web interface allowing any user to upload PGP keys so that emails sent to them from your mail server will be encrypted (see gpg-mailgate-web directory for details)
- people can submit their public key like to any keyserver to gpg-mailgate with the gpg-mailgate-web extension
@ -37,7 +38,7 @@ This is a combined work of many developers and contributors:
# To Do
* clean up code
* rewrite and improve installation instructions
* rewrite readme of gpg-mailgate-web in markdown
* 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
* even more magical stuff

View File

@ -1,12 +1,22 @@
[default]
# whether gpg-mailgate should add a header after it has processed an email
# this may be useful for debugging purposes
# Whether gpg-mailgate should add a header after it has processed an email
# This may be useful for debugging purposes
add_header = yes
# whether we should only sign emails if they are explicitly defined in
# the key mappings below ([keymap] section)
# this means gpg-mailgate won't automatically detect PGP recipients
keymap_only = no
# Whether we should only encrypt emails if they are explicitly defined in
# the key mappings below ([enc_keymap] section)
# This means gpg-mailgate won't automatically detect PGP recipients for encrypting
enc_keymap_only = no
# Whether we should only decrypt emails if they are explicitly defined in
# the key mappings below ([dec_keymap] section)
# This means gpg-mailgate won't automatically detect PGP recipients for decrypting
dec_keymap_only = no
# If dec_keymap_only is set to yes and recipients have private keys present for decrypting
# but are not on in the keymap, this can cause that mails for them will be
# encrypted. Set this to no if you want this behaviour.
failsave_dec = yes
# Convert encrypted text/plain email to MIME-attached encrypt style.
# (Default is to use older inline-style PGP encoding.)
@ -22,11 +32,11 @@ mail_case_insensitive = no
[gpg]
# the directory where gpg-mailgate public keys are stored
# (see INSTALL for details)
keyhome = /var/gpg/.gnupg
keyhome = /var/gpgmailgate/.gnupg
[smime]
# the directory for the S/MIME certificate files
cert_path = /var/smime/certs
cert_path = /var/gpgmailgate/smime
[mailregister]
# settings for the register-handler
@ -62,7 +72,7 @@ host = localhost
username = gpgmw
password = password
[keymap]
[enc_keymap]
# You can find these by running the following command:
# gpg --list-keys --keyid-format long user@example.com
# Which will return output similar to:
@ -72,6 +82,16 @@ password = password
# You want the AAAAAAAAAAAAAAAA not BBBBBBBBBBBBBBBB.
#you@domain.tld = 12345678
[dec_keymap]
# You can find these by running the following command:
# gpg --list-secret-keys --keyid-format long user@example.com
# Which will return output similar to:
# sec 1024D/AAAAAAAAAAAAAAAA 2007-10-22
# uid Joe User <user@example.com>
# ssb 2048g/BBBBBBBBBBBBBBBB 2007-10-22
# You want the AAAAAAAAAAAAAAAA not BBBBBBBBBBBBBBBB.
#you@domain.tld = 12345678
[pgp_style]
# Here a PGP style (inline or PGP/MIME) could be defined for recipients.
# This overwrites the setting mime_conversion for the defined recipients.

View File

@ -22,17 +22,18 @@
from ConfigParser import RawConfigParser
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
import copy
import email
import email.message
import re
import email.utils
import GnuPG
import os
import re
import smtplib
import sys
import syslog
import traceback
import email.utils
import os
import copy
# imports for S/MIME
from M2Crypto import BIO, Rand, SMIME, X509
@ -47,7 +48,7 @@ for sect in _cfg.sections():
for (name, value) in _cfg.items(sect):
cfg[sect][name] = value
def log(msg):
def log( msg ):
if 'logging' in cfg and 'file' in cfg['logging']:
if cfg['logging'].get('file') == "syslog":
syslog.syslog(syslog.LOG_INFO | syslog.LOG_MAIL, msg)
@ -64,6 +65,218 @@ raw_message = email.message_from_string( raw )
from_addr = raw_message['From']
to_addrs = sys.argv[1:]
def gpg_decrypt( raw_message, recipients ):
gpg_to = list()
ungpg_to = list()
# This is needed to avoid encryption if decryption is set to keymap only,
# private key is present but not in keymap.
noenc_to = list()
if not get_bool_from_cfg('gpg', 'keyhome'):
log("No valid entry for gpg keyhome. Decryption aborted.")
return recipients
keys = GnuPG.private_keys( cfg['gpg']['keyhome'] )
for fingerprint in keys:
keys[fingerprint] = sanitize_case_sense(keys[fingerprint])
for to in recipients:
if to in keys.values() and not get_bool_from_cfg('default', 'dec_keymap_only', 'yes'):
gpg_to.append(to)
elif get_bool_from_cfg('dec_keymap', to):
log("Decrypt keymap has key '%s'" % cfg['dec_keymap'][to] )
# Check we've got a matching key! If not, decline to attempt decryption. The key is checked for safty reasons.
if not cfg['dec_keymap'][to] in keys:
log("Key '%s' in decryption keymap not found in keyring for email address '%s'. Won't decrypt." % (cfg['dec_keymap'][to], to))
# Avoid unwanted encryption if set
if to in keys.values() and get_bool_from_cfg('default', 'failsave_dec', 'yes'):
noenc_to.append(to)
else:
ungpg_to.append(to)
else:
gpg_to.append(to)
else:
if verbose:
log("Recipient (%s) not in PGP domain list for decrypting." % to)
# Avoid unwanted encryption if set
if to in keys.values() and get_bool_from_cfg('default', 'failsave_dec', 'yes'):
noenc_to.append(to)
else:
ungpg_to.append(to)
if gpg_to != list():
send_msg( gpg_decrypt_all_payloads( raw_message ).as_string(), gpg_to )
if noenc_to != list():
log("Do not try to encrypt mails for: %s" % ', '.join( noenc_to ))
send_msg(raw_message.as_string(), noenc_to)
return ungpg_to
def gpg_decrypt_all_payloads( message ):
# We don't want to modify the original message
decrypted_message = copy.deepcopy(message)
# Check if message is PGP/MIME encrypted
if not (message.get_param('protocol') is None) and message.get_param('protocol') == 'application/pgp-encrypted' and message.is_multipart():
decrypted_message = decrypt_mime(decrypted_message)
# At this point the message could only be PGP/INLINE encrypted, unencrypted or
# encrypted with a mechanism not covered by GPG-Mailgate
else:
# Check if message is PGP/INLINE encrypted and has attachments (or unencrypted with attachments)
if message.is_multipart():
# Set message's payload to list so payloads can be attached later on
decrypted_message.set_payload(list())
# We only need to hand over the original message here. Not needed for other decrypt implementations.
decrypted_message, success = decrypt_inline_with_attachments(message, False, decrypted_message)
# Add header here to avoid it being appended several times
if get_bool_from_cfg('default', 'add_header', 'yes') and success:
decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate'
# Check if message is PGP/INLINE encrypted without attachments (or unencrypted without attachments)
else:
decrypted_message = decrypt_inline_without_attachments(decrypted_message)
return decrypted_message
def decrypt_mime( decrypted_message ):
# Please note: Signatures will disappear while decrypting and will not be checked
# Getting the part which should be PGP encrypted (according to RFC)
msg_content = decrypted_message.get_payload(1).get_payload()
if "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content:
start = msg_content.find("-----BEGIN PGP MESSAGE-----")
end = msg_content.find("-----END PGP MESSAGE-----")
decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25])
if decrypt_success:
# Making decrypted_message a "normal" unencrypted message
decrypted_message.del_param('protocol')
decrypted_message.set_type(decrypted_payload.get_content_type())
# Restore Content-Disposition header from original message
if not (decrypted_payload.get('Content-Disposition') is None):
if not (decrypted_message.get('Content-Disposition') is None):
decrypted_message.replace_header('Content-Disposition', decrypted_payload.get('Content-Disposition'))
else:
decrypted_message.set_param(decrypted_payload.get('Content-Disposition'), "", 'Content-Disposition')
if decrypted_payload.is_multipart():
# Clear message's original payload and insert the decrypted payloads
decrypted_message.set_payload(list())
decrypted_message = generate_message_from_payloads( decrypted_payload, decrypted_message )
decrypted_message.preamble = "BLA"
else:
decrypted_message.set_payload(decrypted_payload.get_payload())
decrypted_message.preamble = None
if get_bool_from_cfg('default', 'add_header', 'yes'):
decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate'
# If decryption fails, decrypted_message is equal to the original message
return decrypted_message
def decrypt_inline_with_attachments( payloads, success, message = None ):
if message is None:
message = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype())
for payload in payloads.get_payload():
if( type( payload.get_payload() ) == list ):
# Take care of cascaded MIME messages
submessage, subsuccess = decrypt_inline_with_attachments( payload, success )
message.attach(submessage)
success = success or subsuccess
else:
msg_content = payload.get_payload()
# Getting values for different implementations as PGP/INLINE is not implemented
# the same on different clients
pgp_inline_tags = "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content
attachment_filename = payload.get_filename()
if pgp_inline_tags or not (attachment_filename is None) and not (re.search('.\.pgp$', attachment_filename) is None):
if pgp_inline_tags:
start = msg_content.find("-----BEGIN PGP MESSAGE-----")
end = msg_content.find("-----END PGP MESSAGE-----")
decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25])
# Some implementations like Enigmail have strange interpretations of PGP/INLINE
# This tries to cope with it as good as possible.
else:
build_message = """
-----BEGIN PGP MESSAGE-----
%s
-----END PGP MESSAGE-----""" % msg_content
decrypted_payload, decrypt_success = decrypt_payload(build_message)
# Was at least one decryption successful?
success = success or decrypt_success
if decrypt_success:
if not (attachment_filename is None):
attachment_filename = re.sub('\.pgp$', '', attachment_filename)
payload.set_param('filename', attachment_filename, 'Content-Disposition')
payload.set_param('name', attachment_filename, 'Content-Type')
# Need this nasty hack to avoid double blank lines at beginning of message
payload.set_payload(decrypted_payload.as_string()[1:])
message.attach(payload)
else:
# Message could not be decrypted, so non-decrypted message is attached
message.attach(payload)
else:
# There was no encrypted payload found, so the original payload is attached
message.attach(payload)
return message, success
def decrypt_inline_without_attachments( decrypted_message ):
msg_content = decrypted_message.get_payload()
if "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content:
start = msg_content.find("-----BEGIN PGP MESSAGE-----")
end = msg_content.find("-----END PGP MESSAGE-----")
decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25])
if decrypt_success:
# Need this nasty hack to avoid double blank lines at beginning of message
decrypted_message.set_payload(decrypted_payload.as_string()[1:])
if get_bool_from_cfg('default', 'add_header', 'yes'):
decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate'
# If message was not encrypted, this will just return the original message
return decrypted_message
def decrypt_payload( payload ):
gpg = GnuPG.GPGDecryptor( cfg['gpg']['keyhome'] )
gpg.update( payload )
decrypted_data, returncode = gpg.decrypt()
if verbose:
log("Return code from decryption=%d (0 indicates success)." % returncode)
if returncode != 0:
log("Decrytion failed with return code %d. Decryption aborted." % returncode)
return payload, False
# Decryption always generate a new message
decrypted_msg = email.message_from_string(decrypted_data)
return decrypted_msg, True
def gpg_encrypt( raw_message, recipients ):
if not get_bool_from_cfg('gpg', 'keyhome'):
@ -80,25 +293,26 @@ def gpg_encrypt( raw_message, recipients ):
for to in recipients:
# Check if recipient is in keymap
if get_bool_from_cfg('keymap', to):
log("Keymap has key '%s'" % cfg['keymap'][to])
if get_bool_from_cfg('enc_keymap', to):
log("Encrypt keymap has key '%s'" % cfg['enc_keymap'][to] )
# Check we've got a matching key!
if cfg['keymap'][to] in keys:
gpg_to.append( (to, cfg['keymap'][to]) )
if cfg['enc_keymap'][to] in keys:
gpg_to.append( (to, cfg['enc_keymap'][to]) )
continue
else:
log("Key '%s' in keymap not found in keyring for email address '%s'." % (cfg['keymap'][to], to))
log("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (cfg['enc_keymap'][to], to))
if to in keys.values() and not get_bool_from_cfg('default', 'keymap_only', 'yes'):
if to in keys.values() and not get_bool_from_cfg('default', 'enc_keymap_only', 'yes'):
gpg_to.append( (to, to) )
else:
if verbose:
log("Recipient (%s) not in PGP domain list." % to)
log("Recipient (%s) not in PGP domain list for encrypting." % to)
ungpg_to.append(to)
if gpg_to != list():
log("Encrypting email to: %s" % ' '.join( map(lambda x: x[0], gpg_to) ))
# Getting PGP style for recipient
gpg_to_smtp_mime = list()
gpg_to_cmdline_mime = list()
@ -106,6 +320,7 @@ def gpg_encrypt( raw_message, recipients ):
gpg_to_cmdline_inline = list()
for rcpt in gpg_to:
# Checking pre defined styles in settings first
if get_bool_from_cfg('pgp_style', rcpt[0], 'mime'):
gpg_to_smtp_mime.append(rcpt[0])
gpg_to_cmdline_mime.extend(rcpt[1].split(','))
@ -117,6 +332,7 @@ def gpg_encrypt( raw_message, recipients ):
if get_bool_from_cfg('pgp_style', rcpt[0]):
log("Style %s for recipient %s is not known. Use default as fallback." % (cfg['pgp_style'][rcpt[0]], rcpt[0]))
# If no style is in settings defined for recipient, use default from settings
if get_bool_from_cfg('default', 'mime_conversion', 'yes'):
gpg_to_smtp_mime.append(rcpt[0])
gpg_to_cmdline_mime.extend(rcpt[1].split(','))
@ -125,6 +341,7 @@ def gpg_encrypt( raw_message, recipients ):
gpg_to_cmdline_inline.extend(rcpt[1].split(','))
if gpg_to_smtp_mime != list():
# Encrypt mail with PGP/MIME
raw_message_mime = copy.deepcopy(raw_message)
if get_bool_from_cfg('default', 'add_header', 'yes'):
@ -136,6 +353,7 @@ def gpg_encrypt( raw_message, recipients ):
send_msg( raw_message_mime.as_string(), gpg_to_smtp_mime )
if gpg_to_smtp_inline != list():
# Encrypt mail with PGP/INLINE
raw_message_inline = copy.deepcopy(raw_message)
if get_bool_from_cfg('default', 'add_header', 'yes'):
@ -150,6 +368,7 @@ def gpg_encrypt( raw_message, recipients ):
def encrypt_all_payloads_inline( message, gpg_to_cmdline ):
# This breaks cascaded MIME messages. Blame PGP/INLINE.
encrypted_payloads = list()
if type( message.get_payload() ) == str:
return encrypt_payload( message, gpg_to_cmdline ).get_payload()
@ -337,18 +556,18 @@ def sanitize_case_sense( address ):
return address
def generate_message_from_payloads( payloads, submsg = None ):
def generate_message_from_payloads( payloads, message = None ):
if submsg == None:
submsg = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype())
if message == None:
message = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype())
for payload in payloads.get_payload():
if( type( payload.get_payload() ) == list ):
submsg.attach(attach_payload_list_to_message(payload, email.mime.multipart.MIMEMultipart(payload.get_content_subtype())))
message.attach(generate_message_from_payloads(payload))
else:
submsg.attach(payload)
message.attach(payload)
return submsg
return message
def get_first_payload( payloads ):
@ -377,6 +596,11 @@ def sort_recipients( raw_message, from_addr, to_addrs ):
for recipient in to_addrs:
recipients_left.append(sanitize_case_sense(recipient))
# Decrypt mails for recipients with known private PGP keys
recipients_left = gpg_decrypt(raw_message, recipients_left)
if recipients_left == list():
return
# There is no need for nested encryption
first_payload = get_first_payload(raw_message)
if first_payload.get_content_type() == 'application/pkcs7-mime':
@ -398,7 +622,7 @@ def sort_recipients( raw_message, from_addr, to_addrs ):
send_msg(raw_message.as_string(), recipients_left)
return
# Encrypt mails for recipients with known PGP keys
# Encrypt mails for recipients with known public PGP keys
recipients_left = gpg_encrypt(raw_message, recipients_left)
if recipients_left == list():
return