2019-09-06 15:34:52 +02:00
|
|
|
"""
|
|
|
|
src/network/proxy.py
|
|
|
|
====================
|
|
|
|
"""
|
|
|
|
# pylint: disable=protected-access
|
2016-12-01 16:48:04 +01:00
|
|
|
import socket
|
2017-06-24 12:23:56 +02:00
|
|
|
import time
|
2017-06-10 10:13:49 +02:00
|
|
|
|
2017-03-10 23:11:57 +01:00
|
|
|
import asyncore_pollchoose as asyncore
|
2018-07-17 13:28:56 +02:00
|
|
|
import state
|
|
|
|
from advanceddispatcher import AdvancedDispatcher
|
2018-02-18 20:53:16 +01:00
|
|
|
from bmconfigparser import BMConfigParser
|
2017-06-24 12:23:56 +02:00
|
|
|
from debug import logger
|
2018-07-17 13:28:56 +02:00
|
|
|
|
2017-06-24 12:23:56 +02:00
|
|
|
|
|
|
|
class ProxyError(Exception):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Base proxy exception class"""
|
2018-07-17 13:28:56 +02:00
|
|
|
errorCodes = ("Unknown error",)
|
2017-06-24 12:23:56 +02:00
|
|
|
|
2017-12-29 08:49:08 +01:00
|
|
|
def __init__(self, code=-1):
|
2017-06-24 12:23:56 +02:00
|
|
|
self.code = code
|
|
|
|
try:
|
2018-07-17 13:28:56 +02:00
|
|
|
self.message = self.errorCodes[code]
|
2017-06-24 12:23:56 +02:00
|
|
|
except IndexError:
|
2018-07-17 13:28:56 +02:00
|
|
|
self.message = self.errorCodes[-1]
|
2017-06-24 12:23:56 +02:00
|
|
|
super(ProxyError, self).__init__(self.message)
|
|
|
|
|
|
|
|
|
|
|
|
class GeneralProxyError(ProxyError):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""General proxy error class (not specfic to an implementation)"""
|
2018-07-17 13:28:56 +02:00
|
|
|
errorCodes = (
|
|
|
|
"Success",
|
2017-06-24 12:23:56 +02:00
|
|
|
"Invalid data",
|
|
|
|
"Not connected",
|
|
|
|
"Not available",
|
|
|
|
"Bad proxy type",
|
|
|
|
"Bad input",
|
|
|
|
"Timed out",
|
|
|
|
"Network unreachable",
|
|
|
|
"Connection refused",
|
2018-07-17 13:28:56 +02:00
|
|
|
"Host unreachable"
|
|
|
|
)
|
2017-03-10 23:11:57 +01:00
|
|
|
|
2016-12-01 16:48:04 +01:00
|
|
|
|
2017-01-10 21:22:22 +01:00
|
|
|
class Proxy(AdvancedDispatcher):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Base proxy class"""
|
2018-07-17 13:28:56 +02:00
|
|
|
# these are global, and if you change config during runtime,
|
|
|
|
# all active/new instances should change too
|
2017-03-10 23:11:57 +01:00
|
|
|
_proxy = ("127.0.0.1", 9050)
|
2016-12-01 16:48:04 +01:00
|
|
|
_auth = None
|
2018-02-04 21:03:54 +01:00
|
|
|
_onion_proxy = None
|
|
|
|
_onion_auth = None
|
2016-12-01 16:48:04 +01:00
|
|
|
_remote_dns = True
|
|
|
|
|
|
|
|
@property
|
|
|
|
def proxy(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Return proxy IP and port"""
|
2016-12-01 16:48:04 +01:00
|
|
|
return self.__class__._proxy
|
|
|
|
|
|
|
|
@proxy.setter
|
|
|
|
def proxy(self, address):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Set proxy IP and port"""
|
2018-07-17 13:28:56 +02:00
|
|
|
if (not isinstance(address, tuple) or len(address) < 2 or
|
2019-09-06 15:34:52 +02:00
|
|
|
not isinstance(address[0], str) or
|
2018-07-17 13:28:56 +02:00
|
|
|
not isinstance(address[1], int)):
|
2017-03-10 23:11:57 +01:00
|
|
|
raise ValueError
|
2016-12-01 16:48:04 +01:00
|
|
|
self.__class__._proxy = address
|
|
|
|
|
|
|
|
@property
|
|
|
|
def auth(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Return proxy authentication settings"""
|
2016-12-01 16:48:04 +01:00
|
|
|
return self.__class__._auth
|
|
|
|
|
|
|
|
@auth.setter
|
|
|
|
def auth(self, authTuple):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Set proxy authentication (username and password)"""
|
2016-12-01 16:48:04 +01:00
|
|
|
self.__class__._auth = authTuple
|
|
|
|
|
2018-02-04 21:03:54 +01:00
|
|
|
@property
|
|
|
|
def onion_proxy(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""
|
|
|
|
Return separate proxy IP and port for use only with onion
|
|
|
|
addresses. Untested.
|
|
|
|
"""
|
2018-02-04 21:03:54 +01:00
|
|
|
return self.__class__._onion_proxy
|
|
|
|
|
|
|
|
@onion_proxy.setter
|
|
|
|
def onion_proxy(self, address):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Set onion proxy address"""
|
2018-07-17 13:28:56 +02:00
|
|
|
if address is not None and (
|
2019-09-06 15:34:52 +02:00
|
|
|
not isinstance(address, tuple) or len(address) < 2 or
|
|
|
|
not isinstance(address[0], str) or
|
2018-07-17 13:28:56 +02:00
|
|
|
not isinstance(address[1], int)):
|
2018-02-04 21:03:54 +01:00
|
|
|
raise ValueError
|
|
|
|
self.__class__._onion_proxy = address
|
|
|
|
|
|
|
|
@property
|
|
|
|
def onion_auth(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Return proxy authentication settings for onion hosts only"""
|
2018-02-04 21:03:54 +01:00
|
|
|
return self.__class__._onion_auth
|
|
|
|
|
|
|
|
@onion_auth.setter
|
|
|
|
def onion_auth(self, authTuple):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Set proxy authentication for onion hosts only. Untested."""
|
2018-02-04 21:03:54 +01:00
|
|
|
self.__class__._onion_auth = authTuple
|
|
|
|
|
2017-03-10 23:11:57 +01:00
|
|
|
def __init__(self, address):
|
2017-06-10 10:13:49 +02:00
|
|
|
if not isinstance(address, state.Peer):
|
2017-03-10 23:11:57 +01:00
|
|
|
raise ValueError
|
|
|
|
AdvancedDispatcher.__init__(self)
|
2016-12-01 16:48:04 +01:00
|
|
|
self.destination = address
|
2017-06-10 10:13:49 +02:00
|
|
|
self.isOutbound = True
|
2017-11-19 13:48:43 +01:00
|
|
|
self.fullyEstablished = False
|
2019-09-06 15:34:52 +02:00
|
|
|
self.connectedAt = 0
|
2016-12-01 16:48:04 +01:00
|
|
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
2018-07-17 13:28:56 +02:00
|
|
|
if BMConfigParser().safeGetBoolean(
|
|
|
|
"bitmessagesettings", "socksauthentication"):
|
|
|
|
self.auth = (
|
|
|
|
BMConfigParser().safeGet(
|
|
|
|
"bitmessagesettings", "socksusername"),
|
|
|
|
BMConfigParser().safeGet(
|
|
|
|
"bitmessagesettings", "sockspassword")
|
|
|
|
)
|
2018-02-18 20:53:16 +01:00
|
|
|
else:
|
|
|
|
self.auth = None
|
2018-07-17 13:28:56 +02:00
|
|
|
self.connect(
|
|
|
|
self.onion_proxy
|
|
|
|
if address.host.endswith(".onion") and self.onion_proxy else
|
|
|
|
self.proxy
|
|
|
|
)
|
2017-06-10 10:13:49 +02:00
|
|
|
|
|
|
|
def handle_connect(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Handle connection event (to the proxy)"""
|
2017-06-24 12:23:56 +02:00
|
|
|
self.set_state("init")
|
2017-06-10 10:13:49 +02:00
|
|
|
try:
|
|
|
|
AdvancedDispatcher.handle_connect(self)
|
|
|
|
except socket.error as e:
|
|
|
|
if e.errno in asyncore._DISCONNECTED:
|
2018-07-17 13:28:56 +02:00
|
|
|
logger.debug(
|
|
|
|
"%s:%i: Connection failed: %s",
|
|
|
|
self.destination.host, self.destination.port, e)
|
2017-06-10 10:13:49 +02:00
|
|
|
return
|
2017-06-24 12:23:56 +02:00
|
|
|
self.state_init()
|
2017-06-10 10:13:49 +02:00
|
|
|
|
|
|
|
def state_proxy_handshake_done(self):
|
2019-07-08 16:20:29 +02:00
|
|
|
"""Handshake is complete at this point"""
|
2019-09-13 10:41:21 +02:00
|
|
|
self.connectedAt = time.time() # pylint: disable=attribute-defined-outside-init
|
2017-06-10 10:13:49 +02:00
|
|
|
return False
|