Merge pull request 'Fix cron script and more' (#134) from fix/cron-script into main
Reviewed-on: #134
This commit is contained in:
commit
628de8a28d
8 changed files with 58 additions and 33 deletions
|
@ -45,17 +45,15 @@ RX_CONFIRM = re.compile(br'key "([^"]+)" imported')
|
|||
|
||||
|
||||
class EncryptionException(Exception):
|
||||
"""Represents a failure to encrypt a payload."""
|
||||
"""Represents a failure to encrypt a payload.
|
||||
|
||||
def __init__(self, issue: str, recipient: str, cause: str):
|
||||
"""Initialise an exception."""
|
||||
self._issue = issue
|
||||
self._recipient = recipient
|
||||
self._cause = cause
|
||||
|
||||
def __str__(self):
|
||||
"""Return human-readable string representation."""
|
||||
return f"issue: {self._issue}; to: {self._recipient}; cause: {self._cause}"
|
||||
Arguments passed to exception constructor:
|
||||
- issue: human-readable explanation of the issue;
|
||||
- recipient: owner of the key;
|
||||
- cause: any additional information, if present;
|
||||
- key: fingerprint of the key.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def _build_command(key_home, *args, **kwargs):
|
||||
|
@ -232,7 +230,7 @@ class GPGEncryptor:
|
|||
if p.returncode != 0:
|
||||
LOG.debug('Errors: %s', err)
|
||||
details = parse_status(err)
|
||||
raise EncryptionException(details['issue'], details['recipient'], details['cause'])
|
||||
raise EncryptionException(details['issue'], details['recipient'], details['cause'], details['key'])
|
||||
return (encdata, p.returncode)
|
||||
|
||||
def _popen(self):
|
||||
|
@ -337,17 +335,19 @@ def parse_status_lines(lines: list) -> dict:
|
|||
continue
|
||||
|
||||
if line.startswith(KEY_EXPIRED, STATUS_FD_PREFIX_LEN):
|
||||
result['issue'] = KEY_EXPIRED
|
||||
result['issue'] = 'key expired'
|
||||
elif line.startswith(KEY_REVOKED, STATUS_FD_PREFIX_LEN):
|
||||
result['issue'] = KEY_REVOKED
|
||||
result['issue'] = 'key revoked'
|
||||
elif line.startswith(NO_RECIPIENTS, STATUS_FD_PREFIX_LEN):
|
||||
result['issue'] = NO_RECIPIENTS
|
||||
result['issue'] = 'no recipients'
|
||||
elif line.startswith(KEY_CONSIDERED, STATUS_FD_PREFIX_LEN):
|
||||
result['key'] = line.split(b' ')[2]
|
||||
elif line.startswith(INVALID_RECIPIENT, STATUS_FD_PREFIX_LEN):
|
||||
words = line.split(b' ')
|
||||
reason_code = int(words[2])
|
||||
result['recipient'] = words[3]
|
||||
result['cause'] = INVALID_RECIPIENT_CAUSES[reason_code]
|
||||
|
||||
if reason_code:
|
||||
result['cause'] = INVALID_RECIPIENT_CAUSES[reason_code]
|
||||
|
||||
return result
|
||||
|
|
13
doc/admin.md
13
doc/admin.md
|
@ -32,3 +32,16 @@ To preview a particular identity, run:
|
|||
```sh
|
||||
python -m lacre.admin identities -e alice@example.com
|
||||
```
|
||||
|
||||
## Importing identities from existing GnuPG keyring
|
||||
|
||||
If you already have a GnuPG keyring with your users' public keys or for some
|
||||
reason Lacre's identity database needs to be re-populated with identities,
|
||||
there's a command to do that:
|
||||
|
||||
```sh
|
||||
python -m lacre.admin import -d /path/to/gnupg/directory
|
||||
```
|
||||
|
||||
If you want to just re-populate the database, Lacre can remove all identities
|
||||
prior to importing keys -- just add `-r` flag.
|
||||
|
|
|
@ -70,6 +70,9 @@ def sub_import(args):
|
|||
conn = repo.connect(conf.get_item('database', 'url'))
|
||||
identities = repo.IdentityRepository(conn)
|
||||
|
||||
if args.reload:
|
||||
identities.delete_all()
|
||||
|
||||
total = 0
|
||||
for (fingerprint, email) in public.items():
|
||||
LOG.debug('Importing %s - %s', email, fingerprint)
|
||||
|
@ -97,7 +100,10 @@ def main():
|
|||
cmd_import = sub_commands.add_parser('import',
|
||||
help='Load identities from GnuPG directory to Lacre database'
|
||||
)
|
||||
cmd_import.add_argument('-d', '--homedir', help='specify GnuPG directory (default: use configured dir.)')
|
||||
cmd_import.add_argument('-d', '--homedir', default=False,
|
||||
help='specify GnuPG directory (default: use configured dir.)')
|
||||
cmd_import.add_argument('-r', '--reload', action='store_true',
|
||||
help='delete all keys from database before importing')
|
||||
cmd_import.set_defaults(operation=sub_import)
|
||||
|
||||
cmd_queue = sub_commands.add_parser('queue',
|
||||
|
|
|
@ -42,10 +42,6 @@ class MailEncryptionProxy:
|
|||
LOG.debug('Parsed into %s: %s', type(message), repr(message))
|
||||
|
||||
if message.defects:
|
||||
# Sometimes a weird message cannot be encoded back and
|
||||
# delivered, so before bouncing such messages we at least
|
||||
# record information about the issues. Defects are identified
|
||||
# by email.* package.
|
||||
LOG.warning("Issues found: %d; %s", len(message.defects), repr(message.defects))
|
||||
|
||||
if conf.flag_enabled('daemon', 'log_headers'):
|
||||
|
@ -57,13 +53,14 @@ class MailEncryptionProxy:
|
|||
try:
|
||||
new_message = operation.perform(message)
|
||||
send(new_message, operation.recipients())
|
||||
except EncryptionException:
|
||||
except EncryptionException as e:
|
||||
# If the message can't be encrypted, deliver cleartext.
|
||||
LOG.exception('Unable to encrypt message, delivering in cleartext')
|
||||
LOG.error('Unable to encrypt message, delivering in cleartext: %s', e)
|
||||
if not isinstance(operation, KeepIntact):
|
||||
self._send_unencrypted(operation, message, envelope, send)
|
||||
else:
|
||||
LOG.error(f'Cannot perform {operation}')
|
||||
LOG.exception('Cannot perform: %s', operation)
|
||||
raise
|
||||
|
||||
except:
|
||||
LOG.exception('Unexpected exception caught, bouncing message')
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
import logging
|
||||
import lacre
|
||||
import lacre.config as conf
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import markdown
|
||||
|
||||
# Read configuration from /etc/gpg-mailgate.conf
|
||||
conf.load_config()
|
||||
|
|
|
@ -69,6 +69,14 @@ class IdentityRepository(KeyRing):
|
|||
delq = delete(self._identities).where(self._identities.c.email == email)
|
||||
LOG.debug('Deleting keys assigned to %s', email)
|
||||
|
||||
self._conn.execute(delq)
|
||||
|
||||
def delete_all(self):
|
||||
LOG.warn('Deleting all identities from the database')
|
||||
|
||||
delq = delete(self._identities)
|
||||
self._conn.execute(delq)
|
||||
|
||||
def freeze_identities(self) -> KeyCache:
|
||||
"""Return a static, async-safe copy of the identity map."""
|
||||
self._ensure_connected()
|
||||
|
|
|
@ -65,6 +65,10 @@ class GnuPGUtilitiesTest(unittest.TestCase):
|
|||
uid = GnuPG._parse_uid_line(sample_in)
|
||||
self.assertEqual(uid, 'alice@disposlab')
|
||||
|
||||
def test_exception_formatting(self):
|
||||
e = GnuPG.EncryptionException('alice@disposlab', 'key expired', None, b'DEADBEEF')
|
||||
self.assertEqual(str(e), 'To: alice@disposlab; Issue: key expired; Key: DEADBEEF')
|
||||
|
||||
def test_parse_statusfd_key_expired(self):
|
||||
key_expired = b"""
|
||||
[GNUPG:] KEYEXPIRED 1668272263
|
||||
|
@ -74,9 +78,9 @@ class GnuPGUtilitiesTest(unittest.TestCase):
|
|||
"""
|
||||
|
||||
result = GnuPG.parse_status(key_expired)
|
||||
self.assertEqual(result['issue'], b'KEYEXPIRED')
|
||||
self.assertEqual(result['issue'], 'key expired')
|
||||
self.assertEqual(result['recipient'], b'name@domain')
|
||||
self.assertEqual(result['cause'], 'No specific reason given')
|
||||
self.assertEqual(result['cause'], 'Unknown')
|
||||
self.assertEqual(result['key'], b'XXXXXXXXXXXXX')
|
||||
|
||||
def test_parse_statusfd_key_absent(self):
|
||||
|
@ -88,7 +92,7 @@ class GnuPGUtilitiesTest(unittest.TestCase):
|
|||
result = GnuPG.parse_status(non_specific_errors)
|
||||
self.assertEqual(result['issue'], b'n/a')
|
||||
self.assertEqual(result['recipient'], b'name@domain')
|
||||
self.assertEqual(result['cause'], 'No specific reason given')
|
||||
self.assertEqual(result['cause'], 'Unknown')
|
||||
self.assertEqual(result['key'], b'n/a')
|
||||
|
||||
|
||||
|
|
|
@ -19,16 +19,9 @@
|
|||
# along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sqlalchemy
|
||||
import smtplib
|
||||
import markdown
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
import logging
|
||||
import lacre
|
||||
import lacre.config as conf
|
||||
import lacre.dbschema as db
|
||||
from lacre.notify import notify
|
||||
|
||||
# Read configuration from /etc/gpg-mailgate.conf
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue