aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJ08nY2017-06-07 01:27:54 +0200
committerJ08nY2017-06-07 01:27:54 +0200
commit25487795779c05ff8e97680550948443924b98c0 (patch)
tree8646cdebfa7f780851437b4c4099ab8e06c63dea /src
parenta662f9f9681964e10d375c6380cf17e7203421d7 (diff)
downloadmailman-pgp-25487795779c05ff8e97680550948443924b98c0.tar.gz
mailman-pgp-25487795779c05ff8e97680550948443924b98c0.tar.zst
mailman-pgp-25487795779c05ff8e97680550948443924b98c0.zip
Sketch out basic skeleton:
- Encrypted mailing list is created with one of the "Encrypted" styles. Which creates an EncryptedMilingList object in pgpmailman's db. This object lives there for the rest of the life of the mailing list. Holds list configuration, such as action to take on unsigned messages, action to take on non- encrypted messages and so on. See pgpmailman.model.list... - Custom Incoming runner is set instead of the default Mailman one. This runner passes any messages to an ordinary list to the original incoming runner to its (configurable) queue. Any messages for an encrypted mailing list are processed before either being passed to the original incoming runner or discarded / bounced. In case they are to be accepted, they are "unwrapped" of the PGP/MIME related stuff and only the core message is passed to the original incoming runner. The signature is stripped and stored in msgdata if present, for the OutgoingRunner to attach it if configured.
Diffstat (limited to 'src')
-rw-r--r--src/pgpmailman/commands/eml_key.py2
-rw-r--r--src/pgpmailman/config/__init__.py8
-rw-r--r--src/pgpmailman/config/mailman.cfg18
-rw-r--r--src/pgpmailman/config/pgpmailman.cfg10
-rw-r--r--src/pgpmailman/database/__init__.py36
-rw-r--r--src/pgpmailman/model/__init__.py0
-rw-r--r--src/pgpmailman/model/base.py8
-rw-r--r--src/pgpmailman/model/list.py24
-rw-r--r--src/pgpmailman/plugin.py34
-rw-r--r--src/pgpmailman/rest/lists.py49
-rw-r--r--src/pgpmailman/rest/root.py11
-rw-r--r--src/pgpmailman/rest/users.py1
-rw-r--r--src/pgpmailman/runners/incoming.py30
-rw-r--r--src/pgpmailman/runners/outgoing.py17
-rw-r--r--src/pgpmailman/styles/announce.py11
-rw-r--r--src/pgpmailman/styles/base.py23
-rw-r--r--src/pgpmailman/styles/discussion.py10
17 files changed, 260 insertions, 32 deletions
diff --git a/src/pgpmailman/commands/eml_key.py b/src/pgpmailman/commands/eml_key.py
index ba538fa..03c0877 100644
--- a/src/pgpmailman/commands/eml_key.py
+++ b/src/pgpmailman/commands/eml_key.py
@@ -13,7 +13,7 @@ class KeyCommand:
short_description = ''
description = ''
- def process(mlist, msg, msgdata, arguments, results):
+ def process(self, mlist, msg, msgdata, arguments, results):
"""See `IEmailCommand`."""
if len(arguments) == 0:
print('No sub-command specified,'
diff --git a/src/pgpmailman/config/__init__.py b/src/pgpmailman/config/__init__.py
new file mode 100644
index 0000000..a6f7004
--- /dev/null
+++ b/src/pgpmailman/config/__init__.py
@@ -0,0 +1,8 @@
+""""""
+
+from configparser import ConfigParser
+
+from public.public import public
+
+config = ConfigParser()
+public(config=config)
diff --git a/src/pgpmailman/config/mailman.cfg b/src/pgpmailman/config/mailman.cfg
new file mode 100644
index 0000000..38ff416
--- /dev/null
+++ b/src/pgpmailman/config/mailman.cfg
@@ -0,0 +1,18 @@
+
+[plugin.pgp]
+class: pgpmailman.plugin.PGPMailman
+path: pgpmailman
+enable: yes
+configuration: python:pgpmailman.config.pgpmailman
+
+[runner.in]
+class: pgpmailman.runners.incoming.IncomingRunner
+
+[runner.in_default]
+class: mailman.runners.incoming.IncomingRunner
+
+[runner.out]
+class: pgpmailman.runners.outgoing.OutgoingRunner
+
+[runner.out_default]
+class: mailman.runners.outgoing.OutgoingRunner
diff --git a/src/pgpmailman/config/pgpmailman.cfg b/src/pgpmailman/config/pgpmailman.cfg
new file mode 100644
index 0000000..541e9c6
--- /dev/null
+++ b/src/pgpmailman/config/pgpmailman.cfg
@@ -0,0 +1,10 @@
+[db]
+url = sqlite:////$DATA_DIR/pgp.db
+
+[keyrings]
+core = $DATA_DIR/pgp_core.gpp
+users = $DATA_DIR/pgp_users.gpg
+
+[queues]
+in = in_default
+out = out_default \ No newline at end of file
diff --git a/src/pgpmailman/database/__init__.py b/src/pgpmailman/database/__init__.py
index e69de29..01975ad 100644
--- a/src/pgpmailman/database/__init__.py
+++ b/src/pgpmailman/database/__init__.py
@@ -0,0 +1,36 @@
+""""""
+
+from contextlib import contextmanager
+
+from mailman.config import config as mailman_config
+from mailman.utilities.string import expand
+from public import public
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from pgpmailman.config import config
+from pgpmailman.model.base import Base
+
+
+@public
+class Database:
+ def __init__(self):
+ url = config.get('db', 'url')
+ self.url = expand(url, None, mailman_config.paths)
+ self.engine = create_engine(self.url)
+ Session = sessionmaker(bind=self.engine)
+ self.session = Session()
+ Base.metadata.create_all(self.engine)
+ self.session.commit()
+
+
+@public
+@contextmanager
+def transaction():
+ try:
+ yield
+ except:
+ config.db.session.abort()
+ raise
+ else:
+ config.db.session.commit()
diff --git a/src/pgpmailman/model/__init__.py b/src/pgpmailman/model/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pgpmailman/model/__init__.py
diff --git a/src/pgpmailman/model/base.py b/src/pgpmailman/model/base.py
new file mode 100644
index 0000000..e25407f
--- /dev/null
+++ b/src/pgpmailman/model/base.py
@@ -0,0 +1,8 @@
+""""""
+
+from public import public
+from sqlalchemy.ext.declarative import declarative_base
+
+
+Base = declarative_base()
+public(Base=Base)
diff --git a/src/pgpmailman/model/list.py b/src/pgpmailman/model/list.py
new file mode 100644
index 0000000..98b0078
--- /dev/null
+++ b/src/pgpmailman/model/list.py
@@ -0,0 +1,24 @@
+""""""
+
+from mailman.database.types import Enum, SAUnicode
+from mailman.interfaces.action import Action
+from public import public
+from sqlalchemy import Boolean, Column, Integer
+
+from pgpmailman.model.base import Base
+
+
+@public
+class EncryptedMailingList(Base):
+ __tablename__ = 'encrypted_lists'
+
+ id = Column(Integer, primary_key=True)
+ list_id = Column(SAUnicode)
+ key_fingerprint = Column(SAUnicode)
+ unsigned_msg_action = Column(Enum(Action))
+ nonencrypted_msg_action = Column(Enum(Action))
+ strip_original_signature = Column(Boolean)
+ sign_outgoing = Column(Boolean)
+
+ def __init__(self, mlist):
+ self.list_id = mlist.list_id
diff --git a/src/pgpmailman/plugin.py b/src/pgpmailman/plugin.py
index 5ae51a0..820d0a9 100644
--- a/src/pgpmailman/plugin.py
+++ b/src/pgpmailman/plugin.py
@@ -1,21 +1,28 @@
-""" A PGP plugin for GNU Mailman."""
-
+"""A PGP plugin for GNU Mailman."""
+from mailman.app import events
+from mailman.config import config as mailman_config
+from mailman.interfaces.listmanager import ListDeletedEvent
from mailman.interfaces.plugin import IPlugin
-from pgpmailman.rest.root import RESTRoot
+from mailman.utilities.modules import expand_path
from public import public
from zope.interface import implementer
+from pgpmailman.config import config
+from pgpmailman.database import Database, transaction
+from pgpmailman.model.list import EncryptedMailingList
+from pgpmailman.rest.root import RESTRoot
+
@public
@implementer(IPlugin)
class PGPMailman:
-
- def __init__(self):
- self._rest = RESTRoot()
-
def pre_hook(self):
"""See `IPlugin`."""
- pass
+ config.read(
+ expand_path(
+ dict(mailman_config.plugin_configs)[self.name].configuration))
+ config.db = Database()
+ config.name = self.name
def post_hook(self):
"""See `IPlugin`."""
@@ -23,4 +30,13 @@ class PGPMailman:
def rest_object(self):
"""See `IPlugin`."""
- return self._rest
+ return RESTRoot()
+
+
+@events.subscribe(ListDeletedEvent)
+def on_delete(mlist):
+ encrypted_list = config.db.session.query(EncryptedMailingList).filter_by(
+ list_id=mlist.list_id).first()
+ if encrypted_list:
+ with transaction():
+ config.db.session.delete(encrypted_list)
diff --git a/src/pgpmailman/rest/lists.py b/src/pgpmailman/rest/lists.py
index 2c4a58a..fa785ee 100644
--- a/src/pgpmailman/rest/lists.py
+++ b/src/pgpmailman/rest/lists.py
@@ -1,15 +1,54 @@
""""""
+from mailman.rest.helpers import (
+ child, CollectionMixin, etag, not_found, NotFound, okay)
from public import public
+from pgpmailman.config import config
+from pgpmailman.model.list import EncryptedMailingList
+
+
+class _EncryptedBase(CollectionMixin):
+ def _resource_as_dict(self, emlist):
+ """See `CollectionMixin`."""
+ return dict(list_id=emlist.list_id,
+ key_fingerprint=emlist.key_fingerprint,
+ unsigned_msg_action=emlist.unsigned_msg_action,
+ nonencrypted_msg_action=emlist.nonencrypted_msg_action,
+ strip_original_signature=emlist.strip_original_signature,
+ sign_outgoing=emlist.sign_outgoing,
+ self_link=self.api.path_to(
+ '/plugins/{}/lists/{}'.format(config.name,
+ emlist.list_id)))
+
+ def _get_collection(self, request):
+ """See `CollectionMixin`."""
+ return config.db.session.query(EncryptedMailingList).all()
+
@public
-class AllEncryptedLists:
- pass
+class AllEncryptedLists(_EncryptedBase):
+ def on_get(self, request, response):
+ """/lists"""
+ resource = self._make_collection(response)
+ return okay(response, etag(resource))
@public
-class AnEncryptedList:
+class AnEncryptedList(_EncryptedBase):
+ def __init__(self, list_id):
+ self._mlist = config.db.session.query(EncryptedMailingList).filter_by(
+ list_id=list_id).first()
+
+ def on_get(self, request, response):
+ if self._mlist is None:
+ return not_found()
+ else:
+ okay(response, self._resource_as_json(self._mlist))
- def __init__(self, list_name):
- pass
+ @child
+ def key(self, context, segments):
+ if self._mlist is None:
+ return NotFound(), []
+ else:
+ pass
diff --git a/src/pgpmailman/rest/root.py b/src/pgpmailman/rest/root.py
index b937fd3..68afa96 100644
--- a/src/pgpmailman/rest/root.py
+++ b/src/pgpmailman/rest/root.py
@@ -14,10 +14,11 @@ REST root.
"""
from mailman.rest.helpers import child
-from pgpmailman.rest.lists import AllEncryptedLists, AnEncryptedList
-from pgpmailman.rest.users import AUser, AllUsers
from public import public
+from pgpmailman.rest.lists import AllEncryptedLists, AnEncryptedList
+from pgpmailman.rest.users import AllUsers, AUser
+
@public
class RESTRoot:
@@ -26,9 +27,8 @@ class RESTRoot:
if len(segments) == 0:
return AllEncryptedLists(), []
else:
- list_name = segments.pop(0)
- # WIP Check whether it's an encrypted list we know of here.
- return AnEncryptedList(list_name), segments
+ list_id = segments.pop(0)
+ return AnEncryptedList(list_id), segments
@child()
def users(self, context, segments):
@@ -36,5 +36,4 @@ class RESTRoot:
return AllUsers(), []
else:
uid = segments.pop(0)
- # WIP Check whether it's an encrypted user we know of here.
return AUser(uid), segments
diff --git a/src/pgpmailman/rest/users.py b/src/pgpmailman/rest/users.py
index bc6798d..09990f6 100644
--- a/src/pgpmailman/rest/users.py
+++ b/src/pgpmailman/rest/users.py
@@ -10,5 +10,4 @@ class AllUsers:
@public
class AUser:
-
pass
diff --git a/src/pgpmailman/runners/incoming.py b/src/pgpmailman/runners/incoming.py
index ccf90f4..69bbba9 100644
--- a/src/pgpmailman/runners/incoming.py
+++ b/src/pgpmailman/runners/incoming.py
@@ -1,15 +1,39 @@
"""The encryption-aware incoming runner."""
+from mailman.config import config as mailman_config
from mailman.core.runner import Runner
+from mailman.email.message import Message
+from mailman.model.mailinglist import MailingList
from public import public
+from pgpmailman.config import config
+from pgpmailman.model.list import EncryptedMailingList
+
@public
class IncomingRunner(Runner):
- def _dispose(self, mlist, msg, msgdata):
+ def _dispose(self, mlist: MailingList, msg: Message, msgdata: dict):
"""See `IRunner`."""
- pass
# Is the message for an encrypted mailing list? If not, pass to default
# incoming runner. If yes, go on.
-
+ encrypted_list = config.db.query(EncryptedMailingList).filter_by(
+ list_id=mlist.list_id).first()
+ if not encrypted_list:
+ inq = config.get('queues', 'in')
+ mailman_config.switchboards[inq].enqueue(msg, msgdata,
+ listid=mlist.list_id)
+ return False
# Is the message encrypted?
+ if msg.get_content_type() == 'multipart/signed' and msg.get_param(
+ 'protocol') == 'application/pgp-signature':
+ # only signed.
+ pass
+ elif msg.get_content_type() == 'multipart/encrypted' and msg.get_param(
+ 'protocol') == 'application/pgp-encrypted':
+ # definitely encrypted, might still be signed
+ pass
+ else:
+ # not encrypted or signed
+ pass
+ from email.iterators import _structure
+ _structure(msg)
diff --git a/src/pgpmailman/runners/outgoing.py b/src/pgpmailman/runners/outgoing.py
index d7d74bc..11118a2 100644
--- a/src/pgpmailman/runners/outgoing.py
+++ b/src/pgpmailman/runners/outgoing.py
@@ -1,11 +1,24 @@
"""The encryption-aware outgoing runner"""
+from mailman.config import config as mailman_config
from mailman.core.runner import Runner
+from mailman.email.message import Message
+from mailman.model.mailinglist import MailingList
from public import public
+from pgpmailman.config import config
+from pgpmailman.model.list import EncryptedMailingList
+
@public
class OutgoingRunner(Runner):
- def _dispose(self, mlist, msg, msgdata):
+ def _dispose(self, mlist: MailingList, msg: Message, msgdata: dict):
"""See `IRunner`."""
- pass
+ encrypted_list = config.db.query(EncryptedMailingList).filter_by(
+ list_id=mlist.list_id).first()
+ if not encrypted_list:
+ outq = config.get('queues', 'out')
+ mailman_config.switchboards[outq].enqueue(msg,
+ msgdata,
+ listid=mlist.list_id)
+ return False
diff --git a/src/pgpmailman/styles/announce.py b/src/pgpmailman/styles/announce.py
index f83c2ea..26fc01f 100644
--- a/src/pgpmailman/styles/announce.py
+++ b/src/pgpmailman/styles/announce.py
@@ -3,10 +3,15 @@
from mailman.styles.default import LegacyAnnounceOnly
from public import public
+from pgpmailman.styles.base import EncryptedStyle
+
@public
-class Announce(LegacyAnnounceOnly):
+class AnnounceStyle(LegacyAnnounceOnly, EncryptedStyle):
+ name = 'encrypted-announce'
+ description = 'Announce only encrypted mailing list style.'
+
def apply(self, mailing_list):
"""See `IStyle`."""
- super().apply(mailing_list)
- #
+ LegacyAnnounceOnly.apply(self, mailing_list)
+ EncryptedStyle.apply(self, mailing_list)
diff --git a/src/pgpmailman/styles/base.py b/src/pgpmailman/styles/base.py
new file mode 100644
index 0000000..633b95b
--- /dev/null
+++ b/src/pgpmailman/styles/base.py
@@ -0,0 +1,23 @@
+""""""
+
+from public import public
+
+from pgpmailman.config import config
+from pgpmailman.database import transaction
+from pgpmailman.model.list import EncryptedMailingList
+
+
+@public
+class EncryptedStyle:
+ def apply(self, mailing_list):
+ """Creates the encrypted mailing list instance for the list it's
+ applied to.
+ """
+ enc_list = config.db.session.query(EncryptedMailingList).filter_by(
+ list_id=mailing_list.list_id).first()
+ if enc_list:
+ return
+
+ enc_list = EncryptedMailingList(mailing_list)
+ with transaction():
+ config.db.session.add(enc_list)
diff --git a/src/pgpmailman/styles/discussion.py b/src/pgpmailman/styles/discussion.py
index 55304cd..e8c516a 100644
--- a/src/pgpmailman/styles/discussion.py
+++ b/src/pgpmailman/styles/discussion.py
@@ -3,9 +3,15 @@
from mailman.styles.default import LegacyDefaultStyle
from public import public
+from pgpmailman.styles.base import EncryptedStyle
+
@public
-class Discussion(LegacyDefaultStyle):
+class DiscussionStyle(LegacyDefaultStyle, EncryptedStyle):
+ name = 'encrypted-default'
+ description = 'Ordinary discussion encrypted mailing list style.'
+
def apply(self, mailing_list):
"""See `IStyle`."""
- super().apply(mailing_list)
+ LegacyDefaultStyle.apply(self, mailing_list)
+ EncryptedStyle.apply(self, mailing_list)