From 1a40c29d221f036e82a87444ace331afbb8e80c6 Mon Sep 17 00:00:00 2001 From: Peter Surda Date: Tue, 7 Jun 2016 21:59:48 +0200 Subject: [PATCH] Add Tor hidden service support - PyBitmessage can now run as a hidden service on Tor - three new variables in keys.dat: onionhostname, onionport, onionbindip - you need to manually add a hidden service to tor --- src/class_outgoingSynSender.py | 5 ++++- src/class_receiveDataThread.py | 11 ++++++++++- src/class_singleListener.py | 32 ++++++++++++++++++++++---------- src/class_sqlThread.py | 7 +++++++ src/helper_bootstrap.py | 9 ++++++--- src/socks/__init__.py | 5 ++++- 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py index 357b8151..d68d5149 100644 --- a/src/class_outgoingSynSender.py +++ b/src/class_outgoingSynSender.py @@ -50,7 +50,10 @@ class outgoingSynSender(threading.Thread, StoppableThread): if (random.random() <= priority): break time.sleep(0.01) # prevent CPU hogging if something is broken - return peer + try: + return peer + except NameError: + return shared.Peer('127.0.0.1', 8444) def stopThread(self): super(outgoingSynSender, self).stopThread() diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index a0c9a837..6ac89f66 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -601,10 +601,19 @@ class receiveDataThread(threading.Thread): # the right child stream. with shared.knownNodesLock: if len(shared.knownNodes[self.streamNumber]) > 0: + ownPosition = random.randint(0, 499) + sentOwn = False for i in range(500): - peer, = random.sample(shared.knownNodes[self.streamNumber], 1) + # if current connection is over a proxy, sent our own onion address at a random position + if ownPosition == i and ".onion" in shared.config.get("bitmessagesettings", "onionhostname") and self.sock.getproxytype() != 0 and not sentOwn: + peer = shared.Peer(shared.config.get("bitmessagesettings", "onionhostname"), shared.config.getint("bitmessagesettings", "onionport")) + else: + # still may contain own onion address, but we don't change it + peer, = random.sample(shared.knownNodes[self.streamNumber], 1) if isHostInPrivateIPRange(peer.host): continue + if shared.config.get("bitmessagesettings", "onionhostname") == peer.host: + sentOwn = True addrsInMyStream[peer] = shared.knownNodes[ self.streamNumber][peer] if len(shared.knownNodes[self.streamNumber * 2]) > 0: diff --git a/src/class_singleListener.py b/src/class_singleListener.py index 807570dc..0ae079f6 100644 --- a/src/class_singleListener.py +++ b/src/class_singleListener.py @@ -27,6 +27,9 @@ class singleListener(threading.Thread, StoppableThread): def _createListenSocket(self, family): HOST = '' # Symbolic name meaning all available interfaces + # If not sockslisten, but onionhostname defined, only listen on localhost + if not shared.safeConfigGetBoolean('bitmessagesettings', 'sockslisten') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'): + HOST = shared.config.get('bitmessagesettings', 'onionbindip') PORT = shared.config.getint('bitmessagesettings', 'port') sock = socket.socket(family, socket.SOCK_STREAM) if family == socket.AF_INET6: @@ -43,12 +46,14 @@ class singleListener(threading.Thread, StoppableThread): def stopThread(self): super(singleListener, self).stopThread() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect(('127.0.0.1', shared.config.getint('bitmessagesettings', 'port'))) - s.shutdown(socket.SHUT_RDWR) - s.close() - except: - pass + for ip in ('127.0.0.1', shared.config.get('bitmessagesettings', 'onionbindip')): + try: + s.connect((ip, shared.config.getint('bitmessagesettings', 'port'))) + s.shutdown(socket.SHUT_RDWR) + s.close() + break + except: + pass def run(self): # If there is a trusted peer then we don't want to accept @@ -62,8 +67,12 @@ class singleListener(threading.Thread, StoppableThread): # We typically don't want to accept incoming connections if the user is using a # SOCKS proxy, unless they have configured otherwise. If they eventually select # proxy 'none' or configure SOCKS listening then this will start listening for - # connections. - while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and shared.shutdown == 0: + # connections. But if on SOCKS and have an onionhostname, listen + # (socket is then only opened for localhost) + while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \ + (not shared.config.getboolean('bitmessagesettings', 'sockslisten') and \ + ".onion" not in shared.config.get('bitmessagesettings', 'onionhostname')) and \ + shared.shutdown == 0: self.stop.wait(5) logger.info('Listening for incoming connections.') @@ -77,6 +86,7 @@ class singleListener(threading.Thread, StoppableThread): if (isinstance(e.args, tuple) and e.args[0] in (errno.EAFNOSUPPORT, errno.EPFNOSUPPORT, + errno.EADDRNOTAVAIL, errno.ENOPROTOOPT)): sock = self._createListenSocket(socket.AF_INET) else: @@ -90,7 +100,7 @@ class singleListener(threading.Thread, StoppableThread): # SOCKS proxy, unless they have configured otherwise. If they eventually select # proxy 'none' or configure SOCKS listening then this will start listening for # connections. - while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and shared.shutdown == 0: + while shared.config.get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not shared.config.getboolean('bitmessagesettings', 'sockslisten') and ".onion" not in shared.config.get('bitmessagesettings', 'onionhostname') and shared.shutdown == 0: self.stop.wait(10) while len(shared.connectedHostsList) > 220 and shared.shutdown == 0: logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.') @@ -112,7 +122,9 @@ class singleListener(threading.Thread, StoppableThread): # is already connected because the two computers will # share the same external IP. This is here to prevent # connection flooding. - if HOST in shared.connectedHostsList: + # permit repeated connections from Tor + # FIXME: sockshostname may be a hostname rather than IP, in such a case this will break + if HOST in shared.connectedHostsList and (".onion" not in shared.config.get('bitmessagesettings', 'onionhostname') or HOST != shared.config.get('bitmessagesettings', 'sockshostname')): socketObject.close() logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.') else: diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 4147c0d2..0bede66a 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -419,6 +419,13 @@ class sqlThread(threading.Thread): logger.debug('In messages.dat database, done adding address field to the pubkeys table and removing the hash field.') self.cur.execute('''update settings set value=10 WHERE key='version';''') + if not shared.config.has_option('bitmessagesettings', 'onionhostname'): + shared.config.set('bitmessagesettings', 'onionhostname', '') + if not shared.config.has_option('bitmessagesettings', 'onionport'): + shared.config.set('bitmessagesettings', 'onionport', '8444') + if not shared.config.has_option('bitmessagesettings', 'onionbindip'): + shared.config.set('bitmessagesettings', 'onionbindip', '127.0.0.1') + shared.writeKeysFile() # Are you hoping to add a new option to the keys.dat file of existing # Bitmessage users or modify the SQLite database? Add it right above this line! diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index fbb1a81b..e14e932c 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -21,13 +21,16 @@ def knownNodes(): shared.knownNodes[stream] = {} for node_tuple in nodes.items(): try: - host, (port, time) = node_tuple + host, (port, lastseen) = node_tuple peer = shared.Peer(host, port) except: - peer, time = node_tuple - shared.knownNodes[stream][peer] = time + peer, lastseen = node_tuple + shared.knownNodes[stream][peer] = lastseen except: shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(shared.appdata) + # your own onion address, if setup + if shared.config.has_option('bitmessagesettings', 'onionhostname') and ".onion" in shared.config.get('bitmessagesettings', 'onionhostname'): + shared.knownNodes[1][shared.Peer(shared.config.get('bitmessagesettings', 'onionhostname'), shared.config.getint('bitmessagesettings', 'onionport'))] = int(time.time()) if shared.config.getint('bitmessagesettings', 'settingsversion') > 10: logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.') raise SystemExit diff --git a/src/socks/__init__.py b/src/socks/__init__.py index d5d59687..ffcf2c10 100644 --- a/src/socks/__init__.py +++ b/src/socks/__init__.py @@ -299,6 +299,9 @@ class socksocket(socket.socket): """ 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. @@ -425,4 +428,4 @@ class socksocket(socket.socket): self.__negotiatesocks5() return self.__resolvesocks5(host) else: - return None \ No newline at end of file + return None