From cd58ca2316f134346ccbe2bbb0279210fdb4436b Mon Sep 17 00:00:00 2001 From: zPlus Date: Wed, 3 Aug 2022 08:32:22 +0200 Subject: [PATCH] Accept lists subscription via email. Allow +subscribe and +unsubscribe in mailing lists for subscribing to notifications. --- emails.py | 141 ++++++++++++++++++----- templates/mailing_list/mailing_list.html | 5 + 2 files changed, 118 insertions(+), 28 deletions(-) diff --git a/emails.py b/emails.py index 1f47ce8..032b532 100755 --- a/emails.py +++ b/emails.py @@ -52,61 +52,82 @@ logging.basicConfig(filename='/home/git/clif/emails.log', message_raw = sys.stdin.read() message = email.message_from_string(message_raw, policy=email.policy.default) -email_id = message.get('message-id') -email_id_hash = hashlib.sha256(email_id.encode('utf-8')).hexdigest()[:8] # This will be used as thread ID -email_from = email.utils.parseaddr(message.get('from')) -email_to = email.utils.parseaddr(message.get('to')) -email_in_reply_to = message.get('in-reply-to') -email_subject = message.get('subject') -email_body = message.get_body(('plain',)).get_content() # Accept plaintext only! +try: + email_id = message.get('message-id').strip() +except: + logging.error('Refuting email without a Message-ID: {}'.format(email_subject)) + exit() -logging.info('Received email from {} to {} with subject {}'.format(email_from, email_to, email_subject)) +email_id_hash = hashlib.sha256(email_id.encode('utf-8')).hexdigest()[:8] # This will be used as thread ID -if not email_id: - logging.info('Refuting email without a Message-ID: {}'.format(email_subject)) +try: + email_from = email.utils.parseaddr(message.get('from')) + assert len(email_from[1]) > 0 +except: + logging.error('Refuting email with From header: {}'.format(email_from)) exit() -if not email_body: - logging.warning('Refuting email without plaintext body: {}'.format(email_subject)) +try: + email_to = email.utils.parseaddr(message.get('to')) + assert len(email_from[1]) > 0 + assert email_to[1].endswith('@' + SERVER_DOMAIN) +except: + logging.error('Refuting email with To header: {}'.format(email_to)) exit() -if not email_to[1].endswith('@' + SERVER_DOMAIN): - logging.warning('Refuting email with bad recipient domain: {}'.format(email_to)) - exit() +email_in_reply_to = message.get('in-reply-to') +if email_in_reply_to: + email_in_reply_to = email_in_reply_to.strip() + +try: + email_subject = message.get('subject').strip() +except: + email_subject = '' + +try: + # Accept plaintext only! + email_body = message.get_body(('plain',)).get_content() +except: + email_body = '' + +logging.info('Received email from {} to {} with subject "{}"'.format(email_from, email_to, email_subject)) # Get the repository name. We use email addresses formatted as @SERVER_DOMAIN repository_name = email_to[1].rsplit('@', 1)[0] + +# Is this a request for subscription? +request_subscribe = repository_name.endswith('+subscribe') +request_unsubscribe = repository_name.endswith('+unsubscribe') + +# Remove leading command: from address +if request_subscribe: repository_name = repository_name[:-10] +if request_unsubscribe: repository_name = repository_name[:-12] + repository_path = os.path.join(REPOSITORIES_PATH, repository_name + '.mlist.git') if '..' in repository_name: - logging.warning('Refuting email because the repository name contains "..": {}'.format(repository_name)) + logging.error('Refuting email because the repository name contains "..": {}'.format(repository_name)) exit() +# All repositories should be / if '/' not in repository_name: - logging.warning('Refuting email because the repository name does not contain a namespace: {}'.format(repository_name)) + logging.error('Refuting email because the repository name does not contain a namespace: {}'.format(repository_name)) exit() - - - -############################################################################### -# ADD EMAIL TO USER REPOSITORY -############################################################################### - if not os.path.isdir(repository_path): - logging.warning('Repository path does not exist: {}'.format(repository_path)) + logging.error('Repository path does not exist: {}'.format(repository_path)) exit() try: repo = pygit2.Repository(repository_path) except: - logging.warning('Not a valid repository: {}'.format(repository_path)) + logging.error('Not a valid repository: {}'.format(repository_path)) exit() try: head_tree = repo.revparse_single('HEAD').tree except: - logging.warning('Could not find HEAD ref: {}'.format(repository_path)) + logging.error('Could not find HEAD ref: {}'.format(repository_path)) exit() try: @@ -117,7 +138,71 @@ try: subscribers.append(addr) except: subscribers = [] - logging.info('Could not load subscribers file: {}'.format(repository_path)) + logging.info('Subscribers file not found in {}'.format(repository_path)) + + + + +############################################################################### +# LISTS SUBSCRIPTION +############################################################################### + +if request_subscribe \ +and email_from[1] in subscribers: + # Already subscribed + exit() + +if request_unsubscribe \ +and email_from[1] not in subscribers: + # No address to removed + exit() + +if request_subscribe or request_unsubscribe: + if request_subscribe: + subscribers.append(email_from[1]) + commit_message = 'Subscribe' + + if request_unsubscribe: + subscribers = [ address for address in subscribers if address != email_from[1] ] + commit_message = 'Unsubscribe' + + # Add a new BLOB to the git store + oid = repo.create_blob('\n'.join(subscribers).encode('UTF-8')) + + # Add the blob that we've just created to the HEAD tree + head_tree_builder = repo.TreeBuilder(head_tree) + head_tree_builder.insert('subscribers', oid, pygit2.GIT_FILEMODE_BLOB) + head_tree_oid = head_tree_builder.write() + + repo.create_commit( + repo.head.name, # reference name + pygit2.Signature('CLIF', '-'), # author + pygit2.Signature('CLIF', '-'), # committer + commit_message, # message + head_tree_oid, # tree of this commit + [ repo.head.target ] # parents commit + ) + + exit() + + + + +############################################################################### +# ADD EMAIL TO USER REPOSITORY +############################################################################### + +if len(email_subject) == 0: + logging.info('Refuting email with no subject: {}'.format(email_id)) + exit() + +if not email_body: + logging.warning('Refuting email without plaintext body: {}'.format(email_subject)) + exit() + +if len(email_body.strip()) == 0: + logging.info('Refuting email with empty body: {}'.format(email_id)) + exit() logging.debug('Accepting email from {} to {} with subject {}'.format(email_from, email_to, email_subject)) diff --git a/templates/mailing_list/mailing_list.html b/templates/mailing_list/mailing_list.html index 8de2765..0d7b47b 100644 --- a/templates/mailing_list/mailing_list.html +++ b/templates/mailing_list/mailing_list.html @@ -3,6 +3,11 @@ {% block path %} home ยป {{ list_address }} + ( + Subscribe + - + Unsubscribe + ) {% endblock %} {% block context %}