Merge pull request 'Improve code quality' (#103) from rc2-improvements into main
Reviewed-on: #103 Reviewed-by: muppeth <muppeth@no-reply@disroot.org>
This commit is contained in:
commit
c3cc37bf56
21 changed files with 292 additions and 224 deletions
|
@ -26,28 +26,29 @@ import logging
|
|||
import lacre
|
||||
import lacre.config as conf
|
||||
|
||||
start = time.time()
|
||||
start = time.process_time()
|
||||
conf.load_config()
|
||||
lacre.init_logging(conf.get_item('logging', 'config'))
|
||||
|
||||
# This has to be executed *after* logging initialisation.
|
||||
import lacre.mailgate as mailgate
|
||||
import lacre.core as core
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG = logging.getLogger('gpg-mailgate.py')
|
||||
|
||||
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
|
||||
# Read e-mail from stdin, parse it
|
||||
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
|
||||
mailgate.deliver_message(raw_message, from_addr, to_addrs)
|
||||
(elapsed_s, process_t) = mailgate.exec_time_info(start)
|
||||
core.deliver_message(raw_message, from_addr, to_addrs)
|
||||
process_t = (time.process_time() - start) * 1000
|
||||
|
||||
LOG.info("Elapsed-time: {elapsed:.2f}s; Process-time: {process:.4f}s".format(elapsed=elapsed_s, process=process_t))
|
||||
LOG.info("Message delivered in {process:.2f} ms".format(process=process_t))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Lacre configuration
|
||||
"""Lacre configuration.
|
||||
|
||||
Routines defined here are responsible for processing configuration.
|
||||
Routines defined here are responsible for processing and validating
|
||||
configuration.
|
||||
"""
|
||||
|
||||
from configparser import RawConfigParser
|
||||
|
@ -18,7 +19,8 @@ CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG"
|
|||
MANDATORY_CONFIG_ITEMS = [("relay", "host"),
|
||||
("relay", "port"),
|
||||
("daemon", "host"),
|
||||
("daemon", "port")]
|
||||
("daemon", "port"),
|
||||
("gpg", "keyhome")]
|
||||
|
||||
# Global dict to keep configuration parameters. It's hidden behind several
|
||||
# utility functions to make it easy to replace it with ConfigParser object in
|
||||
|
@ -38,6 +40,9 @@ 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
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
"""Lacre's actual mail-delivery module.
|
||||
|
||||
IMPORTANT: This module has to be loaded _after_ initialisation of the logging
|
||||
module.
|
||||
"""
|
||||
|
||||
#
|
||||
# gpg-mailgate
|
||||
#
|
||||
|
@ -23,6 +17,12 @@ module.
|
|||
# along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""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,7 +32,6 @@ import GnuPG
|
|||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
# imports for S/MIME
|
||||
|
@ -286,7 +285,7 @@ def _encrypt_all_payloads_inline(message, gpg_to_cmdline):
|
|||
return encrypted_payloads
|
||||
|
||||
|
||||
def _encrypt_all_payloads_mime(message, gpg_to_cmdline):
|
||||
def _encrypt_all_payloads_mime(message: email.message.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)
|
||||
|
@ -309,7 +308,7 @@ def _encrypt_all_payloads_mime(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
|
||||
(base, encoding) = text.parse_content_type(message['Content-Type'])
|
||||
encoding = message.get_content_charset(sys.getdefaultencoding())
|
||||
LOG.debug(f"Identified encoding as {encoding}")
|
||||
encrypted_part.set_payload(additionalSubHeader+text.EOL + message.get_payload(decode=True).decode(encoding))
|
||||
check_nested = True
|
||||
|
@ -336,7 +335,7 @@ def _encrypt_all_payloads_mime(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_pgp_inline(raw_payload):
|
||||
if check_nested and text.is_payload_pgp_inline(raw_payload):
|
||||
LOG.debug("Message is already pgp encrypted. No nested encryption needed.")
|
||||
return payload
|
||||
|
||||
|
@ -383,7 +382,7 @@ def _smime_encrypt(raw_message, recipients):
|
|||
unsmime_to = list()
|
||||
|
||||
for addr in recipients:
|
||||
cert_and_email = _get_cert_for_email(addr[0], cert_path)
|
||||
cert_and_email = _get_cert_for_email(addr, cert_path)
|
||||
|
||||
if not (cert_and_email is None):
|
||||
(to_cert, normal_email) = cert_and_email
|
||||
|
@ -493,7 +492,7 @@ def send_msg(message: str, recipients, fromaddr=None):
|
|||
LOG.info("No recipient found")
|
||||
|
||||
|
||||
def _is_encrypted(raw_message):
|
||||
def _is_encrypted(raw_message: email.message.Message):
|
||||
if raw_message.get_content_type() == 'multipart/encrypted':
|
||||
return True
|
||||
|
||||
|
@ -501,12 +500,15 @@ def _is_encrypted(raw_message):
|
|||
if first_part.get_content_type() == 'application/pkcs7-mime':
|
||||
return True
|
||||
|
||||
first_payload = first_part.get_payload(decode=True)
|
||||
return text.is_pgp_inline(first_payload)
|
||||
return text.is_message_pgp_inline(first_part)
|
||||
|
||||
|
||||
def delivery_plan(recipients, key_cache: kcache.KeyCache):
|
||||
def delivery_plan(recipients, message: email.message.Message, 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 = \
|
||||
|
@ -532,7 +534,8 @@ 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
|
||||
|
||||
recipients_left = [text.sanitize_case_sense(recipient) for recipient in to_addrs]
|
||||
sanitize = text.choose_sanitizer(conf.get_item('default', 'mail_case_insensitive'))
|
||||
recipients_left = [sanitize(recipient) for recipient in to_addrs]
|
||||
|
||||
# There is no need for nested encryption
|
||||
LOG.debug("Seeing if it's already encrypted")
|
||||
|
@ -556,10 +559,3 @@ 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)
|
|
@ -16,15 +16,14 @@ 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(__name__)
|
||||
LOG = logging.getLogger('lacre.daemon')
|
||||
|
||||
import lacre.mailgate as gate
|
||||
import lacre.core as gate
|
||||
import lacre.keyring as kcache
|
||||
|
||||
|
||||
|
@ -41,7 +40,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, keys):
|
||||
for operation in gate.delivery_plan(envelope.rcpt_tos, message, keys):
|
||||
LOG.debug(f"Sending mail via {operation!r}")
|
||||
new_message = operation.perform(message)
|
||||
gate.send_msg(new_message, operation.recipients(), envelope.mail_from)
|
||||
|
@ -103,6 +102,8 @@ 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()
|
||||
|
|
|
@ -5,9 +5,10 @@ module.
|
|||
"""
|
||||
|
||||
import lacre.text as text
|
||||
import lacre.config as conf
|
||||
import logging
|
||||
from os import stat
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
||||
from asyncio import Semaphore, run
|
||||
import copy
|
||||
|
||||
|
@ -17,10 +18,8 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _sanitize(keys):
|
||||
for fingerprint in keys:
|
||||
keys[fingerprint] = text.sanitize_case_sense(keys[fingerprint])
|
||||
|
||||
return keys
|
||||
sanitize = text.choose_sanitizer(conf.get_item('default', 'mail_case_insensitive'))
|
||||
return {fingerprint: sanitize(keys[fingerprint]) for fingerprint in keys}
|
||||
|
||||
|
||||
class KeyCacheMisconfiguration(Exception):
|
||||
|
@ -118,6 +117,7 @@ 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,10 +141,11 @@ class KeyringModificationListener(FileSystemEventHandler):
|
|||
"""Initialise a listener with a callback to be executed upon each change."""
|
||||
self._keyring = keyring
|
||||
|
||||
def handle(self, event):
|
||||
def handle(self, event: FileSystemEvent):
|
||||
"""Reload keys upon FS event."""
|
||||
LOG.debug(f'Reloading on event {event!r}')
|
||||
self._keyring.reload()
|
||||
if 'pubring.kbx' in event.src_path:
|
||||
LOG.debug(f'Reloading on event {event!r}')
|
||||
self._keyring.reload()
|
||||
|
||||
# All methods should do the same: reload the key cache.
|
||||
# on_created = handle
|
||||
|
|
|
@ -14,7 +14,7 @@ There are 3 operations available:
|
|||
"""
|
||||
|
||||
import logging
|
||||
import lacre.mailgate as mailgate
|
||||
import lacre.core as core
|
||||
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 mailgate._gpg_encrypt_and_return(msg,
|
||||
self._keys, self._recipients,
|
||||
mailgate._encrypt_all_payloads_inline)
|
||||
return core._gpg_encrypt_and_return(msg,
|
||||
self._keys, self._recipients,
|
||||
core._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 mailgate._gpg_encrypt_and_return(msg,
|
||||
self._keys, self._recipients,
|
||||
mailgate._encrypt_all_payloads_mime)
|
||||
return core._gpg_encrypt_and_return(msg,
|
||||
self._keys, self._recipients,
|
||||
core._encrypt_all_payloads_mime)
|
||||
|
||||
|
||||
class SMimeEncrypt(MailOperation):
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
"""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 = b"-----BEGIN PGP MESSAGE-----"
|
||||
PGP_INLINE_END = b"-----END PGP MESSAGE-----"
|
||||
PGP_INLINE_BEGIN = EOL_BYTES + b"-----BEGIN PGP MESSAGE-----" + EOL_BYTES
|
||||
PGP_INLINE_END = EOL_BYTES + b"-----END PGP MESSAGE-----" + EOL_BYTES
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_content_type(content_type):
|
||||
def parse_content_type(content_type: str):
|
||||
"""Analyse Content-Type email header.
|
||||
|
||||
Return a pair: type and sub-type.
|
||||
|
@ -49,19 +52,36 @@ def parse_delimiter(address: str):
|
|||
return (address, None)
|
||||
|
||||
|
||||
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()
|
||||
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()
|
||||
else:
|
||||
splitted_address = address.split('@')
|
||||
if len(splitted_address) > 1:
|
||||
address = splitted_address[0] + '@' + splitted_address[1].lower()
|
||||
|
||||
return address
|
||||
return address
|
||||
|
||||
|
||||
def is_pgp_inline(payload) -> bool:
|
||||
"""Find out if the payload (bytes) contains PGP/INLINE markers."""
|
||||
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."""
|
||||
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))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
aiosmtpd==1.4.2
|
||||
SQLAlchemy==1.4.32
|
||||
Markdown==3.4.1
|
||||
M2Crypto==0.38.0
|
||||
|
|
|
@ -65,14 +65,24 @@ def _load_test_config():
|
|||
return cp
|
||||
|
||||
|
||||
def _report_result(message_file, expected, test_output):
|
||||
def _identity(x):
|
||||
return x
|
||||
|
||||
|
||||
def _inversion(x):
|
||||
return not(x)
|
||||
|
||||
|
||||
def _report_result(message_file, expected, test_output, boolean_func=_identity):
|
||||
status = None
|
||||
if expected in test_output:
|
||||
expected_line = "\r\n" + expected # + "\r\n"
|
||||
cond_met = boolean_func(expected_line in test_output)
|
||||
if cond_met:
|
||||
status = "Success"
|
||||
else:
|
||||
status = "Failure"
|
||||
|
||||
print(message_file.ljust(30), status)
|
||||
print(message_file.ljust(35), status)
|
||||
|
||||
|
||||
def _execute_case(config, case_name):
|
||||
|
@ -91,7 +101,10 @@ 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}'")
|
||||
|
||||
_report_result(config.get(case_name, "in"), config.get(case_name, "out"), 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)
|
||||
|
||||
|
||||
def _main():
|
||||
|
@ -107,7 +120,7 @@ def _main():
|
|||
|
||||
server = _spawn([python, "-m", "lacre.daemon"])
|
||||
|
||||
for case_no in range(1, conf.getint("tests", "cases")):
|
||||
for case_no in range(1, conf.getint("tests", "cases") + 1):
|
||||
_execute_case(conf, case_name=f"case-{case_no}")
|
||||
|
||||
_interrupt(server)
|
||||
|
|
|
@ -30,7 +30,7 @@ certs: test/certs
|
|||
|
||||
[tests]
|
||||
# Number of "test-*" sections in this file, describing test cases.
|
||||
cases: 8
|
||||
cases: 9
|
||||
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,3 +84,9 @@ 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.
|
||||
|
|
|
@ -81,14 +81,24 @@ def _load_file(name):
|
|||
return bytes(contents, 'utf-8')
|
||||
|
||||
|
||||
def _report_result(message_file, expected, test_output):
|
||||
def _identity(x):
|
||||
return x
|
||||
|
||||
|
||||
def _inversion(x):
|
||||
return not(x)
|
||||
|
||||
|
||||
def _report_result(message_file, expected, test_output, boolean_func=_identity):
|
||||
status = None
|
||||
if expected in test_output:
|
||||
expected_line = "\r\n" + expected # + "\r\n"
|
||||
cond_met = boolean_func(expected_line in test_output)
|
||||
if cond_met:
|
||||
status = "Success"
|
||||
else:
|
||||
status = "Failure"
|
||||
|
||||
print(message_file.ljust(30), status)
|
||||
print(message_file.ljust(35), status)
|
||||
|
||||
|
||||
def _execute_e2e_test(case_name, config, config_path):
|
||||
|
@ -131,7 +141,10 @@ def _execute_e2e_test(case_name, config, config_path):
|
|||
|
||||
logging.debug(f"Read {len(testout)} characters of test output: '{testout}'")
|
||||
|
||||
_report_result(config.get(case_name, "in"), config.get(case_name, "out"), 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)
|
||||
|
||||
|
||||
def _load_test_config():
|
||||
|
@ -154,12 +167,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')}")
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Carlos <carlos@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
From: Dave <dave@localhost>
|
||||
To: Carlos <carlos@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Bob <bob+foobar@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
From: Dave <dave@localhost>
|
||||
To: Bob <bob+foobar@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Bob <bob@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
From: Dave <dave@localhost>
|
||||
To: Bob <bob@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Alice <alice@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
From: Dave <dave@localhost>
|
||||
To: Alice <alice@localhost>
|
||||
Subject: Test
|
||||
|
||||
Body of the message.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
From: Dave <dave@disposlab
|
||||
To: Evan <evan@disposlab>
|
||||
Subject: Test
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
|
||||
Body of the message.
|
||||
From: Dave <dave@disposlab
|
||||
To: Evan <evan@disposlab>
|
||||
Subject: Test
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
|
||||
Body of the message.
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Bob <bob@localhost>
|
||||
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 <dave@localhost>
|
||||
To: Bob <bob@localhost>
|
||||
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-----
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
Date: Sun, 18 Jul 2021 16:53:45 +0200
|
||||
From: User Alice <alice@disposlab>
|
||||
To: User Bob <bob@disposlab>
|
||||
Subject: encrypted
|
||||
Message-ID: <YPRAeEEc3z2M9BCy@disposlab>
|
||||
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 <alice@disposlab>
|
||||
To: User Bob <bob@disposlab>
|
||||
Subject: encrypted
|
||||
Message-ID: <YPRAeEEc3z2M9BCy@disposlab>
|
||||
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--
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
Date: Sun, 18 Jul 2021 12:08:41 +0200
|
||||
From: User Alice <alice@disposlab>
|
||||
To: User Bob <bob@disposlab>
|
||||
Subject: signed
|
||||
Message-ID: <YPP9qer9j2u4qXsq@disposlab>
|
||||
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 <alice@disposlab>
|
||||
To: User Bob <bob@disposlab>
|
||||
Subject: signed
|
||||
Message-ID: <YPP9qer9j2u4qXsq@disposlab>
|
||||
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--
|
||||
|
|
11
test/msgin/with-markers2clear.msg
Normal file
11
test/msgin/with-markers2clear.msg
Normal file
|
@ -0,0 +1,11 @@
|
|||
From: Dave <dave@localhost>
|
||||
To: Carlos <carlos@localhost>
|
||||
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.
|
|
@ -94,7 +94,7 @@ def _define_db_schema():
|
|||
conf.load_config()
|
||||
|
||||
lacre.init_logging(conf.get_item('logging', 'config'))
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG = logging.getLogger('webgate-cron.py')
|
||||
|
||||
|
||||
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 row in result_set:
|
||||
for key_id, row_id, email in result_set:
|
||||
# delete any other public keys associated with this confirmed email address
|
||||
delq = delete(gpgmw_keys).where(and_(gpgmw_keys.c.email == row[2], gpgmw_keys.c.id != row[1]))
|
||||
delq = delete(gpgmw_keys).where(and_(gpgmw_keys.c.email == email, gpgmw_keys.c.id != row_id))
|
||||
LOG.debug(f"Deleting public keys associated with confirmed email: {delq}")
|
||||
conn.execute(delq)
|
||||
GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[2])
|
||||
LOG.info('Deleted key for <' + row[2] + '> via import request')
|
||||
GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), email)
|
||||
LOG.info('Deleted key for <' + email + '> via import request')
|
||||
|
||||
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)
|
||||
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)
|
||||
LOG.debug(f"Key imported, updating key: {modq}")
|
||||
conn.execute(modq) # mark key as accepted
|
||||
LOG.warning('Imported key from <' + row[2] + '>')
|
||||
LOG.warning('Imported key from <' + email + '>')
|
||||
if conf.config_item_equals('cron', 'send_email', 'yes'):
|
||||
_send_msg("PGP key registration successful", "registrationSuccess.md", row[2])
|
||||
_send_msg("PGP key registration successful", "registrationSuccess.md", email)
|
||||
else:
|
||||
delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1])
|
||||
delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row_id)
|
||||
LOG.debug(f"Cannot confirm key, deleting it: {delq}")
|
||||
conn.execute(delq) # delete key
|
||||
LOG.warning('Import confirmation failed for <' + row[2] + '>')
|
||||
LOG.warning('Import confirmation failed for <' + email + '>')
|
||||
if conf.config_item_equals('cron', 'send_email', 'yes'):
|
||||
_send_msg("PGP key registration failed", "registrationError.md", row[2])
|
||||
_send_msg("PGP key registration failed", "registrationError.md", email)
|
||||
else:
|
||||
# delete key so we don't continue processing it
|
||||
delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1])
|
||||
delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row_id)
|
||||
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", row[2])
|
||||
_send_msg("PGP key deleted", "keyDeleted.md", email)
|
||||
|
||||
# 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 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])
|
||||
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)
|
||||
LOG.debug(f"Deleting keys that have already been processed: {delq}")
|
||||
conn.execute(delq)
|
||||
LOG.info('Deleted key for <' + row[0] + '>')
|
||||
LOG.info('Deleted key for <' + email + '>')
|
||||
else:
|
||||
print("Warning: doing nothing since database settings are not configured!")
|
||||
LOG.error("Warning: doing nothing since database settings are not configured!")
|
||||
|
|
Loading…
Reference in a new issue