[tor-commits] [bridgedb/develop] Add a personalised greeting and footer to emails.

isis at torproject.org isis at torproject.org
Wed May 14 03:42:28 UTC 2014


commit 9c6994cc8f912b5d2f05dbc0dd10057a7cedf4f5
Author: Isis Lovecruft <isis at torproject.org>
Date:   Mon May 12 15:02:32 2014 +0000

    Add a personalised greeting and footer to emails.
    
     * FIXES a problem noted by Robert Ransom (in ticket #5463) with
       adversaries being able to capture a signed email for Alice and resend
       it to Bob to target Bob by having influence over his available
       bridges.
    
     * ADD a footer to emails, which includes the client's email address and
       a timestamp for when the email was generated.
    
     * ADD the answer bridge lines to the answer template creation
       parameters, so that the answer string isn't formatted into the
       message after message creation.
    
    Rename email.template functions for adding text. To distinguish them
    from the templates.build* functions, which build entire emails. The
    following functions have been renamed:
    
     * RENAME templates.buildCommands()     → templates.addCommands()
     * RENAME templates.buildKeyfile()      → templates.addKeyfile()
     * RENAME templates.buildHowto()        → templates.addHowto()
     * RENAME templates.buildBridgeAnswer() → templates.buildBridgeAnswer()
---
 lib/bridgedb/email/server.py           |   22 +++---
 lib/bridgedb/email/templates.py        |  136 +++++++++++++++++++++++---------
 lib/bridgedb/strings.py                |    9 +++
 lib/bridgedb/test/test_email_server.py |    2 +-
 4 files changed, 122 insertions(+), 47 deletions(-)

diff --git a/lib/bridgedb/email/server.py b/lib/bridgedb/email/server.py
index 7aeb4ff..678c154 100644
--- a/lib/bridgedb/email/server.py
+++ b/lib/bridgedb/email/server.py
@@ -77,7 +77,7 @@ def checkDKIM(message, rules):
             return False
     return True
 
-def createResponseBody(lines, context, toAddress, lang='en'):
+def createResponseBody(lines, context, clientAddress, lang='en'):
     """Parse the **lines** from an incoming email request and determine how to
     respond.
 
@@ -85,7 +85,8 @@ def createResponseBody(lines, context, toAddress, lang='en'):
         client.
     :type context: class:`MailContext`
     :param context: The context which contains settings for the email server.
-    :param str toAddress: The rfc:`2821` email address which should be in the
+    :type clientAddress: :api:`twisted.mail.smtp.Address`
+    :param clientAddress: The client's email address which should be in the
         :header:`To:` header of the response email.
     :param str lang: The 2-5 character locale code to use for translating the
         email. This is obtained from a client sending a email to a valid plus
@@ -98,6 +99,7 @@ def createResponseBody(lines, context, toAddress, lang='en'):
         string containing the (optionally translated) body for the email
         response which we should send out.
     """
+    clientAddr = '@'.join([clientAddress.local, clientAddress.domain])
     t = translations.installTranslations(lang)
 
     bridges = None
@@ -108,25 +110,25 @@ def createResponseBody(lines, context, toAddress, lang='en'):
         # valid email commands:
         if not bridgeRequest.isValid():
             raise EmailRequestedHelp("Email request from %r was invalid."
-                                     % toAddress)
+                                     % clientAddr)
 
         # Otherwise they must have requested bridges:
         interval = context.schedule.getInterval(time.time())
         bridges = context.distributor.getBridgesForEmail(
-            toAddress,
+            clientAddr,
             interval,
             context.nBridges,
             countryCode=None,
             bridgeFilterRules=bridgeRequest.filters)
     except EmailRequestedHelp as error:
         logging.info(error)
-        return templates.buildWelcomeText(t)
+        return templates.buildWelcomeText(t, clientAddress)
     except EmailRequestedKey as error:
         logging.info(error)
-        return templates.buildKeyfile(t)
+        return templates.buildKeyMessage(t, clientAddress)
     except TooSoonEmail as error:
         logging.info("Got a mail too frequently: %s." % error)
-        return templates.buildSpamWarning(t)
+        return templates.buildSpamWarning(t, clientAddress)
     except (IgnoreEmail, BadEmail) as error:
         logging.info(error)
         # Don't generate a response if their email address is unparsable or
@@ -140,8 +142,8 @@ def createResponseBody(lines, context, toAddress, lang='en'):
                 includeFingerprint=context.includeFingerprints,
                 addressClass=bridgeRequest.addressClass,
                 transport=transport,
-                request=toAddress) for b in bridges)
-        return templates.buildMessage(t) % answer
+                request=clientAddr) for b in bridges)
+        return templates.buildAnswerMessage(t, clientAddress, answer)
 
 def generateResponse(fromAddress, clientAddress, body, subject=None,
                      messageID=None, gpgContext=None):
@@ -636,7 +638,7 @@ class MailMessage(object):
         lang = translations.getLocaleFromPlusAddr(recipient)
         logging.info("Client requested email translation: %s" % lang)
 
-        body = createResponseBody(self.lines, self.context, clientAddr, lang)
+        body = createResponseBody(self.lines, self.context, client, lang)
         if not body: return d  # The client was already warned.
 
         response = generateResponse(self.context.fromAddr, clientAddr, body,
diff --git a/lib/bridgedb/email/templates.py b/lib/bridgedb/email/templates.py
index 6c25038..8a7f4aa 100644
--- a/lib/bridgedb/email/templates.py
+++ b/lib/bridgedb/email/templates.py
@@ -18,12 +18,14 @@ from __future__ import unicode_literals
 import logging
 import os
 
+from datetime import datetime
+
 from bridgedb import strings
 from bridgedb.Dist import MAX_EMAIL_RATE
 from bridgedb.HTTPServer import TEMPLATE_DIR
 
 
-def buildCommands(template):
+def addCommands(template):
     # Tell them about the various email commands:
     cmdlist = []
     cmdlist.append(template.gettext(strings.EMAIL_MISC_TEXT.get(3)))
@@ -44,21 +46,23 @@ def buildCommands(template):
 
     return commands
 
-def buildHowto(template):
-    howToTBB  = template.gettext(strings.HOWTO_TBB[1]) % strings.EMAIL_SPRINTF["HOWTO_TBB1"]
-    howToTBB += u'\n\n'
-    howToTBB += template.gettext(strings.HOWTO_TBB[2])
-    howToTBB += u'\n\n'
-    howToTBB += u'\n'.join(["> {0}".format(ln) for ln in
-                            template.gettext(strings.HOWTO_TBB[3]).split('\n')])
-    howToTBB += u'\n\n'
-    howToTBB += template.gettext(strings.HOWTO_TBB[4])
-    howToTBB += u'\n\n'
-    howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB1")
-    howToTBB += u'\n\n'
-    return howToTBB
+def addGreeting(template, clientName=None, welcome=False):
+    greeting = ""
+
+    if not clientName:
+        greeting = template.gettext(strings.EMAIL_MISC_TEXT[7])
+    else:
+        greeting = template.gettext(strings.EMAIL_MISC_TEXT[6]) % clientName
+
+    if greeting:
+        if welcome:
+            greeting += u' '
+            greeting += template.gettext(strings.EMAIL_MISC_TEXT[4])
+        greeting += u'\n\n'
 
-def buildKeyfile(template):
+    return greeting
+
+def addKeyfile(template):
     filename = os.path.join(TEMPLATE_DIR, 'bridgedb.asc')
 
     try:
@@ -72,11 +76,69 @@ def buildKeyfile(template):
 
     return keyFile
 
-def buildWelcomeText(template):
+def addBridgeAnswer(template, answer):
+    # Give the user their bridges, i.e. the `answer`:
+    bridgeLines  = template.gettext(strings.EMAIL_MISC_TEXT[0])
+    bridgeLines += u"\n\n"
+    bridgeLines += template.gettext(strings.EMAIL_MISC_TEXT[1])
+    bridgeLines += u"\n\n"
+    bridgeLines += u"%s\n\n" % answer
+
+    return bridgeLines
+
+def addHowto(template):
+    howToTBB  = template.gettext(strings.HOWTO_TBB[1]) % strings.EMAIL_SPRINTF["HOWTO_TBB1"]
+    howToTBB += u'\n\n'
+    howToTBB += template.gettext(strings.HOWTO_TBB[2])
+    howToTBB += u'\n\n'
+    howToTBB += u'\n'.join(["> {0}".format(ln) for ln in
+                            template.gettext(strings.HOWTO_TBB[3]).split('\n')])
+    howToTBB += u'\n\n'
+    howToTBB += template.gettext(strings.HOWTO_TBB[4])
+    howToTBB += u'\n\n'
+    howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB1")
+    howToTBB += u'\n\n'
+    return howToTBB
+
+def addFooter(template, clientAddress=None):
+    """Add a footer.
+
+    --
+     <3 BridgeDB
+
+    -------------------------------------------------------------------------
+    Public Keys: https://bridges.torproject.org/keys
+
+    This email was generated with rainbows, unicorns, and sparkles
+    for alice at example.com on Friday, 09 May, 2014 at 18:59:39.
+    """
+    now = datetime.utcnow()
+    clientAddr = clientAddress.addrstr
+
+    footer  = u'--\n'
+    footer += u' <3 BridgeDB\n\n'
+    footer += u'-' * 70
+    footer += u'\n'
+    footer += template.gettext(strings.EMAIL_MISC_TEXT[8])
+    footer += u': https://bridges.torproject.org/keys\n'
+    footer += template.gettext(strings.EMAIL_MISC_TEXT[9]) \
+              % (clientAddr,
+                 now.strftime('%A, %d %B, %Y'),
+                 now.strftime('%H:%M:%S'))
+    footer += u'\n'
+
+    return footer
+
+def buildKeyMessage(template, clientAddress=None):
+    message  = addKeyfile(template)
+    message += addFooter(template, clientAddress)
+    return message
+
+def buildWelcomeText(template, clientAddress=None):
     sections = []
-    sections.append(template.gettext(strings.EMAIL_MISC_TEXT[4]))
+    sections.append(addGreeting(template, clientAddress.local, welcome=True))
 
-    commands = buildCommands(template)
+    commands = addCommands(template)
     sections.append(commands)
 
     # Include the same messages as the homepage of the HTTPS distributor:
@@ -88,36 +150,38 @@ def buildWelcomeText(template):
     message  = u"\n\n".join(sections)
     # Add the markdown links at the end:
     message += strings.EMAIL_REFERENCE_LINKS.get("WELCOME0")
-    message += u"\n"
-
-    return message
+    message += u"\n\n"
+    message += addFooter(template, clientAddress)
 
-def buildBridgeAnswer(template):
-    # Give the user their bridges, i.e. the `answer`:
-    message = template.gettext(strings.EMAIL_MISC_TEXT[0]) + u"\n\n" \
-              + template.gettext(strings.EMAIL_MISC_TEXT[1]) + u"\n\n" \
-              + u"%s\n\n"
     return message
 
-def buildMessage(template):
-    message = None
+def buildAnswerMessage(template, clientAddress=None, answer=None):
     try:
-        message  = buildBridgeAnswer(template)
-        message += buildHowto(template)
+        message  = addGreeting(template, clientAddress.local)
+        message += addBridgeAnswer(template, answer)
+        message += addHowto(template)
+        message += u'\n\n'
+        message += addCommands(template)
         message += u'\n\n'
-        message += buildCommands(template)
+        message += addFooter(template, clientAddress)
     except Exception as error:  # pragma: no cover
         logging.error("Error while formatting email message template:")
         logging.exception(error)
+
     return message
 
-def buildSpamWarning(template):
-    message = None
+def buildSpamWarning(template, clientAddress=None):
+    message = addGreeting(template, clientAddress.local)
+
     try:
-        message = template.gettext(strings.EMAIL_MISC_TEXT[0]) + u"\n\n" \
-                  + template.gettext(strings.EMAIL_MISC_TEXT[2]) + u"\n"
-        message = message % str(MAX_EMAIL_RATE / 3600)
+        message += template.gettext(strings.EMAIL_MISC_TEXT[0])
+        message += u"\n\n"
+        message += template.gettext(strings.EMAIL_MISC_TEXT[2]) \
+                   % str(MAX_EMAIL_RATE / 3600)
+        message += u"\n\n"
+        message += addFooter(template, clientAddress)
     except Exception as error:  # pragma: no cover
         logging.error("Error while formatting email spam template:")
         logging.exception(error)
+
     return message
diff --git a/lib/bridgedb/strings.py b/lib/bridgedb/strings.py
index 421f8ec..e001d45 100644
--- a/lib/bridgedb/strings.py
+++ b/lib/bridgedb/strings.py
@@ -27,6 +27,15 @@ COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"""),
     4: _("Welcome to BridgeDB!"),
     # TRANLATORS: Please DO NOT tranlate the words "transport" or "TYPE".
     5: _("Currently supported tranport TYPEs:"),
+    6: _("Hey, %s!"),
+    7: _("Hello, friend!"),
+    8: _("Public Keys"),
+    # TRANSLATORS: This string will end up saying something like:
+    # "This email was generated with rainbows, unicorns, and sparkles
+    #  for alice at example.com on Friday, 09 May, 2014 at 18:59:39."
+    9: _("""\
+This email was generated with rainbows, unicorns, and sparkles
+for %s on %s at %s."""),
 }
 
 WELCOME = {
diff --git a/lib/bridgedb/test/test_email_server.py b/lib/bridgedb/test/test_email_server.py
index d856a72..b7e12c6 100644
--- a/lib/bridgedb/test/test_email_server.py
+++ b/lib/bridgedb/test/test_email_server.py
@@ -154,7 +154,7 @@ class CreateResponseBodyTests(unittest.TestCase):
 
     def _getIncomingLines(self, clientAddress="user at example.com"):
         """Generate the lines of an incoming email from **clientAddress**."""
-        self.toAddress = clientAddress
+        self.toAddress = server.smtp.Address(clientAddress)
         lines = [
             "From: %s" % clientAddress,
             "To: bridges at localhost",





More information about the tor-commits mailing list