-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrsfa-filter.py
executable file
·143 lines (124 loc) · 6.44 KB
/
rsfa-filter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python3
################################################################################
# #
# Recipient Specific From Addressing extension for mailcow-dockerized #
# #
# Name: rsfa-filter.py #
# Purpose: Perform the actual header rewriting and re-inject mail to #
# postfix's queue #
# #
# Args: see help #
# #
# Author: Christoph Bott <[email protected]> #
# (c) 2022 #
# #
# #
# DISCLAIMER: Use at your own risk! This might break your mailcow setup! #
# #
################################################################################
import sys
import subprocess
import re
import argparse
from email import message_from_file
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
EX_TEMPFAIL = 75
EX_UNAVAILABLE = 69
POSTMASTER = "!POSTMASTER!"
BOUNCETEMPLATE = """\
!!!!! Unable to send message !!!!!
Based on the subject tags in your original subject,
you requested to rewrite the sender address from %s to %s.
However, based on defined policies, you are not allowed to send mails as
%s, when authenticating as %s.
Please find your original email attached.
"""
def makeBounceMail(bm_from, bm_to, subject, text, bouncemail):
msg = MIMEMultipart()
msg['From'] = bm_from
msg['To'] = bm_to
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(MIMEText(text))
part = MIMEApplication(bouncemail.as_string(), Name=bouncemail.get('subject') + ".eml")
part['Content-Disposition'] = 'attachment; filename="%s.eml"' % bouncemail.get('subject')
msg.attach(part)
return(msg)
def checkACL(auth, nf):
## Search email address in new from header
m = re.search(r'[^<]*<(?P<addr>[^>]*)>.*',nf)
CHECKCMD = ['/usr/bin/sudo', '-u', 'postfix', '/usr/sbin/postmap', '-q', m.group('addr').lower(), 'mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf']
check_res = subprocess.run(CHECKCMD, capture_output=True).stdout.decode('UTF-8').strip().lower()
if check_res == auth.lower():
return(True)
else:
print(f'Auth Failure: {check_res} does not match {auth}')
return(False)
def rewriteHeaders(msg,sender,subj):
# remove DKIM Signature
for header in msg._headers:
if header[0].lower() == "dkim-signature":
msg._headers.remove(header)
msg.replace_header("Subject",subj)
msg.replace_header("From",sender)
if msg.get("Return-Path") != None:
msg.replace_header("Return-Path",sender)
return(msg)
def extractSMTPaddr(text):
return(re.findall(r"[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+", text))
def main():
parser = argparse.ArgumentParser(
prog = "rsfa-filter.py",
description = "Read a mail msg from stdin, rewrite Header-From based on subject tags and submit it for delivery."
)
parser.add_argument('-f', '--from', required=True, dest='sender')
parser.add_argument('-a', '--auth-user', required=True, dest='authenticated_as')
parser.add_argument('recipients', nargs='+')
argv = parser.parse_args()
msg_in = message_from_file(sys.stdin)
try:
re_plusext = re.compile(r'^(?P<subjstart>[^\[]*)\[(?P<ext>.*)\] (?P<subjrest>.*)$')
re_subdom = re.compile(r'^(?P<subjstart>[^|]*)[|](?P<ext>[^|]+@[^|]+)[|] (?P<subjrest>.*)$')
subject_in = ''.join(msg_in.get("Subject").splitlines())
m = re_plusext.search(subject_in)
sender = msg_in.get("From")
#sender = argv.sender
if m != None:
## A plus extension tag was found in the subject
subject = m.group('subjstart') + m.group('subjrest')
new_from = sender.replace('@','+'+m.group('ext')+'@')
msg_out = rewriteHeaders(msg_in,new_from,subject)
sendmail_sender = sender
sendmail_recipients = " ".join(argv.recipients)
else:
m = re_subdom.search(subject_in)
if m != None:
## A subdomain tag was found in the subject
subject = m.group('subjstart') + m.group('subjrest')
new_from = re.sub(r'[^< ]+@([^> ]*)',m.group('ext')+r'.\1',sender)
sendmail_sender = extractSMTPaddr(new_from)[0]
sendmail_recipients = " ".join(argv.recipients)
# ACL checks are only required for subdomain addressing
if checkACL(argv.authenticated_as,new_from):
msg_out = rewriteHeaders(msg_in,new_from,subject)
else:
bouncetext = BOUNCETMPL % (sender, new_from, new_from, argv.authenticated_as)
msg_out = makeBounceMail("MAILER DAEMON <" + POSTMASTER + ">", sender, "Delivery failed: Unauthorized sender rewrite requested", bouncetext, msg_in)
sendmail_sender = POSTMASTER
sendmail_recipients = argv.authenticated_as
else:
sys.stderr.write("rsfa-filter: [error] found neither subdomain addressing nor plus-extension in original subject.\n")
sys.exit(EX_UNAVAILABLE)
SENDMAIL = ["/usr/sbin/sendmail", "-G", "-i", "-C", "/opt/postfix/conf", "-f", sendmail_sender, "--", sendmail_recipients]
queue_cmd = subprocess.run(SENDMAIL,input=msg_out.as_string(),text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
print("rsfa-filter: [info] finished mail processing, handing over to postfix again.")
print(queue_cmd.stdout)
sys.exit(queue_cmd.returncode)
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
sys.exit(EX_TEMPFAIL)
if __name__ == '__main__':
main()