[tor-commits] [bridgedb/master] Merge branch 'fix/11522-exc-in-email-dist' into develop
isis at torproject.org
isis at torproject.org
Sat Apr 19 17:02:43 UTC 2014
commit 39e3cf66a2324e262ae1fc617f4f2d0e79c0bf49
Merge: 63c32b5 f6f1bd8
Author: Isis Lovecruft <isis at torproject.org>
Date: Thu Apr 17 04:21:05 2014 +0000
Merge branch 'fix/11522-exc-in-email-dist' into develop
Conflicts:
lib/bridgedb/EmailServer.py
lib/bridgedb/Dist.py | 117 ++-----------
lib/bridgedb/EmailServer.py | 293 ++++++++++++++-------------------
lib/bridgedb/Tests.py | 9 +-
lib/bridgedb/crypto.py | 179 ++++++++++++++++++++
lib/bridgedb/parse/addr.py | 171 ++++++++++++++++++-
lib/bridgedb/test/test_EmailServer.py | 278 ++++++++++++++++++++-----------
6 files changed, 673 insertions(+), 374 deletions(-)
diff --cc lib/bridgedb/EmailServer.py
index c58ec53,022dd41..a1980d1
--- a/lib/bridgedb/EmailServer.py
+++ b/lib/bridgedb/EmailServer.py
@@@ -29,28 -28,19 +28,19 @@@ from zope.interface import implement
from bridgedb import Dist
from bridgedb import I18n
from bridgedb import safelog
+from bridgedb import translations
+ from bridgedb.crypto import getGPGContext
+ from bridgedb.crypto import gpgSignMessage
from bridgedb.Filters import filterBridgesByIP6
from bridgedb.Filters import filterBridgesByIP4
from bridgedb.Filters import filterBridgesByTransport
from bridgedb.Filters import filterBridgesByNotBlockedIn
+ from bridgedb.parse import addr
+ from bridgedb.parse.addr import BadEmail
+ from bridgedb.parse.addr import UnsupportedDomain
+ from bridgedb.parse.addr import canonicalizeEmailDomain
- class MailFile:
- """A file-like object used to hand rfc822.Message a list of lines
- as though it were reading them from a file."""
- def __init__(self, lines):
- self.lines = lines
- self.idx = 0
- def readline(self):
- try :
- line = self.lines[self.idx]
- self.idx += 1
- return line
- except IndexError:
- return ""
--
def getBridgeDBEmailAddrFromList(ctx, address_list):
"""Loop through a list of (full name, email address) pairs and look up our
mail address. If our address isn't found (which can't happen), return
@@@ -75,46 -65,67 +65,67 @@@ def getMailResponse(lines, ctx)
will receive the response, and a readable filelike object containing
the response. Return None,None if we shouldn't answer.
"""
+ raw = io.StringIO()
+ raw.writelines([unicode('{0}\n'.format(line)) for line in lines])
+ raw.seek(0)
+
+ msg = smtp.rfc822.Message(raw)
# Extract data from the headers.
- msg = rfc822.Message(MailFile(lines))
- subject = msg.getheader("Subject", None)
- if not subject: subject = "[no subject]"
- clientFromAddr = msg.getaddr("From")
- clientSenderAddr = msg.getaddr("Sender")
- # RFC822 requires at least one 'To' address
- clientToList = msg.getaddrlist("To")
- clientToaddr = getBridgeDBEmailAddrFromList(ctx, clientToList)
msgID = msg.getheader("Message-ID", None)
- if clientSenderAddr and clientSenderAddr[1]:
- clientAddr = clientSenderAddr[1]
- elif clientFromAddr and clientFromAddr[1]:
- clientAddr = clientFromAddr[1]
- else:
- logging.info("No From or Sender header on incoming mail.")
- return None,None
+ subject = msg.getheader("Subject", None) or "[no subject]"
- # Look up the locale part in the 'To:' address, if there is one and get
- # the appropriate Translation object
- lang = translations.getLocaleFromPlusAddr(clientToaddr)
- t = translations.installTranslations(lang)
+ fromHeader = msg.getaddr("From")
+ senderHeader = msg.getaddr("Sender")
+ clientAddrHeader = None
try:
- _, addrdomain = Dist.extractAddrSpec(clientAddr.lower())
- except BadEmail:
- logging.info("Ignoring bad address on incoming email.")
+ clientAddrHeader = fromHeader[1]
+ except (IndexError, TypeError, AttributeError):
+ pass
+
+ if not clientAddrHeader:
+ logging.warn("No From header on incoming mail.")
+ try:
+ clientAddrHeader = senderHeader[1]
+ except (IndexError, TypeError, AttributeError):
+ pass
+
+ if not clientAddrHeader:
+ logging.warn("No Sender header on incoming mail.")
return None, None
- if not addrdomain:
- logging.info("Couldn't parse domain from %r" % clientAddr)
+ try:
+ clientAddr = addr.normalizeEmail(clientAddrHeader,
+ ctx.cfg.EMAIL_DOMAIN_MAP,
+ ctx.cfg.EMAIL_DOMAIN_RULES)
+ except (UnsupportedDomain, BadEmail) as error:
+ logging.warn(error)
+ return None, None
- if addrdomain and ctx.cfg.EMAIL_DOMAIN_MAP:
- addrdomain = ctx.cfg.EMAIL_DOMAIN_MAP.get(addrdomain, addrdomain)
+ # RFC822 requires at least one 'To' address
+ clientToList = msg.getaddrlist("To")
+ clientToAddr = getBridgeDBEmailAddrFromList(ctx, clientToList)
+
+ # Look up the locale part in the 'To:' address, if there is one and get
+ # the appropriate Translation object
- lang = getLocaleFromPlusAddr(clientToAddr)
- t = I18n.getLang(lang)
++ lang = translations.getLocaleFromPlusAddr(clientToAddr)
++ t = translations.installTranslations(lang)
- if addrdomain not in ctx.cfg.EMAIL_DOMAINS:
- logging.warn("Unrecognized email domain %r", addrdomain)
+ canon = ctx.cfg.EMAIL_DOMAIN_MAP
+ for domain, rule in ctx.cfg.EMAIL_DOMAIN_RULES.items():
+ if domain not in canon.keys():
+ canon[domain] = domain
+ for domain in ctx.cfg.EMAIL_DOMAINS:
+ canon[domain] = domain
+
+ try:
+ _, clientDomain = addr.extractEmailAddress(clientAddr.lower())
+ canonical = canonicalizeEmailDomain(clientDomain, canon)
+ except (UnsupportedDomain, BadEmail) as error:
+ logging.warn(error)
return None, None
- rules = ctx.cfg.EMAIL_DOMAIN_RULES.get(addrdomain, [])
+ rules = ctx.cfg.EMAIL_DOMAIN_RULES.get(canonical, [])
if 'dkim' in rules:
# getheader() returns the last of a given kind of header; we want
@@@ -287,7 -297,71 +297,46 @@@ def replyToMail(lines, ctx)
reactor.connectTCP(ctx.smtpServer, ctx.smtpPort, factory)
return d
-def getLocaleFromPlusAddr(address):
- """See whether the user sent his email to a 'plus' address, for
- instance to bridgedb+fa at tpo. Plus addresses are the current
- mechanism to set the reply language
- """
- replyLocale = "en"
- r = '.*(<)?(\w+\+(\w+)@\w+(?:\.\w+)+)(?(1)>)'
- match = re.match(r, address)
- if match:
- replyLocale = match.group(3)
-
- return replyLocale
-
-def getLocaleFromRequest(request):
- # See if we did get a request for a certain locale, otherwise fall back
- # to 'en':
- # Try evaluating the path /foo first, then check if we got a ?lang=foo
- default_lang = lang = "en"
- if len(request.path) > 1:
- lang = request.path[1:]
- if lang == default_lang:
- lang = request.args.get("lang", [default_lang])
- lang = lang[0]
- return I18n.getLang(lang)
-
+ def composeEmail(fromAddr, clientAddr, subject, body,
+ msgID=None, gpgContext=None):
+
+ if not subject.startswith("Re:"):
+ subject = "Re: %s" % subject
+
+ msg = smtp.rfc822.Message(io.StringIO())
+ msg.setdefault("From", fromAddr)
+ msg.setdefault("To", clientAddr)
+ msg.setdefault("Message-ID", smtp.messageid())
+ msg.setdefault("Subject", subject)
+ if msgID:
+ msg.setdefault("In-Reply-To", msgID)
+ msg.setdefault("Date", smtp.rfc822date())
+ msg.setdefault('Content-Type', 'text/plain; charset="utf-8"')
+ headers = [': '.join(m) for m in msg.items()]
+
+ mail = io.BytesIO()
+ mail.writelines(buffer("\r\n".join(headers)))
+ mail.writelines(buffer("\r\n"))
+ mail.writelines(buffer("\r\n"))
+
+ if not gpgContext:
+ mail.write(buffer(body))
+ else:
+ signature, siglist = gpgSignMessage(gpgContext, body)
+ if signature:
+ mail.writelines(buffer(signature))
+ mail.seek(0)
+
+ # Only log the email text (including all headers) if SAFE_LOGGING is
+ # disabled:
+ if not safelog.safe_logging:
+ logging.debug("Email contents:\n\n%s" % mail.read())
+ mail.seek(0)
+ else:
+ logging.debug("Email text for %r created." % clientAddr)
+
+ return clientAddr, mail
+
class MailContext(object):
"""Helper object that holds information used by email subsystem."""
More information about the tor-commits
mailing list