Created
April 20, 2016 07:57
-
-
Save 7900ms/a4f7613b5dc5e4e86b49f081c8d93d74 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import re | |
import socket | |
import binascii | |
import sys | |
import os | |
import time | |
import gevent | |
import subprocess | |
import atexit | |
from Config import config | |
from Crypt import CryptRsa | |
from Site import SiteManager | |
from lib.PySocks import socks | |
from gevent.coros import RLock | |
from util import helper | |
from Debug import Debug | |
class TorManager: | |
def __init__(self, fileserver_ip=None, fileserver_port=None): | |
self.privatekeys = {} # Onion: Privatekey | |
self.site_onions = {} # Site address: Onion | |
self.tor_exe = "tools/tor/tor.exe" | |
self.tor_process = None | |
self.log = logging.getLogger("TorManager") | |
self.start_onions = None | |
self.conn = None | |
self.lock = RLock() | |
if config.tor == "disable": | |
self.enabled = False | |
self.start_onions = False | |
self.status = "Disabled" | |
else: | |
self.enabled = True | |
self.status = "Waiting" | |
if fileserver_port: | |
self.fileserver_port = fileserver_port | |
else: | |
self.fileserver_port = config.fileserver_port | |
self.ip, self.port = config.tor_controller.split(":") | |
self.port = int(self.port) | |
self.proxy_ip, self.proxy_port = config.tor_proxy.split(":") | |
self.proxy_port = int(self.proxy_port) | |
# Test proxy port | |
if config.tor != "disable": | |
try: | |
assert self.connect(), "No connection" | |
self.log.debug("Tor proxy port %s check ok" % config.tor_proxy) | |
except Exception, err: | |
self.log.debug("Tor proxy port %s check error: %s" % (config.tor_proxy, err)) | |
self.enabled = False | |
# Change to self-bundled Tor ports | |
from lib.PySocks import socks | |
self.port = 49051 | |
self.proxy_port = 49050 | |
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", self.proxy_port) | |
if os.path.isfile(self.tor_exe): # Already, downloaded: sync mode | |
self.startTor() | |
else: # Not downloaded yet: Async mode | |
gevent.spawn(self.startTor) | |
def startTor(self): | |
if sys.platform.startswith("win"): | |
try: | |
if not os.path.isfile(self.tor_exe): | |
self.downloadTor() | |
self.log.info("Starting Tor client %s..." % self.tor_exe) | |
tor_dir = os.path.dirname(self.tor_exe) | |
self.tor_process = subprocess.Popen(r"%s -f torrc" % self.tor_exe, cwd=tor_dir, close_fds=True) | |
for wait in range(1,10): # Wait for startup | |
time.sleep(wait * 0.5) | |
self.enabled = True | |
if self.connect(): | |
break | |
# Terminate on exit | |
atexit.register(self.stopTor) | |
except Exception, err: | |
self.log.error("Error starting Tor client: %s" % Debug.formatException(err)) | |
self.enabled = False | |
return False | |
def stopTor(self): | |
self.log.debug("Stopping...") | |
try: | |
self.tor_process.terminate() | |
except Exception, err: | |
self.log.error("Error stopping Tor: %s" % err) | |
def downloadTor(self): | |
self.log.info("Downloading Tor...") | |
# Check Tor webpage for link | |
download_page = helper.httpRequest("https://www.torproject.org/download/download.html").read() | |
download_url = re.search('href="(.*?tor.*?win32.*?zip)"', download_page).group(1) | |
if not download_url.startswith("http"): | |
download_url = "https://www.torproject.org/download/" + download_url | |
# Download Tor client | |
self.log.info("Downloading %s" % download_url) | |
data = helper.httpRequest(download_url, as_file=True) | |
data_size = data.tell() | |
# Handle redirect | |
if data_size < 1024 and "The document has moved" in data.getvalue(): | |
download_url = re.search('href="(.*?tor.*?win32.*?zip)"', data.getvalue()).group(1) | |
data = helper.httpRequest(download_url, as_file=True) | |
data_size = data.tell() | |
if data_size > 1024: | |
import zipfile | |
zip = zipfile.ZipFile(data) | |
self.log.info("Unpacking Tor") | |
for inner_path in zip.namelist(): | |
if ".." in inner_path: | |
continue | |
dest_path = inner_path | |
dest_path = re.sub("^Data/Tor/", "tools/tor/data/", dest_path) | |
dest_path = re.sub("^Data/", "tools/tor/data/", dest_path) | |
dest_path = re.sub("^Tor/", "tools/tor/", dest_path) | |
dest_dir = os.path.dirname(dest_path) | |
if dest_dir and not os.path.isdir(dest_dir): | |
os.makedirs(dest_dir) | |
if dest_dir != dest_path.strip("/"): | |
data = zip.read(inner_path) | |
if not os.path.isfile(dest_path): | |
open(dest_path, 'wb').write(data) | |
else: | |
self.log.error("Bad response from server: %s" % data.getvalue()) | |
return False | |
def connect(self): | |
if not self.enabled: | |
return False | |
self.site_onions = {} | |
self.privatekeys = {} | |
if "socket_noproxy" in dir(socket): # Socket proxy-patched, use non-proxy one | |
conn = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM) | |
else: | |
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
self.log.debug("Connecting to %s:%s" % (self.ip, self.port)) | |
try: | |
with self.lock: | |
conn.connect((self.ip, self.port)) | |
res_protocol = self.send("PROTOCOLINFO", conn) | |
version = re.search('Tor="([0-9\.]+)', res_protocol).group(1) | |
# Version 0.2.7.5 required because ADD_ONION support | |
assert float(version.replace(".", "0", 2)) >= 207.5, "Tor version >=0.2.7.5 required, found: %s" % version | |
# Auth cookie file | |
cookie_match = re.search('COOKIEFILE="(.*?)"', res_protocol) | |
if cookie_match: | |
cookie_file = cookie_match.group(1) | |
auth_hex = binascii.b2a_hex(open(cookie_file, "rb").read()) | |
res_auth = self.send("AUTHENTICATE %s" % auth_hex, conn) | |
else: | |
res_auth = self.send("AUTHENTICATE", conn) | |
assert "250 OK" in res_auth, "Authenticate error %s" % res_auth | |
self.status = u"Connected (%s)" % res_auth | |
self.conn = conn | |
except Exception, err: | |
self.conn = None | |
self.status = u"Error (%s)" % err | |
self.log.error("Tor controller connect error: %s" % Debug.formatException(err)) | |
self.enabled = False | |
return self.conn | |
def disconnect(self): | |
self.conn.close() | |
self.conn = None | |
def startOnions(self): | |
if self.enabled: | |
self.log.debug("Start onions") | |
self.start_onions = True | |
# Get new exit node ip | |
def resetCircuits(self): | |
res = self.request("SIGNAL NEWNYM") | |
if "250 OK" not in res: | |
self.status = u"Reset circuits error (%s)" % res | |
self.log.error("Tor reset circuits error: %s" % res) | |
def addOnion(self): | |
res = self.request("ADD_ONION NEW:RSA1024 port=%s" % self.fileserver_port) | |
match = re.search("ServiceID=([A-Za-z0-9]+).*PrivateKey=RSA1024:(.*?)[\r\n]", res, re.DOTALL) | |
if match: | |
onion_address, onion_privatekey = match.groups() | |
self.privatekeys[onion_address] = onion_privatekey | |
self.status = u"OK (%s onion running)" % len(self.privatekeys) | |
SiteManager.peer_blacklist.append((onion_address + ".onion", self.fileserver_port)) | |
return onion_address | |
else: | |
self.status = u"AddOnion error (%s)" % res | |
self.log.error("Tor addOnion error: %s" % res) | |
return False | |
def delOnion(self, address): | |
res = self.request("DEL_ONION %s" % address) | |
if "250 OK" in res: | |
del self.privatekeys[address] | |
self.status = "OK (%s onion running)" % len(self.privatekeys) | |
return True | |
else: | |
self.status = u"DelOnion error (%s)" % res | |
self.log.error("Tor delOnion error: %s" % res) | |
self.disconnect() | |
return False | |
def request(self, cmd): | |
with self.lock: | |
if not self.enabled: | |
return False | |
if not self.conn: | |
if not self.connect(): | |
return "" | |
return self.send(cmd) | |
def send(self, cmd, conn=None): | |
if not conn: | |
conn = self.conn | |
self.log.debug("> %s" % cmd) | |
conn.send("%s\r\n" % cmd) | |
back = conn.recv(1024 * 64).decode("utf8", "ignore") | |
self.log.debug("< %s" % back.strip()) | |
return back | |
def getPrivatekey(self, address): | |
return self.privatekeys[address] | |
def getPublickey(self, address): | |
return CryptRsa.privatekeyToPublickey(self.privatekeys[address]) | |
def getOnion(self, site_address): | |
with self.lock: | |
if not self.enabled: | |
return None | |
if self.start_onions: # Different onion for every site | |
onion = self.site_onions.get(site_address) | |
else: # Same onion for every site | |
onion = self.site_onions.get("global") | |
site_address = "global" | |
if not onion: | |
self.site_onions[site_address] = self.addOnion() | |
onion = self.site_onions[site_address] | |
self.log.debug("Created new hidden service for %s: %s" % (site_address, onion)) | |
return onion | |
def createSocket(self, onion, port): | |
if not self.enabled: | |
return False | |
self.log.debug("Creating new socket to %s:%s" % (onion, port)) | |
if config.tor == "always": # Every socket is proxied by default | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.connect((onion, int(port))) | |
else: | |
sock = socks.socksocket() | |
sock.set_proxy(socks.SOCKS5, self.proxy_ip, self.proxy_port) | |
sock.connect((onion, int(port))) | |
return sock | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment