[tor-commits] [bridgedb/develop] Merge branch 'fix/11522-exc-in-email-dist' into develop

isis at torproject.org isis at torproject.org
Thu Apr 17 05:10:03 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