aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Ryabitsev <konstantin@linuxfoundation.org>2022-08-23 13:22:22 -0400
committerKonstantin Ryabitsev <konstantin@linuxfoundation.org>2022-08-23 13:22:22 -0400
commitae63faa8e546381ab16f18bdfeb66a9cee86c88b (patch)
treefa3af49c537416d8f4539716be95fa81e45e3c42
parent138adb3faf9b5c622100169c2518f5d9e81672bb (diff)
downloadb4-ae63faa8e546381ab16f18bdfeb66a9cee86c88b.tar.gz
send-receive: add some anti-spam protections
The goal of this service is to accept and send patches, nothing else. It's not a replacement for an SMTP server, just a replacement for really terrible SMTP servers that mangle patches. So, add some anti-spam protections: - only accept email that looks like patches or cover letters - don't accept anything other than text/plain mail - require that one of the to/cc addresses matches a predefined list of recognized mailing lists Not a guarantee that this service won't get abused, but it's a start to make sure that it won't be quite as tasty of a target. Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
-rw-r--r--misc/send-receive.py54
1 files changed, 45 insertions, 9 deletions
diff --git a/misc/send-receive.py b/misc/send-receive.py
index c54e0ef..574eb10 100644
--- a/misc/send-receive.py
+++ b/misc/send-receive.py
@@ -42,12 +42,15 @@ Deet-doot-dot, I'm a bot
https://korg.docs.kernel.org/
'''
-DEFAULT_CFG = '''
+DEFAULT_CFG = r'''
[main]
myname = Web Endpoint
myurl = http://localhost:8000/_b4_submit
dburl = sqlite:///:memory:
mydomains = kernel.org, linux.dev
+ # One of the To: or Cc: addrs must match this regex
+ # (to ensure that the message was intended to go to mailing lists)
+ mustdest = .*@(vger\.kernel\.org|lists\.linux\.dev|lists\.infradead\.org)
dryrun = false
[sendemail]
smtpserver = localhost
@@ -297,6 +300,10 @@ class SendReceiveListener(object):
if not umsgs:
self.send_error(resp, message='Missing the messages array')
return
+
+ diffre = re.compile(r'^(---.*\n\+\+\+|GIT binary patch|diff --git \w/\S+ \w/\S+)', flags=re.M | re.I)
+ diffstatre = re.compile(r'^\s*\d+ file.*\d+ (insertion|deletion)', flags=re.M | re.I)
+
msgs = list()
conn = self._engine.connect()
md = sa.MetaData()
@@ -310,6 +317,26 @@ class SendReceiveListener(object):
self.send_error(resp, message=f'Signature validation failed for message {at}')
return
msg = email.message_from_string(umsg)
+ # Some quick sanity checking:
+ # - Subject has to start with [PATCH
+ # - Content-type may ONLY be text/plain
+ # - Has to include a diff or a diffstat
+ passes = True
+ if not msg.get('Subject', '').startswith('[PATCH '):
+ passes = False
+ if passes:
+ cte = msg.get_content_type()
+ if cte.lower() != 'text/plain':
+ passes = False
+ if passes:
+ payload = msg.get_payload()
+ if not (diffre.search(payload) or diffstatre.search(payload)):
+ passes = False
+
+ if not passes:
+ self.send_error(resp, message='This service only accepts patches')
+ return
+
msg.add_header('X-Endpoint-Received', f'by {servicename} with auth_id={auth_id}')
msgs.append(msg)
@@ -354,21 +381,30 @@ class SendReceiveListener(object):
else:
msg.add_header('Reply-To', f'<{origpair[1]}>')
- # Does the subject start with [PATCH?
- if subject.startswith('[PATCH '):
- body = msg.get_payload()
- # Parse it as a message and see if we get a From: header
- cmsg = email.message_from_string(body)
- if cmsg.get('From') is None:
- cmsg.add_header('From', origfrom)
- msg.set_payload(cmsg.as_string(policy=emlpolicy, maxheaderlen=0), charset='utf-8')
+ body = msg.get_payload()
+ # Parse it as a message and see if we get a From: header
+ cmsg = email.message_from_string(body)
+ if cmsg.get('From') is None:
+ cmsg.add_header('From', origfrom)
+ msg.set_payload(cmsg.as_string(policy=emlpolicy, maxheaderlen=0), charset='utf-8')
alldests = utils.getaddresses([str(x) for x in msg.get_all('to', [])])
alldests += utils.getaddresses([str(x) for x in msg.get_all('cc', [])])
+
alwaysbcc = self._config['main'].get('alwaysbcc')
if alwaysbcc:
alldests += utils.getaddresses([alwaysbcc])
destaddrs = {x[1] for x in alldests}
+ mustdest = self._config['main'].get('mustdest')
+ if mustdest:
+ matched = False
+ for destaddr in destaddrs:
+ if re.search(mustdest, destaddr, flags=re.I):
+ matched = True
+ break
+ if not matched:
+ self.send_error(resp, message='Destinations must include a mailing list we recognize.')
+ return
bdata = msg.as_string(policy=emlpolicy).encode()