Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol

This commit is contained in:
HelloZeroNet 2016-01-05 00:20:52 +01:00
parent c9578e9037
commit e9d2cdfd37
99 changed files with 9476 additions and 267 deletions

274
src/Tor/TorManager.py Normal file
View file

@ -0,0 +1,274 @@
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:
if "socket_noproxy" in dir(socket): # Socket proxy-patched, use non-proxy one
self.log.debug("Socket proxy patched, using original")
conn = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM)
else:
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.settimeout(1)
conn.connect((self.proxy_ip, self.proxy_port))
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...")
self.tor_process.terminate()
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 int(version.replace(".", "0")) >= 20705, "Tor version >=0.2.7.5 required"
# 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 = "Connected (%s)" % res_auth
self.conn = conn
except Exception, err:
self.conn = None
self.status = "Error (%s)" % err
self.log.error("Tor controller connect error: %s" % err)
self.enabled = False
return self.conn
def disconnect(self):
self.conn.close()
self.conn = None
def startOnions(self):
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 = "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 = "OK (%s onion running)" % len(self.privatekeys)
SiteManager.peer_blacklist.append((onion_address + ".onion", self.fileserver_port))
return onion_address
else:
self.status = "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 = "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)
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

1
src/Tor/__init__.py Normal file
View file

@ -0,0 +1 @@
from TorManager import TorManager