From d90b50f7e7b5769dff3f8cb62b9d30fc7245bcf3 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 25 Feb 2022 23:38:08 +0100 Subject: [PATCH 01/12] Extract config, separate logging, split into smaller functions - Move configuration-processing code to a separate module (lacre.config) and provide a simple API to access configuration parameters. - Prepare to use builtin logging module to log diagnostic data. - Rework the configuration-processing file to make it cleaner. - Log additional information while processing configuration. - Reorder functions. --- gpg-mailgate.py | 98 +++++++++++++++++++---------------------------- lacre/__init__.py | 16 ++++++++ lacre/config.py | 73 +++++++++++++++++++++++++++++++++++ test/relay.py | 17 ++++++++ 4 files changed, 146 insertions(+), 58 deletions(-) create mode 100644 lacre/__init__.py create mode 100644 lacre/config.py diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 02e1c80..dabb98d 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -39,30 +39,20 @@ import traceback from M2Crypto import BIO, Rand, SMIME, X509 from email.mime.message import MIMEMessage -# Environment variable name we read to retrieve configuration path. This is to -# enable non-root users to set up and run GPG Mailgate and to make the software -# testable. -CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" - -# Read configuration from /etc/gpg-mailgate.conf -_cfg = RawConfigParser() -_cfg.read(os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf')) -cfg = dict() -for sect in _cfg.sections(): - cfg[sect] = dict() - for (name, value) in _cfg.items(sect): - cfg[sect][name] = value +import logging +import lacre +import lacre.config as conf def log( msg ): - if 'logging' in cfg and 'file' in cfg['logging']: - if cfg['logging'].get('file') == "syslog": + if conf.config_item_set("logging", "file"): + if conf.config_item_equals('logging', 'file', "syslog"): syslog.syslog(syslog.LOG_INFO | syslog.LOG_MAIL, msg) else: - logfile = open(cfg['logging']['file'], 'a') + logfile = open(conf.get_item('logging', 'file'), 'a') logfile.write(msg + "\n") logfile.close() -verbose = 'logging' in cfg and 'verbose' in cfg['logging'] and cfg['logging'].get('verbose') == 'yes' +verbose = conf.verbose_logging_enabled() # Read e-mail from stdin raw = sys.stdin.read() @@ -72,11 +62,11 @@ to_addrs = sys.argv[1:] def gpg_encrypt( raw_message, recipients ): - if not get_bool_from_cfg('gpg', 'keyhome'): + if not conf.config_item_set('gpg', 'keyhome'): log("No valid entry for gpg keyhome. Encryption aborted.") return recipients - keys = GnuPG.public_keys( cfg['gpg']['keyhome'] ) + keys = GnuPG.public_keys( conf.get_item('gpg', 'keyhome') ) for fingerprint in keys: keys[fingerprint] = sanitize_case_sense(keys[fingerprint]) @@ -86,17 +76,17 @@ def gpg_encrypt( raw_message, recipients ): for to in recipients: # Check if recipient is in keymap - if get_bool_from_cfg('enc_keymap', to): - log("Encrypt keymap has key '%s'" % cfg['enc_keymap'][to] ) + if conf.config_item_set('enc_keymap', to): + log("Encrypt keymap has key '%s'" % conf.get_item('enc_keymap', to) ) # Check we've got a matching key! - if cfg['enc_keymap'][to] in keys: - gpg_to.append( (to, cfg['enc_keymap'][to]) ) + if conf.get_item('enc_keymap', to) in keys: + gpg_to.append( (to, conf.get_item('enc_keymap', to)) ) continue else: - log("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (cfg['enc_keymap'][to], to)) + log("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to)) # Check if key in keychain is present - if to in keys.values() and not get_bool_from_cfg('default', 'enc_keymap_only', 'yes'): + if to in keys.values() and not conf.config_item_equals('default', 'enc_keymap_only', 'yes'): gpg_to.append( (to, to) ) continue @@ -104,15 +94,15 @@ def gpg_encrypt( raw_message, recipients ): splitted_to = to.split('@') if len(splitted_to) > 1: domain = splitted_to[1] - if get_bool_from_cfg('enc_domain_keymap', domain): - log("Encrypt domain keymap has key '%s'" % cfg['enc_dec_keymap'][domain] ) + if conf.config_item_set('enc_domain_keymap', domain): + log("Encrypt domain keymap has key '%s'" % conf.get_item('enc_dec_keymap', domain) ) # Check we've got a matching key! - if cfg['enc_domain_keymap'][domain] in keys: + if conf.get_item('enc_domain_keymap', domain) in keys: log("Using default domain key for recipient '%s'" % to) - gpg_to.append( (to, cfg['enc_domain_keymap'][domain]) ) + gpg_to.append( (to, conf.get_item('enc_domain_keymap', domain)) ) continue else: - log("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (cfg['enc_domain_keymap'][domain], to)) + log("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (conf.get_item('enc_domain_keymap', domain), to)) # At this point no key has been found if verbose: @@ -131,19 +121,19 @@ def gpg_encrypt( raw_message, recipients ): for rcpt in gpg_to: # Checking pre defined styles in settings first - if get_bool_from_cfg('pgp_style', rcpt[0], 'mime'): + if conf.config_item_equals('pgp_style', rcpt[0], 'mime'): gpg_to_smtp_mime.append(rcpt[0]) gpg_to_cmdline_mime.extend(rcpt[1].split(',')) - elif get_bool_from_cfg('pgp_style', rcpt[0], 'inline'): + elif conf.config_item_equals('pgp_style', rcpt[0], 'inline'): gpg_to_smtp_inline.append(rcpt[0]) gpg_to_cmdline_inline.extend(rcpt[1].split(',')) else: # Log message only if an unknown style is defined - 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 conf.config_item_set('pgp_style', rcpt[0]): + log("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("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'): + if conf.config_item_equals('default', 'mime_conversion', 'yes'): gpg_to_smtp_mime.append(rcpt[0]) gpg_to_cmdline_mime.extend(rcpt[1].split(',')) else: @@ -154,7 +144,7 @@ def gpg_encrypt( raw_message, recipients ): # Encrypt mail with PGP/MIME raw_message_mime = copy.deepcopy(raw_message) - if get_bool_from_cfg('default', 'add_header', 'yes'): + if conf.config_item_equals('default', 'add_header', 'yes'): raw_message_mime['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate' if 'Content-Transfer-Encoding' in raw_message_mime: @@ -171,7 +161,7 @@ def gpg_encrypt( raw_message, recipients ): # Encrypt mail with PGP/INLINE raw_message_inline = copy.deepcopy(raw_message) - if get_bool_from_cfg('default', 'add_header', 'yes'): + if conf.config_item_equals('default', 'add_header', 'yes'): raw_message_inline['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate' if 'Content-Transfer-Encoding' in raw_message_inline: @@ -254,8 +244,8 @@ def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ): log("Message is already pgp encrypted. No nested encryption needed.") return payload - # No check is needed for cfg['gpg']['keyhome'] as this is already done in method gpg_encrypt - gpg = GnuPG.GPGEncryptor( cfg['gpg']['keyhome'], gpg_to_cmdline, payload.get_content_charset() ) + # No check is needed for conf.get_item('gpg', 'keyhome') as this is already done in method gpg_encrypt + gpg = GnuPG.GPGEncryptor( conf.get_item('gpg', 'keyhome'), gpg_to_cmdline, payload.get_content_charset() ) gpg.update( raw_payload ) encrypted_data, returncode = gpg.encrypt() if verbose: @@ -282,11 +272,11 @@ def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ): def smime_encrypt( raw_message, recipients ): - if not get_bool_from_cfg('smime', 'cert_path'): + if not conf.config_item_set('smime', 'cert_path'): log("No valid path for S/MIME certs found in config file. S/MIME encryption aborted.") return recipients - cert_path = cfg['smime']['cert_path']+"/" + cert_path = conf.get_item('smime', 'cert_path')+"/" s = SMIME.SMIME() sk = X509.X509_Stack() smime_to = list() @@ -320,7 +310,7 @@ def smime_encrypt( raw_message, recipients ): if raw_message['Subject']: out.write('Subject: '+ raw_message['Subject'] + '\n') - if get_bool_from_cfg('default', 'add_header', 'yes'): + if conf.config_item_equals('default', 'add_header', 'yes'): out.write('X-GPG-Mailgate: Encrypted by GPG Mailgate\n') s.write(out, p7) @@ -343,7 +333,7 @@ def get_cert_for_email( to_addr, cert_path ): if not os.path.isfile(file_path): continue - if get_bool_from_cfg('default', 'mail_case_insensitive', 'yes'): + if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'): if filename.lower() == to_addr: return (file_path, to_addr) else: @@ -359,20 +349,9 @@ def get_cert_for_email( to_addr, cert_path ): return None -def get_bool_from_cfg( section, key = None, evaluation = None ): - - if not (key is None) and not (evaluation is None): - return section in cfg and cfg[section].get(key) == evaluation - - elif not (key is None) and (evaluation is None): - return section in cfg and not (cfg[section].get(key) is None) - - else: - return section in cfg - def sanitize_case_sense( address ): - if get_bool_from_cfg('default', 'mail_case_insensitive', 'yes'): + if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'): address = address.lower() else: if isinstance(address, str): @@ -409,13 +388,13 @@ def send_msg( message, recipients ): recipients = [_f for _f in recipients if _f] if recipients: - if not (get_bool_from_cfg('relay', 'host') and get_bool_from_cfg('relay', 'port')): + if not (conf.config_item_set('relay', 'host') and conf.config_item_set('relay', 'port')): log("Missing settings for relay. Sending email aborted.") return None log("Sending email to: <%s>" % '> <'.join( recipients )) - relay = (cfg['relay']['host'], int(cfg['relay']['port'])) + relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'port'))) smtp = smtplib.SMTP(relay[0], relay[1]) - if 'relay' in cfg and 'starttls' in cfg['relay'] and cfg['relay']['starttls'] == 'yes': + if conf.config_item_equals('relay', 'starttls', 'yes'): smtp.starttls() smtp.sendmail( from_addr, recipients, message ) else: @@ -462,5 +441,8 @@ def sort_recipients( raw_message, from_addr, to_addrs ): send_msg(raw_message.as_string(), recipients_left) +conf.load_config() +lacre.init_logging(conf.get_item('logging', 'file')) + # Let's start sort_recipients(raw_message, from_addr, to_addrs) diff --git a/lacre/__init__.py b/lacre/__init__.py new file mode 100644 index 0000000..2582b89 --- /dev/null +++ b/lacre/__init__.py @@ -0,0 +1,16 @@ +"""Lacre --- the Postfix mail filter encrypting incoming email +""" + +import logging + +# Initialise logging infrastructure. +# +# This is going to be removed at some point of time, but is used at the moment +# because it's easy and keeps the rest of the code quite clean. + +def init_logging(log_filename): + logging.basicConfig(filename = log_filename, + format = '%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', + datefmt = '%Y-%m-%d %H:%M:%S', + level = logging.DEBUG) + logging.info("Starting logs from lacre module...") diff --git a/lacre/config.py b/lacre/config.py new file mode 100644 index 0000000..d1bf6fc --- /dev/null +++ b/lacre/config.py @@ -0,0 +1,73 @@ +"""Lacre configuration + +Routines defined here are responsible for processing configuration. +""" + +from configparser import RawConfigParser + +import os + +import logging + +# Environment variable name we read to retrieve configuration path. This is to +# enable non-root users to set up and run GPG Mailgate and to make the software +# testable. +CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" + +# Global dict to keep configuration parameters. It's hidden behind several +# utility functions to make it easy to replace it with ConfigParser object in +# the future. +cfg = dict() + +def load_config() -> dict: + """Parses configuration file. + + If environment variable identified by CONFIG_PATH_ENV + variable is set, its value is taken as a configuration file + path. Otherwise, the default is taken + ('/etc/gpg-mailgate.conf'). + """ + configFile = os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf') + + logging.debug(f"Loading configuration file: {configFile}") + parser = read_config(configFile) + + global cfg + cfg = copy_to_dict(parser) + return cfg + +def read_config(fileName) -> RawConfigParser: + cp = RawConfigParser() + cp.read(fileName) + + return cp + +def copy_to_dict(confParser) -> dict: + config = dict() + + for sect in confParser.sections(): + config[sect] = dict() + for (name, value) in confParser.items(sect): + config[sect][name] = value + + return config + +def get_item(section, key): + global cfg + return cfg[section][key] + +def has_section(section) -> bool: + global cfg + return section in cfg + +def config_item_set(section, key) -> bool: + global cfg + return section in cfg and (key in cfg[section]) and not (cfg[section][key] is None) + +def config_item_equals(section, key, value) -> bool: + global cfg + return section in cfg and key in cfg[section] and cfg[section][key] == value + +def verbose_logging_enabled() -> bool: + global cfg + return config_item_equals("logging", "verbose", "yes") diff --git a/test/relay.py b/test/relay.py index a8e3db7..7e850dc 100644 --- a/test/relay.py +++ b/test/relay.py @@ -11,6 +11,8 @@ import sys import socket +import logging + EXIT_UNAVAILABLE = 1 @@ -44,12 +46,17 @@ def serve(port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(localhost_at(port)) + logging.info(f"Listening on localhost, port {port}") s.listen(1) + logging.info("Listening...") except socket.error as e: print("Cannot connect", e) + logging.error(f"Cannot connect {e}") sys.exit(EXIT_UNAVAILABLE) + logging.debug("About to accept a connection...") (conn, addr) = s.accept() + logging.debug(f"Accepting connection from {conn}") conn.sendall(welcome(b"TEST SERVER")) receive_and_confirm(conn) # Ignore HELO/EHLO @@ -70,14 +77,24 @@ def serve(port): conn.close() + logging.debug(f"Received {len(message)} characters of data") + # Trim EOM marker as we're only interested in the message body. return message[:-len(EOM)] def error(msg, exit_code): + logging.error(msg) print("ERROR: %s" % (msg)) sys.exit(exit_code) +# filename is relative to where we run the tests from, i.e. the project root +# directory +logging.basicConfig(filename = 'test/logs/relay.log', + format = '%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', + datefmt = '%Y-%m-%d %H:%M:%S', + level = logging.DEBUG) + if len(sys.argv) < 2: error("Usage: relay.py PORT_NUMBER", EXIT_UNAVAILABLE) From 031c7234f680f945acd4d39833d9a2531b065a08 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 11 Mar 2022 22:18:16 +0100 Subject: [PATCH 02/12] Reorder top-level expressions --- gpg-mailgate.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gpg-mailgate.py b/gpg-mailgate.py index dabb98d..308f7c4 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -52,14 +52,6 @@ def log( msg ): logfile.write(msg + "\n") logfile.close() -verbose = conf.verbose_logging_enabled() - -# Read e-mail from stdin -raw = sys.stdin.read() -raw_message = email.message_from_string( raw ) -from_addr = raw_message['From'] -to_addrs = sys.argv[1:] - def gpg_encrypt( raw_message, recipients ): if not conf.config_item_set('gpg', 'keyhome'): @@ -442,7 +434,15 @@ def sort_recipients( raw_message, from_addr, to_addrs ): conf.load_config() +verbose = conf.verbose_logging_enabled() + lacre.init_logging(conf.get_item('logging', 'file')) +# Read e-mail from stdin +raw = sys.stdin.read() +raw_message = email.message_from_string( raw ) +from_addr = raw_message['From'] +to_addrs = sys.argv[1:] + # Let's start sort_recipients(raw_message, from_addr, to_addrs) From baf79542703c61cf7bfe16ed2309e2aa2ba320ee Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 11 Mar 2022 22:18:54 +0100 Subject: [PATCH 03/12] Use list booleanness instead of comparing with empty list --- gpg-mailgate.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 308f7c4..f938131 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -101,7 +101,7 @@ def gpg_encrypt( raw_message, recipients ): log("Recipient (%s) not in PGP domain list for encrypting." % to) ungpg_to.append(to) - if gpg_to != list(): + if gpg_to: log("Encrypting email to: %s" % ' '.join( x[0] for x in gpg_to )) # Getting PGP style for recipient @@ -132,7 +132,7 @@ def gpg_encrypt( raw_message, recipients ): gpg_to_smtp_inline.append(rcpt[0]) gpg_to_cmdline_inline.extend(rcpt[1].split(',')) - if gpg_to_smtp_mime != list(): + if gpg_to_smtp_mime: # Encrypt mail with PGP/MIME raw_message_mime = copy.deepcopy(raw_message) @@ -149,7 +149,7 @@ def gpg_encrypt( raw_message, recipients ): send_msg( raw_message_mime.as_string(), gpg_to_smtp_mime ) - if gpg_to_smtp_inline != list(): + if gpg_to_smtp_inline: # Encrypt mail with PGP/INLINE raw_message_inline = copy.deepcopy(raw_message) @@ -287,7 +287,7 @@ def smime_encrypt( raw_message, recipients ): else: unsmime_to.append(addr) - if smime_to != list(): + if smime_to: s.set_x509_stack(sk) s.set_cipher(SMIME.Cipher('aes_192_cbc')) p7 = s.encrypt( BIO.MemoryBuffer( raw_message.as_string() ) ) @@ -311,7 +311,7 @@ def smime_encrypt( raw_message, recipients ): log("Sending message from " + from_addr + " to " + str(smime_to)) send_msg(out.read(), smime_to) - if unsmime_to != list(): + if unsmime_to: if verbose: log("Unable to find valid S/MIME certificates for " + str(unsmime_to)) @@ -421,12 +421,12 @@ def sort_recipients( raw_message, from_addr, to_addrs ): # Encrypt mails for recipients with known public PGP keys recipients_left = gpg_encrypt(raw_message, recipients_left) - if recipients_left == list(): + if not recipients_left: return # Encrypt mails for recipients with known S/MIME certificate recipients_left = smime_encrypt(raw_message, recipients_left) - if recipients_left == list(): + if not recipients_left: return # Send out mail to recipients which are left From 75ccfb0850e1cb416db7845c7076eb20654abedf Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sat, 26 Mar 2022 09:52:56 +0100 Subject: [PATCH 04/12] Use logging module - Replace custom logging code with calls to logging module. - Use logging.config to provide configuration parameters. To make Lacre's logging more flexible, use fileConfig from logging.config to set up all parameters. If the configuration file is missing, use dictConfig with hardcoded reasonable defaults. --- gpg-mailgate.conf.sample | 6 ++-- gpg-mailgate.py | 76 +++++++++++++++++----------------------- lacre/__init__.py | 40 +++++++++++++++------ lacre/config.py | 13 +++---- test/e2e.ini | 1 + test/e2e_test.py | 7 ++-- test/gpg-lacre-log.ini | 24 +++++++++++++ 7 files changed, 98 insertions(+), 69 deletions(-) create mode 100644 test/gpg-lacre-log.ini diff --git a/gpg-mailgate.conf.sample b/gpg-mailgate.conf.sample index fad9a79..1e4292d 100644 --- a/gpg-mailgate.conf.sample +++ b/gpg-mailgate.conf.sample @@ -66,9 +66,9 @@ notification_email = gpg-mailgate@yourdomain.tld mail_templates = /var/gpgmailgate/cron_templates [logging] -# For logging to syslog. 'file = syslog', otherwise use path to the file. -file = syslog -verbose = yes +# path to the logging configuration; see documentation for details: +# https://docs.python.org/3/library/logging.config.html#logging-config-fileformat +config = /etc/gpg-lacre-logging.ini [relay] # the relay settings to use for Postfix diff --git a/gpg-mailgate.py b/gpg-mailgate.py index f938131..e01066e 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -43,19 +43,11 @@ import logging import lacre import lacre.config as conf -def log( msg ): - if conf.config_item_set("logging", "file"): - if conf.config_item_equals('logging', 'file', "syslog"): - syslog.syslog(syslog.LOG_INFO | syslog.LOG_MAIL, msg) - else: - logfile = open(conf.get_item('logging', 'file'), 'a') - logfile.write(msg + "\n") - logfile.close() - def gpg_encrypt( raw_message, recipients ): + global LOG if not conf.config_item_set('gpg', 'keyhome'): - log("No valid entry for gpg keyhome. Encryption aborted.") + LOG.info("No valid entry for gpg keyhome. Encryption aborted.") return recipients keys = GnuPG.public_keys( conf.get_item('gpg', 'keyhome') ) @@ -69,13 +61,13 @@ def gpg_encrypt( raw_message, recipients ): # Check if recipient is in keymap if conf.config_item_set('enc_keymap', to): - log("Encrypt keymap has key '%s'" % conf.get_item('enc_keymap', to) ) + LOG.info("Encrypt keymap has key '%s'" % conf.get_item('enc_keymap', to) ) # Check we've got a matching key! if conf.get_item('enc_keymap', to) in keys: gpg_to.append( (to, conf.get_item('enc_keymap', to)) ) continue else: - log("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to)) + LOG.info("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to)) # Check if key in keychain is present if to in keys.values() and not conf.config_item_equals('default', 'enc_keymap_only', 'yes'): @@ -87,22 +79,21 @@ def gpg_encrypt( raw_message, recipients ): if len(splitted_to) > 1: domain = splitted_to[1] if conf.config_item_set('enc_domain_keymap', domain): - log("Encrypt domain keymap has key '%s'" % conf.get_item('enc_dec_keymap', domain) ) + LOG.info("Encrypt domain keymap has key '%s'" % conf.get_item('enc_dec_keymap', domain) ) # Check we've got a matching key! if conf.get_item('enc_domain_keymap', domain) in keys: - log("Using default domain key for recipient '%s'" % to) + LOG.info("Using default domain key for recipient '%s'" % to) gpg_to.append( (to, conf.get_item('enc_domain_keymap', domain)) ) continue else: - log("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (conf.get_item('enc_domain_keymap', domain), to)) + LOG.info("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (conf.get_item('enc_domain_keymap', domain), to)) # At this point no key has been found - if verbose: - log("Recipient (%s) not in PGP domain list for encrypting." % to) + LOG.debug("Recipient (%s) not in PGP domain list for encrypting." % to) ungpg_to.append(to) if gpg_to: - log("Encrypting email to: %s" % ' '.join( x[0] for x in gpg_to )) + LOG.info("Encrypting email to: %s" % ' '.join( x[0] for x in gpg_to )) # Getting PGP style for recipient gpg_to_smtp_mime = list() @@ -122,7 +113,7 @@ def gpg_encrypt( raw_message, recipients ): else: # Log message only if an unknown style is defined if conf.config_item_set('pgp_style', rcpt[0]): - log("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0])) + LOG.info("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0])) # If no style is in settings defined for recipient, use default from settings if conf.config_item_equals('default', 'mime_conversion', 'yes'): @@ -229,21 +220,20 @@ def encrypt_all_payloads_mime( message, gpg_to_cmdline ): return [ submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested) ] def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ): + global LOG raw_payload = payload.get_payload(decode=True) if check_nested and b"-----BEGIN PGP MESSAGE-----" in raw_payload and b"-----END PGP MESSAGE-----" in raw_payload: - if verbose: - log("Message is already pgp encrypted. No nested encryption needed.") + LOG.debug("Message is already pgp encrypted. No nested encryption needed.") return payload # No check is needed for conf.get_item('gpg', 'keyhome') as this is already done in method gpg_encrypt gpg = GnuPG.GPGEncryptor( conf.get_item('gpg', 'keyhome'), gpg_to_cmdline, payload.get_content_charset() ) gpg.update( raw_payload ) encrypted_data, returncode = gpg.encrypt() - if verbose: - log("Return code from encryption=%d (0 indicates success)." % returncode) + LOG.debug("Return code from encryption=%d (0 indicates success)." % returncode) if returncode != 0: - log("Encrytion failed with return code %d. Encryption aborted." % returncode) + LOG.info("Encrytion failed with return code %d. Encryption aborted." % returncode) return payload payload.set_payload( encrypted_data ) @@ -263,9 +253,10 @@ def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ): return payload def smime_encrypt( raw_message, recipients ): + global LOG if not conf.config_item_set('smime', 'cert_path'): - log("No valid path for S/MIME certs found in config file. S/MIME encryption aborted.") + LOG.info("No valid path for S/MIME certs found in config file. S/MIME encryption aborted.") return recipients cert_path = conf.get_item('smime', 'cert_path')+"/" @@ -279,8 +270,7 @@ def smime_encrypt( raw_message, recipients ): if not (cert_and_email is None): (to_cert, normal_email) = cert_and_email - if verbose: - log("Found cert " + to_cert + " for " + addr + ": " + normal_email) + LOG.debug("Found cert " + to_cert + " for " + addr + ": " + normal_email) smime_to.append(addr) x509 = X509.load_cert(to_cert, format=X509.FORMAT_PEM) sk.push(x509) @@ -307,17 +297,16 @@ def smime_encrypt( raw_message, recipients ): s.write(out, p7) - if verbose: - log("Sending message from " + from_addr + " to " + str(smime_to)) + LOG.debug("Sending message from " + from_addr + " to " + str(smime_to)) send_msg(out.read(), smime_to) if unsmime_to: - if verbose: - log("Unable to find valid S/MIME certificates for " + str(unsmime_to)) + LOG.debug("Unable to find valid S/MIME certificates for " + str(unsmime_to)) return unsmime_to def get_cert_for_email( to_addr, cert_path ): + global LOG files_in_directory = os.listdir(cert_path) for filename in files_in_directory: @@ -335,8 +324,7 @@ def get_cert_for_email( to_addr, cert_path ): multi_email = re.match('^([^\+]+)\+([^@]+)@(.*)$', to_addr) if multi_email: fixed_up_email = "%s@%s" % (multi_email.group(1), multi_email.group(3)) - if verbose: - log("Multi-email %s converted to %s" % (to_addr, fixed_up_email)) + LOG.debug("Multi-email %s converted to %s" % (to_addr, fixed_up_email)) return get_cert_for_email(fixed_up_email) return None @@ -377,22 +365,24 @@ def get_first_payload( payloads ): return payloads def send_msg( message, recipients ): + global LOG recipients = [_f for _f in recipients if _f] if recipients: if not (conf.config_item_set('relay', 'host') and conf.config_item_set('relay', 'port')): - log("Missing settings for relay. Sending email aborted.") + LOG.info("Missing settings for relay. Sending email aborted.") return None - log("Sending email to: <%s>" % '> <'.join( recipients )) + LOG.info("Sending email to: <%s>" % '> <'.join( recipients )) relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'port'))) smtp = smtplib.SMTP(relay[0], relay[1]) if conf.config_item_equals('relay', 'starttls', 'yes'): smtp.starttls() smtp.sendmail( from_addr, recipients, message ) else: - log("No recipient found") + LOG.info("No recipient found") def sort_recipients( raw_message, from_addr, to_addrs ): + global LOG recipients_left = list() for recipient in to_addrs: @@ -401,21 +391,18 @@ def sort_recipients( raw_message, from_addr, to_addrs ): # There is no need for nested encryption first_payload = get_first_payload(raw_message) if first_payload.get_content_type() == 'application/pkcs7-mime': - if verbose: - log("Message is already encrypted with S/MIME. Encryption aborted.") + LOG.debug("Message is already encrypted with S/MIME. Encryption aborted.") send_msg(raw_message.as_string(), recipients_left) return first_payload = first_payload.get_payload(decode=True) if b"-----BEGIN PGP MESSAGE-----" in first_payload and b"-----END PGP MESSAGE-----" in first_payload: - if verbose: - log("Message is already encrypted as PGP/INLINE. Encryption aborted.") + LOG.debug("Message is already encrypted as PGP/INLINE. Encryption aborted.") send_msg(raw_message.as_string(), recipients_left) return if raw_message.get_content_type() == 'multipart/encrypted': - if verbose: - log("Message is already encrypted. Encryption aborted.") + LOG.debug("Message is already encrypted. Encryption aborted.") send_msg(raw_message.as_string(), recipients_left) return @@ -434,9 +421,10 @@ def sort_recipients( raw_message, from_addr, to_addrs ): conf.load_config() -verbose = conf.verbose_logging_enabled() +lacre.init_logging(conf.get_item('logging', 'config')) -lacre.init_logging(conf.get_item('logging', 'file')) + +LOG = logging.getLogger(__name__) # Read e-mail from stdin raw = sys.stdin.read() diff --git a/lacre/__init__.py b/lacre/__init__.py index 2582b89..44fb255 100644 --- a/lacre/__init__.py +++ b/lacre/__init__.py @@ -2,15 +2,35 @@ """ import logging +import logging.config -# Initialise logging infrastructure. -# -# This is going to be removed at some point of time, but is used at the moment -# because it's easy and keeps the rest of the code quite clean. +# Following structure configures logging iff a file-based configuration cannot +# be performed. It only sets up a syslog handler, so that the admin has at +# least some basic information. +FAIL_OVER_LOGGING_CONFIG = { + 'version': 1, + 'formatters': { + 'sysfmt': { + 'format': '%(asctime)s %(module)s %(message)s', + 'datefmt': '%Y-%m-%d %H:%M:%S' + }, + }, + 'handlers': { + 'syslog': { + 'class': 'logging.handlers.SysLogHandler', + 'level': 'INFO', + 'formatter': 'sysfmt' + } + }, + 'root': { + 'level': 'INFO', + 'handlers': ['syslog'] + } +} -def init_logging(log_filename): - logging.basicConfig(filename = log_filename, - format = '%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', - datefmt = '%Y-%m-%d %H:%M:%S', - level = logging.DEBUG) - logging.info("Starting logs from lacre module...") +def init_logging(config_filename): + if config_filename is not None: + logging.config.fileConfig(config_filename) + else: + logging.config.dictConfig(FAIL_OVER_LOGGING_CONFIG) + logging.warning('Lacre logging configuration missing, using syslog as default') diff --git a/lacre/config.py b/lacre/config.py index d1bf6fc..0b5fb77 100644 --- a/lacre/config.py +++ b/lacre/config.py @@ -7,7 +7,6 @@ from configparser import RawConfigParser import os -import logging # Environment variable name we read to retrieve configuration path. This is to # enable non-root users to set up and run GPG Mailgate and to make the software @@ -29,7 +28,6 @@ def load_config() -> dict: """ configFile = os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf') - logging.debug(f"Loading configuration file: {configFile}") parser = read_config(configFile) global cfg @@ -52,9 +50,12 @@ def copy_to_dict(confParser) -> dict: return config -def get_item(section, key): +def get_item(section, key, empty_value = None): global cfg - return cfg[section][key] + if config_item_set(section, key): + return cfg[section][key] + else: + return empty_value def has_section(section) -> bool: global cfg @@ -67,7 +68,3 @@ def config_item_set(section, key) -> bool: def config_item_equals(section, key, value) -> bool: global cfg return section in cfg and key in cfg[section] and cfg[section][key] == value - -def verbose_logging_enabled() -> bool: - global cfg - return config_item_equals("logging", "verbose", "yes") diff --git a/test/e2e.ini b/test/e2e.ini index 134a005..ba32999 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -35,6 +35,7 @@ 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 lacre_log: test/logs/gpg-mailgate.log +log_config: test/gpg-lacre-log.ini [case-1] descr: Clear text message to a user without a key diff --git a/test/e2e_test.py b/test/e2e_test.py index f937fa9..2898528 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -33,11 +33,10 @@ RELAY_SCRIPT = "test/relay.py" CONFIG_FILE = "test/gpg-mailgate.conf" def build_config(config): - cp = configparser.ConfigParser() + cp = configparser.RawConfigParser() cp.add_section("logging") - cp.set("logging", "file", config["log_file"]) - cp.set("logging", "verbose", "yes") + cp.set("logging", "config", config["log_config"]) cp.add_section("gpg") cp.set("gpg", "keyhome", config["gpg_keyhome"]) @@ -147,7 +146,7 @@ write_test_config(config_path, port = config.get("relay", "port"), gpg_keyhome = config.get("dirs", "keys"), smime_certpath = config.get("dirs", "certs"), - log_file = config.get("tests", "lacre_log")) + log_config = config.get("tests", "log_config")) for case_no in range(1, config.getint("tests", "cases")+1): case_name = f"case-{case_no}" diff --git a/test/gpg-lacre-log.ini b/test/gpg-lacre-log.ini new file mode 100644 index 0000000..ef33cfd --- /dev/null +++ b/test/gpg-lacre-log.ini @@ -0,0 +1,24 @@ +[loggers] +keys=root + +[logger_root] +level=NOTSET +handlers=lacrelog + +[handlers] +keys=lacrelog + +[formatters] +keys=postfixfmt + +[handler_lacrelog] +class=FileHandler +level=DEBUG +formatter=postfixfmt +args=('test/logs/lacre.log', 'a+') + +[formatter_postfixfmt] +format=%(asctime)s %(module)s[%(process)d]: %(message)s +datefmt=%b %e %H:%M:%S +style=% +validate=True From af5a5b4176099d6ac4624964ddf6c75b80fc9f7e Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Tue, 12 Apr 2022 21:43:52 +0200 Subject: [PATCH 05/12] Use Lacre logging and configuration in cron.py --- gpg-mailgate-web/cron.py | 65 ++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/gpg-mailgate-web/cron.py b/gpg-mailgate-web/cron.py index 45fded7..ac40d22 100644 --- a/gpg-mailgate-web/cron.py +++ b/gpg-mailgate-web/cron.py @@ -30,20 +30,9 @@ import os from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart -# Environment variable name we read to retrieve configuration path. This is to -# enable non-root users to set up and run GPG Mailgate and to make the software -# testable. -CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" - -def appendLog(msg): - print(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) - else: - logfile = open(cfg['logging']['file'], 'a') - logfile.write(msg + "\n") - logfile.close() +import logging +import lacre +import lacre.config as conf def load_file(name): f = open(name) @@ -61,23 +50,23 @@ def authenticate_maybe(smtp): smtp.login(cfg['smtp']['username'], cfg['smtp']['password']) def send_msg( mailsubject, messagefile, recipients = None ): - mailbody = load_file( cfg['cron']['mail_templates'] + "/" + messagefile) + mailbody = load_file( conf.get_item('cron', 'mail_templates') + "/" + messagefile).read() msg = MIMEMultipart("alternative") - msg["From"] = cfg['cron']['notification_email'] + msg["From"] = conf.get_item('cron', 'notification_email') msg["To"] = recipients msg["Subject"] = mailsubject msg.attach(MIMEText(mailbody, 'plain')) msg.attach(MIMEText(markdown.markdown(mailbody), 'html')) - - if 'relay' in cfg and 'host' in cfg['relay'] and 'enc_port' in cfg['relay']: - relay = (cfg['relay']['host'], int(cfg['relay']['enc_port'])) + + if conf.config_item_set('relay', 'host') and conf.config_item_set('relay', 'enc_port'): + relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'enc_port'))) smtp = smtplib.SMTP(relay[0], relay[1]) authenticate_maybe(smtp) - smtp.sendmail( cfg['cron']['notification_email'], recipients, msg.as_string() ) + smtp.sendmail( conf.get_item('cron', 'notification_email'), recipients, msg.as_string() ) else: - appendLog("Could not send mail due to wrong configuration") + LOG.info("Could not send mail due to wrong configuration") def setup_db_connection(url): engine = sqlalchemy.create_engine(url) @@ -98,15 +87,13 @@ def define_db_schema(): # Read configuration from /etc/gpg-mailgate.conf -_cfg = RawConfigParser() -_cfg.read(os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf')) -cfg = dict() -for sect in _cfg.sections(): - cfg[sect] = dict() - for (name, value) in _cfg.items(sect): - cfg[sect][name] = value +conf.load_config() -if 'database' in cfg and 'enabled' in cfg['database'] and cfg['database']['enabled'] == 'yes' and 'url' in cfg['database']: +lacre.init_logging(conf.get_item('logging', 'config')) +LOG = logging.getLogger(__name__) + + +if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_set('database', 'url'): (engine, conn) = setup_db_connection(cfg["database"]["url"]) (gpgmw_keys) = define_db_schema() @@ -119,28 +106,28 @@ if 'database' in cfg and 'enabled' in cfg['database'] and cfg['database']['enabl # 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])) conn.execute(delq) - GnuPG.delete_key(cfg['gpg']['keyhome'], row[2]) - appendLog('Deleted key for <' + row[2] + '> via import request') + GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[2]) + LOG.info('Deleted key for <' + row[2] + '> 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(cfg['gpg']['keyhome'], row[0]) # import the key to gpg + 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) conn.execute(modq) # mark key as accepted - appendLog('Imported key from <' + row[2] + '>') - if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes': + LOG.info('Imported key from <' + row[2] + '>') + if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key registration successful", "registrationSuccess.md", row[2] ) else: delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) conn.execute(delq) # delete key - appendLog('Import confirmation failed for <' + row[2] + '>') - if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes': + LOG.info('Import confirmation failed for <' + row[2] + '>') + if conf.config_item_equals('cron', 'send_email', 'yes'): 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[1]) conn.execute(delq) - if 'send_email' in cfg['cron'] and cfg['cron']['send_email'] == 'yes': + if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key deleted", "keyDeleted.md", row[2]) # delete keys @@ -148,9 +135,9 @@ if 'database' in cfg and 'enabled' in cfg['database'] and cfg['database']['enabl stat2_result_set = conn.execute(stat2q) for row in stat2_result_set: - GnuPG.delete_key(cfg['gpg']['keyhome'], row[0]) + GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[0]) delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) conn.execute(delq) - appendLog('Deleted key for <' + row[0] + '>') + LOG.info('Deleted key for <' + row[0] + '>') else: print("Warning: doing nothing since database settings are not configured!") From 9dfc447169c681daa1107216d4e50d4198772ce0 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sun, 24 Apr 2022 22:29:08 +0200 Subject: [PATCH 06/12] Use Lacre logging and configuration in register-handler --- register-handler.py | 65 +++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/register-handler.py b/register-handler.py index f4c1e57..6475a5f 100644 --- a/register-handler.py +++ b/register-handler.py @@ -7,37 +7,28 @@ from M2Crypto import BIO, Rand, SMIME, X509 from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart -# Read configuration from /etc/gpg-mailgate.conf -_cfg = RawConfigParser() -_cfg.read('/etc/gpg-mailgate.conf') -cfg = dict() -for sect in _cfg.sections(): - cfg[sect] = dict() - for (name, value) in _cfg.items(sect): - cfg[sect][name] = value +import logging -def log(msg): - if 'logging' in cfg and 'file' in cfg['logging']: - if cfg['logging']['file'] == "syslog": - syslog.syslog(syslog.LOG_INFO | syslog.LOG_MAIL, msg) - else: - logfile = open(cfg['logging']['file'], 'a') - logfile.write(msg + "\n") - logfile.close() - -CERT_PATH = cfg['smime']['cert_path']+"/" +import lacre +import lacre.config as conf def send_msg( message, from_addr, recipients = None ): - - if 'relay' in cfg and 'host' in cfg['relay'] and 'enc_port' in cfg['relay']: - relay = (cfg['relay']['host'], int(cfg['relay']['enc_port'])) + if conf.config_item_set('relay', 'host') and conf.config_item_set('relay', 'enc_port'): + relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'enc_port'))) smtp = smtplib.SMTP(relay[0], relay[1]) smtp.sendmail( from_addr, recipients, message.as_string() ) else: - log("Could not send mail due to wrong configuration") + LOG.info("Could not send mail due to wrong configuration") if __name__ == "__main__": # try: + conf.load_config() + lacre.init_logging(conf.get_item('logging', 'config')) + + LOG = logging.getLogger(__name__) + + CERT_PATH = conf.get_item('smime', 'cert_path') + '/' + # Read e-mail from stdin raw = sys.stdin.read() register_msg = email.message_from_string( raw ) @@ -63,18 +54,18 @@ if __name__ == "__main__": break if sign_part == None: - log("Unable to find PKCS7 signature or public PGP key in registration email") + LOG.info("Unable to find PKCS7 signature or public PGP key in registration email") - failure_msg = file( cfg['mailregister']['mail_templates'] + "/registrationError.md").read() + failure_msg = file( conf.get_item('mailregister', 'mail_templates') + "/registrationError.md").read() msg = MIMEMultipart("alternative") - msg["From"] = cfg['mailregister']['register_email'] + msg["From"] = conf.get_item('mailregister', 'register_email') msg["To"] = from_addr msg["Subject"] = "S/MIME / OpenPGP registration failed" msg.attach(MIMEText(failure_msg, 'plain')) msg.attach(MIMEText(markdown.markdown(failure_msg), 'html')) - send_msg(msg, cfg['mailregister']['register_email'], [from_addr]) + send_msg(msg, conf.get_item('mailregister', 'register_email'), [from_addr]) sys.exit(0) if sign_type == 'smime': @@ -105,42 +96,42 @@ if __name__ == "__main__": # format in user-specific data # sending success mail only for S/MIME as GPGMW handles this on its own - success_msg = file(cfg['mailregister']['mail_templates']+"/registrationSuccess.md").read() + success_msg = file(conf.get_item('mailregister', 'mail_templates')+"/registrationSuccess.md").read() success_msg = success_msg.replace("[:FROMADDRESS:]", from_addr) msg = MIMEMultipart("alternative") - msg["From"] = cfg['mailregister']['register_email'] + msg["From"] = conf.get_item('mailregister', 'register_email') msg["To"] = from_addr msg["Subject"] = "S/MIME certificate registration succeeded" msg.attach(MIMEText(success_msg, 'plain')) msg.attach(MIMEText(markdown.markdown(success_msg), 'html')) - send_msg(msg, cfg['mailregister']['register_email'], [from_addr]) + send_msg(msg, conf.get_item('mailregister', 'register_email'), [from_addr]) - log("S/MIME Registration succeeded") + LOG.info("S/MIME Registration succeeded") elif sign_type == 'pgp': # send POST to gpg-mailgate webpanel sig = sign_part payload = {'email': from_addr, 'key': sig} - r = requests.post(cfg['mailregister']['webpanel_url'], data=payload) + r = requests.post(conf.get_item('mailregister', 'webpanel_url'), data=payload) if r.status_code != 200: - log("Could not hand registration over to GPGMW. Error: %s" % r.status_code) - error_msg = file(cfg['mailregister']['mail_templates']+"/gpgmwFailed.md").read() + LOG.info("Could not hand registration over to GPGMW. Error: %s" % r.status_code) + error_msg = open(conf.get_item('mailregister', 'mail_templates')+"/gpgmwFailed.md").read() error_msg = error_msg.replace("[:FROMADDRESS:]", from_addr) msg = MIMEMultipart("alternative") - msg["From"] = cfg['mailregister']['register_email'] + msg["From"] = conf.get_item('mailregister', 'register_email') msg["To"] = from_addr msg["Subject"] = "PGP key registration failed" msg.attach(MIMEText(error_msg, 'plain')) msg.attach(MIMEText(markdown.markdown(error_msg), 'html')) - send_msg(msg, cfg['mailregister']['register_email'], [from_addr]) + send_msg(msg, conf.get_item('mailregister', 'register_email'), [from_addr]) else: - log("PGP registration is handed over to GPGMW") + LOG.info("PGP registration is handed over to GPGMW") # except: -# log("Registration exception") +# LOG.info("Registration exception") # sys.exit(0) From 617a208fe97ddb65f2823cafac1054c7025803d7 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 6 May 2022 20:25:22 +0200 Subject: [PATCH 07/12] Fix indentation and names after rebase --- gpg-mailgate-web/cron.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gpg-mailgate-web/cron.py b/gpg-mailgate-web/cron.py index ac40d22..e7e0a1b 100644 --- a/gpg-mailgate-web/cron.py +++ b/gpg-mailgate-web/cron.py @@ -94,7 +94,7 @@ LOG = logging.getLogger(__name__) if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_set('database', 'url'): - (engine, conn) = setup_db_connection(cfg["database"]["url"]) + (engine, conn) = setup_db_connection(conf.get_item("database", "url")) (gpgmw_keys) = define_db_schema() selq = select(gpgmw_keys.c.publickey, gpgmw_keys.c.id, gpgmw_keys.c.email)\ @@ -107,20 +107,20 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se delq = delete(gpgmw_keys).where(and_(gpgmw_keys.c.email == row[2], gpgmw_keys.c.id != row[1])) conn.execute(delq) GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[2]) - LOG.info('Deleted key for <' + row[2] + '> via import request') + LOG.info('Deleted key for <' + row[2] + '> 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) conn.execute(modq) # mark key as accepted - LOG.info('Imported key from <' + row[2] + '>') + LOG.info('Imported key from <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key registration successful", "registrationSuccess.md", row[2] ) else: delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) conn.execute(delq) # delete key - LOG.info('Import confirmation failed for <' + row[2] + '>') + LOG.info('Import confirmation failed for <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key registration failed", "registrationError.md", row[2] ) else: @@ -138,6 +138,6 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se GnuPG.delete_key(conf.get_item('gpg', 'keyhome'), row[0]) delq = delete(gpgmw_keys).where(gpgmw_keys.c.id == row[1]) conn.execute(delq) - LOG.info('Deleted key for <' + row[0] + '>') + LOG.info('Deleted key for <' + row[0] + '>') else: print("Warning: doing nothing since database settings are not configured!") From 8f0d8f49333383b751b07c60e340945331afbe6d Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 6 May 2022 20:38:54 +0200 Subject: [PATCH 08/12] Add newlines to key material --- test/schema.py | 104 ++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/test/schema.py b/test/schema.py index c0c40d7..5e99760 100644 --- a/test/schema.py +++ b/test/schema.py @@ -31,60 +31,60 @@ conn = test_db.connect() # Populate the database with dummy data conn.execute(gpgmw_keys.insert(), [ - {"id": 1, "email": "alice@disposlab", "publickey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\ -\ -mQGNBGDYY5oBDAC+HAVjA05jsIpHfQ2KQ9m2olo1Qnlk+dkjD+Gagxj1ACezyiGL\ -cfZfoE/MJYLCH9yPcX1fUIAPwdAyfJKlvkVcz+MhEpgl3aP3NM2L2unSx3v9ZFwT\ -/qyMo9Zst5VSD04TVx2ySQB1vucd2ppgp66X7hlCxs+P8d0FV7VcdrNYol2oOtYP\ -yEFXkdyXLI/INI6jrqNkBF87ej+dlTQZAm3zoj61Xwq4gW0YesAZoJyXs8X+a4Am\ -8KF7YYcTcIy89yXflotmExpE+i77datSBLM/FpIPiUfkfK6q/TNyno8Z3PBC0QD5\ -21leqfp/QHRkwmqFbIVuoeonCvrAccjM0ITLjW+P0xXJa3q0lQQCgcGOgqTuNWPT\ -6FhlmvkXt6fBZ11C2I1b033HTePvjIwxOrEY8pSqYwerVX9EU7FXT+S98HNW/1nF\ -cNk3SoofzUOcKZOwc5n0NEESrW7sWpmD6Qmf52+GURuO+15DSUt13xqmnte19Xqd\ -n98y0wrYAUgyUY8AEQEAAbQPYWxpY2VAZGlzcG9zbGFiiQHUBBMBCAA+FiEEHNJF\ -MI8JY9A46INXlzz02Th8RNcFAmDYY5oCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYC\ -AwECHgECF4AACgkQlzz02Th8RNdZeAv+IVVK49f0tY5QOSERu5RqdyFNpsVlUws9\ -swvSvXXK/ZQxZ3YD3o0WEJG5G8jRO+Zjrljx6zzH39ofEKn8QMQUuw+SVPrzbqQb\ -Yp/idn1E9RZCyyhtwcYnIwUObq2NNsCk8UmnjYvpwoh/QcHic13/RSUj7vejujtB\ -SRTjNUE/RK5ROY8r+xZW9ZV/Q0NEzKl2wQtmbt8vTRX9yNEB171XZHG7dg4bTzm+\ -zs0jPGNT0ygcx+uE7DZ3RkyPLRk3fB+GPiYrL2lfPF1KkrHGY4PGhClKdR1kjfBA\ -Kweb6ExZg0fBYlB8ia8z3RZQF29pztoVfk8KIimg9RoYNOKw3Jp5SnHsbz9JygmZ\ -mp3M3Lrs7357oSn9x25/nrFGeUBWbbKoXSdoXZr0Ix4xxkOJPAK966w0pQq+sP+o\ -Ozg3F2rFRc6SoQw1pNLQ57hhWTblQlz8ETY7GnVJ+0xiqkAq2hrLt0jhQ5taWjV6\ -Fgy8fKUPd5OAMvB9bfmAErclWcqKarMcuQGNBGDYY5oBDAC6yOtgUwtKUsI3jTu2\ -VdjNDEnt/VLdRseT4JosSMglZ963nlA4mltCjxj59DeM0Ft8eyF7Bu4EFw5Kid+O\ -vKGA5rGZBE0IVROOvSJQNbcELkY9XYtZjOJ7elfG37rDQKfDk82xqod9iTd48nm7\ -vrllvylQhKfXa+m99KxWabtKqCyXVjaZP9vfD3nVauu16oHW6rQavlLXo5MetFan\ -Iwv1sTqnpzCt+cuG/7vUt89rOiJRalRP3/e1K5MSM6aWC/SHZs6HcrT+WT5nuPA+\ -5VQ4gFCSb8UlscF4sI++hhB/k821vyl9hIjnR3aRiFWdrkykQOfZNhovvsnmJmk9\ -+Zcq0M3pZBnBuLgxVwJNVa4gi63cYwtExpcAZcG28wSVmcXcPN2wxEpYg5n/nvvG\ -8Dsk0AA5WU5WW8aLLLQNBmVg2y4Oa1Fy0M7yfSylLWBAdj7y8+UzspN6JCbYhOpP\ -lLRCJv5+JOgR0MrA+lxfFZwfcSO12x+gkfQ9oyUBdXNuydMAEQEAAYkBtgQYAQgA\ -IBYhBBzSRTCPCWPQOOiDV5c89Nk4fETXBQJg2GOaAhsMAAoJEJc89Nk4fETXpooL\ -/iJKgNF80neUamewma1aZJjwKWoHysSWWSlPeU6pGctuJv15fbAfI/NM1iXnSEGt\ -odsn0oHtuAASlVB0ckSFdE0a2DwLgO6s6oEJof/yrE5hIAAlwzjHsi1G/dtHcfIo\ -SjHzE22qUZwwm5ketuvKvEDKKp3b1ccu37AZC1caRFh3q8xB5ByLh1gPiDJ+ehwU\ -puXkXPdFQhQTZib4LYuMxzh6A+S9U0AM7WMKjX7PhJ68maOeQ+yOIBSWtBKyWwZu\ -Sx01w+Y/USPz02AxUn102se52FCISc/NijlX1JvFQdzf/WaZu28nTmW9OXSW3WeK\ -ql7zNQqj494JD8gJuRGCU9AaiCmOaBokRdLiGbin/wxiG1CkXGRDN5/r0m/1IoNz\ -I4m2SLsB/a89WACQ//CKJyNn4xPOEQoix35tXjdjTLAVyTrX502vHGieZ3HJU2tb\ -nmmMf/H0kReMtNYFwHxoTpBJ8vk+xcZ+6ETzH8nk6av+zZ/5T5Y0aD5zO89PcQk6\ -pw==\ -=Tbwz\ + {"id": 1, "email": "alice@disposlab", "publickey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\ +\n\ +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\n\ +pw==\n\ +=Tbwz\n\ -----END PGP PUBLIC KEY BLOCK-----\ ", "status": 0, "confirm": "", "time": None}, - {"id": 2, "email": "bob@disposlab", "publickey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\ -\ -mDMEYdTFkRYJKwYBBAHaRw8BAQdA2tgdP1pMt3cv3XAW7ov5AFn74mMZvyTksp9Q\ -eO1PkpK0GkJvYiBGb29iYXIgPGJvYkBkaXNwb3NsYWI+iJYEExYIAD4WIQQZz0tH\ -7MnEevqE1L2W85/aDjG7ZwUCYdTFkQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID\ -AQIeAQIXgAAKCRCW85/aDjG7ZxVnAP49t7BU2H+/WCpa3fCAlMEcik82sU4p+U9D\ -pMsbjawwYgEA1SbA5CF835cMjoEufy1h+2M4T9gI/0X2lk8OAtwwggm4OARh1MXg\ -EgorBgEEAZdVAQUBAQdAUVNKx2OsGtNdRsnl3J/uv6obkUC0KcO4ikdRs+iejlMD\ -AQgHiHgEGBYIACAWIQQZz0tH7MnEevqE1L2W85/aDjG7ZwUCYdTF4AIbDAAKCRCW\ -85/aDjG7Z039APwLGP5ibqCC9yIr4YVbdWff1Ch+2C91MR2ObF93Up9+ogD8D2zd\ -OjjB6xRD0Q2FN+alsNGCtdutAs18AZ5l33RMzws=\ -=wWoq\ + {"id": 2, "email": "bob@disposlab", "publickey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\ +\n\ +mDMEYdTFkRYJKwYBBAHaRw8BAQdA2tgdP1pMt3cv3XAW7ov5AFn74mMZvyTksp9Q\n\ +eO1PkpK0GkJvYiBGb29iYXIgPGJvYkBkaXNwb3NsYWI+iJYEExYIAD4WIQQZz0tH\n\ +7MnEevqE1L2W85/aDjG7ZwUCYdTFkQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID\n\ +AQIeAQIXgAAKCRCW85/aDjG7ZxVnAP49t7BU2H+/WCpa3fCAlMEcik82sU4p+U9D\n\ +pMsbjawwYgEA1SbA5CF835cMjoEufy1h+2M4T9gI/0X2lk8OAtwwggm4OARh1MXg\n\ +EgorBgEEAZdVAQUBAQdAUVNKx2OsGtNdRsnl3J/uv6obkUC0KcO4ikdRs+iejlMD\n\ +AQgHiHgEGBYIACAWIQQZz0tH7MnEevqE1L2W85/aDjG7ZwUCYdTF4AIbDAAKCRCW\n\ +85/aDjG7Z039APwLGP5ibqCC9yIr4YVbdWff1Ch+2C91MR2ObF93Up9+ogD8D2zd\n\ +OjjB6xRD0Q2FN+alsNGCtdutAs18AZ5l33RMzws=\n\ +=wWoq\n\ -----END PGP PUBLIC KEY BLOCK-----\ ", "status": 0, "confirm": "", "time": None}, {"id": 3, "email": "cecil@lacre.io", "publickey": "RUBBISH", "status": 0, "confirm": "", "time": None} From 7a8720c14217c5f0ac332a1b855568a6fbfbd63d Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Wed, 11 May 2022 19:15:59 +0200 Subject: [PATCH 09/12] Update installation instructions, add sample logging config --- INSTALL.md | 26 ++++++++++--------- gpg-lacre-logging.conf.sample | 49 +++++++++++++++++++++++++++++++++++ gpg-mailgate.conf.sample | 2 +- 3 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 gpg-lacre-logging.conf.sample diff --git a/INSTALL.md b/INSTALL.md index dcbc275..5898d51 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 2.X is already installed (GPG-Mailgate is not Python 3 compatible) +- 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 @@ -39,11 +39,13 @@ These instructions are based on an installation on an Ubuntu 14.04 LTS virtual m 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/python2.7/dist-packages` (replace 2.7 with your Python 2 version) +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. -7. Add the following to the end of `/etc/postfix/master.cf` +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} @@ -60,15 +62,15 @@ These instructions are based on an installation on an Ubuntu 14.04 LTS virtual m 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` +9. Add the following line to `/etc/postfix/main.cf` content_filter = gpg-mailgate -9. Optional: GPG can automatically download new public keys for automatic signature verification. To enable automatic create the file `/var/gpgmailgate/.gnupg/gpg.conf`. Add the following line to the file: +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 -10. Restart Postfix +11. Restart Postfix You are now ready to go. To add a public key for encryption just use the following command: @@ -112,10 +114,10 @@ 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 2.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. +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: @@ -127,7 +129,7 @@ All files you need can be found in the [gpg-mailgate-web] (gpg-mailgate-web/) di 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. +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. @@ -135,7 +137,7 @@ All files you need can be found in the [gpg-mailgate-web] (gpg-mailgate-web/) di 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: +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 @@ -151,7 +153,7 @@ All files you need can be found in the [gpg-mailgate-web] (gpg-mailgate-web/) di 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). +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 @@ -168,7 +170,7 @@ GPG-Mailgate-Web can also be used as a keyserver. For more information have a lo 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: +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 diff --git a/gpg-lacre-logging.conf.sample b/gpg-lacre-logging.conf.sample new file mode 100644 index 0000000..8de6b67 --- /dev/null +++ b/gpg-lacre-logging.conf.sample @@ -0,0 +1,49 @@ +# Example configuration for Lacre logging. If you don't intend to change the +# log format, you can just keep this file unchanged. + +[loggers] +keys=root + +[logger_root] +level=NOTSET +# Append ",syslog" to the following line if you want to send entries +# to syslog too. +handlers=lacrelog + +[handlers] +# Append ",syslog" to the following line if you want to send entries +# to syslog too. +keys=lacrelog + +[formatters] +keys=postfixfmt + +# +# By default, include messages from all log levels up to DEBUG. +# However, productive systems may use something less verbose, like +# WARN or even ERROR. +# +[handler_lacrelog] +class=FileHandler +level=DEBUG +formatter=postfixfmt +args=('test/logs/lacre.log', 'a+') + +# +# Logging to syslog is disabled by default, but you can enable it if +# you like. +# +[handler_syslog] +class=handlers.SysLogHandler +level=INFO +formatter=postfixfmt +args=('/dev/log', handlers.SysLogHandler.LOG_MAIL) + +# +# Default Postfix log format. +# +[formatter_postfixfmt] +format=%(asctime)s %(module)s[%(process)d]: %(message)s +datefmt=%b %e %H:%M:%S +style=% +validate=True diff --git a/gpg-mailgate.conf.sample b/gpg-mailgate.conf.sample index 1e4292d..9467be7 100644 --- a/gpg-mailgate.conf.sample +++ b/gpg-mailgate.conf.sample @@ -68,7 +68,7 @@ mail_templates = /var/gpgmailgate/cron_templates [logging] # path to the logging configuration; see documentation for details: # https://docs.python.org/3/library/logging.config.html#logging-config-fileformat -config = /etc/gpg-lacre-logging.ini +config = /etc/gpg-lacre-logging.conf [relay] # the relay settings to use for Postfix From acdb2dd5c874cd8969361d34ca9bfd2dd5f25847 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Wed, 11 May 2022 19:57:26 +0200 Subject: [PATCH 10/12] Log cron-job inner workings --- gpg-mailgate-web/cron.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gpg-mailgate-web/cron.py b/gpg-mailgate-web/cron.py index e7e0a1b..18d080e 100644 --- a/gpg-mailgate-web/cron.py +++ b/gpg-mailgate-web/cron.py @@ -41,13 +41,15 @@ def load_file(name): return data def authenticate_maybe(smtp): - if 'smtp' in cfg and 'enabled' in cfg['smtp'] and cfg['smtp']['enabled'] == 'true': - smtp.connect(cfg['smtp']['host'],cfg['smtp']['port']) + if conf.config_item_equals('smtp', 'enabled', 'true'): + LOG.debug(f"Connecting to {conf.get_item('smtp', 'host')}:{conf.get_item('smtp', 'port')}") + smtp.connect(conf.get_item('smtp', 'host'), conf.get_item('smtp', 'port')) smtp.ehlo() - if 'starttls' in cfg['smtp'] and cfg['smtp']['starttls'] == 'true': + if conf.config_item_equals('smtp', 'starttls', 'true'): + LOG.debug("StartTLS enabled") smtp.starttls() smtp.ehlo() - smtp.login(cfg['smtp']['username'], cfg['smtp']['password']) + smtp.login(conf.get_item('smtp', 'username'), conf.get_item('smtp', 'password')) def send_msg( mailsubject, messagefile, recipients = None ): mailbody = load_file( conf.get_item('cron', 'mail_templates') + "/" + messagefile).read() @@ -100,11 +102,13 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se selq = select(gpgmw_keys.c.publickey, gpgmw_keys.c.id, gpgmw_keys.c.email)\ .where(and_(gpgmw_keys.c.status == 0, gpgmw_keys.c.confirm == ""))\ .limit(100) + LOG.debug(f"Retrieving keys to be processed: {selq}") result_set = conn.execute(selq) 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 == 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'), row[2]) LOG.info('Deleted key for <' + row[2] + '> via import request') @@ -113,12 +117,14 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se 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.info('Imported key from <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key registration successful", "registrationSuccess.md", row[2] ) else: 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.info('Import confirmation failed for <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): @@ -126,6 +132,7 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se else: # delete key so we don't continue processing it 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", row[2]) @@ -137,6 +144,7 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se 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 <' + row[0] + '>') else: From 11b78ce0fb1071bcc1a71bc83f83e95813ac057c Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 13 May 2022 21:27:50 +0200 Subject: [PATCH 11/12] Adjust log entry levels for severe conditions When Lacre is misconfigured or can't proceed, report WARNING or even ERROR level messages. --- gpg-mailgate-web/cron.py | 5 +++-- gpg-mailgate.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gpg-mailgate-web/cron.py b/gpg-mailgate-web/cron.py index 18d080e..48c02e0 100644 --- a/gpg-mailgate-web/cron.py +++ b/gpg-mailgate-web/cron.py @@ -119,14 +119,14 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se 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.info('Imported key from <' + row[2] + '>') + LOG.warning('Imported key from <' + row[2] + '>') if conf.config_item_equals('cron', 'send_email', 'yes'): send_msg( "PGP key registration successful", "registrationSuccess.md", row[2] ) else: 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.info('Import confirmation failed for <' + row[2] + '>') + 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", row[2] ) else: @@ -149,3 +149,4 @@ if conf.config_item_equals('database', 'enabled', 'yes') and conf.config_item_se 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!") diff --git a/gpg-mailgate.py b/gpg-mailgate.py index e01066e..7d45855 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -47,7 +47,7 @@ def gpg_encrypt( raw_message, recipients ): global LOG if not conf.config_item_set('gpg', 'keyhome'): - LOG.info("No valid entry for gpg keyhome. Encryption aborted.") + LOG.error("No valid entry for gpg keyhome. Encryption aborted.") return recipients keys = GnuPG.public_keys( conf.get_item('gpg', 'keyhome') ) @@ -370,7 +370,7 @@ def send_msg( message, recipients ): recipients = [_f for _f in recipients if _f] if recipients: if not (conf.config_item_set('relay', 'host') and conf.config_item_set('relay', 'port')): - LOG.info("Missing settings for relay. Sending email aborted.") + LOG.warning("Missing settings for relay. Sending email aborted.") return None LOG.info("Sending email to: <%s>" % '> <'.join( recipients )) relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'port'))) From d8bef9cdb0db9c38db829f78b8408ce64f79bd7b Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 13 May 2022 21:57:29 +0200 Subject: [PATCH 12/12] Explain syslog logging better in sample logging config --- gpg-lacre-logging.conf.sample | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/gpg-lacre-logging.conf.sample b/gpg-lacre-logging.conf.sample index 8de6b67..c3d458c 100644 --- a/gpg-lacre-logging.conf.sample +++ b/gpg-lacre-logging.conf.sample @@ -1,19 +1,23 @@ # Example configuration for Lacre logging. If you don't intend to change the # log format, you can just keep this file unchanged. +# HANDLERS: +# +# Two main targets for log entries are defined here: syslog and a plain text +# log file. They are available as "handlers" named "syslog" and "lacrelog" +# respectively. + [loggers] keys=root [logger_root] level=NOTSET -# Append ",syslog" to the following line if you want to send entries -# to syslog too. -handlers=lacrelog +# Comma-separated handler names, see HANDLERS note at the top. +handlers=syslog [handlers] -# Append ",syslog" to the following line if you want to send entries -# to syslog too. -keys=lacrelog +# Comma-separated handler names, see HANDLERS note at the top. +keys=syslog [formatters] keys=postfixfmt @@ -29,10 +33,14 @@ level=DEBUG formatter=postfixfmt args=('test/logs/lacre.log', 'a+') +# You may want to change the second argument (handlers.SysLogHandler.LOG_MAIL) +# to change the syslog facility used to record messages from Lacre. # -# Logging to syslog is disabled by default, but you can enable it if -# you like. +# Options you can consider are "localX" facilities, available under names from +# handlers.SysLogHandler.LOG_LOCAL0 to handlers.SysLogHandler.LOG_LOCAL7. # +# Please refer to your syslog configuration for details on how to separate +# records from different facilities. [handler_syslog] class=handlers.SysLogHandler level=INFO