1 # BridgeDB by Nick Mathewson.
2 # Copyright (c) 2007, The Tor Project, Inc.
3 # See LICENSE for licensing information
6 This module implements the web and email interfaces to the bridge database.
9 from cStringIO
import StringIO
15 from zope
.interface
import implements
17 from twisted
.internet
import reactor
18 from twisted
.internet
.defer
import Deferred
19 import twisted
.web
.resource
20 import twisted
.web
.server
21 import twisted
.mail
.smtp
25 HTML_MESSAGE_TEMPLATE
= """
27 <p>Here are your bridge relays:
32 <p>Bridge relays (or "bridges" for short) are Tor relays that aren't listed
33 in the main directory. Since there is no complete public list of them,
34 even if your ISP is filtering connections to all the known Tor relays,
35 they probably won't be able to block all the bridges.</p>
36 <p>To use the above lines, go to Vidalia's Network settings page, and click
37 "My ISP blocks connections to the Tor network". Then add each bridge
38 address one at a time.</p>
39 <p>Configuring more than one bridge address will make your Tor connection
40 more stable, in case some of the bridges become unreachable.</p>
41 <p>Another way to find public bridge addresses is to send mail to
42 bridges@torproject.org with the line "get bridges" by itself in the body
43 of the mail. However, so we can make it harder for an attacker to learn
44 lots of bridge addresses, you must send this request from a gmail or
49 EMAIL_MESSAGE_TEMPLATE
= """\
50 [This is an automated message; please do not reply.]
52 Here are your bridge relays:
56 Bridge relays (or "bridges" for short) are Tor relays that aren't listed
57 in the main directory. Since there is no complete public list of them,
58 even if your ISP is filtering connections to all the known Tor relays,
59 they probably won't be able to block all the bridges.
61 To use the above lines, go to Vidalia's Network settings page, and click
62 "My ISP blocks connections to the Tor network". Then add each bridge
63 address one at a time.
65 Configuring more than one bridge address will make your Tor connection
66 more stable, in case some of the bridges become unreachable.
68 Another way to find public bridge addresses is to visit
69 https://bridges.torproject.org/. The answers you get from that page
70 will change every few days, so check back periodically if you need more
74 class WebResource(twisted
.web
.resource
.Resource
):
75 """This resource is used by Twisted Web to give a web page with some
76 bridges in response to a request."""
79 def __init__(self
, distributor
, schedule
, N
=1):
80 """Create a new WebResource.
81 distributor -- an IPBasedDistributor object
82 schedule -- an IntervalSchedule object
83 N -- the number of bridges to hand out per query.
85 twisted
.web
.resource
.Resource
.__init
__(self
)
86 self
.distributor
= distributor
87 self
.schedule
= schedule
88 self
.nBridgesToGive
= N
90 def render_GET(self
, request
):
91 interval
= self
.schedule
.getInterval(time
.time())
92 ip
= request
.getClientIP()
93 bridges
= self
.distributor
.getBridgesForIP(ip
, interval
,
96 answer
= "".join("%s\n" % b
.getConfigLine() for b
in bridges
)
98 answer
= "No bridges available."
100 logging
.info("Replying to web request from %s", ip
)
101 return HTML_MESSAGE_TEMPLATE
% answer
103 def addWebServer(cfg
, dist
, sched
):
104 """Set up a web server.
105 cfg -- a configuration object from Main. We use these options:
106 HTTPS_N_BRIDGES_PER_ANSWER
107 HTTP_UNENCRYPTED_PORT
108 HTTP_UNENCRYPTED_BIND_IP
111 dist -- an IPBasedDistributor object.
112 sched -- an IntervalSchedule object.
114 Site
= twisted
.web
.server
.Site
115 resource
= WebResource(dist
, sched
, cfg
.HTTPS_N_BRIDGES_PER_ANSWER
)
116 site
= Site(resource
)
117 if cfg
.HTTP_UNENCRYPTED_PORT
:
118 ip
= cfg
.HTTP_UNENCRYPTED_BIND_IP
or ""
119 reactor
.listenTCP(cfg
.HTTP_UNENCRYPTED_PORT
, site
, interface
=ip
)
121 from twisted
.internet
.ssl
import DefaultOpenSSLContextFactory
122 #from OpenSSL.SSL import SSLv3_METHOD
123 ip
= cfg
.HTTPS_BIND_IP
or ""
124 factory
= DefaultOpenSSLContextFactory(cfg
.HTTPS_KEY_FILE
,
126 reactor
.listenSSL(cfg
.HTTPS_PORT
, site
, factory
, interface
=ip
)
130 """A file-like object used to hand rfc822.Message a list of lines
131 as though it were reading them from a file."""
132 def __init__(self
, lines
):
137 line
= self
.lines
[self
.idx
]
143 def getMailResponse(lines
, ctx
):
144 """Given a list of lines from an incoming email message, and a
145 MailContext object, parse the email and decide what to do in response.
146 If we want to answer, return a 2-tuple containing the address that
147 will receive the response, and a readable filelike object containing
148 the response. Return None,None if we shouldn't answer.
150 # Extract data from the headers.
151 msg
= rfc822
.Message(MailFile(lines
))
152 subject
= msg
.getheader("Subject", None)
153 if not subject
: subject
= "[no subject]"
154 clientFromAddr
= msg
.getaddr("From")
155 clientSenderAddr
= msg
.getaddr("Sender")
156 msgID
= msg
.getheader("Message-ID")
157 if clientSenderAddr
and clientSenderAddr
[1]:
158 clientAddr
= clientSenderAddr
[1]
159 elif clientFromAddr
and clientFromAddr
[1]:
160 clientAddr
= clientFromAddr
[1]
162 logging
.info("No From or Sender header on incoming mail.")
165 _
, addrdomain
= bridgedb
.Dist
.extractAddrSpec(clientAddr
.lower())
167 logging
.info("Couldn't parse domain from %r", clientAddr
)
168 if addrdomain
and ctx
.cfg
.EMAIL_DOMAIN_MAP
:
169 addrdomain
= ctx
.cfg
.EMAIL_DOMAIN_MAP
.get(addrdomain
, addrdomain
)
170 rules
= ctx
.cfg
.EMAIL_DOMAIN_RULES
.get(addrdomain
, [])
172 # getheader() returns the last of a given kind of header; we want
173 # to get the first, so we use getheaders() instead.
174 dkimHeaders
= msg
.getheaders("X-DKIM-Authentication-Result")
175 dkimHeader
= "<no header>"
176 if dkimHeaders
: dkimHeader
= dkimHeaders
[0]
177 if not dkimHeader
.startswith("pass"):
178 logging
.info("Got a bad dkim header (%r) on an incoming mail; "
179 "rejecting it.", dkimHeader
)
182 # Was the magic string included
184 if ln
.strip().lower() in ("get bridges", "subject: get bridges"):
187 logging
.info("Got a mail from %r with no bridge request; dropping",
191 # Figure out which bridges to send
193 interval
= ctx
.schedule
.getInterval(time
.time())
194 bridges
= ctx
.distributor
.getBridgesForEmail(clientAddr
,
196 except bridgedb
.Dist
.BadEmail
, e
:
197 logging
.info("Got a mail from a bad email address %r: %s.",
201 # Generate the message.
203 w
= MimeWriter
.MimeWriter(f
)
204 w
.addheader("From", ctx
.fromAddr
)
205 w
.addheader("To", clientAddr
)
206 w
.addheader("Message-ID", twisted
.mail
.smtp
.messageid())
207 if not subject
.startswith("Re:"): subject
= "Re: %s"%subject
208 w
.addheader("Subject", subject
)
210 w
.addheader("In-Reply-To", msgID
)
211 w
.addheader("Date", twisted
.mail
.smtp
.rfc822date())
212 body
= w
.startbody("text/plain")
215 answer
= "".join(" %s\n" % b
.getConfigLine() for b
in bridges
)
217 answer
= "(no bridges currently available)"
218 body
.write(EMAIL_MESSAGE_TEMPLATE
% answer
)
223 def replyToMail(lines
, ctx
):
224 """Given a list of lines from an incoming email message, and a
225 MailContext object, possibly send a reply.
227 logging
.info("Got a completed email; attempting to reply.")
228 sendToUser
, response
= getMailResponse(lines
, ctx
)
233 factory
= twisted
.mail
.smtp
.SMTPSenderFactory(
238 reactor
.connectTCP(ctx
.smtpServer
, ctx
.smtpPort
, factory
)
239 logging
.info("Sending reply to %r", sendToUser
)
243 """Helper object that holds information used by email subsystem."""
244 def __init__(self
, cfg
, dist
, sched
):
245 # Reject any RCPT TO lines that aren't to this user.
246 self
.username
= "bridges"
247 # Reject any mail longer than this.
248 self
.maximumSize
= 32*1024
249 # Use this server for outgoing mail.
250 self
.smtpServer
= "127.0.0.1"
252 # Use this address in the MAIL FROM line for outgoing mail.
253 self
.smtpFromAddr
= (cfg
.EMAIL_SMTP_FROM_ADDR
or
254 "bridges@torproject.org")
255 # Use this address in the "From:" header for outgoing mail.
256 self
.fromAddr
= (cfg
.EMAIL_FROM_ADDR
or
257 "bridges@torproject.org")
258 # An EmailBasedDistributor object
259 self
.distributor
= dist
260 # An IntervalSchedule object
261 self
.schedule
= sched
262 # The number of bridges to send for each email.
263 self
.N
= cfg
.EMAIL_N_BRIDGES_PER_ANSWER
266 """Plugs into the Twisted Mail and receives an incoming message.
267 Once the message is in, we reply or we don't. """
268 implements(twisted
.mail
.smtp
.IMessage
)
270 def __init__(self
, ctx
):
271 """Create a new MailMessage from a MailContext."""
275 self
.ignoring
= False
277 def lineReceived(self
, line
):
278 """Called when we get another line of an incoming message."""
279 self
.nBytes
+= len(line
)
280 if self
.nBytes
> self
.ctx
.maximumSize
:
283 self
.lines
.append(line
)
285 def eomReceived(self
):
286 """Called when we receive the end of a message."""
287 if not self
.ignoring
:
288 replyToMail(self
.lines
, self
.ctx
)
289 return twisted
.internet
.defer
.succeed(None)
291 def connectionLost(self
):
292 """Called if we die partway through reading a message."""
296 """Plugs into Twisted Mail and handles SMTP commands."""
297 implements(twisted
.mail
.smtp
.IMessageDelivery
)
298 def setBridgeDBContext(self
, ctx
):
300 def receivedHeader(self
, helo
, origin
, recipients
):
301 #XXXX what is this for? what should it be?
302 return "Received: BridgeDB"
303 def validateFrom(self
, helo
, origin
):
305 def validateTo(self
, user
):
306 if user
.dest
.local
!= self
.ctx
.username
:
307 raise twisted
.mail
.smtp
.SMTPBadRcpt(user
)
308 return lambda: MailMessage(self
.ctx
)
310 class MailFactory(twisted
.mail
.smtp
.SMTPFactory
):
311 """Plugs into Twisted Mail; creates a new MailDelivery whenever we get
312 a connection on the SMTP port."""
313 def __init__(self
, *a
, **kw
):
314 twisted
.mail
.smtp
.SMTPFactory
.__init
__(self
, *a
, **kw
)
315 self
.delivery
= MailDelivery()
317 def setBridgeDBContext(self
, ctx
):
319 self
.delivery
.setBridgeDBContext(ctx
)
321 def buildProtocol(self
, addr
):
322 p
= twisted
.mail
.smtp
.SMTPFactory
.buildProtocol(self
, addr
)
323 p
.delivery
= self
.delivery
326 def addSMTPServer(cfg
, dist
, sched
):
327 """Set up a smtp server.
328 cfg -- a configuration object from Main. We use these options:
331 EMAIL_N_BRIDGES_PER_ANSWER
333 dist -- an EmailBasedDistributor object.
334 sched -- an IntervalSchedule object.
336 ctx
= MailContext(cfg
, dist
, sched
)
337 factory
= MailFactory()
338 factory
.setBridgeDBContext(ctx
)
339 ip
= cfg
.EMAIL_BIND_IP
or ""
340 reactor
.listenTCP(cfg
.EMAIL_PORT
, factory
, interface
=ip
)
344 """Start all the servers that we've configured. Exits when they do."""