blob: 5cbc5f9564871b2b297c767569c2853fa291bb27 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This webhook receives events from groups_io and injects them into the
# local mail queue to be processed as if it came in via the SMTP gateway.
#
__author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
import falcon
import json
import socket
import email
import email.utils
import email.header
import hmac
import smtplib
import os
# noinspection PyBroadException, PyMethodMayBeStatic
class GroupsioListener(object):
def on_get(self, req, resp): # noqa
resp.status = falcon.HTTP_200
resp.body = "We don't serve GETs here\n"
def _inject_message(self, doc, req):
success = True
try:
msg = email.message_from_string(doc['message'])
try:
theirname = socket.gethostbyaddr(req.remote_addr)[0]
them = f'{theirname} [{req.remote_addr}]'
except:
them = f'[{req.remote_addr}]'
try:
us = socket.gethostname()
except:
us = 'localhost'
groupaddr = doc['group']['email_address']
groupurl = doc['group']['group_url']
listid = groupaddr.replace('@', '.')
scheme = req.scheme.upper()
rdate = email.utils.formatdate()
rline = f'from {them} by {us} with {scheme} for <{groupaddr}>; {rdate}'
rhdr = email.header.make_header([(rline.encode(), 'us-ascii')], maxlinelen=78)
if msg.get('list-id'):
msg.replace_header('List-Id', f'<{listid}>')
else:
msg.add_header('List-Id', f'<{listid}>')
msg.add_header('X-Webhook-Received', rhdr.encode())
msgnum = doc['msg_num']
msg.add_header('X-Groupsio-URL', f'{groupurl}/message/{msgnum}')
try:
mailfrom = doc['member_info']['email']
except:
mailfrom = 'webhook@localhost'
mailhost = os.getenv('MAILHOST', 'localhost')
mailto = os.getenv('MAILTO', 'webhook@archiver.kernel.org')
smtp = smtplib.SMTP(mailhost)
smtp.sendmail(mailfrom, [mailto], msg.as_bytes())
smtp.close()
except:
return False
return success
def _verify_psk(self, psk, raw, vdigest):
hm = hmac.new(psk.encode(), digestmod='sha256')
hm.update(raw)
return hmac.compare_digest(vdigest, hm.hexdigest())
def on_post(self, req, resp):
if not req.content_length:
resp.status = falcon.HTTP_500
resp.body = 'Payload required\n'
return
raw = req.stream.read()
psk = os.getenv('GROUPSIO_PSK')
if psk:
vdigest = req.get_header('X-Groupsio-Signature')
if not vdigest:
resp.status = falcon.HTTP_401
resp.body = 'HMAC signature header required\n'
return
if not self._verify_psk(psk, raw, vdigest):
resp.status = falcon.HTTP_401
resp.body = 'HMAC signature verification failed\n'
return
try:
doc = json.loads(raw)
except:
resp.status = falcon.HTTP_500
resp.body = 'Failed to parse payload as json\n'
return
success = True
if doc.get('action', '') == 'sent_message_accepted' and 'message' in doc:
success = self._inject_message(doc, req)
if success:
resp.status = falcon.HTTP_200
resp.body = 'OK thanks\n'
else:
resp.status = falcon.HTTP_500
resp.body = 'Something went wrong, sorry.\n'
app = falcon.API()
gl = GroupsioListener()
mp = os.getenv('MOUNTPOINT', '/groupsio_webhook')
app.add_route(mp, gl)