URI: 
       tMerge pull request #124 from andrewtoth/tor-broadcast - electrum-personal-server - Maximally lightweight electrum server for a single user
  HTML git clone https://git.parazyd.org/electrum-personal-server
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit 2b955afb0b9b3051cc4ccb6643774e26252ccaf6
   DIR parent f9decf97364060c06fb12f0ddad6d6f42f1271e7
  HTML Author: chris-belcher <belcher@riseup.net>
       Date:   Fri, 21 Jun 2019 22:37:59 +0100
       
       Merge pull request #124 from andrewtoth/tor-broadcast
       
       Broadcast transactions through tor
       Diffstat:
         M config.ini_sample                   |       5 +++++
         M electrumpersonalserver/server/__in… |       8 ++++++++
         M electrumpersonalserver/server/comm… |      23 +++++++++++++++++++++--
         A electrumpersonalserver/server/peer… |     356 +++++++++++++++++++++++++++++++
         A electrumpersonalserver/server/sock… |     410 ++++++++++++++++++++++++++++++
       
       5 files changed, 800 insertions(+), 2 deletions(-)
       ---
   DIR diff --git a/config.ini_sample b/config.ini_sample
       t@@ -73,10 +73,15 @@ disable_mempool_fee_histogram = false
        # Parameter for broadcasting unconfirmed transactions
        # Options are:
        # * own-node         (broadcast using the connected full node)
       +# * tor              (broadcast to random nodes over tor)
        # * system <cmd> %s  (save transaction to file, and invoke system command
        #                     with file path as parameter %s)
        broadcast_method = own-node
        
       +# For tor broadcasting (broadcast_method = tor) configure the tor proxy host and port below
       +tor_host = localhost
       +tor_port = 9050
       +
        
        [watch-only-addresses]
        #Add individual addresses to this section, for example paper wallets
   DIR diff --git a/electrumpersonalserver/server/__init__.py b/electrumpersonalserver/server/__init__.py
       t@@ -28,3 +28,11 @@ from electrumpersonalserver.server.deterministicwallet import (
            parse_electrum_master_public_key,
            DeterministicWallet,
        )
       +from electrumpersonalserver.server.socks import (
       +    socksocket,
       +    setdefaultproxy,
       +    PROXY_TYPE_SOCKS5,
       +)
       +from electrumpersonalserver.server.peertopeer import (
       +    tor_broadcast_tx,
       +)
   DIR diff --git a/electrumpersonalserver/server/common.py b/electrumpersonalserver/server/common.py
       t@@ -5,12 +5,14 @@ import traceback, sys, platform
        from ipaddress import ip_network, ip_address
        import logging
        import tempfile
       +import threading
        
        from electrumpersonalserver.server.jsonrpc import JsonRpc, JsonRpcError
        import electrumpersonalserver.server.hashes as hashes
        import electrumpersonalserver.server.merkleproof as merkleproof
        import electrumpersonalserver.server.deterministicwallet as deterministicwallet
        import electrumpersonalserver.server.transactionmonitor as transactionmonitor
       +import electrumpersonalserver.server.peertopeer as p2p
        
        SERVER_VERSION_NUMBER = "0.1.7"
        
       t@@ -93,7 +95,7 @@ def on_disconnect(txmonitor):
            txmonitor.unsubscribe_all_addresses()
        
        def handle_query(sock, line, rpc, txmonitor, disable_mempool_fee_histogram,
       -        broadcast_method):
       +        broadcast_method, tor_hostport=None):
            logger = logging.getLogger('ELECTRUMPERSONALSERVER')
            logger.debug("=> " + line)
            try:
       t@@ -244,6 +246,20 @@ def handle_query(sock, line, rpc, txmonitor, disable_mempool_fee_histogram,
                                rpc.call("sendrawtransaction", [txhex])
                            except JsonRpcError as e:
                                pass
       +            elif broadcast_method == "tor":
       +                # send through tor
       +                TOR_CONNECTIONS = 8
       +                network = "mainnet"
       +                chaininfo = rpc.call("getblockchaininfo", [])
       +                if chaininfo["chain"] == "test":
       +                    network = "testnet"
       +                elif chaininfo["chain"] == "regtest":
       +                    network = "regtest"
       +                for i in range(TOR_CONNECTIONS):
       +                    t = threading.Thread(target=p2p.tor_broadcast_tx,
       +                                         args=(txhex, tor_hostport, network, rpc,))
       +                    t.start()
       +                    time.sleep(0.1)
                    elif broadcast_method.startswith("system "):
                        with tempfile.NamedTemporaryFile() as fd:
                            system_line = broadcast_method[7:].replace("%s", fd.name)
       t@@ -451,6 +467,9 @@ def run_electrum_server(rpc, txmonitor, config):
                "disable_mempool_fee_histogram", fallback=False)
            broadcast_method = config.get("electrum-server", "broadcast_method",
                fallback="own-node")
       +    tor_host = config.get("electrum-server", "tor_host", fallback="localhost")
       +    tor_port = int(config.get("electrum-server", "tor_port", fallback="9050"))
       +    tor_hostport = (tor_host, tor_port)
        
            server_sock = create_server_socket(hostport)
            server_sock.settimeout(poll_interval_listening)
       t@@ -491,7 +510,7 @@ def run_electrum_server(rpc, txmonitor, config):
                                lb = recv_buffer.find(b'\n')
                                handle_query(sock, line.decode("utf-8"), rpc,
                                    txmonitor, disable_mempool_fee_histogram,
       -                            broadcast_method)
       +                            broadcast_method, tor_hostport)
                        except socket.timeout:
                            on_heartbeat_connected(sock, rpc, txmonitor)
                except (IOError, EOFError) as e:
   DIR diff --git a/electrumpersonalserver/server/peertopeer.py b/electrumpersonalserver/server/peertopeer.py
       t@@ -0,0 +1,356 @@
       +#! /usr/bin/env python
       +
       +import socket, time
       +import base64
       +from struct import pack, unpack
       +from datetime import datetime
       +
       +import electrumpersonalserver.bitcoin as btc
       +from electrumpersonalserver.server.socks import socksocket, setdefaultproxy, PROXY_TYPE_SOCKS5
       +from electrumpersonalserver.server.jsonrpc import JsonRpcError
       +
       +import logging as log
       +
       +PROTOCOL_VERSION = 70012
       +DEFAULT_USER_AGENT = '/Satoshi:0.18.0/'
       +NODE_WITNESS = (1 << 3)
       +
       +# protocol versions above this also send a relay boolean
       +RELAY_TX_VERSION = 70001
       +
       +# length of bitcoin p2p packets
       +HEADER_LENGTH = 24
       +
       +# if no message has been seen for this many seconds, send a ping
       +KEEPALIVE_INTERVAL = 2 * 60
       +
       +# close connection if keep alive ping isnt responded to in this many seconds
       +KEEPALIVE_TIMEOUT = 20 * 60
       +
       +def ip_to_hex(ip_str):
       +    # ipv4 only for now
       +    return socket.inet_pton(socket.AF_INET, ip_str)
       +
       +
       +def create_net_addr(hexip, port): # doesnt contain time as in bitcoin wiki
       +    services = 0
       +    hex = bytes(10) + b'\xFF\xFF' + hexip
       +    return pack('<Q16s', services, hex) + pack('>H', port)
       +
       +
       +def create_var_str(s):
       +    return btc.num_to_var_int(len(s)) + s.encode()
       +
       +
       +def read_int(ptr, payload, n, littleendian=True):
       +    data = payload[ptr[0] : ptr[0]+n]
       +    if littleendian:
       +        data = data[::-1]
       +    ret =  btc.decode(data, 256)
       +    ptr[0] += n
       +    return ret
       +
       +
       +def read_var_int(ptr, payload):
       +    val = payload[ptr[0]]
       +    ptr[0] += 1
       +    if val < 253:
       +        return val
       +    return read_int(ptr, payload, 2**(val - 252))
       +
       +
       +def read_var_str(ptr, payload):
       +    l = read_var_int(ptr, payload)
       +    ret = payload[ptr[0]: ptr[0] + l]
       +    ptr[0] += l
       +    return ret
       +
       +
       +def ip_hex_to_str(ip_hex):
       +    # https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
       +    # https://www.cypherpunk.at/onioncat_trac/wiki/OnionCat
       +    if ip_hex[:14] == '\x00'*10 + '\xff'*2:
       +        # ipv4 mapped ipv6 addr
       +        return socket.inet_ntoa(ip_hex[12:])
       +    elif ip_hex[:6] == '\xfd\x87\xd8\x7e\xeb\x43':
       +        return base64.b32encode(ip_hex[6:]).lower() + '.onion'
       +    else:
       +        return socket.inet_ntop(socket.AF_INET6, ip_hex)
       +
       +
       +class P2PMessageHandler(object):
       +    def __init__(self):
       +        self.last_message = datetime.now()
       +        self.waiting_for_keepalive = False
       +        self.log = (log if log else
       +                    log.getLogger('ELECTRUMPERSONALSERVER'))
       +
       +    def check_keepalive(self, p2p):
       +        if self.waiting_for_keepalive:
       +            if (datetime.now() - self.last_message).total_seconds() < KEEPALIVE_TIMEOUT:
       +                return
       +            log.info('keepalive timed out, closing')
       +            p2p.sock.close()
       +        else:
       +            if (datetime.now() - self.last_message).total_seconds() < KEEPALIVE_INTERVAL:
       +                return
       +            log.debug('sending keepalive to peer')
       +            self.waiting_for_keepalive = True
       +            p2p.sock.sendall(p2p.create_message('ping', '\x00'*8))
       +
       +    def handle_message(self, p2p, command, length, payload):
       +        self.last_message = datetime.now()
       +        self.waiting_for_keepalive = False
       +        ptr = [0]
       +        if command == b'version':
       +            version = read_int(ptr, payload, 4)
       +            services = read_int(ptr, payload, 8)
       +            timestamp = read_int(ptr, payload, 8)
       +            addr_recv_services = read_int(ptr, payload, 8)
       +            addr_recv_ip = payload[ptr[0] : ptr[0]+16]
       +            ptr[0] += 16
       +            addr_recv_port = read_int(ptr, payload, 2, False)
       +            addr_trans_services = read_int(ptr, payload, 8)
       +            addr_trans_ip = payload[ptr[0] : ptr[0]+16]
       +            ptr[0] += 16
       +            addr_trans_port = read_int(ptr, payload, 2, False)
       +            ptr[0] += 8 # skip over nonce
       +            user_agent = read_var_str(ptr, payload)
       +            start_height = read_int(ptr, payload, 4)
       +            if version > RELAY_TX_VERSION:
       +                relay = read_int(ptr, payload, 1) != 0
       +            else: # must check this node accepts unconfirmed transactions for the broadcast
       +                relay = True
       +            log.debug(('peer version message: version=%d services=0x%x'
       +                + ' timestamp=%s user_agent=%s start_height=%d relay=%i'
       +                + ' them=%s:%d us=%s:%d') % (version,
       +                services, str(datetime.fromtimestamp(timestamp)),
       +                user_agent, start_height, relay, ip_hex_to_str(addr_trans_ip)
       +                , addr_trans_port, ip_hex_to_str(addr_recv_ip), addr_recv_port))
       +            p2p.sock.sendall(p2p.create_message('verack', b''))
       +            self.on_recv_version(p2p, version, services, timestamp,
       +                addr_recv_services, addr_recv_ip, addr_trans_services,
       +                addr_trans_ip, addr_trans_port, user_agent, start_height,
       +                relay)
       +        elif command == b'verack':
       +            self.on_connected(p2p)
       +        elif command == b'ping':
       +            p2p.sock.sendall(p2p.create_message('pong', payload))
       +
       +    # optional override these in a subclass
       +
       +    def on_recv_version(self, p2p, version, services, timestamp,
       +            addr_recv_services, addr_recv_ip, addr_trans_services,
       +            addr_trans_ip, addr_trans_port, user_agent, start_height, relay):
       +        pass
       +
       +    def on_connected(self, p2p):
       +        pass
       +
       +    def on_heartbeat(self, p2p):
       +        pass
       +
       +
       +class P2PProtocol(object):
       +    def __init__(self, p2p_message_handler, remote_hostport,
       +                 network, user_agent=DEFAULT_USER_AGENT,
       +                 socks5_hostport=("localhost", 9050), connect_timeout=30, heartbeat_interval=15):
       +        self.log = (log if log else
       +                    log.getLogger('ELECTRUMPERSONALSERVER'))
       +        self.p2p_message_handler = p2p_message_handler
       +        self.network = network
       +        self.user_agent = user_agent
       +        self.socks5_hostport = socks5_hostport
       +        self.heartbeat_interval = heartbeat_interval
       +        self.connect_timeout = connect_timeout
       +        if self.network == "testnet":
       +            self.magic = 0x0709110b
       +        elif self.network == "regtest":
       +            self.magic = 0xdab5bffa
       +        else:
       +            self.magic = 0xd9b4bef9
       +
       +        self.closed = False
       +
       +        self.remote_hostport = remote_hostport
       +
       +    def run(self):
       +        services = NODE_WITNESS
       +        st = int(time.time())
       +        nonce = 0
       +        start_height = 0
       +
       +        netaddr = create_net_addr(ip_to_hex('0.0.0.0'), 0)
       +        version_message = (pack('<iQQ', PROTOCOL_VERSION, services, st)
       +                           + netaddr
       +                           + netaddr
       +                           + pack('<Q', nonce)
       +                           + create_var_str(self.user_agent)
       +                           + pack('<I', start_height)
       +                           + b'\x01')
       +        data = self.create_message('version', version_message)
       +        while True:
       +            log.info('connecting to bitcoin peer (magic=' + hex(self.magic)
       +                     + ') at ' + str(self.remote_hostport) + ' with proxy ' +
       +                     str(self.socks5_hostport))
       +            if self.socks5_hostport is None:
       +                self.sock = socket.socket(socket.AF_INET,
       +                                          socket.SOCK_STREAM)
       +            else:
       +                setdefaultproxy(PROXY_TYPE_SOCKS5, self.socks5_hostport[0],
       +                                self.socks5_hostport[1], True)
       +                self.sock = socksocket()
       +            self.sock.settimeout(self.connect_timeout)
       +            self.sock.connect(self.remote_hostport)
       +            self.sock.sendall(data)
       +            break
       +
       +        log.info('connected')
       +        self.sock.settimeout(self.heartbeat_interval)
       +        self.closed = False
       +        try:
       +            recv_buffer = b''
       +            payload_length = -1  # -1 means waiting for header
       +            command = None
       +            checksum = None
       +            while not self.closed:
       +                try:
       +                    recv_data = self.sock.recv(4096)
       +                    if not recv_data or len(recv_data) == 0:
       +                        raise EOFError()
       +                    recv_buffer += recv_data
       +                    # this is O(N^2) scaling in time, another way would be to store in a list
       +                    # and combine at the end with "".join()
       +                    # but this isnt really timing critical so didnt optimize it
       +
       +                    data_remaining = True
       +                    while data_remaining and not self.closed:
       +                        if payload_length == -1 and len(recv_buffer) >= HEADER_LENGTH:
       +                            net_magic, command, payload_length, checksum = unpack('<I12sI4s', recv_buffer[:HEADER_LENGTH])
       +                            recv_buffer = recv_buffer[HEADER_LENGTH:]
       +                            if net_magic != self.magic:
       +                                log.debug('wrong MAGIC: ' + hex(net_magic))
       +                                self.sock.close()
       +                                break
       +                            command = command.strip(b'\0')
       +                        else:
       +
       +                            if payload_length >= 0 and len(recv_buffer) >= payload_length:
       +                                payload = recv_buffer[:payload_length]
       +                                recv_buffer = recv_buffer[payload_length:]
       +                                if btc.bin_dbl_sha256(payload)[:4] == checksum:
       +                                    self.p2p_message_handler.handle_message(self, command,
       +                                        payload_length, payload)
       +                                else:
       +                                    log.debug('wrong checksum, dropping message, cmd=' + command + ' payloadlen=' + str(payload_length))
       +                                payload_length = -1
       +                                data_remaining = True
       +                            else:
       +                                data_remaining = False
       +                except socket.timeout:
       +                    self.p2p_message_handler.check_keepalive(self)
       +                    self.p2p_message_handler.on_heartbeat(self)
       +        except EOFError as e:
       +            self.closed = True
       +        except IOError as e:
       +            import traceback
       +            log.debug("logging traceback from %s: \n" %
       +                traceback.format_exc())
       +            self.closed = True
       +        finally:
       +            try:
       +                self.sock.close()
       +            except Exception as _:
       +                pass
       +
       +
       +    def close(self):
       +        self.closed = True
       +
       +    def create_message(self, command, payload):
       +        return (pack("<I12sI", self.magic, command.encode(), len(payload))
       +            + btc.bin_dbl_sha256(payload)[:4] + payload)
       +
       +class P2PBroadcastTx(P2PMessageHandler):
       +    def __init__(self, txhex):
       +        P2PMessageHandler.__init__(self)
       +        self.txhex = bytes.fromhex(txhex)
       +        self.txid = btc.bin_txhash(self.txhex)
       +        log.debug('broadcasting txid ' + str(self.txid))
       +        self.uploaded_tx = False
       +        self.time_marker = datetime.now()
       +        self.connected = False
       +
       +    def on_recv_version(self, p2p, version, services, timestamp,
       +            addr_recv_services, addr_recv_ip, addr_trans_services,
       +            addr_trans_ip, addr_trans_port, user_agent, start_height, relay):
       +        if not relay:
       +            log.debug('peer not accepting unconfirmed txes, trying another')
       +            # this happens if the other node is using blockonly=1
       +            p2p.close()
       +        if not services & NODE_WITNESS:
       +            log.debug('peer not accepting witness data, trying another')
       +            p2p.close()
       +
       +    def on_connected(self, p2p):
       +        log.debug('sending inv')
       +        MSG = 1 #msg_tx
       +        inv_payload = pack('<BI', 1, MSG) + self.txid
       +        p2p.sock.sendall(p2p.create_message('inv', inv_payload))
       +        self.time_marker = datetime.now()
       +        self.uploaded_tx = False
       +        self.connected = True
       +
       +    def on_heartbeat(self, p2p):
       +        log.debug('broadcaster heartbeat')
       +        VERACK_TIMEOUT = 40
       +        GETDATA_TIMEOUT = 60
       +        if not self.connected:
       +            if (datetime.now() - self.time_marker).total_seconds() < VERACK_TIMEOUT:
       +                return
       +            log.debug('timed out of waiting for verack')
       +        else:
       +            if (datetime.now() - self.time_marker).total_seconds() < GETDATA_TIMEOUT:
       +                return
       +            log.debug('timed out of waiting for getdata, node already has tx')
       +            self.uploaded_tx = True
       +        p2p.close()
       +
       +    def handle_message(self, p2p, command, length, payload):
       +        P2PMessageHandler.handle_message(self, p2p, command, length, payload)
       +        ptr = [0]
       +        if command == b'getdata':
       +            count = read_var_int(ptr, payload)
       +            for _ in range(count):
       +                ptr[0] += 4
       +                hash_id = payload[ptr[0] : ptr[0] + 32]
       +                ptr[0] += 32
       +                log.debug('hashid=' + hash_id.hex())
       +                if hash_id == self.txid:
       +                    log.debug('uploading tx')
       +                    p2p.sock.sendall(p2p.create_message('tx', self.txhex))
       +                    self.uploaded_tx = True
       +                    p2p.close()
       +
       +def tor_broadcast_tx(txhex, tor_hostport, network, rpc):
       +    ATTEMPTS = 8 # how many times to search for a node that accepts txes
       +    try:
       +        node_addrs = rpc.call("getnodeaddresses", [ATTEMPTS])
       +    except JsonRpcError:
       +        log.error("BitcoinCore v0.18.0 must be running to broadcast through Tor")
       +        return False
       +
       +    node_addrs = [addr for addr in node_addrs if addr["services"] & NODE_WITNESS]
       +    for i in range(len(node_addrs)):
       +        remote_hostport = (node_addrs[i]["address"], node_addrs[i]["port"])
       +        p2p_msg_handler = P2PBroadcastTx(txhex)
       +        p2p = P2PProtocol(p2p_msg_handler, remote_hostport=remote_hostport,
       +            network=network, socks5_hostport=tor_hostport, heartbeat_interval=20)
       +        try:
       +            p2p.run()
       +        except IOError:
       +            continue
       +        log.debug('uploaded={}'.format(p2p_msg_handler.uploaded_tx))
       +        if p2p_msg_handler.uploaded_tx:
       +            return True
       +    return False # never find a node that accepted unconfirms
   DIR diff --git a/electrumpersonalserver/server/socks.py b/electrumpersonalserver/server/socks.py
       t@@ -0,0 +1,410 @@
       +"""SocksiPy - Python SOCKS module.
       +Version 1.00
       +
       +Copyright 2006 Dan-Haim. All rights reserved.
       +
       +Redistribution and use in source and binary forms, with or without modification,
       +are permitted provided that the following conditions are met:
       +1. Redistributions of source code must retain the above copyright notice, this
       +   list of conditions and the following disclaimer.
       +2. Redistributions in binary form must reproduce the above copyright notice,
       +   this list of conditions and the following disclaimer in the documentation
       +   and/or other materials provided with the distribution.
       +3. Neither the name of Dan Haim nor the names of his contributors may be used
       +   to endorse or promote products derived from this software without specific
       +   prior written permission.
       +
       +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
       +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
       +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
       +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
       +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
       +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
       +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
       +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
       +
       +
       +This module provides a standard socket-like interface for Python
       +for tunneling connections through SOCKS proxies.
       +
       +"""
       +
       +import socket
       +import struct
       +import random
       +
       +PROXY_TYPE_SOCKS4 = 1
       +PROXY_TYPE_SOCKS5 = 2
       +PROXY_TYPE_HTTP = 3
       +
       +_defaultproxy = None
       +_orgsocket = socket.socket
       +
       +
       +class ProxyError(IOError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +class GeneralProxyError(ProxyError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +class Socks5AuthError(ProxyError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +class Socks5Error(ProxyError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +class Socks4Error(ProxyError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +class HTTPError(ProxyError):
       +    def __init__(self, value):
       +        self.value = value
       +
       +    def __str__(self):
       +        return repr(self.value)
       +
       +
       +_generalerrors = ("success", "invalid data", "not connected", "not available",
       +                  "bad proxy type", "bad input")
       +
       +_socks5errors = ("succeeded", "general SOCKS server failure",
       +                 "connection not allowed by ruleset", "Network unreachable",
       +                 "Host unreachable", "Connection refused", "TTL expired",
       +                 "Command not supported", "Address type not supported",
       +                 "Unknown error")
       +
       +_socks5autherrors = ("succeeded", "authentication is required",
       +                     "all offered authentication methods were rejected",
       +                     "unknown username or invalid password", "unknown error")
       +
       +_socks4errors = (
       +    "request granted", "request rejected or failed",
       +    "request rejected because SOCKS server cannot connect to identd on the client",
       +    "request rejected because the client program and identd report different user-ids",
       +    "unknown error")
       +
       +
       +def setdefaultproxy(proxytype=None,
       +                    addr=None,
       +                    port=None,
       +                    rdns=True,
       +                    username=str(random.randrange(10000000, 99999999)),
       +                    password=str(random.randrange(10000000, 99999999))):
       +    """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
       +    Sets a default proxy which all further socksocket objects will use,
       +    unless explicitly changed.
       +    """
       +    global _defaultproxy
       +    _defaultproxy = (proxytype, addr, port, rdns, username, password)
       +
       +
       +class socksocket(socket.socket):
       +    """socksocket([family[, type[, proto]]]) -> socket object
       +
       +    Open a SOCKS enabled socket. The parameters are the same as
       +    those of the standard socket init. In order for SOCKS to work,
       +    you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
       +    """
       +
       +    def __init__(self,
       +                 family=socket.AF_INET,
       +                 type=socket.SOCK_STREAM,
       +                 proto=0,
       +                 _sock=None):
       +        _orgsocket.__init__(self, family, type, proto, _sock)
       +        if _defaultproxy is not None:
       +            self.__proxy = _defaultproxy
       +        else:
       +            self.__proxy = (None, None, None, None, None, None)
       +        self.__proxysockname = None
       +        self.__proxypeername = None
       +
       +    def __recvall(self, bytes):
       +        """__recvall(bytes) -> data
       +        Receive EXACTLY the number of bytes requested from the socket.
       +        Blocks until the required number of bytes have been received.
       +        """
       +        data = b''
       +        while len(data) < bytes:
       +            data = data + self.recv(bytes - len(data))
       +        return data
       +
       +    def setproxy(self,
       +                 proxytype=None,
       +                 addr=None,
       +                 port=None,
       +                 rdns=True,
       +                 username=None,
       +                 password=None):
       +        """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
       +        Sets the proxy to be used.
       +        proxytype - The type of the proxy to be used. Three types
       +                are supported: PROXY_TYPE_SOCKS4 (including socks4a),
       +                PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
       +        addr -      The address of the server (IP or DNS).
       +        port -      The port of the server. Defaults to 1080 for SOCKS
       +                servers and 8080 for HTTP proxy servers.
       +        rdns -      Should DNS queries be preformed on the remote side
       +                (rather than the local side). The default is True.
       +                Note: This has no effect with SOCKS4 servers.
       +        username -  Username to authenticate with to the server.
       +                The default is no authentication.
       +        password -  Password to authenticate with to the server.
       +                Only relevant when username is also provided.
       +        """
       +        self.__proxy = (proxytype, addr, port, rdns, username, password)
       +
       +    def __negotiatesocks5(self, destaddr, destport):
       +        """__negotiatesocks5(self,destaddr,destport)
       +        Negotiates a connection through a SOCKS5 server.
       +        """
       +        # First we'll send the authentication packages we support.
       +        if (self.__proxy[4] is not None) and (self.__proxy[5] is not None):
       +            # The username/password details were supplied to the
       +            # setproxy method so we support the USERNAME/PASSWORD
       +            # authentication (in addition to the standard none).
       +            self.sendall(b'\x05\x02\x00\x02')
       +        else:
       +            # No username/password were entered, therefore we
       +            # only support connections with no authentication.
       +            self.sendall(b'\x05\x01\x00')
       +        # We'll receive the server's response to determine which
       +        # method was selected
       +        chosenauth = self.__recvall(2)
       +        if chosenauth[0:1] != b"\x05":
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        # Check the chosen authentication method
       +        if chosenauth[1:2] == b"\x00":
       +            # No authentication is required
       +            pass
       +        elif chosenauth[1:2] == b"\x02":
       +            # Okay, we need to perform a basic username/password
       +            # authentication.
       +            self.sendall(b'\x01' + bytes([len(self.__proxy[4])]) + self.__proxy[4].encode() +
       +                         bytes([len(self.__proxy[5])]) + self.__proxy[5].encode())
       +            authstat = self.__recvall(2)
       +            if authstat[0:1] != b"\x01":
       +                # Bad response
       +                self.close()
       +                raise GeneralProxyError((1, _generalerrors[1]))
       +            if authstat[1:2] != b"\x00":
       +                # Authentication failed
       +                self.close()
       +                raise Socks5AuthError((3, _socks5autherrors[3]))
       +                # Authentication succeeded
       +        else:
       +            # Reaching here is always bad
       +            self.close()
       +            if chosenauth[1:2] == b"\xFF":
       +                raise Socks5AuthError((2, _socks5autherrors[2]))
       +            else:
       +                raise GeneralProxyError((1, _generalerrors[1]))
       +        # Now we can request the actual connection
       +        req = b"\x05\x01\x00"
       +        # If the given destination address is an IP address, we'll
       +        # use the IPv4 address request even if remote resolving was specified.
       +        try:
       +            ipaddr = socket.inet_aton(destaddr)
       +            req = req + b"\x01" + ipaddr
       +        except socket.error:
       +            # Well it's not an IP number,  so it's probably a DNS name.
       +            if self.__proxy[3]:
       +                # Resolve remotely
       +                ipaddr = None
       +                req = req + b"\x03" + bytes([len(destaddr)]) + destaddr.encode()
       +            else:
       +                # Resolve locally
       +                ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
       +                req = req + b"\x01" + ipaddr
       +        req += struct.pack(">H", destport)
       +        self.sendall(req)
       +        # Get the response
       +        resp = self.__recvall(4)
       +        if resp[0:1] != b"\x05":
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        elif resp[1:2] != b"\x00":
       +            # Connection failed
       +            self.close()
       +            raise Socks5Error(_socks5errors[min(9, ord(resp[1:2]))])
       +        # Get the bound address/port
       +        elif resp[3:4] == b"\x01":
       +            boundaddr = self.__recvall(4)
       +        elif resp[3:4] == b"\x03":
       +            resp = resp + self.recv(1)
       +            boundaddr = self.__recvall(resp[4:5])
       +        else:
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        boundport = struct.unpack(">H", self.__recvall(2))[0]
       +        self.__proxysockname = (boundaddr, boundport)
       +        if ipaddr is not None:
       +            self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
       +        else:
       +            self.__proxypeername = (destaddr, destport)
       +
       +    def getproxysockname(self):
       +        """getsockname() -> address info
       +        Returns the bound IP address and port number at the proxy.
       +        """
       +        return self.__proxysockname
       +
       +    def getproxypeername(self):
       +        """getproxypeername() -> address info
       +        Returns the IP and port number of the proxy.
       +        """
       +        return _orgsocket.getpeername(self)
       +
       +    def getpeername(self):
       +        """getpeername() -> address info
       +        Returns the IP address and port number of the destination
       +        machine (note: getproxypeername returns the proxy)
       +        """
       +        return self.__proxypeername
       +
       +    def __negotiatesocks4(self, destaddr, destport):
       +        """__negotiatesocks4(self,destaddr,destport)
       +        Negotiates a connection through a SOCKS4 server.
       +        """
       +        # Check if the destination address provided is an IP address
       +        rmtrslv = False
       +        try:
       +            ipaddr = socket.inet_aton(destaddr)
       +        except socket.error:
       +            # It's a DNS name. Check where it should be resolved.
       +            if self.__proxy[3]:
       +                ipaddr = b"\x00\x00\x00\x01"
       +                rmtrslv = True
       +            else:
       +                ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
       +        # Construct the request packet
       +        req = b"\x04\x01" + struct.pack(">H", destport) + ipaddr
       +        # The username parameter is considered userid for SOCKS4
       +        if self.__proxy[4] is not None:
       +            req = req + self.__proxy[4].encode()
       +        req += b"\x00"
       +        # DNS name if remote resolving is required
       +        # NOTE: This is actually an extension to the SOCKS4 protocol
       +        # called SOCKS4A and may not be supported in all cases.
       +        if rmtrslv:
       +            req = req + destaddr + b"\x00"
       +        self.sendall(req)
       +        # Get the response from the server
       +        resp = self.__recvall(8)
       +        if resp[0:1] != b"\x00":
       +            # Bad data
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        if resp[1:2] != b"\x5A":
       +            # Server returned an error
       +            self.close()
       +            if ord(resp[1:2]) in (91, 92, 93):
       +                self.close()
       +                raise Socks4Error((ord(resp[1]), _socks4errors[ord(resp[1:2]) -
       +                                                               90]))
       +            else:
       +                raise Socks4Error((94, _socks4errors[4]))
       +        # Get the bound address/port
       +        self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(
       +                ">H", resp[2:4])[0])
       +        if rmtrslv is not None:
       +            self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
       +        else:
       +            self.__proxypeername = (destaddr, destport)
       +
       +    def __negotiatehttp(self, destaddr, destport):
       +        """__negotiatehttp(self,destaddr,destport)
       +        Negotiates a connection through an HTTP server.
       +        """
       +        # If we need to resolve locally, we do this now
       +        if not self.__proxy[3]:
       +            addr = socket.gethostbyname(destaddr)
       +        else:
       +            addr = destaddr
       +        self.sendall("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" +
       +                     "Host: " + destaddr + "\r\n\r\n")
       +        # We read the response until we get the string "\r\n\r\n"
       +        resp = self.recv(1)
       +        while resp.find(b"\r\n\r\n") == -1:
       +            resp = resp + self.recv(1)
       +        # We just need the first line to check if the connection
       +        # was successful
       +        statusline = resp.splitlines()[0].split(b" ", 2)
       +        if statusline[0] not in ("HTTP/1.0", "HTTP/1.1"):
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        try:
       +            statuscode = int(statusline[1])
       +        except ValueError:
       +            self.close()
       +            raise GeneralProxyError((1, _generalerrors[1]))
       +        if statuscode != 200:
       +            self.close()
       +            raise HTTPError((statuscode, statusline[2]))
       +        self.__proxysockname = ("0.0.0.0", 0)
       +        self.__proxypeername = (addr, destport)
       +
       +    def connect(self, destpair):
       +        """connect(self,despair)
       +        Connects to the specified destination through a proxy.
       +        destpar - A tuple of the IP/DNS address and the port number.
       +        (identical to socket's connect).
       +        To select the proxy server use setproxy().
       +        """
       +        # Do a minimal input check first
       +        if (type(destpair) in
       +                (list, tuple) == False) or (len(destpair) < 2) or (
       +                    type(destpair[0]) != str) or (type(destpair[1]) != int):
       +            raise GeneralProxyError((5, _generalerrors[5]))
       +        if self.__proxy[0] == PROXY_TYPE_SOCKS5:
       +            if self.__proxy[2] is not None:
       +                portnum = self.__proxy[2]
       +            else:
       +                portnum = 1080
       +            _orgsocket.connect(self, (self.__proxy[1], portnum))
       +            self.__negotiatesocks5(destpair[0], destpair[1])
       +        elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
       +            if self.__proxy[2] is not None:
       +                portnum = self.__proxy[2]
       +            else:
       +                portnum = 1080
       +            _orgsocket.connect(self, (self.__proxy[1], portnum))
       +            self.__negotiatesocks4(destpair[0], destpair[1])
       +        elif self.__proxy[0] == PROXY_TYPE_HTTP:
       +            if self.__proxy[2] is not None:
       +                portnum = self.__proxy[2]
       +            else:
       +                portnum = 8080
       +            _orgsocket.connect(self, (self.__proxy[1], portnum))
       +            self.__negotiatehttp(destpair[0], destpair[1])
       +        elif self.__proxy[0] is None:
       +            _orgsocket.connect(self, (destpair[0], destpair[1]))
       +        else:
       +            raise GeneralProxyError((4, _generalerrors[4]))