[tor-commits] [bridgedb/master] Add a personalised greeting and footer to emails.
isis at torproject.org
isis at torproject.org
Fri May 16 18:52:52 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