diff --git a/electrum/commands.py b/electrum/commands.py index 23d7710e0..034c6a0a3 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1563,11 +1563,20 @@ def add_network_options(parser): parser.add_argument("-f", "--serverfingerprint", dest=SimpleConfig.NETWORK_SERVERFINGERPRINT.key(), default=None, help="only allow connecting to servers with a matching SSL certificate SHA256 fingerprint. " + "To calculate this yourself: '$ openssl x509 -noout -fingerprint -sha256 -inform pem -in mycertfile.crt'. Enter as 64 hex chars.") - parser.add_argument("-1", "--oneserver", action="store_true", dest=SimpleConfig.NETWORK_ONESERVER.key(), default=None, help="connect to one server only") - parser.add_argument("-s", "--server", dest=SimpleConfig.NETWORK_SERVER.key(), default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)") - parser.add_argument("-p", "--proxy", dest=SimpleConfig.NETWORK_PROXY.key(), default=None, help="set proxy [type:]host[:port] (or 'none' to disable proxy), where type is socks4,socks5 or http") - parser.add_argument("--noonion", action="store_true", dest=SimpleConfig.NETWORK_NOONION.key(), default=None, help="do not try to connect to onion servers") - parser.add_argument("--skipmerklecheck", action="store_true", dest=SimpleConfig.NETWORK_SKIPMERKLECHECK.key(), default=None, help="Tolerate invalid merkle proofs from server") + parser.add_argument("-1", "--oneserver", action="store_true", dest=SimpleConfig.NETWORK_ONESERVER.key(), default=None, + help="connect to one server only") + parser.add_argument("-s", "--server", dest=SimpleConfig.NETWORK_SERVER.key(), default=None, + help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)") + parser.add_argument("-p", "--proxy", dest=SimpleConfig.NETWORK_PROXY.key(), default=None, + help="set proxy [type:]host:port (or 'none' to disable proxy), where type is socks4 or socks5") + parser.add_argument("--proxyuser", dest=SimpleConfig.NETWORK_PROXY_USER.key(), default=None, + help="set proxy username") + parser.add_argument("--proxypassword", dest=SimpleConfig.NETWORK_PROXY_PASSWORD.key(), default=None, + help="set proxy password") + parser.add_argument("--noonion", action="store_true", dest=SimpleConfig.NETWORK_NOONION.key(), default=None, + help="do not try to connect to onion servers") + parser.add_argument("--skipmerklecheck", action="store_true", dest=SimpleConfig.NETWORK_SKIPMERKLECHECK.key(), default=None, + help="Tolerate invalid merkle proofs from server") def add_global_options(parser): group = parser.add_argument_group('global options') diff --git a/electrum/gui/text.py b/electrum/gui/text.py index e18ce3c9a..7bc46612a 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -470,6 +470,8 @@ class ElectrumGui(BaseElectrumGui, EventListener): out = self.run_popup('', ['Transaction ID:', self.txid[self.pos]]) def edit_str(self, target, c, is_num=False): + if target is None: + target = '' # detect backspace cc = curses.unctrl(c).decode() if c in [8, 127, 263] and target: @@ -721,10 +723,13 @@ class ElectrumGui(BaseElectrumGui, EventListener): proxy_config, auto_connect = net_params.proxy, net_params.auto_connect srv = 'auto-connect' if auto_connect else str(self.network.default_server) out = self.run_dialog('Network', [ - {'label':'server', 'type':'str', 'value':srv}, - {'label':'proxy', 'type':'str', 'value':self.config.NETWORK_PROXY}, - ], buttons = 1) + {'label': 'server', 'type': 'str', 'value': srv}, + {'label': 'proxy', 'type': 'str', 'value': self.config.NETWORK_PROXY}, + {'label': 'proxy user', 'type': 'str', 'value': self.config.NETWORK_PROXY_USER}, + {'label': 'proxy pass', 'type': 'str', 'value': self.config.NETWORK_PROXY_PASSWORD}, + ], buttons=1) if out: + self.show_message(repr(proxy_config)) if out.get('server'): server_str = out.get('server') auto_connect = server_str == 'auto-connect' @@ -734,11 +739,14 @@ class ElectrumGui(BaseElectrumGui, EventListener): except Exception: self.show_message("Error:" + server_str + "\nIn doubt, type \"auto-connect\"") return False - if out.get('server') or out.get('proxy'): - proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config + if out.get('server') or out.get('proxy') or out.get('proxy user') or out.get('proxy pass'): + new_proxy_config = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config + if new_proxy_config: + new_proxy_config['user'] = out.get('proxy user') if 'proxy user' in out else proxy_config['user'] + new_proxy_config['pass'] = out.get('proxy pass') if 'proxy pass' in out else proxy_config['pass'] net_params = NetworkParameters( server=server_addr, - proxy=proxy, + proxy=new_proxy_config, auto_connect=auto_connect) self.network.run_from_another_thread(self.network.set_parameters(net_params)) diff --git a/electrum/network.py b/electrum/network.py index cc7e49feb..f8c76063e 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -40,7 +40,7 @@ import functools from enum import IntEnum import aiorpcx -from aiorpcx import ignore_after +from aiorpcx import ignore_after, NetAddress from aiohttp import ClientResponse from . import util @@ -168,35 +168,52 @@ proxy_modes = ['socks4', 'socks5'] def serialize_proxy(p): if not isinstance(p, dict): return None - return ':'.join([p.get('mode'), p.get('host'), p.get('port'), - p.get('user', ''), p.get('password', '')]) + return ':'.join([p.get('mode'), p.get('host'), p.get('port')]) -def deserialize_proxy(s: Optional[str]) -> Optional[dict]: +def deserialize_proxy(s: Optional[str], user: str = None, password: str = None) -> Optional[dict]: if not isinstance(s, str): return None if s.lower() == 'none': return None - proxy = {"mode":"socks5", "host":"localhost"} - # FIXME raw IPv6 address fails here + proxy = {"mode": "socks5", "host": "localhost"} + args = s.split(':') - n = 0 - if proxy_modes.count(args[n]) == 1: - proxy["mode"] = args[n] - n += 1 - if len(args) > n: - proxy["host"] = args[n] - n += 1 - if len(args) > n: - proxy["port"] = args[n] - n += 1 - else: - proxy["port"] = "8080" if proxy["mode"] == "http" else "1080" - if len(args) > n: - proxy["user"] = args[n] - n += 1 - if len(args) > n: - proxy["password"] = args[n] + if args[0] in proxy_modes: + proxy['mode'] = args[0] + args = args[1:] + + def is_valid_port(ps: str): + try: + assert 0 < int(ps) < 65535 + except (ValueError, AssertionError): + return False + return True + + def is_valid_host(ph: str): + try: + NetAddress(ph, '1') + except ValueError: + return False + return True + + # detect migrate from old settings + if len(args) == 4 and is_valid_host(args[0]) and is_valid_port(args[1]): # host:port:user:pass, + proxy['host'] = args[0] + proxy['port'] = args[1] + proxy['user'] = args[2] + proxy['password'] = args[3] + return proxy + + proxy['host'] = ':'.join(args[:-1]) + proxy['port'] = args[-1] + + if not is_valid_host(proxy['host']) or not is_valid_port(proxy['port']): + return None + + proxy['user'] = user + proxy['password'] = password + return proxy @@ -496,7 +513,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): def _init_parameters_from_config(self) -> None: self.auto_connect = self.config.NETWORK_AUTO_CONNECT self._set_default_server() - self._set_proxy(deserialize_proxy(self.config.NETWORK_PROXY)) + self._set_proxy(deserialize_proxy(self.config.NETWORK_PROXY, self.config.NETWORK_PROXY_USER, + self.config.NETWORK_PROXY_PASSWORD)) self._maybe_set_oneserver() def get_donation_address(self): @@ -625,6 +643,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): async def set_parameters(self, net_params: NetworkParameters): proxy = net_params.proxy proxy_str = serialize_proxy(proxy) + proxy_user = proxy['user'] if proxy else None + proxy_pass = proxy['password'] if proxy else None server = net_params.server # sanitize parameters try: @@ -636,10 +656,14 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self.config.NETWORK_AUTO_CONNECT = net_params.auto_connect self.config.NETWORK_ONESERVER = net_params.oneserver self.config.NETWORK_PROXY = proxy_str + self.config.NETWORK_PROXY_USER = proxy_user + self.config.NETWORK_PROXY_PASSWORD = proxy_pass self.config.NETWORK_SERVER = str(server) # abort if changes were not allowed by config if self.config.NETWORK_SERVER != str(server) \ or self.config.NETWORK_PROXY != proxy_str \ + or self.config.NETWORK_PROXY_USER != proxy_user \ + or self.config.NETWORK_PROXY_PASSWORD != proxy_pass \ or self.config.NETWORK_ONESERVER != net_params.oneserver: return diff --git a/electrum/simple_config.py b/electrum/simple_config.py index a5dd98e82..cd053b05b 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -926,6 +926,8 @@ class SimpleConfig(Logger): NETWORK_AUTO_CONNECT = ConfigVar('auto_connect', default=True, type_=bool) NETWORK_ONESERVER = ConfigVar('oneserver', default=False, type_=bool) NETWORK_PROXY = ConfigVar('proxy', default=None) + NETWORK_PROXY_USER = ConfigVar('proxy_user', default=None) + NETWORK_PROXY_PASSWORD = ConfigVar('proxy_password', default=None) NETWORK_SERVER = ConfigVar('server', default=None, type_=str) NETWORK_NOONION = ConfigVar('noonion', default=False, type_=bool) NETWORK_OFFLINE = ConfigVar('offline', default=False, type_=bool)