mirror of
https://kernel.googlesource.com/pub/scm/linux/kernel/git/tglx/remail.git
synced 2024-11-09 14:02:38 +01:00
remail: Retrieve sender information
For open lists and especially contact points the 'From' mangling is suboptimal as the senders email address is not contained in the mail itself. Due to re-encryption a eventual signature is not longer intact which means that the GPG key or the S/MIME certificate which are embedded into the signature are not transported either. Add infrastructure to collect sender information including key/certificate if available and attach it to the mail. The first attachment contains sender information and the second one if available contains the key or the certificate. The information is only stored when the config switch is enabled and the sender is not subscribed to the list. Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
parent
ea361f973c
commit
3407aa262f
6 changed files with 80 additions and 24 deletions
|
@ -262,6 +262,7 @@ The list base configuration for each list consists of the following items:
|
||||||
listname:
|
listname:
|
||||||
enabled: True
|
enabled: True
|
||||||
moderated: True
|
moderated: True
|
||||||
|
attach_sender_info: False
|
||||||
listid: ...
|
listid: ...
|
||||||
archive:
|
archive:
|
||||||
...
|
...
|
||||||
|
@ -278,6 +279,7 @@ The list base items:
|
||||||
listname:
|
listname:
|
||||||
enabled: True
|
enabled: True
|
||||||
moderated: True
|
moderated: True
|
||||||
|
attach_sender_info: False
|
||||||
|
|
||||||
enabled:
|
enabled:
|
||||||
|
|
||||||
|
@ -292,6 +294,16 @@ The list base items:
|
||||||
with a subscriber. Mails from non-subscribers are not delivered to the
|
with a subscriber. Mails from non-subscribers are not delivered to the
|
||||||
list, they are delivered to the list administrator
|
list, they are delivered to the list administrator
|
||||||
|
|
||||||
|
attach_sender_info:
|
||||||
|
|
||||||
|
Collects information about the sender, email-address, encryption method
|
||||||
|
and if the mail is signed the GPG key or S/MIME certificate contained
|
||||||
|
in the signature. This information is attached to the original mail as
|
||||||
|
two seperate attachments (text info and key/certificate) if this is
|
||||||
|
enabled and the sender is not subscribed to the list. This is for open
|
||||||
|
lists and especially contact points so that the subscribers are able
|
||||||
|
to contact the sender.
|
||||||
|
|
||||||
listid:
|
listid:
|
||||||
|
|
||||||
Optional item to override the default list-id with a custom value.
|
Optional item to override the default list-id with a custom value.
|
||||||
|
|
|
@ -261,6 +261,7 @@ class archive_options(object):
|
||||||
list_defaults = {
|
list_defaults = {
|
||||||
'enabled' : False,
|
'enabled' : False,
|
||||||
'moderated' : False,
|
'moderated' : False,
|
||||||
|
'attach_sender_info' : False,
|
||||||
}
|
}
|
||||||
|
|
||||||
class list_config(object):
|
class list_config(object):
|
||||||
|
|
|
@ -175,7 +175,7 @@ class gpg_crypt(object):
|
||||||
msg_set_payload(msg, pl)
|
msg_set_payload(msg, pl)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def decrypt(self, msg):
|
def decrypt(self, msg, sinfo):
|
||||||
'''
|
'''
|
||||||
Try to handle received mail with PGP. Return decoded or plain mail
|
Try to handle received mail with PGP. Return decoded or plain mail
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -20,6 +20,41 @@ import time
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
class sender_info(object):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.mfrom = msg.get('From')
|
||||||
|
self.info = None
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
info = 'Sent from: %s\n' %self.mfrom
|
||||||
|
if self.info:
|
||||||
|
info += self.info.get_info()
|
||||||
|
else:
|
||||||
|
info += 'No GPG/SMIME info available'
|
||||||
|
return info
|
||||||
|
|
||||||
|
def store_in_msg(self, msg):
|
||||||
|
# Convert the message into a multipart/mixed message
|
||||||
|
ct = msg.get_content_type()
|
||||||
|
if ct != 'multipart/mixed':
|
||||||
|
msg.make_mixed()
|
||||||
|
|
||||||
|
# Attach the sender information as plain/text
|
||||||
|
msg.add_attachment(self.get_info())
|
||||||
|
|
||||||
|
if not self.info:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check whether the GPG key or the S/MIME cert is
|
||||||
|
# available and attach it.
|
||||||
|
fname, data, maintype, subtype = self.info.get_file()
|
||||||
|
if fname:
|
||||||
|
if maintype != 'plain':
|
||||||
|
msg.add_attachment(data, filename=fname, maintype=maintype,
|
||||||
|
subtype=subtype)
|
||||||
|
else:
|
||||||
|
msg.add_attachment(data, filename=fname)
|
||||||
|
|
||||||
def sanitize_headers(msg):
|
def sanitize_headers(msg):
|
||||||
'''
|
'''
|
||||||
Sanitize headers by keeping only the ones which are interesting
|
Sanitize headers by keeping only the ones which are interesting
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
from remail.mail import msg_set_header, msg_force_msg_id, send_mail
|
from remail.mail import msg_set_header, msg_force_msg_id, send_mail
|
||||||
from remail.mail import msg_sanitize_incoming, msg_is_autoreply
|
from remail.mail import msg_sanitize_incoming, msg_is_autoreply
|
||||||
from remail.mail import get_raw_email_addr, decode_addrs
|
from remail.mail import get_raw_email_addr, decode_addrs
|
||||||
from remail.mail import msg_from_string
|
from remail.mail import msg_from_string, sender_info
|
||||||
|
|
||||||
from remail.smime import smime_crypt, RemailSmimeException
|
from remail.smime import smime_crypt, RemailSmimeException
|
||||||
from remail.gpg import gpg_crypt, RemailGPGException
|
from remail.gpg import gpg_crypt, RemailGPGException
|
||||||
|
@ -138,7 +138,7 @@ class maillist(object):
|
||||||
mbox.add(msg)
|
mbox.add(msg)
|
||||||
mbox.close()
|
mbox.close()
|
||||||
|
|
||||||
def decrypt_mail(self, msg):
|
def decrypt_mail(self, msg, sinfo):
|
||||||
'''
|
'''
|
||||||
Decrypt mail after sanitizing it from HTML and outlook magic
|
Decrypt mail after sanitizing it from HTML and outlook magic
|
||||||
and decoding base64 transport.
|
and decoding base64 transport.
|
||||||
|
@ -147,9 +147,9 @@ class maillist(object):
|
||||||
|
|
||||||
msg_plain = None
|
msg_plain = None
|
||||||
if self.smime:
|
if self.smime:
|
||||||
msg_plain = self.smime.decrypt(msg)
|
msg_plain = self.smime.decrypt(msg, sinfo)
|
||||||
if not msg_plain:
|
if not msg_plain:
|
||||||
msg_plain = self.gpg.decrypt(msg)
|
msg_plain = self.gpg.decrypt(msg, sinfo)
|
||||||
return msg_plain
|
return msg_plain
|
||||||
|
|
||||||
def disable_subscribers(self, addrs, msgid):
|
def disable_subscribers(self, addrs, msgid):
|
||||||
|
@ -180,7 +180,11 @@ class maillist(object):
|
||||||
subj = prefix + msg['Subject']
|
subj = prefix + msg['Subject']
|
||||||
msg_set_header(msg, 'Subject', subj)
|
msg_set_header(msg, 'Subject', subj)
|
||||||
|
|
||||||
def moderate(self, msg, dest):
|
def is_subscribed(self, mfrom):
|
||||||
|
r = self.config.subscribers.has_account(mfrom)
|
||||||
|
return r or self.config.admins.has_account(mfrom)
|
||||||
|
|
||||||
|
def moderate(self, msg, dest, mfrom):
|
||||||
'''
|
'''
|
||||||
If the list is moderated make sure that the sender of a mail
|
If the list is moderated make sure that the sender of a mail
|
||||||
is subscribed or an administrator. This checks also aliases.
|
is subscribed or an administrator. This checks also aliases.
|
||||||
|
@ -188,10 +192,7 @@ class maillist(object):
|
||||||
if not self.config.moderated:
|
if not self.config.moderated:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
mfrom = get_raw_email_addr(msg.get('From'))
|
if not self.is_subscribed(mfrom):
|
||||||
r = self.config.subscribers.has_account(mfrom)
|
|
||||||
r = r or self.config.admins.has_account(mfrom)
|
|
||||||
if not r:
|
|
||||||
self.modsubject(msg, '[MODERATED] ')
|
self.modsubject(msg, '[MODERATED] ')
|
||||||
dest.toadmins = True
|
dest.toadmins = True
|
||||||
dest.accounts = self.config.admins
|
dest.accounts = self.config.admins
|
||||||
|
@ -219,7 +220,7 @@ class maillist(object):
|
||||||
dest.toadmins = True
|
dest.toadmins = True
|
||||||
dest.accounts = self.config.admins
|
dest.accounts = self.config.admins
|
||||||
|
|
||||||
def mangle_from(self, msg):
|
def mangle_from(self, msg, mfrom):
|
||||||
'''
|
'''
|
||||||
Build 'From' string so the original 'From' is 'visible':
|
Build 'From' string so the original 'From' is 'visible':
|
||||||
From: $LISTNAME for $ORIGINAL_FROM <$LISTADDRESS>
|
From: $LISTNAME for $ORIGINAL_FROM <$LISTADDRESS>
|
||||||
|
@ -227,23 +228,25 @@ class maillist(object):
|
||||||
If $ORIGINAL_FROM does not contain a name, mangle the email
|
If $ORIGINAL_FROM does not contain a name, mangle the email
|
||||||
address by replacing @ with _at_
|
address by replacing @ with _at_
|
||||||
'''
|
'''
|
||||||
mfrom = msg.get('From').split('<')[0].replace('@', '_at_').strip()
|
|
||||||
return '%s for %s <%s>' % (self.config.name, mfrom,
|
return '%s for %s <%s>' % (self.config.name, mfrom,
|
||||||
self.config.listaddrs.post)
|
self.config.listaddrs.post)
|
||||||
|
|
||||||
def do_process_mail(self, msg, dest):
|
def do_process_mail(self, msg, dest):
|
||||||
|
|
||||||
|
msgid = msg.get('Message-Id', '<No ID>')
|
||||||
|
msgto = msg.get('To')
|
||||||
|
msgfrom = get_raw_email_addr(msg.get('From'))
|
||||||
|
sinfo = sender_info(msg)
|
||||||
|
|
||||||
# Archive the incoming mail
|
# Archive the incoming mail
|
||||||
self.archive_mail(msg, incoming=True)
|
self.archive_mail(msg, incoming=True)
|
||||||
# Destination was already established. Check for bounces first
|
# Destination was already established. Check for bounces first
|
||||||
self.check_bounces(msg, dest)
|
self.check_bounces(msg, dest)
|
||||||
# Check for moderation
|
# Check for moderation
|
||||||
self.moderate(msg, dest)
|
self.moderate(msg, dest, msgfrom)
|
||||||
|
|
||||||
msgid = msg.get('Message-Id', '<No ID>')
|
|
||||||
msgto = msg.get('To')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg_plain = self.decrypt_mail(msg)
|
msg_plain = self.decrypt_mail(msg, sinfo)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
txt = 'Failed to decrypt incoming %s to %s\n' %(msgid, msgto)
|
txt = 'Failed to decrypt incoming %s to %s\n' %(msgid, msgto)
|
||||||
self.logger.log_exception(txt, ex)
|
self.logger.log_exception(txt, ex)
|
||||||
|
@ -251,7 +254,12 @@ class maillist(object):
|
||||||
|
|
||||||
self.archive_mail(msg_plain, admin=dest.toadmin)
|
self.archive_mail(msg_plain, admin=dest.toadmin)
|
||||||
|
|
||||||
mfrom = self.mangle_from(msg)
|
mfrom = self.mangle_from(msg, msgfrom)
|
||||||
|
# Save sender information in the outgoing message?
|
||||||
|
if self.config.attach_sender_info:
|
||||||
|
# Only do so for non-subscribers
|
||||||
|
if not self.is_subscribed(msgfrom):
|
||||||
|
sinfo.store_in_msg(msg_plain)
|
||||||
|
|
||||||
for account in dest.accounts.values():
|
for account in dest.accounts.values():
|
||||||
if not account.enabled:
|
if not account.enabled:
|
||||||
|
|
|
@ -158,7 +158,7 @@ class smime_crypt(object):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def decrypt(self, msg):
|
def decrypt(self, msg, sinfo):
|
||||||
try:
|
try:
|
||||||
envto = msg.get('To', None)
|
envto = msg.get('To', None)
|
||||||
msgid = msg.get('Message-Id', None)
|
msgid = msg.get('Message-Id', None)
|
||||||
|
|
Loading…
Reference in a new issue