[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [bridgedb/master] Add mechanism/script for querying a remote TorBulkExitList.py.
commit 5bf4a24c6715ab6c35ff4fae017c852e4b013749
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
Date: Sat Nov 2 07:15:33 2013 +0000
Add mechanism/script for querying a remote TorBulkExitList.py.
* This uses a twisted.internet.protocol.ProcessProtocol to handle, process,
and store within a ProxyCategory class, the data asynchronously as it is
received from the connection, and uses SSL at the transport layer.
---
lib/bridgedb/Bridges.py | 3 +-
scripts/get-tor-exits | 199 +++++++++++++++++++++++++++++++++++++++++++++++
setup.py | 3 +-
3 files changed, 203 insertions(+), 2 deletions(-)
diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
index a3f50b4..fa718c0 100644
--- a/lib/bridgedb/Bridges.py
+++ b/lib/bridgedb/Bridges.py
@@ -64,7 +64,8 @@ def is_valid_ip(ip):
:param str ip: A string representing an IPv4 or IPv6 address.
"""
-
+ logging.warn(PendingDeprecationWarning(
+ "Bridges.is_valid_ip() is replaced with parse.isIPAddress()"))
# ipaddr does not treat "1.2" as a synonym for "0.0.1.2"
try:
ipaddr.IPAddress(ip)
diff --git a/scripts/get-tor-exits b/scripts/get-tor-exits
new file mode 100755
index 0000000..32b457d
--- /dev/null
+++ b/scripts/get-tor-exits
@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@xxxxxxxxxxxxxx>
+# please also see AUTHORS file
+# :copyright: (c) 2013 Isis Lovecruft
+# (c) 2007-2013, The Tor Project, Inc.
+# (c) 2007-2013, all entities within the AUTHORS file
+# :license: 3-clause BSD, see included LICENSE for information
+
+"""get-tor-exits -- Download the current list of Tor exit relays."""
+
+from __future__ import print_function
+
+import os.path
+import socket
+import sys
+
+from ipaddr import IPAddress
+
+from OpenSSL import SSL
+
+from twisted.python import log
+from twisted.python import usage
+from twisted.names import client as dnsclient
+from twisted.names import error as dnserror
+from twisted.web import client
+from twisted.internet import defer
+from twisted.internet import protocol
+from twisted.internet import reactor
+from twisted.internet import ssl
+from twisted.internet.error import TimeoutError
+
+
+log.startLogging(sys.stderr)
+
+
+def backupFile(filename):
+ """Move our old exit list file so that we don't append to it."""
+ if os.path.isfile(filename):
+ backup = filename + '.bak'
+ log.msg("get-tor-exits: Moving old exit list file to %s"
+ % backup)
+ os.renames(filename, backup)
+
+def getSelfIPAddress():
+ """Get external IP address, to ask check which relays can exit to here.
+
+ TODO: This is blocking. Make it use Twisted.
+ """
+ ip = s = None
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(('bridges.torproject.org', 443))
+ name = s.getsockname()[0]
+ ip = IPAddress(name)
+ if ip.is_link_local or ip.is_private or ip.is_reserved:
+ name = s.getpeername()[0]
+ ip = IPAddress(name)
+ except ValueError as error:
+ log.err("get-tor-exits: A socket gave us something that wasn't an IP: %s"
+ % error)
+ except Exception as error:
+ log.err("get-tor-exits: Unhandled Exception: %s\n%s\n"
+ % (error.message, error))
+ finally:
+ if s is not None:
+ s.close()
+ return ip.compressed
+
+def handleTimeout(failure):
+ """Handle a **failure** due to a timedout connection attempt."""
+ if failure.type == TimeoutError:
+ log.msg("get-tor-exits: Could not download exitlist; connection timed out.")
+ failure.trap(TimeoutError)
+
+def writeToFile(response, filename):
+ log.msg("get-tor-exits: Downloading list of Tor exit relays.")
+ finished = defer.Deferred()
+ response.deliverBody(FileWriter(finished, filename))
+ return finished
+
+
+class GetTorExitsOptions(usage.Options):
+ """Options for this script"""
+ optFlags = [['stdout', 's', "Write results to stdout instead of file"]]
+ optParameters = [['file', 'f', 'exit-list', "File to write results to"],
+ ['address', 'a', '1.1.1.1', "Only exits which can reach this address"],
+ ['port', 'p', 443, "Only exits which can reach this port"]]
+
+
+class FileWriter(protocol.Protocol):
+ """Read a downloaded file incrementally and write to file."""
+ def __init__(self, finished, file):
+ """Create a FileWriter.
+
+ .. warning:: We currently only handle the first 2MB of a file. Files
+ over 2MB will be truncated prematurely.
+
+ :param finished: A :class:`~twisted.internet.defer.Deferred` which
+ will fire when another portion of the download is complete.
+ """
+ self.finished = finished
+ self.remaining = 1024 * 1024 * 2
+ self.fh = file
+
+ def dataReceived(self, bytes):
+ """Write a portion of the download with ``bytes`` size to disk."""
+ if self.remaining:
+ display = bytes[:self.remaining]
+ self.fh.write(display)
+ self.fh.flush()
+ self.remaining -= len(display)
+
+ def connectionLost(self, reason):
+ """Called when the download is complete."""
+ log.msg('get-tor-exits: Finished receiving exit list: %s'
+ % reason.getErrorMessage())
+ self.finished.callback(None)
+
+
+class WebClientContextFactory(ssl.ClientContextFactory):
+ """An HTTPS client."""
+
+ def getContext(self, hostname, port):
+ """Get this connection's OpenSSL context.
+
+ By default, :api:`twisted.internet.ssl.ClientContextFactory` uses
+ ``OpenSSL.SSL.SSLv23_METHOD``, which allows SSLv2, SSLv3, and TLSv1,
+ then they disable SSLv2 in the
+ :api:`twisted.internet.ssl.ClientContextFactory.getContext` method.
+
+ We disable SSLv3 also.
+
+ :rtype: ``OpenSSL.SSL.Context``
+ :returns: An OpenSSL context with options set.
+ """
+ ctx = self._contextFactory(self.method)
+ ctx.set_options(SSL.OP_NO_SSLv2 ^ SSL.OP_NO_SSLv3)
+ return ctx
+
+
+def main(filename=None, address=None, port=None):
+
+ fh = filename
+ if filename:
+ if (not isinstance(filename, file)) and (filename is not sys.stdout):
+ fh = open(filename, 'w')
+
+ if not address:
+ address = getSelfIPAddress()
+
+ check = "https://check.torproject.org/cgi-bin/TorBulkExitList.py"
+ params = []
+
+ params.append('ip=%s' % address)
+ if port is not None:
+ params.append("port=%s" % port)
+
+ check += '?' + '&'.join(params)
+
+ log.msg("get-tor-exits: Requesting %s..." % check)
+
+ contextFactory = WebClientContextFactory()
+ agent = client.Agent(reactor, contextFactory)
+ d = agent.request("GET", check)
+ d.addCallback(writeToFile, fh)
+ d.addErrback(handleTimeout)
+ d.addCallbacks(log.msg, log.err)
+
+ if not reactor.running:
+ d.addCallback(lambda ignored: reactor.stop())
+ reactor.run()
+
+ if filename:
+ if (not isinstance(filename, file)) and (filename is not sys.stdout):
+ fh.flush()
+ fh.close()
+
+
+if __name__ == "__main__":
+ try:
+ options = GetTorExitsOptions()
+ options.parseOptions()
+ except usage.UsageError as error:
+ log.err(error)
+ raise SystemExit(options.getUsage())
+
+ if options['stdout']:
+ filename = sys.stdout
+ elif options['file']:
+ filename = options['file']
+ log.msg("get-tor-exits: Saving Tor exit relay list to file: '%s'"
+ % filename)
+ backupFile(filename)
+
+ main(filename, options['address'], options['port'])
diff --git a/setup.py b/setup.py
index d6f9a20..1e293bd 100644
--- a/setup.py
+++ b/setup.py
@@ -282,7 +282,8 @@ setuptools.setup(
'bridgedb.email',
'bridgedb.parse',
'bridgedb.test'],
- scripts=['scripts/bridgedb'],
+ scripts=['scripts/bridgedb',
+ 'scripts/get-tor-exits'],
extras_require={'test': ["sure==1.2.2",
"coverage==3.7.1",
"leekspin==1.1.3"]},
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits