diff --git a/INSTALL.md b/INSTALL.md index 5898d51..cc62f20 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,7 +14,7 @@ These instructions are based on an installation on an Ubuntu 14.04 LTS virtual m ## Install GPG-Mailgate ### Requirements -- Python 3.X is already installed +- Python 3.x is already installed - 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 @@ -22,63 +22,79 @@ These instructions are based on an installation on an Ubuntu 14.04 LTS virtual m 1. Install the Python-M2Crypto module: - apt-get install python-m2crypto - +``` +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 - +``` +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/ + +``` +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: - chown nobody:nogroup /usr/local/bin/gpg-mailgate.py - chmod u+x /usr/local/bin/gpg-mailgate.py +``` +chown nobody:nogroup /usr/local/bin/gpg-mailgate.py +chmod u+x /usr/local/bin/gpg-mailgate.py +``` 5. Place the `GnuPG` directory in `/usr/local/lib/python3.x/dist-packages` (replace 3.x with your Python 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. +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. Configure logging by copying `gpg-lacre-logging.conf.sample` to `/etc/gpg-lacre-logging.conf` and editing it according to your needs. The path to this file is included in `[logging]` section of `gpg-mailgate.conf` file, so if you place it somewhere else, make sure to update the path too. See also: [Configuration file format](https://docs.python.org/3/library/logging.config.html#configuration-file-format). +7. Configure logging by copying `gpg-lacre-logging.conf.sample` to `/etc/gpg-lacre-logging.conf` and editing it according to your needs. The path to this file is included in `[logging]` section of `gpg-mailgate.conf` file, so if you place it somewhere else, make sure to update the path too. See also: [Configuration file format](https://docs.python.org/3/library/logging.config.html#configuration-file-format). 8. 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} +``` +gpg-mailgate unix - n n - - pipe + flags= user=nobody argv=/usr/local/bin/gpg-mailgate.py ${recipient} - 127.0.0.1:10028 inet n - n - 10 smtpd - -o content_filter= - -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks - -o smtpd_helo_restrictions= - -o smtpd_client_restrictions= - -o smtpd_sender_restrictions= - -o smtpd_recipient_restrictions=permit_mynetworks,reject - -o mynetworks=127.0.0.0/8 - -o smtpd_authorized_xforward_hosts=127.0.0.0/8 +127. 0. 0. 1:10028 inet n - n - 10 smtpd + -o content_filter= + -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks + -o smtpd_helo_restrictions= + -o smtpd_client_restrictions= + -o smtpd_sender_restrictions= + -o smtpd_recipient_restrictions=permit_mynetworks,reject + -o mynetworks=127. 0. 0. 0/8 + -o smtpd_authorized_xforward_hosts=127. 0. 0. 0/8 +``` - 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. +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. 9. Add the following line to `/etc/postfix/main.cf` - content_filter = gpg-mailgate +``` +content_filter = gpg-mailgate +``` 10. 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 +``` +keyserver-options auto-key-retrieve +``` 11. Restart Postfix You are now ready to go. To add a public key for encryption just use the following command: - - sudo -u nobody /usr/bin/gpg --homedir=/var/gpgmailgate/.gnupg --import /some/public.key + +``` +sudo -u nobody /usr/bin/gpg --homedir=/var/gpgmailgate/.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: +- Confirm that it's working: `sudo -u nobody /usr/bin/gpg --list-keys --homedir=/var/gpgmailgate/.gnupg` Please also test your installation before using it. @@ -87,7 +103,7 @@ GPG-Mailgate is also able to handle S/MIME certificates for encrypting mails. Ho ####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: +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` @@ -102,11 +118,11 @@ With this functionality you could use GPG-Mailgate to decrypt incoming PGP encry There are two main types of PGP encryption: PGP/MIME and PGP/INLINE. PGP/MIME is standardized while PGP/INLINE isn't completely clear standardized (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. Due to this limitations decrypting PGP/INLINE encrypted mails is disabled by default. If you want to take the risk you can set `no_inline_dec` to `no` in the `[default]` section. You have been warned. #### Setting up decryption -You need the recipient's private key for whom you want to decrypt mails. Only unprotected keys are supported. Keys protected by a passphrase could not be used. To add the private key, use the following command: +You need the recipient's private key for whom you want to decrypt mails. Only unprotected keys are supported. Keys protected by a passphrase could not be used. 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: +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 @@ -114,14 +130,16 @@ You also can remove a private key by using the following command. Replace `user@ - A webserver is installed and reachable - The webserver is able to handle PHP scripts - MySQL is installed -- Python 3.X is already installed +- Python 3.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 +``` +apt-get install python-mysqldb python-markdown +``` 2. Create a new database for GPG-Mailgate-Web. @@ -134,20 +152,26 @@ All files you need can be found in the [gpg-mailgate-web](gpg-mailgate-web/) dir 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 - + +``` +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 +``` +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 +``` +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` +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. @@ -164,21 +188,29 @@ GPG-Mailgate-Web can also be used as a keyserver. For more information have a lo 1. Install the Python-requests module: - apt-get install python-requests - +``` +apt-get install python-requests +``` + 2. Create directories for storing email templates: - - mkdir -p /var/gpgmailgate/register_templates - + +``` +mkdir -p /var/gpgmailgate/register_templates +``` + 3. Copy the templates found in the [register_templates](register_templates/) directory into the newly created directory and transfer ownership: - chown -R nobody:nogroup /var/gpgmailgate/register_templates - +``` +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`: - chown nobody:nogroup /usr/local/bin/register-handler.py - chmod a+x /usr/local/bin/register-handler.py - +``` +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` @@ -187,4 +219,4 @@ GPG-Mailgate-Web can also be used as a keyserver. For more information have a lo 8. Restart postfix. -9. Test your installation. +9. Test your installation. \ No newline at end of file diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 00c6b44..68b2eb1 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -26,29 +26,28 @@ import logging import lacre import lacre.config as conf -start = time.process_time() +start = time.time() conf.load_config() lacre.init_logging(conf.get_item('logging', 'config')) # This has to be executed *after* logging initialisation. -import lacre.core as core +import lacre.mailgate as mailgate -LOG = logging.getLogger('gpg-mailgate.py') +LOG = logging.getLogger(__name__) missing_params = conf.validate_config() if missing_params: LOG.error(f"Aborting delivery! Following mandatory config parameters are missing: {missing_params!r}") sys.exit(lacre.EX_CONFIG) -# Read e-mail from stdin, parse it +# Read e-mail from stdin raw = sys.stdin.read() raw_message = email.message_from_string(raw) from_addr = raw_message['From'] -# Read recipients from the command-line to_addrs = sys.argv[1:] # Let's start -core.deliver_message(raw_message, from_addr, to_addrs) -process_t = (time.process_time() - start) * 1000 +mailgate.deliver_message(raw_message, from_addr, to_addrs) +(elapsed_s, process_t) = mailgate.exec_time_info(start) -LOG.info("Message delivered in {process:.2f} ms".format(process=process_t)) +LOG.info("Elapsed-time: {elapsed:.2f}s; Process-time: {process:.4f}s".format(elapsed=elapsed_s, process=process_t)) diff --git a/lacre/config.py b/lacre/config.py index 707a1c5..d1d7821 100644 --- a/lacre/config.py +++ b/lacre/config.py @@ -1,7 +1,6 @@ -"""Lacre configuration. +"""Lacre configuration -Routines defined here are responsible for processing and validating -configuration. +Routines defined here are responsible for processing configuration. """ from configparser import RawConfigParser @@ -19,8 +18,7 @@ CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" MANDATORY_CONFIG_ITEMS = [("relay", "host"), ("relay", "port"), ("daemon", "host"), - ("daemon", "port"), - ("gpg", "keyhome")] + ("daemon", "port")] # Global dict to keep configuration parameters. It's hidden behind several # utility functions to make it easy to replace it with ConfigParser object in @@ -40,9 +38,6 @@ def load_config() -> dict: parser = _read_config(configFile) - # XXX: Global variable. It is a left-over from old GPG-Mailgate code. We - # should drop it and probably use ConfigParser instance where configuration - # parameters are needed. global cfg cfg = _copy_to_dict(parser) return cfg diff --git a/lacre/daemon.py b/lacre/daemon.py index 9607ecb..b400d79 100644 --- a/lacre/daemon.py +++ b/lacre/daemon.py @@ -16,14 +16,15 @@ from watchdog.observers import Observer # These are the only values that our mail handler is allowed to return. RESULT_OK = '250 OK' RESULT_ERROR = '500 Could not process your message' +RESULT_NOT_IMPLEMENTED = '500 Not implemented yet' # Load configuration and init logging, in this order. Only then can we load # the last Lacre module, i.e. lacre.mailgate. conf.load_config() lacre.init_logging(conf.get_item("logging", "config")) -LOG = logging.getLogger('lacre.daemon') +LOG = logging.getLogger(__name__) -import lacre.core as gate +import lacre.mailgate as gate import lacre.keyring as kcache @@ -40,7 +41,7 @@ class MailEncryptionProxy: try: keys = await self._keyring.freeze_identities() message = email.message_from_bytes(envelope.content) - for operation in gate.delivery_plan(envelope.rcpt_tos, message, keys): + for operation in gate.delivery_plan(envelope.rcpt_tos, keys): LOG.debug(f"Sending mail via {operation!r}") new_message = operation.perform(message) gate.send_msg(new_message, operation.recipients(), envelope.mail_from) @@ -102,8 +103,6 @@ def _main(): asyncio.run(_sleep()) except KeyboardInterrupt: LOG.info("Finishing...") - except: - LOG.exception('Unexpected exception caught, your system may be unstable') finally: LOG.info('Shutting down keyring watcher and the daemon...') reloader.stop() diff --git a/lacre/keyring.py b/lacre/keyring.py index 9baa75e..34f1d70 100644 --- a/lacre/keyring.py +++ b/lacre/keyring.py @@ -5,10 +5,9 @@ module. """ import lacre.text as text -import lacre.config as conf import logging from os import stat -from watchdog.events import FileSystemEventHandler, FileSystemEvent +from watchdog.events import FileSystemEventHandler from asyncio import Semaphore, run import copy @@ -18,8 +17,10 @@ LOG = logging.getLogger(__name__) def _sanitize(keys): - sanitize = text.choose_sanitizer(conf.get_item('default', 'mail_case_insensitive')) - return {fingerprint: sanitize(keys[fingerprint]) for fingerprint in keys} + for fingerprint in keys: + keys[fingerprint] = text.sanitize_case_sense(keys[fingerprint]) + + return keys class KeyCacheMisconfiguration(Exception): @@ -117,7 +118,6 @@ class KeyRing: def _read_mod_time(self): # (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) - # 0 1 2 3 4 5 6 7 8 9 MTIME = 8 st = stat(self._path) return st[MTIME] @@ -141,11 +141,10 @@ class KeyringModificationListener(FileSystemEventHandler): """Initialise a listener with a callback to be executed upon each change.""" self._keyring = keyring - def handle(self, event: FileSystemEvent): + def handle(self, event): """Reload keys upon FS event.""" - if 'pubring.kbx' in event.src_path: - LOG.debug(f'Reloading on event {event!r}') - self._keyring.reload() + LOG.debug(f'Reloading on event {event!r}') + self._keyring.reload() # All methods should do the same: reload the key cache. # on_created = handle diff --git a/lacre/core.py b/lacre/mailgate.py similarity index 96% rename from lacre/core.py rename to lacre/mailgate.py index f5eb3ce..f92a691 100644 --- a/lacre/core.py +++ b/lacre/mailgate.py @@ -1,3 +1,9 @@ +"""Lacre's actual mail-delivery module. + +IMPORTANT: This module has to be loaded _after_ initialisation of the logging +module. +""" + # # gpg-mailgate # @@ -17,12 +23,6 @@ # along with gpg-mailgate source code. If not, see . # -"""Lacre's actual mail-delivery module. - -IMPORTANT: This module has to be loaded _after_ initialisation of the logging -module. -""" - from email.mime.multipart import MIMEMultipart import copy import email @@ -32,6 +32,7 @@ import GnuPG import os import smtplib import sys +import time import asyncio # imports for S/MIME @@ -285,7 +286,7 @@ def _encrypt_all_payloads_inline(message, gpg_to_cmdline): return encrypted_payloads -def _encrypt_all_payloads_mime(message: email.message.Message, gpg_to_cmdline): +def _encrypt_all_payloads_mime(message, gpg_to_cmdline): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. pgp_ver_part = email.message.Message() pgp_ver_part.set_payload("Version: 1"+text.EOL) @@ -308,7 +309,7 @@ def _encrypt_all_payloads_mime(message: email.message.Message, gpg_to_cmdline): encoding = sys.getdefaultencoding() if 'Content-Type' in message and not message['Content-Type'].startswith('multipart'): additionalSubHeader = "Content-Type: " + message['Content-Type'] + text.EOL - encoding = message.get_content_charset(sys.getdefaultencoding()) + (base, encoding) = text.parse_content_type(message['Content-Type']) LOG.debug(f"Identified encoding as {encoding}") encrypted_part.set_payload(additionalSubHeader+text.EOL + message.get_payload(decode=True).decode(encoding)) check_nested = True @@ -335,7 +336,7 @@ def _encrypt_all_payloads_mime(message: email.message.Message, gpg_to_cmdline): def _encrypt_payload(payload, gpg_to_cmdline, check_nested=True): raw_payload = payload.get_payload(decode=True) - if check_nested and text.is_payload_pgp_inline(raw_payload): + if check_nested and text.is_pgp_inline(raw_payload): LOG.debug("Message is already pgp encrypted. No nested encryption needed.") return payload @@ -382,7 +383,7 @@ def _smime_encrypt(raw_message, recipients): unsmime_to = list() for addr in recipients: - cert_and_email = _get_cert_for_email(addr, cert_path) + cert_and_email = _get_cert_for_email(addr[0], cert_path) if not (cert_and_email is None): (to_cert, normal_email) = cert_and_email @@ -492,7 +493,7 @@ def send_msg(message: str, recipients, fromaddr=None): LOG.info("No recipient found") -def _is_encrypted(raw_message: email.message.Message): +def _is_encrypted(raw_message): if raw_message.get_content_type() == 'multipart/encrypted': return True @@ -500,15 +501,12 @@ def _is_encrypted(raw_message: email.message.Message): if first_part.get_content_type() == 'application/pkcs7-mime': return True - return text.is_message_pgp_inline(first_part) + first_payload = first_part.get_payload(decode=True) + return text.is_pgp_inline(first_payload) -def delivery_plan(recipients, message: email.message.Message, key_cache: kcache.KeyCache): +def delivery_plan(recipients, key_cache: kcache.KeyCache): """Generate a sequence of delivery strategies.""" - if _is_encrypted(message): - LOG.debug(f'Message is already encrypted: {message!r}') - return [KeepIntact(recipients)] - gpg_to, ungpg_to = _identify_gpg_recipients(recipients, key_cache) gpg_mime_to, gpg_mime_cmd, gpg_inline_to, gpg_inline_cmd = \ @@ -534,8 +532,7 @@ def deliver_message(raw_message: email.message.Message, from_address, to_addrs): # Ugly workaround to keep the code working without too many changes. from_addr = from_address - sanitize = text.choose_sanitizer(conf.get_item('default', 'mail_case_insensitive')) - recipients_left = [sanitize(recipient) for recipient in to_addrs] + recipients_left = [text.sanitize_case_sense(recipient) for recipient in to_addrs] # There is no need for nested encryption LOG.debug("Seeing if it's already encrypted") @@ -559,3 +556,10 @@ def deliver_message(raw_message: email.message.Message, from_address, to_addrs): # Send out mail to recipients which are left LOG.debug("Sending the rest as text/plain") send_msg(raw_message.as_string(), recipients_left) + + +def exec_time_info(start_timestamp): + """Calculate time since the given timestamp.""" + elapsed_s = time.time() - start_timestamp + process_t = time.process_time() + return (elapsed_s, process_t) diff --git a/lacre/mailop.py b/lacre/mailop.py index db0f8b2..ca24c7d 100644 --- a/lacre/mailop.py +++ b/lacre/mailop.py @@ -14,7 +14,7 @@ There are 3 operations available: """ import logging -import lacre.core as core +import lacre.mailgate as mailgate from email.message import Message @@ -72,9 +72,9 @@ class InlineOpenPGPEncrypt(OpenPGPEncrypt): def perform(self, msg: Message): """Encrypt with PGP Inline.""" LOG.debug('Sending PGP/Inline...') - return core._gpg_encrypt_and_return(msg, - self._keys, self._recipients, - core._encrypt_all_payloads_inline) + return mailgate._gpg_encrypt_and_return(msg, + self._keys, self._recipients, + mailgate._encrypt_all_payloads_inline) class MimeOpenPGPEncrypt(OpenPGPEncrypt): @@ -87,9 +87,9 @@ class MimeOpenPGPEncrypt(OpenPGPEncrypt): def perform(self, msg: Message): """Encrypt with PGP MIME.""" LOG.debug('Sending PGP/MIME...') - return core._gpg_encrypt_and_return(msg, - self._keys, self._recipients, - core._encrypt_all_payloads_mime) + return mailgate._gpg_encrypt_and_return(msg, + self._keys, self._recipients, + mailgate._encrypt_all_payloads_mime) class SMimeEncrypt(MailOperation): diff --git a/lacre/text.py b/lacre/text.py index 84f8939..3f9cc6a 100644 --- a/lacre/text.py +++ b/lacre/text.py @@ -1,23 +1,20 @@ -"""Basic payload-processing routines.""" - import sys import re import logging -from email.message import Message +import lacre.config as conf # The standard way to encode line-ending in email: EOL = "\r\n" -EOL_BYTES = b"\r\n" -PGP_INLINE_BEGIN = EOL_BYTES + b"-----BEGIN PGP MESSAGE-----" + EOL_BYTES -PGP_INLINE_END = EOL_BYTES + b"-----END PGP MESSAGE-----" + EOL_BYTES +PGP_INLINE_BEGIN = b"-----BEGIN PGP MESSAGE-----" +PGP_INLINE_END = b"-----END PGP MESSAGE-----" LOG = logging.getLogger(__name__) -def parse_content_type(content_type: str): +def parse_content_type(content_type): """Analyse Content-Type email header. Return a pair: type and sub-type. @@ -52,36 +49,19 @@ def parse_delimiter(address: str): return (address, None) -def _lowercase_whole_address(address: str): - return address.lower() - - -def _lowercase_domain_only(address: str): - parts = address.split('@', maxsplit=2) - if len(parts) > 1: - return parts[0] + '@' + parts[1].lower() +def sanitize_case_sense(address): + """Sanitize email case.""" + # TODO: find a way to make it more unit-testable + if conf.flag_enabled('default', 'mail_case_insensitive'): + address = address.lower() else: - return address + splitted_address = address.split('@') + if len(splitted_address) > 1: + address = splitted_address[0] + '@' + splitted_address[1].lower() + + return address -def choose_sanitizer(mail_case_insensitive: bool): - """Return a function to sanitize email case sense.""" - if mail_case_insensitive: - return _lowercase_whole_address - else: - return _lowercase_domain_only - - -def is_payload_pgp_inline(payload: bytes) -> bool: - """Find out if the payload (bytes) contains PGP/inline markers.""" +def is_pgp_inline(payload) -> bool: + """Find out if the payload (bytes) contains PGP/INLINE markers.""" return PGP_INLINE_BEGIN in payload and PGP_INLINE_END in payload - - -def is_message_pgp_inline(message: Message) -> bool: - """Find out if a message is already PGP-Inline encrypted.""" - if message.is_multipart() or isinstance(message.get_payload(), list): - # more than one payload, check each one of them - return any(is_message_pgp_inline(m.payload()) for m in message.iter_parts()) - else: - # one payload, check it - return is_payload_pgp_inline(message.get_payload(decode=True)) diff --git a/requirements.txt b/requirements.txt index 9ad1b9a..7d073a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -aiosmtpd==1.4.2 SQLAlchemy==1.4.32 Markdown==3.4.1 M2Crypto==0.38.0 diff --git a/test/daemon_test.py b/test/daemon_test.py index 8488eef..5edcb4f 100644 --- a/test/daemon_test.py +++ b/test/daemon_test.py @@ -65,24 +65,14 @@ def _load_test_config(): return cp -def _identity(x): - return x - - -def _inversion(x): - return not(x) - - -def _report_result(message_file, expected, test_output, boolean_func=_identity): +def _report_result(message_file, expected, test_output): status = None - expected_line = "\r\n" + expected # + "\r\n" - cond_met = boolean_func(expected_line in test_output) - if cond_met: + if expected in test_output: status = "Success" else: status = "Failure" - print(message_file.ljust(35), status) + print(message_file.ljust(30), status) def _execute_case(config, case_name): @@ -101,10 +91,7 @@ def _execute_case(config, case_name): test_out = test_out.decode('utf-8') logging.debug(f"Read {len(test_out)} characters of output: '{test_out}'") - if 'out' in config[case_name]: - _report_result(config.get(case_name, "in"), config.get(case_name, "out"), test_out) - else: - _report_result(config.get(case_name, "in"), config.get(case_name, "out-not"), test_out, boolean_func=_inversion) + _report_result(config.get(case_name, "in"), config.get(case_name, "out"), test_out) def _main(): @@ -120,7 +107,7 @@ def _main(): server = _spawn([python, "-m", "lacre.daemon"]) - for case_no in range(1, conf.getint("tests", "cases") + 1): + for case_no in range(1, conf.getint("tests", "cases")): _execute_case(conf, case_name=f"case-{case_no}") _interrupt(server) diff --git a/test/e2e.ini b/test/e2e.ini index e27eafc..a704dcc 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -30,7 +30,7 @@ certs: test/certs [tests] # Number of "test-*" sections in this file, describing test cases. -cases: 9 +cases: 8 e2e_log: test/logs/e2e.log e2e_log_format: %(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s e2e_log_datefmt: %Y-%m-%d %H:%M:%S @@ -84,9 +84,3 @@ descr: Clear text message to address with delimiter and a user with an Ed25519 k to: bob@disposlab in: test/msgin/clear2ed-delim.msg out: -----BEGIN PGP MESSAGE----- - -[case-9] -descr: Clear text message with inline PGP markers to recipient without a key. -to: carlos@disposlab -in: test/msgin/with-markers2clear.msg -out-not: This message includes inline PGP markers. diff --git a/test/e2e_test.py b/test/e2e_test.py index 7d0255d..0cd044f 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -81,24 +81,14 @@ def _load_file(name): return bytes(contents, 'utf-8') -def _identity(x): - return x - - -def _inversion(x): - return not(x) - - -def _report_result(message_file, expected, test_output, boolean_func=_identity): +def _report_result(message_file, expected, test_output): status = None - expected_line = "\r\n" + expected # + "\r\n" - cond_met = boolean_func(expected_line in test_output) - if cond_met: + if expected in test_output: status = "Success" else: status = "Failure" - print(message_file.ljust(35), status) + print(message_file.ljust(30), status) def _execute_e2e_test(case_name, config, config_path): @@ -141,10 +131,7 @@ def _execute_e2e_test(case_name, config, config_path): logging.debug(f"Read {len(testout)} characters of test output: '{testout}'") - if 'out' in config[case_name]: - _report_result(config.get(case_name, "in"), config.get(case_name, "out"), testout) - else: - _report_result(config.get(case_name, "in"), config.get(case_name, "out-not"), testout, boolean_func=_inversion) + _report_result(config.get(case_name, "in"), config.get(case_name, "out"), testout) def _load_test_config(): @@ -167,12 +154,12 @@ logging.basicConfig(filename = config.get("tests", "e2e_log"), config_path = os.getcwd() + "/" + CONFIG_FILE _write_test_config(config_path, - port = config.get("relay", "port"), - gpg_keyhome = config.get("dirs", "keys"), - smime_certpath = config.get("dirs", "certs"), - log_config = config.get("tests", "log_config")) + port = config.get("relay", "port"), + gpg_keyhome = config.get("dirs", "keys"), + smime_certpath = config.get("dirs", "certs"), + log_config = config.get("tests", "log_config")) -for case_no in range(1, config.getint("tests", "cases") + 1): +for case_no in range(1, config.getint("tests", "cases")+1): case_name = f"case-{case_no}" logging.info(f"Executing {case_name}: {config.get(case_name, 'descr')}") diff --git a/test/msgin/clear2clear.msg b/test/msgin/clear2clear.msg index d24c2de..ae577d1 100644 --- a/test/msgin/clear2clear.msg +++ b/test/msgin/clear2clear.msg @@ -1,5 +1,5 @@ -From: Dave -To: Carlos -Subject: Test - -Body of the message. +From: Dave +To: Carlos +Subject: Test + +Body of the message. diff --git a/test/msgin/clear2ed-delim.msg b/test/msgin/clear2ed-delim.msg index 6cf66bd..3fa6a32 100644 --- a/test/msgin/clear2ed-delim.msg +++ b/test/msgin/clear2ed-delim.msg @@ -1,5 +1,5 @@ -From: Dave -To: Bob -Subject: Test - -Body of the message. +From: Dave +To: Bob +Subject: Test + +Body of the message. diff --git a/test/msgin/clear2ed.msg b/test/msgin/clear2ed.msg index 38a708d..63b6c66 100644 --- a/test/msgin/clear2ed.msg +++ b/test/msgin/clear2ed.msg @@ -1,5 +1,5 @@ -From: Dave -To: Bob -Subject: Test - -Body of the message. +From: Dave +To: Bob +Subject: Test + +Body of the message. diff --git a/test/msgin/clear2rsa.msg b/test/msgin/clear2rsa.msg index a42bd7e..9dfd134 100644 --- a/test/msgin/clear2rsa.msg +++ b/test/msgin/clear2rsa.msg @@ -1,5 +1,5 @@ -From: Dave -To: Alice -Subject: Test - -Body of the message. +From: Dave +To: Alice +Subject: Test + +Body of the message. diff --git a/test/msgin/clear2rsa2.msg b/test/msgin/clear2rsa2.msg index 44c707f..0b7dcf2 100644 --- a/test/msgin/clear2rsa2.msg +++ b/test/msgin/clear2rsa2.msg @@ -1,6 +1,6 @@ -From: Dave -Subject: Test -Content-Type: text/plain; charset="utf-8" - -Body of the message. +From: Dave +Subject: Test +Content-Type: text/plain; charset="utf-8" + +Body of the message. diff --git a/test/msgin/ed2ed.msg b/test/msgin/ed2ed.msg index 695a222..90b73a8 100644 --- a/test/msgin/ed2ed.msg +++ b/test/msgin/ed2ed.msg @@ -1,13 +1,13 @@ -From: Dave -To: Bob -Subject: Test -Content-Transfer-Encoding: 7bit - ------BEGIN PGP MESSAGE----- - -hF4DujWCoRS24dYSAQdAyGDF9Us11JDr8+XPmvlJHsMS7A4UBIcCiresJyZpSxYw -Cqcugy5AX5fgSAiL1Cd2b1zpQ/rYdTWkFYMVbH4jBEoPC3z/aSd+hTnneJFDUdXl -0koBDIw7NQylu6SrW+Y/DmXgalIHtwACuKivJTq/z9jdwFScV7adRR/VO53Inah3 -L1+Ho7Zta95AYW3UPu71Gw3rrkfjY4uGDiFAFg== -=yTzD ------END PGP MESSAGE----- +From: Dave +To: Bob +Subject: Test +Content-Transfer-Encoding: 7bit + +-----BEGIN PGP MESSAGE----- + +hF4DujWCoRS24dYSAQdAyGDF9Us11JDr8+XPmvlJHsMS7A4UBIcCiresJyZpSxYw +Cqcugy5AX5fgSAiL1Cd2b1zpQ/rYdTWkFYMVbH4jBEoPC3z/aSd+hTnneJFDUdXl +0koBDIw7NQylu6SrW+Y/DmXgalIHtwACuKivJTq/z9jdwFScV7adRR/VO53Inah3 +L1+Ho7Zta95AYW3UPu71Gw3rrkfjY4uGDiFAFg== +=yTzD +-----END PGP MESSAGE----- diff --git a/test/msgin/multipart2rsa.msg b/test/msgin/multipart2rsa.msg index fb81f85..3b8f623 100644 --- a/test/msgin/multipart2rsa.msg +++ b/test/msgin/multipart2rsa.msg @@ -1,43 +1,43 @@ -Date: Sun, 18 Jul 2021 16:53:45 +0200 -From: User Alice -To: User Bob -Subject: encrypted -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; - boundary="95hZs/zeBetwhuEy" -Content-Disposition: inline -Status: RO -Content-Length: 1140 -Lines: 30 - - ---95hZs/zeBetwhuEy -Content-Type: application/pgp-encrypted -Content-Disposition: attachment - -Version: 1 - ---95hZs/zeBetwhuEy -Content-Type: application/octet-stream -Content-Disposition: attachment; filename="msg.asc" - ------BEGIN PGP MESSAGE----- - -hQGMA/vsqjpkmZurAQwAnb+2kDPgVFWVLkafuzVJGqFWKNtdVsvk7I1zhzFw5Hsr -h4irSHcH0X0QjaHprNiMBDfIZaCx5VVsvGYLiu/iQkdVPXItugTpln8aAvDt8/Bp -Hse69tgG5S9o4fPK4K2bMjNdomclDdz51cu9NXYjk/6OtzVwcSypyEmxgw24Oo1+ -Q8KfZN9n6VTXGNlrV9KnAZYs/5aaSABTeC+cDvOcjDbPAmwDHYS3qsbITYoGHnEz -QfPIakYWPtPWkajhm4Z/iyEUSTeqew1/gAJ8sZnJpV0eg1Cr/44XgklZKFr8aJgk -SG8PkQxsyzAZklpwMSWdbb+t9a5nEKvky3zMpdmS1GE7ubTO7nQ1geUdBiv1UUNh -BY9d4nlGirqxX1MZUTGZidJgCy0365xbJSKkU0yFFW2uWtCKzJTEQBk3YZkNmnGH -h8BiVvMhQ8SxKBRPeH6Zb6HHlbcgkPvJAAI4VLqkZPCBvp9irmcdFGmrgCWLxzgk -sIjYGLA+ZuSXOKuAssXE0sAbASPAkUJRTIjzXFrCnr/MB3ZonESH01fsbsX+E/Qi -+2oLrgjjPHcPq76fvdO6fJP6c1pM8TlOoZKn/RkPm1llULtOn4n5JZJjeUA0F2ID -Te/U9i4YtcFZbuvw2bjeu8sAf77U6O3iTTBWkPWQT3H4YMskQc7lS1Mug6A9HL/n -TQvAwh2MIveYyEy/y/dKeFUbpSKxyOInhTg1XtYFiT8bzEF7OEJLU9GyF5oMs67d -o12uYlEnPhWz9oZp11aSdnyeADpVu6BQsPbwfTifcpajQSarH5sG8+rDSPju -=7CnH ------END PGP MESSAGE----- - ---95hZs/zeBetwhuEy-- +Date: Sun, 18 Jul 2021 16:53:45 +0200 +From: User Alice +To: User Bob +Subject: encrypted +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; + boundary="95hZs/zeBetwhuEy" +Content-Disposition: inline +Status: RO +Content-Length: 1140 +Lines: 30 + + +--95hZs/zeBetwhuEy +Content-Type: application/pgp-encrypted +Content-Disposition: attachment + +Version: 1 + +--95hZs/zeBetwhuEy +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="msg.asc" + +-----BEGIN PGP MESSAGE----- + +hQGMA/vsqjpkmZurAQwAnb+2kDPgVFWVLkafuzVJGqFWKNtdVsvk7I1zhzFw5Hsr +h4irSHcH0X0QjaHprNiMBDfIZaCx5VVsvGYLiu/iQkdVPXItugTpln8aAvDt8/Bp +Hse69tgG5S9o4fPK4K2bMjNdomclDdz51cu9NXYjk/6OtzVwcSypyEmxgw24Oo1+ +Q8KfZN9n6VTXGNlrV9KnAZYs/5aaSABTeC+cDvOcjDbPAmwDHYS3qsbITYoGHnEz +QfPIakYWPtPWkajhm4Z/iyEUSTeqew1/gAJ8sZnJpV0eg1Cr/44XgklZKFr8aJgk +SG8PkQxsyzAZklpwMSWdbb+t9a5nEKvky3zMpdmS1GE7ubTO7nQ1geUdBiv1UUNh +BY9d4nlGirqxX1MZUTGZidJgCy0365xbJSKkU0yFFW2uWtCKzJTEQBk3YZkNmnGH +h8BiVvMhQ8SxKBRPeH6Zb6HHlbcgkPvJAAI4VLqkZPCBvp9irmcdFGmrgCWLxzgk +sIjYGLA+ZuSXOKuAssXE0sAbASPAkUJRTIjzXFrCnr/MB3ZonESH01fsbsX+E/Qi ++2oLrgjjPHcPq76fvdO6fJP6c1pM8TlOoZKn/RkPm1llULtOn4n5JZJjeUA0F2ID +Te/U9i4YtcFZbuvw2bjeu8sAf77U6O3iTTBWkPWQT3H4YMskQc7lS1Mug6A9HL/n +TQvAwh2MIveYyEy/y/dKeFUbpSKxyOInhTg1XtYFiT8bzEF7OEJLU9GyF5oMs67d +o12uYlEnPhWz9oZp11aSdnyeADpVu6BQsPbwfTifcpajQSarH5sG8+rDSPju +=7CnH +-----END PGP MESSAGE----- + +--95hZs/zeBetwhuEy-- diff --git a/test/msgin/signed.msg b/test/msgin/signed.msg index eb76adf..917291e 100644 --- a/test/msgin/signed.msg +++ b/test/msgin/signed.msg @@ -1,39 +1,39 @@ -Date: Sun, 18 Jul 2021 12:08:41 +0200 -From: User Alice -To: User Bob -Subject: signed -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/signed; micalg=pgp-sha256; - protocol="application/pgp-signature"; boundary="U/XjR71RAixRcb28" -Content-Disposition: inline -Status: RO -Content-Length: 870 -Lines: 26 - - ---U/XjR71RAixRcb28 -Content-Type: text/plain; charset=us-ascii -Content-Disposition: inline - -A signed msg. - ---U/XjR71RAixRcb28 -Content-Type: application/pgp-signature; name="signature.asc" - ------BEGIN PGP SIGNATURE----- - -iQGzBAEBCAAdFiEEHNJFMI8JY9A46INXlzz02Th8RNcFAmDz/aUACgkQlzz02Th8 -RNdtOQv/ca8c51KoVq7CyPJUr54n4DEk/LlYniR0W51tL2a4rQxyF2AxqjdI8T4u -bT1+bqPNYgegesyCLokeZKqhLVtCH+UVOTdtUq5bB1J7ALuuVTOIdR5woMBBsazV -ETYEMzL6y2sGPW92ynriEw6B9pPnFKFPhOOZLrnMzM8CpkTfNmGoej+EdV74s0z4 -RayKu/WaZ1Dtx2Vy2YDtG36p/Y3n62bnzQJCRyPYfrmCxH5X5i5oibQwxLROCFNE -4X3iVZLPHFg/DS9m4L7mBe0MJewGa1oPFr7t3ZfJ+24aJ/AvUv5uQIO+s6a7AcjD -Pgw/IjeM/uZdPrzniZI2zsWEgsjRCL1fj49XWVNkTHrWCqLvkBg+suucNO2SR0/d -ps+RP5mkJJHaSZyPpxwo9/PHKX67Mkpn/uEXlE8nV6IqKoXRzr1N0qwyhvbZQZLD -FMumxx/eOSiOpaiRhGhoZiUpf+VdnV/1ClpAcdbthy/psx/CMYVblAM8xg74NR9+ -Q/WlFbRl -=uMdE ------END PGP SIGNATURE----- - ---U/XjR71RAixRcb28-- +Date: Sun, 18 Jul 2021 12:08:41 +0200 +From: User Alice +To: User Bob +Subject: signed +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/signed; micalg=pgp-sha256; + protocol="application/pgp-signature"; boundary="U/XjR71RAixRcb28" +Content-Disposition: inline +Status: RO +Content-Length: 870 +Lines: 26 + + +--U/XjR71RAixRcb28 +Content-Type: text/plain; charset=us-ascii +Content-Disposition: inline + +A signed msg. + +--U/XjR71RAixRcb28 +Content-Type: application/pgp-signature; name="signature.asc" + +-----BEGIN PGP SIGNATURE----- + +iQGzBAEBCAAdFiEEHNJFMI8JY9A46INXlzz02Th8RNcFAmDz/aUACgkQlzz02Th8 +RNdtOQv/ca8c51KoVq7CyPJUr54n4DEk/LlYniR0W51tL2a4rQxyF2AxqjdI8T4u +bT1+bqPNYgegesyCLokeZKqhLVtCH+UVOTdtUq5bB1J7ALuuVTOIdR5woMBBsazV +ETYEMzL6y2sGPW92ynriEw6B9pPnFKFPhOOZLrnMzM8CpkTfNmGoej+EdV74s0z4 +RayKu/WaZ1Dtx2Vy2YDtG36p/Y3n62bnzQJCRyPYfrmCxH5X5i5oibQwxLROCFNE +4X3iVZLPHFg/DS9m4L7mBe0MJewGa1oPFr7t3ZfJ+24aJ/AvUv5uQIO+s6a7AcjD +Pgw/IjeM/uZdPrzniZI2zsWEgsjRCL1fj49XWVNkTHrWCqLvkBg+suucNO2SR0/d +ps+RP5mkJJHaSZyPpxwo9/PHKX67Mkpn/uEXlE8nV6IqKoXRzr1N0qwyhvbZQZLD +FMumxx/eOSiOpaiRhGhoZiUpf+VdnV/1ClpAcdbthy/psx/CMYVblAM8xg74NR9+ +Q/WlFbRl +=uMdE +-----END PGP SIGNATURE----- + +--U/XjR71RAixRcb28-- diff --git a/test/msgin/with-markers2clear.msg b/test/msgin/with-markers2clear.msg deleted file mode 100644 index 729aa82..0000000 --- a/test/msgin/with-markers2clear.msg +++ /dev/null @@ -1,11 +0,0 @@ -From: Dave -To: Carlos -Subject: Test - -This message includes inline PGP markers. -It's enough to include these two lines: - ------BEGIN PGP MESSAGE----- ------END PGP MESSAGE----- - -Test logs will give a hint which path this message takes. diff --git a/webgate-cron.py b/webgate-cron.py index 9f1dcba..980ee8a 100755 --- a/webgate-cron.py +++ b/webgate-cron.py @@ -94,7 +94,7 @@ def _define_db_schema(): conf.load_config() lacre.init_logging(conf.get_item('logging', 'config')) -LOG = logging.getLogger('webgate-cron.py') +LOG = logging.getLogger(__name__) if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_set('database', 'url'): @@ -107,48 +107,48 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se LOG.debug(f"Retrieving keys to be processed: {selq}") result_set = conn.execute(selq) - for key_id, row_id, email in result_set: + for row in result_set: # delete any other public keys associated with this confirmed email address - delq = delete(gpgmw_keys).where(and_(gpgmw_keys.c.email == email, gpgmw_keys.c.id != row_id)) + delq = delete(gpgmw_keys).where(and_(gpgmw_keys.c.email == row[2], gpgmw_keys.c.id != row[1])) LOG.debug(f"Deleting public keys associated with confirmed email: {delq}") conn.execute(delq) - GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), email) - LOG.info('Deleted key for <' + email + '> via import request') + GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[2]) + LOG.info('Deleted key for <' + row[2] + '> via import request') - if key_id.strip(): # we have this so that user can submit blank key to remove any encryption - if GnuPG.confirm_key(key_id, email): - GnuPG.add_key(conf.get_item('gpg', 'keyhome'), key_id) # import the key to gpg - modq = gpgmw_keys.update().where(gpgmw_keys.c.id == row_id).values(status=1) + if row[0].strip(): # we have this so that user can submit blank key to remove any encryption + if GnuPG.confirm_key(row[0], row[2]): + GnuPG.add_key(conf.get_item('gpg', 'keyhome'), row[0]) # import the key to gpg + modq = gpgmw_keys.update().where(gpgmw_keys.c.id == row[1]).values(status=1) LOG.debug(f"Key imported, updating key: {modq}") conn.execute(modq) # mark key as accepted - LOG.warning('Imported key from <' + email + '>') + LOG.warning('Imported key from <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): - _send_msg("PGP key registration successful", "registrationSuccess.md", email) + _send_msg("PGP key registration successful", "registrationSuccess.md", row[2]) else: - delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row_id) + delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) LOG.debug(f"Cannot confirm key, deleting it: {delq}") conn.execute(delq) # delete key - LOG.warning('Import confirmation failed for <' + email + '>') + LOG.warning('Import confirmation failed for <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): - _send_msg("PGP key registration failed", "registrationError.md", email) + _send_msg("PGP key registration failed", "registrationError.md", row[2]) else: # delete key so we don't continue processing it - delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row_id) + delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) LOG.debug(f"Deleting key: {delq}") conn.execute(delq) if conf.config_item_equals('cron', 'send_email', 'yes'): - _send_msg("PGP key deleted", "keyDeleted.md", email) + _send_msg("PGP key deleted", "keyDeleted.md", row[2]) # delete keys stat2q = select(gpgmw_keys.c.email, gpgmw_keys.c.id).where(gpgmw_keys.c.status == 2).limit(100) stat2_result_set = conn.execute(stat2q) - for email, row_id in stat2_result_set: - GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), email) - delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row_id) + for row in stat2_result_set: + GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[0]) + delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) LOG.debug(f"Deleting keys that have already been processed: {delq}") conn.execute(delq) - LOG.info('Deleted key for <' + email + '>') + LOG.info('Deleted key for <' + row[0] + '>') else: print("Warning: doing nothing since database settings are not configured!") LOG.error("Warning: doing nothing since database settings are not configured!")