From 7d0e23e31ae7bcac700e7b3d78f58c3cd8f4b353 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 23 Sep 2019 17:53:59 +0300 Subject: [PATCH 01/10] Delete from addressbook only by address (Fixes: #1484) --- src/bitmessageqt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 94c00e38..00b0f4e5 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -3191,8 +3191,7 @@ class MyForm(settingsmixin.SMainWindow): 0].row() item = self.ui.tableWidgetAddressBook.item(currentRow, 0) sqlExecute( - 'DELETE FROM addressbook WHERE label=? AND address=?', - item.label, item.address) + 'DELETE FROM addressbook WHERE address=?', item.address) self.ui.tableWidgetAddressBook.removeRow(currentRow) self.rerenderMessagelistFromLabels() self.rerenderMessagelistToLabels() From 42a89ad3672678985f6fd14d531ffd6c84283717 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 23 Sep 2019 18:15:31 +0300 Subject: [PATCH 02/10] Delete from addressbook by pressing DEL --- src/bitmessageqt/__init__.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 00b0f4e5..440d36b2 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -781,6 +781,9 @@ class MyForm(settingsmixin.SMainWindow): self.ui.treeWidgetSubscriptions.keyPressEvent = self.treeWidgetKeyPressEvent self.ui.treeWidgetChans.keyPressEvent = self.treeWidgetKeyPressEvent + # Key press in addressbook + self.ui.tableWidgetAddressBook.keyPressEvent = self.addressbookKeyPressEvent + # Key press in messagelist self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetKeyPressEvent self.ui.tableWidgetInboxSubscriptions.keyPressEvent = self.tableWidgetKeyPressEvent @@ -1450,6 +1453,15 @@ class MyForm(settingsmixin.SMainWindow): def treeWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentTreeWidget()) + # addressbook + def addressbookKeyPressEvent(self, event): + """Handle keypress event in addressbook widget""" + if event.key() == QtCore.Qt.Key_Delete: + self.on_action_AddressBookDelete() + else: + return QtGui.QTableWidget.keyPressEvent( + self.ui.tableWidgetAddressBook, event) + # inbox / sent def tableWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessagelist()) @@ -1458,11 +1470,12 @@ class MyForm(settingsmixin.SMainWindow): def textEditKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessageTextedit()) - def handleKeyPress(self, event, focus = None): + def handleKeyPress(self, event, focus=None): + """This method handles keypress events for all widgets on MyForm""" messagelist = self.getCurrentMessagelist() folder = self.getCurrentFolder() if event.key() == QtCore.Qt.Key_Delete: - if isinstance (focus, MessageView) or isinstance(focus, QtGui.QTableWidget): + if isinstance(focus, MessageView) or isinstance(focus, QtGui.QTableWidget): if folder == "sent": self.on_action_SentTrash() else: @@ -1498,17 +1511,17 @@ class MyForm(settingsmixin.SMainWindow): self.ui.lineEditTo.setFocus() event.ignore() elif event.key() == QtCore.Qt.Key_F: - searchline = self.getCurrentSearchLine(retObj = True) + searchline = self.getCurrentSearchLine(retObj=True) if searchline: searchline.setFocus() event.ignore() if not event.isAccepted(): return - if isinstance (focus, MessageView): + if isinstance(focus, MessageView): return MessageView.keyPressEvent(focus, event) - elif isinstance (focus, QtGui.QTableWidget): + elif isinstance(focus, QtGui.QTableWidget): return QtGui.QTableWidget.keyPressEvent(focus, event) - elif isinstance (focus, QtGui.QTreeWidget): + elif isinstance(focus, QtGui.QTreeWidget): return QtGui.QTreeWidget.keyPressEvent(focus, event) # menu button 'manage keys' From 0a065670715605e8f676f8a6008b51d6f16f42a0 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 12 Jul 2019 18:19:06 +0300 Subject: [PATCH 03/10] Connect to bootstrap nodes by name --- src/knownnodes.py | 10 +++++++++- src/network/connectionchooser.py | 3 ++- src/network/connectionpool.py | 3 +-- src/network/socks5.py | 10 ++++------ src/network/tcp.py | 13 ++++++++----- src/protocol.py | 5 ++++- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/knownnodes.py b/src/knownnodes.py index f4e00b90..6d4629a2 100644 --- a/src/knownnodes.py +++ b/src/knownnodes.py @@ -11,7 +11,6 @@ import time import state from bmconfigparser import BMConfigParser from debug import logger -from helper_bootstrap import dns knownNodesLock = threading.Lock() knownNodes = {stream: {} for stream in range(1, 4)} @@ -123,6 +122,8 @@ def readKnownNodes(): logger.debug( 'Failed to read nodes from knownnodes.dat', exc_info=True) createDefaultKnownNodes() + if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': + createDefaultKnownNodes(onion=True) config = BMConfigParser() @@ -177,6 +178,13 @@ def trimKnownNodes(recAddrStream=1): del knownNodes[recAddrStream][oldest] +def dns(): + """Add DNS names to knownnodes""" + for port in [8080, 8444]: + addKnownNode( + 1, state.Peer('bootstrap%s.bitmessage.org' % port, port)) + + def cleanupKnownNodes(): """ Cleanup knownnodes: remove old nodes and nodes with low rating diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index e116ec53..4b1565a2 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -46,7 +46,8 @@ def chooseConnection(stream): # onion addresses have a higher priority when SOCKS if peer.host.endswith('.onion') and rating > 0: rating = 1 - else: + # TODO: need better check + elif not peer.host.startswith('bootstrap'): encodedAddr = protocol.encodeHost(peer.host) # don't connect to local IPs when using SOCKS if not protocol.checkIPAddress(encodedAddr, False): diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 461c2b77..05358c28 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -8,7 +8,6 @@ import socket import time import asyncore_pollchoose as asyncore -import helper_bootstrap import helper_random import knownnodes import protocol @@ -185,7 +184,7 @@ class BMConnectionPool(object): # pylint: disable=too-many-nested-blocks if spawnConnections: if not knownnodes.knownNodesActual: - helper_bootstrap.dns() + knownnodes.dns() if not self.bootstrapped: self.bootstrapped = True Proxy.proxy = ( diff --git a/src/network/socks5.py b/src/network/socks5.py index 86616f30..e0cb7202 100644 --- a/src/network/socks5.py +++ b/src/network/socks5.py @@ -8,6 +8,7 @@ src/network/socks5.py import socket import struct +import state from proxy import GeneralProxyError, Proxy, ProxyError @@ -160,9 +161,6 @@ class Socks5(Proxy): class Socks5Connection(Socks5): """Child socks5 class used for making outbound connections.""" - def __init__(self, address): - Socks5.__init__(self, address=address) - def state_auth_done(self): """Request connection to be made""" # Now we can request the actual connection @@ -172,9 +170,9 @@ class Socks5Connection(Socks5): try: self.ipaddr = socket.inet_aton(self.destination[0]) self.append_write_buf(chr(0x01).encode() + self.ipaddr) - except socket.error: + except socket.error: # may be IPv6! # Well it's not an IP number, so it's probably a DNS name. - if Proxy._remote_dns: # pylint: disable=protected-access + if self._remote_dns: # Resolve remotely self.ipaddr = None self.append_write_buf(chr(0x03).encode() + chr( @@ -202,7 +200,7 @@ class Socks5Resolver(Socks5): def __init__(self, host): self.host = host self.port = 8444 - Socks5.__init__(self, address=(self.host, self.port)) + Socks5.__init__(self, address=state.Peer(self.host, self.port)) def state_auth_done(self): """Perform resolving""" diff --git a/src/network/tcp.py b/src/network/tcp.py index 5ebd6a21..9b92cb52 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -73,11 +73,14 @@ class TCPConnection(BMProto, TLSDispatcher): logger.debug( 'Connecting to %s:%i', self.destination.host, self.destination.port) - encodedAddr = protocol.encodeHost(self.destination.host) - self.local = all([ - protocol.checkIPAddress(encodedAddr, True), - not protocol.checkSocksIP(self.destination.host) - ]) + try: + self.local = ( + protocol.checkIPAddress( + protocol.encodeHost(self.destination.host), True) and + not protocol.checkSocksIP(self.destination.host) + ) + except socket.error: + pass # it's probably a hostname ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called self.bm_proto_reset() self.set_state("bm_header", expectBytes=protocol.Header.size) diff --git a/src/protocol.py b/src/protocol.py index ab81e5e5..1031b950 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -264,7 +264,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= else: # use first 16 bytes if host data is longer # for example in case of onion v3 service - payload += encodeHost(remoteHost)[:16] + try: + payload += encodeHost(remoteHost)[:16] + except socket.error: + payload += encodeHost('127.0.0.1') payload += pack('>H', remotePort) # remote IPv6 and port # bitflags of the services I offer. From 4825c5a136450656932f8157677d221c55953d2e Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sat, 20 Jul 2019 11:41:49 +0300 Subject: [PATCH 04/10] Universal bootstrap procedure for any connection type --- src/network/connectionpool.py | 29 ++++++++++++++++++++++++++--- src/network/tcp.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 05358c28..1554a585 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -18,7 +18,8 @@ from debug import logger from proxy import Proxy from singleton import Singleton from tcp import ( - TCPServer, Socks5BMConnection, Socks4aBMConnection, TCPConnection) + bootstrap, Socks4aBMConnection, Socks5BMConnection, + TCPConnection, TCPServer) from udp import UDPSocket @@ -159,7 +160,28 @@ class BMConnectionPool(object): udpSocket = UDPSocket(host=bind, announcing=True) self.udpSockets[udpSocket.listening.host] = udpSocket - def loop(self): # pylint: disable=too-many-branches, too-many-statements + def startBootstrappers(self): + """Run the process of resolving bootstrap hostnames""" + proxy_type = BMConfigParser().safeGet( + 'bitmessagesettings', 'socksproxytype') + # A plugins may be added here + if not proxy_type or proxy_type == 'none': + connection_base = TCPConnection + elif proxy_type == 'SOCKS5': + connection_base = Socks5BMConnection + elif proxy_type == 'SOCKS4a': + connection_base = Socks4aBMConnection # FIXME: I cannot test + else: + # This should never happen because socksproxytype setting + # is handled in bitmessagemain before starting the connectionpool + return + + bootstrapper = bootstrap(connection_base) + port = helper_random.randomchoice([8080, 8444]) + hostname = 'bootstrap%s.bitmessage.org' % port + self.addConnection(bootstrapper(hostname, port)) + + def loop(self): # pylint: disable=too-many-branches,too-many-statements """Main Connectionpool's loop""" # defaults to empty loop if outbound connections are maxed spawnConnections = False @@ -184,7 +206,8 @@ class BMConnectionPool(object): # pylint: disable=too-many-nested-blocks if spawnConnections: if not knownnodes.knownNodesActual: - knownnodes.dns() + self.startBootstrappers() + knownnodes.knownNodesActual = True if not self.bootstrapped: self.bootstrapped = True Proxy.proxy = ( diff --git a/src/network/tcp.py b/src/network/tcp.py index 9b92cb52..da02df2f 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -325,6 +325,39 @@ class Socks4aBMConnection(Socks4aConnection, TCPConnection): return True +def bootstrap(connection_class): + """Make bootstrapper class for connection type (connection_class)""" + class Bootstrapper(connection_class): + """Base class for bootstrappers""" + _connection_base = connection_class + + def __init__(self, host, port): + self._connection_base.__init__(self, state.Peer(host, port)) + self.close_reason = self._succeed = False + + def bm_command_addr(self): + """ + Got addr message - the bootstrap succeed. + Let BMProto process the addr message and switch state to 'close' + """ + BMProto.bm_command_addr(self) + self._succeed = True + # pylint: disable=attribute-defined-outside-init + self.close_reason = "Thanks for bootstrapping!" + self.set_state("close") + + def handle_close(self): + """ + After closing the connection switch knownnodes.knownNodesActual + back to False if the bootstrapper failed. + """ + self._connection_base.handle_close(self) + if not self._succeed: + knownnodes.knownNodesActual = False + + return Bootstrapper + + class TCPServer(AdvancedDispatcher): """TCP connection server for Bitmessage protocol""" From 7215003c6f2ad50ec785392cbf598f5be778d5fa Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Sun, 21 Jul 2019 16:50:49 +0300 Subject: [PATCH 05/10] No DNS resolving in knownnodes --- src/knownnodes.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/knownnodes.py b/src/knownnodes.py index 6d4629a2..2735edbf 100644 --- a/src/knownnodes.py +++ b/src/knownnodes.py @@ -191,33 +191,24 @@ def cleanupKnownNodes(): """ now = int(time.time()) needToWriteKnownNodesToDisk = False - dns_done = False - spawnConnections = not BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'dontconnect' - ) and BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections') with knownNodesLock: for stream in knownNodes: if stream not in state.streamsInWhichIAmParticipating: continue keys = knownNodes[stream].keys() - if len(keys) <= 1: # leave at least one node - if not dns_done and spawnConnections: - dns() - dns_done = True - continue for node in keys: + if len(knownNodes[stream]) <= 1: # leave at least one node + break try: - # scrap old nodes - if (now - knownNodes[stream][node]["lastseen"] > - 2419200): # 28 days + age = now - knownNodes[stream][node]["lastseen"] + # scrap old nodes (age > 28 days) + if age > 2419200: needToWriteKnownNodesToDisk = True del knownNodes[stream][node] continue - # scrap old nodes with low rating - if (now - knownNodes[stream][node]["lastseen"] > 10800 and - knownNodes[stream][node]["rating"] <= + # scrap old nodes (age > 3 hours) with low rating + if (age > 10800 and knownNodes[stream][node]["rating"] <= knownNodesForgetRating): needToWriteKnownNodesToDisk = True del knownNodes[stream][node] From bdb09c2d00d2bdbb7ad6ec6076a92f99c246abda Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 29 Jul 2019 14:37:56 +0300 Subject: [PATCH 06/10] Ignore self node in connectionchooser.chooseConnection() --- src/network/connectionchooser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index 4b1565a2..53ce30b7 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-branches import random # nosec import knownnodes @@ -38,7 +39,10 @@ def chooseConnection(stream): for _ in range(50): peer = random.choice(knownnodes.knownNodes[stream].keys()) try: - rating = knownnodes.knownNodes[stream][peer]['rating'] + peer_info = knownnodes.knownNodes[stream][peer] + if peer_info.get('self'): + continue + rating = peer_info["rating"] except TypeError: logger.warning('Error in %s', peer) rating = 0 From bcb29facaac12b47df7adfadeddacde08c9ebfaf Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 29 Jul 2019 14:19:18 +0300 Subject: [PATCH 07/10] A test for bootstrapping, have problem with test_tcpconnection ): --- src/tests/core.py | 48 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/tests/core.py b/src/tests/core.py index a323be83..a9df05fc 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -13,9 +13,11 @@ import unittest import knownnodes import state +from bmconfigparser import BMConfigParser from helper_msgcoding import MsgEncode, MsgDecode from network import asyncore_pollchoose as asyncore -from network.tcp import TCPConnection +from network.connectionpool import BMConnectionPool +from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection from queues import excQueue knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') @@ -80,8 +82,10 @@ class TestCore(unittest.TestCase): ' with no subject!' % e ) + @unittest.skip('Bad environment for asyncore.loop') def test_tcpconnection(self): """initial fill script from network.tcp""" + BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') try: for peer in (state.Peer("127.0.0.1", 8448),): direct = TCPConnection(peer) @@ -91,10 +95,18 @@ class TestCore(unittest.TestCase): except: self.fail('Exception in test loop') - def _wipe_knownnodes(self): + @staticmethod + def _wipe_knownnodes(): with knownnodes.knownNodesLock: knownnodes.knownNodes = {stream: {} for stream in range(1, 4)} + @staticmethod + def _outdate_knownnodes(): + with knownnodes.knownNodesLock: + for nodes in knownnodes.knownNodes.itervalues(): + for node in nodes.itervalues(): + node['lastseen'] -= 2419205 # older than 28 days + def test_knownnodes_pickle(self): """ensure that 3 nodes was imported for each stream""" pickle_knownnodes() @@ -117,9 +129,7 @@ class TestCore(unittest.TestCase): def test_0_cleaner(self): """test knownnodes starvation leading to IndexError in Asyncore""" - for nodes in knownnodes.knownNodes.itervalues(): - for node in nodes.itervalues(): - node['lastseen'] -= 2419205 # older than 28 days + self._outdate_knownnodes() # time.sleep(303) # singleCleaner wakes up every 5 min knownnodes.cleanupKnownNodes() while True: @@ -130,6 +140,34 @@ class TestCore(unittest.TestCase): if thread == 'Asyncore' and isinstance(exc, IndexError): self.fail("IndexError because of empty knownNodes!") + def test_bootstrap(self): + """test bootstrapping""" + BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') + self._outdate_knownnodes() + knownnodes.cleanupKnownNodes() + # it's weird, knownnodes appear empty + knownnodes.addKnownNode(1, state.Peer('127.0.0.1', 8444), is_self=True) + time.sleep(0.25) + BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') + proxy_type = BMConfigParser().safeGet( + 'bitmessagesettings', 'socksproxytype') + if proxy_type == 'SOCKS5': + connection_base = Socks5BMConnection + elif proxy_type == 'SOCKS4a': + connection_base = Socks4aBMConnection + else: + connection_base = TCPConnection + _started = time.time() + for _ in range(180): + time.sleep(1) + for peer, con in BMConnectionPool().outboundConnections.iteritems(): + if not peer.host.startswith('bootstrap'): + self.assertIsInstance(con, connection_base) + return + else: # pylint: disable=useless-else-on-loop + self.fail( + 'Failed to connect during %s sec' % (time.time() - _started)) + def run(): """Starts all tests defined in this module""" From 6a0c3ae075d787d40e3fb4507393eb46fafe64c8 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 1 Aug 2019 15:28:32 +0300 Subject: [PATCH 08/10] Remove obsolete helper_bootstrap and bundled SocksiPy --- setup.py | 1 - src/helper_bootstrap.py | 84 ------- src/socks/BUGS | 25 --- src/socks/LICENSE | 22 -- src/socks/README | 201 ----------------- src/socks/__init__.py | 476 ---------------------------------------- 6 files changed, 809 deletions(-) delete mode 100644 src/helper_bootstrap.py delete mode 100644 src/socks/BUGS delete mode 100644 src/socks/LICENSE delete mode 100644 src/socks/README delete mode 100644 src/socks/__init__.py diff --git a/setup.py b/setup.py index 3a9c7a3c..61afa91e 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,6 @@ if __name__ == "__main__": 'pybitmessage.network', 'pybitmessage.plugins', 'pybitmessage.pyelliptic', - 'pybitmessage.socks', 'pybitmessage.storage' ] diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py deleted file mode 100644 index 1710b09c..00000000 --- a/src/helper_bootstrap.py +++ /dev/null @@ -1,84 +0,0 @@ -import socket - -import knownnodes -import socks -import state -from bmconfigparser import BMConfigParser -from debug import logger - - -def dns(): - """ - DNS bootstrap. This could be programmed to use the SOCKS proxy to do the - DNS lookup some day but for now we will just rely on the entries in - defaultKnownNodes.py. Hopefully either they are up to date or the user - has run Bitmessage recently without SOCKS turned on and received good - bootstrap nodes using that method. - """ - - def try_add_known_node(stream, addr, port, method=''): - try: - socket.inet_aton(addr) - except (TypeError, socket.error): - return - logger.info( - 'Adding %s to knownNodes based on %s DNS bootstrap method', - addr, method) - knownnodes.addKnownNode(stream, state.Peer(addr, port)) - - proxy_type = BMConfigParser().get('bitmessagesettings', 'socksproxytype') - - if proxy_type == 'none': - for port in [8080, 8444]: - try: - for item in socket.getaddrinfo( - 'bootstrap%s.bitmessage.org' % port, 80): - try_add_known_node(1, item[4][0], port) - except: - logger.error( - 'bootstrap%s.bitmessage.org DNS bootstrapping failed.', - port, exc_info=True - ) - elif proxy_type == 'SOCKS5': - knownnodes.createDefaultKnownNodes(onion=True) - logger.debug('Adding default onion knownNodes.') - for port in [8080, 8444]: - logger.debug("Resolving %i through SOCKS...", port) - address_family = socket.AF_INET - sock = socks.socksocket(address_family, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.settimeout(20) - proxytype = socks.PROXY_TYPE_SOCKS5 - sockshostname = BMConfigParser().get( - 'bitmessagesettings', 'sockshostname') - socksport = BMConfigParser().getint( - 'bitmessagesettings', 'socksport') - # Do domain name lookups through the proxy; - # though this setting doesn't really matter since we won't - # be doing any domain name lookups anyway. - rdns = True - if BMConfigParser().getboolean( - 'bitmessagesettings', 'socksauthentication'): - socksusername = BMConfigParser().get( - 'bitmessagesettings', 'socksusername') - sockspassword = BMConfigParser().get( - 'bitmessagesettings', 'sockspassword') - sock.setproxy( - proxytype, sockshostname, socksport, rdns, - socksusername, sockspassword) - else: - sock.setproxy( - proxytype, sockshostname, socksport, rdns) - try: - ip = sock.resolve("bootstrap" + str(port) + ".bitmessage.org") - sock.shutdown(socket.SHUT_RDWR) - sock.close() - except: - logger.error("SOCKS DNS resolving failed", exc_info=True) - else: - try_add_known_node(1, ip, port, 'SOCKS') - else: - logger.info( - 'DNS bootstrap skipped because the proxy type does not support' - ' DNS resolution.' - ) diff --git a/src/socks/BUGS b/src/socks/BUGS deleted file mode 100644 index fa8ccfad..00000000 --- a/src/socks/BUGS +++ /dev/null @@ -1,25 +0,0 @@ -SocksiPy version 1.00 -A Python SOCKS module. -(C) 2006 Dan-Haim. All rights reserved. -See LICENSE file for details. - - -KNOWN BUGS AND ISSUES ----------------------- - -There are no currently known bugs in this module. -There are some limits though: - -1) Only outgoing connections are supported - This module currently only supports -outgoing TCP connections, though some servers may support incoming connections -as well. UDP is not supported either. - -2) GSSAPI Socks5 authenticaion is not supported. - - -If you find any new bugs, please contact the author at: - -negativeiq@users.sourceforge.net - - -Thank you! diff --git a/src/socks/LICENSE b/src/socks/LICENSE deleted file mode 100644 index 04b6b1f3..00000000 --- a/src/socks/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -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. diff --git a/src/socks/README b/src/socks/README deleted file mode 100644 index a52f55f3..00000000 --- a/src/socks/README +++ /dev/null @@ -1,201 +0,0 @@ -SocksiPy version 1.00 -A Python SOCKS module. -(C) 2006 Dan-Haim. All rights reserved. -See LICENSE file for details. - - -WHAT IS A SOCKS PROXY? -A SOCKS proxy is a proxy server at the TCP level. In other words, it acts as -a tunnel, relaying all traffic going through it without modifying it. -SOCKS proxies can be used to relay traffic using any network protocol that -uses TCP. - -WHAT IS SOCKSIPY? -This Python module allows you to create TCP connections through a SOCKS -proxy without any special effort. - -PROXY COMPATIBILITY -SocksiPy is compatible with three different types of proxies: -1. SOCKS Version 4 (Socks4), including the Socks4a extension. -2. SOCKS Version 5 (Socks5). -3. HTTP Proxies which support tunneling using the CONNECT method. - -SYSTEM REQUIREMENTS -Being written in Python, SocksiPy can run on any platform that has a Python -interpreter and TCP/IP support. -This module has been tested with Python 2.3 and should work with greater versions -just as well. - - -INSTALLATION -------------- - -Simply copy the file "socks.py" to your Python's lib/site-packages directory, -and you're ready to go. - - -USAGE ------- - -First load the socks module with the command: - ->>> import socks ->>> - -The socks module provides a class called "socksocket", which is the base to -all of the module's functionality. -The socksocket object has the same initialization parameters as the normal socket -object to ensure maximal compatibility, however it should be noted that socksocket -will only function with family being AF_INET and type being SOCK_STREAM. -Generally, it is best to initialize the socksocket object with no parameters - ->>> s = socks.socksocket() ->>> - -The socksocket object has an interface which is very similiar to socket's (in fact -the socksocket class is derived from socket) with a few extra methods. -To select the proxy server you would like to use, use the setproxy method, whose -syntax is: - -setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - -Explaination of the parameters: - -proxytype - The type of the proxy server. This can be one of three possible -choices: PROXY_TYPE_SOCKS4, PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP for Socks4, -Socks5 and HTTP servers respectively. - -addr - The IP address or DNS name of the proxy server. - -port - The port of the proxy server. Defaults to 1080 for socks and 8080 for http. - -rdns - This is a boolean flag than modifies the behavior regarding DNS resolving. -If it is set to True, DNS resolving will be preformed remotely, on the server. -If it is set to False, DNS resolving will be preformed locally. Please note that -setting this to True with Socks4 servers actually use an extension to the protocol, -called Socks4a, which may not be supported on all servers (Socks5 and http servers -always support DNS). The default is True. - -username - For Socks5 servers, this allows simple username / password authentication -with the server. For Socks4 servers, this parameter will be sent as the userid. -This parameter is ignored if an HTTP server is being used. If it is not provided, -authentication will not be used (servers may accept unauthentication requests). - -password - This parameter is valid only for Socks5 servers and specifies the -respective password for the username provided. - -Example of usage: - ->>> s.setproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") ->>> - -After the setproxy method has been called, simply call the connect method with the -traditional parameters to establish a connection through the proxy: - ->>> s.connect(("www.sourceforge.net",80)) ->>> - -Connection will take a bit longer to allow negotiation with the proxy server. -Please note that calling connect without calling setproxy earlier will connect -without a proxy (just like a regular socket). - -Errors: Any errors in the connection process will trigger exceptions. The exception -may either be generated by the underlying socket layer or may be custom module -exceptions, whose details follow: - -class ProxyError - This is a base exception class. It is not raised directly but -rather all other exception classes raised by this module are derived from it. -This allows an easy way to catch all proxy-related errors. - -class GeneralProxyError - When thrown, it indicates a problem which does not fall -into another category. The parameter is a tuple containing an error code and a -description of the error, from the following list: -1 - invalid data - This error means that unexpected data has been received from -the server. The most common reason is that the server specified as the proxy is -not really a Socks4/Socks5/HTTP proxy, or maybe the proxy type specified is wrong. -4 - bad proxy type - This will be raised if the type of the proxy supplied to the -setproxy function was not PROXY_TYPE_SOCKS4/PROXY_TYPE_SOCKS5/PROXY_TYPE_HTTP. -5 - bad input - This will be raised if the connect method is called with bad input -parameters. - -class Socks5AuthError - This indicates that the connection through a Socks5 server -failed due to an authentication problem. The parameter is a tuple containing a -code and a description message according to the following list: - -1 - authentication is required - This will happen if you use a Socks5 server which -requires authentication without providing a username / password at all. -2 - all offered authentication methods were rejected - This will happen if the proxy -requires a special authentication method which is not supported by this module. -3 - unknown username or invalid password - Self descriptive. - -class Socks5Error - This will be raised for Socks5 errors which are not related to -authentication. The parameter is a tuple containing a code and a description of the -error, as given by the server. The possible errors, according to the RFC are: - -1 - General SOCKS server failure - If for any reason the proxy server is unable to -fulfill your request (internal server error). -2 - connection not allowed by ruleset - If the address you're trying to connect to -is blacklisted on the server or requires authentication. -3 - Network unreachable - The target could not be contacted. A router on the network -had replied with a destination net unreachable error. -4 - Host unreachable - The target could not be contacted. A router on the network -had replied with a destination host unreachable error. -5 - Connection refused - The target server has actively refused the connection -(the requested port is closed). -6 - TTL expired - The TTL value of the SYN packet from the proxy to the target server -has expired. This usually means that there are network problems causing the packet -to be caught in a router-to-router "ping-pong". -7 - Command not supported - The client has issued an invalid command. When using this -module, this error should not occur. -8 - Address type not supported - The client has provided an invalid address type. -When using this module, this error should not occur. - -class Socks4Error - This will be raised for Socks4 errors. The parameter is a tuple -containing a code and a description of the error, as given by the server. The -possible error, according to the specification are: - -1 - Request rejected or failed - Will be raised in the event of an failure for any -reason other then the two mentioned next. -2 - request rejected because SOCKS server cannot connect to identd on the client - -The Socks server had tried an ident lookup on your computer and has failed. In this -case you should run an identd server and/or configure your firewall to allow incoming -connections to local port 113 from the remote server. -3 - request rejected because the client program and identd report different user-ids - -The Socks server had performed an ident lookup on your computer and has received a -different userid than the one you have provided. Change your userid (through the -username parameter of the setproxy method) to match and try again. - -class HTTPError - This will be raised for HTTP errors. The parameter is a tuple -containing the HTTP status code and the description of the server. - - -After establishing the connection, the object behaves like a standard socket. -Call the close method to close the connection. - -In addition to the socksocket class, an additional function worth mentioning is the -setdefaultproxy function. The parameters are the same as the setproxy method. -This function will set default proxy settings for newly created socksocket objects, -in which the proxy settings haven't been changed via the setproxy method. -This is quite useful if you wish to force 3rd party modules to use a socks proxy, -by overriding the socket object. -For example: - ->>> socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") ->>> socket.socket = socks.socksocket ->>> urllib.urlopen("http://www.sourceforge.net/") - - -PROBLEMS ---------- - -If you have any problems using this module, please first refer to the BUGS file -(containing current bugs and issues). If your problem is not mentioned you may -contact the author at the following E-Mail address: - -negativeiq@users.sourceforge.net - -Please allow some time for your question to be received and handled. - - -Dan-Haim, -Author. diff --git a/src/socks/__init__.py b/src/socks/__init__.py deleted file mode 100644 index 7fd2cba3..00000000 --- a/src/socks/__init__.py +++ /dev/null @@ -1,476 +0,0 @@ -"""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. - -""" - -""" - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) -for use in PyLoris (http://pyloris.sourceforge.net/) - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge - -""" - -import socket -import struct -import sys - -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 - -_defaultproxy = None -_orgsocket = socket.socket - -class ProxyError(Exception): pass -class GeneralProxyError(ProxyError): pass -class Socks5AuthError(ProxyError): pass -class Socks5Error(ProxyError): pass -class Socks4Error(ProxyError): pass -class HTTPError(ProxyError): pass - -_generalerrors = ("success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", - "timed out", - "network unreachable", - "connection refused", - "host unreachable") - -_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=None, password=None): - """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) - -def wrapmodule(module): - """wrapmodule(module) - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using setdefaultproxy(...) first. - This will only work on modules that import socket directly into the namespace; - most of the Python Standard Library falls into this category. - """ - if _defaultproxy != None: - module.socket.socket = socksocket - else: - raise GeneralProxyError((4, "no proxy specified")) - -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 != None: - self.__proxy = _defaultproxy - else: - self.__proxy = (None, None, None, None, None, None) - self.__proxysockname = None - self.__proxypeername = None - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - try: - data = self.recv(count) - except socket.timeout: - raise GeneralProxyError((6, "timed out")) - while len(data) < count: - d = self.recv(count-len(data)) - if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - 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): - """__negotiatesocks5(self,destaddr,destport) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (self.__proxy[4]!=None) and (self.__proxy[5]!=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(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - - def __connectsocks5(self, destaddr, destport): - # Now we can request the actual connection - req = struct.pack('BBB', 0x05, 0x01, 0x00) - # 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 + chr(0x01).encode() + 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 + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2])<=8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(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 != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __resolvesocks5(self, host): - # Now we can request the actual connection - req = struct.pack('BBB', 0x05, 0xF0, 0x00) - req += chr(0x03).encode() + chr(len(host)).encode() + host - req = req + struct.pack(">H", 8444) - self.sendall(req) - # Get the response - ip = "" - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2])<=8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - ip = socket.inet_ntoa(self.__recvall(4)) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - ip = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - return ip - - 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 getproxytype(self): - return self.__proxy[0] - - 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 = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: - req = req + self.__proxy[4] - req = req + chr(0x00).encode() - # 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 + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _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 != 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").encode()) - # We read the response until we get the string "\r\n\r\n" - resp = self.recv(1) - while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - 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 (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): - raise GeneralProxyError((5, _generalerrors[5])) - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - try: - _orgsocket.connect(self, (self.__proxy[1], portnum)) - except socket.error as e: - # ENETUNREACH, WSAENETUNREACH - if e[0] in [101, 10051]: - raise GeneralProxyError((7, _generalerrors[7])) - # ECONNREFUSED, WSAECONNREFUSED - if e[0] in [111, 10061]: - raise GeneralProxyError((8, _generalerrors[8])) - # EHOSTUNREACH, WSAEHOSTUNREACH - if e[0] in [113, 10065]: - raise GeneralProxyError((9, _generalerrors[9])) - raise - self.__negotiatesocks5() - self.__connectsocks5(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != 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] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - try: - _orgsocket.connect(self,(self.__proxy[1], portnum)) - except socket.error as e: - # ENETUNREACH, WSAENETUNREACH - if e[0] in [101, 10051]: - raise GeneralProxyError((7, _generalerrors[7])) - # ECONNREFUSED, WSAECONNREFUSED - if e[0] in [111, 10061]: - raise GeneralProxyError((8, _generalerrors[8])) - # EHOSTUNREACH, WSAEHOSTUNREACH - if e[0] in [113, 10065]: - raise GeneralProxyError((9, _generalerrors[9])) - raise - self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == None: - _orgsocket.connect(self, (destpair[0], destpair[1])) - else: - raise GeneralProxyError((4, _generalerrors[4])) - - def resolve(self, host): - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5() - return self.__resolvesocks5(host) - else: - return None From a7cfe5ba326b950d882619f88ae17b3cd2d18ba3 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Fri, 9 Aug 2019 17:15:00 +0300 Subject: [PATCH 09/10] Try to test with tor --- .travis.yml | 1 + requirements.txt | 1 + src/bitmessagemain.py | 5 +++-- src/plugins/proxyconfig_stem.py | 13 +++++++++++-- src/tests/core.py | 28 +++++++++++++++++++++------- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1edba418..d7141188 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ addons: packages: - build-essential - libcap-dev + - tor install: - pip install -r requirements.txt - ln -s src pybitmessage # tests environment diff --git a/requirements.txt b/requirements.txt index c55e5cf1..be429a9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ python_prctl psutil pycrypto +stem diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 5dcfea6c..1dd2f271 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -193,7 +193,8 @@ class Main: from plugins.plugin import get_plugin try: proxyconfig_start = time.time() - get_plugin('proxyconfig', name=proxy_type)(config) + if not get_plugin('proxyconfig', name=proxy_type)(config): + raise TypeError except TypeError: logger.error( 'Failed to run proxy config plugin %s', @@ -424,7 +425,7 @@ class Main: self.stop() elif not state.enableGUI: from tests import core as test_core # pylint: disable=relative-import - test_core_result = test_core.run() + test_core_result = test_core.run(self) state.enableGUI = True self.stop() test_core.cleanup() diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index 75605c07..e8b5417e 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -8,6 +8,7 @@ import tempfile import stem import stem.control import stem.process +import stem.version class DebugLogger(object): @@ -31,7 +32,7 @@ class DebugLogger(object): self._logger.log(self._levels.get(level, 10), '(tor)' + line) -def connect_plugin(config): +def connect_plugin(config): # pylint: disable=too-many-branches """Run stem proxy configurator""" logwrite = DebugLogger() if config.safeGet('bitmessagesettings', 'sockshostname') not in ( @@ -60,8 +61,14 @@ def connect_plugin(config): # So if there is a system wide tor, use it for outbound connections. try: stem.process.launch_tor_with_config( - tor_config, take_ownership=True, init_msg_handler=logwrite) + tor_config, take_ownership=True, timeout=20, + init_msg_handler=logwrite) except OSError: + if not attempt: + try: + stem.version.get_system_tor_version() + except IOError: + return continue else: logwrite('Started tor on port %s' % port) @@ -108,3 +115,5 @@ def connect_plugin(config): onionhostname, 'keytype', response.private_key_type) config.save() config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') + + return True diff --git a/src/tests/core.py b/src/tests/core.py index a9df05fc..005900d0 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -21,6 +21,7 @@ from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection from queues import excQueue knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') +program = None def pickle_knownnodes(): @@ -132,6 +133,7 @@ class TestCore(unittest.TestCase): self._outdate_knownnodes() # time.sleep(303) # singleCleaner wakes up every 5 min knownnodes.cleanupKnownNodes() + self.assertTrue(knownnodes.knownNodes[1]) while True: try: thread, exc = excQueue.get(block=False) @@ -140,14 +142,15 @@ class TestCore(unittest.TestCase): if thread == 'Asyncore' and isinstance(exc, IndexError): self.fail("IndexError because of empty knownNodes!") - def test_bootstrap(self): - """test bootstrapping""" + def _initiate_bootstrap(self): BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') self._outdate_knownnodes() - knownnodes.cleanupKnownNodes() - # it's weird, knownnodes appear empty knownnodes.addKnownNode(1, state.Peer('127.0.0.1', 8444), is_self=True) - time.sleep(0.25) + knownnodes.cleanupKnownNodes() + time.sleep(2) + + def _check_bootstrap(self): + _started = time.time() BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') proxy_type = BMConfigParser().safeGet( 'bitmessagesettings', 'socksproxytype') @@ -157,20 +160,31 @@ class TestCore(unittest.TestCase): connection_base = Socks4aBMConnection else: connection_base = TCPConnection - _started = time.time() for _ in range(180): time.sleep(1) for peer, con in BMConnectionPool().outboundConnections.iteritems(): if not peer.host.startswith('bootstrap'): self.assertIsInstance(con, connection_base) + self.assertNotEqual(peer.host, '127.0.0.1') return else: # pylint: disable=useless-else-on-loop self.fail( 'Failed to connect during %s sec' % (time.time() - _started)) + def test_bootstrap(self): + """test bootstrapping""" + self._initiate_bootstrap() + self._check_bootstrap() + self._initiate_bootstrap() + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'stem') + program.start_proxyconfig(BMConfigParser()) + self._check_bootstrap() -def run(): + +def run(prog): """Starts all tests defined in this module""" + global program # pylint: disable=global-statement + program = prog loader = unittest.TestLoader() loader.sortTestMethodsUsing = None suite = loader.loadTestsFromTestCase(TestCore) From 88f2c51595470bc83f1f10740b01a7eab4dc5e62 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Mon, 2 Sep 2019 14:19:45 +0300 Subject: [PATCH 10/10] quzwelsuziwqgpt2.onion:8444 is also a bootstrap server --- src/knownnodes.py | 13 +++---------- src/network/connectionpool.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/knownnodes.py b/src/knownnodes.py index 2735edbf..ba21bac7 100644 --- a/src/knownnodes.py +++ b/src/knownnodes.py @@ -34,10 +34,6 @@ DEFAULT_NODES = ( state.Peer('178.11.46.221', 8444) ) -DEFAULT_NODES_ONION = ( - state.Peer('quzwelsuziwqgpt2.onion', 8444), -) - def json_serialize_knownnodes(output): """ @@ -66,8 +62,7 @@ def json_deserialize_knownnodes(source): if ( not (knownNodesActual or info.get('self')) and - peer not in DEFAULT_NODES and - peer not in DEFAULT_NODES_ONION + peer not in DEFAULT_NODES ): knownNodesActual = True @@ -102,9 +97,9 @@ def addKnownNode(stream, peer, lastseen=None, is_self=False): } -def createDefaultKnownNodes(onion=False): +def createDefaultKnownNodes(): past = time.time() - 2418600 # 28 days - 10 min - for peer in DEFAULT_NODES_ONION if onion else DEFAULT_NODES: + for peer in DEFAULT_NODES: addKnownNode(1, peer, past) saveKnownNodes() @@ -122,8 +117,6 @@ def readKnownNodes(): logger.debug( 'Failed to read nodes from knownnodes.dat', exc_info=True) createDefaultKnownNodes() - if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': - createDefaultKnownNodes(onion=True) config = BMConfigParser() diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 1554a585..4d16df49 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -165,10 +165,14 @@ class BMConnectionPool(object): proxy_type = BMConfigParser().safeGet( 'bitmessagesettings', 'socksproxytype') # A plugins may be added here + hostname = None if not proxy_type or proxy_type == 'none': connection_base = TCPConnection elif proxy_type == 'SOCKS5': connection_base = Socks5BMConnection + hostname = helper_random.randomchoice([ + 'quzwelsuziwqgpt2.onion', None + ]) elif proxy_type == 'SOCKS4a': connection_base = Socks4aBMConnection # FIXME: I cannot test else: @@ -177,8 +181,11 @@ class BMConnectionPool(object): return bootstrapper = bootstrap(connection_base) - port = helper_random.randomchoice([8080, 8444]) - hostname = 'bootstrap%s.bitmessage.org' % port + if not hostname: + port = helper_random.randomchoice([8080, 8444]) + hostname = 'bootstrap%s.bitmessage.org' % port + else: + port = 8444 self.addConnection(bootstrapper(hostname, port)) def loop(self): # pylint: disable=too-many-branches,too-many-statements