From 0dcef9780b59805c980247c98d2fe12c6554ce93 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 24 Mar 2026 17:07:26 +0000 Subject: [PATCH] daemon: forbid "setconfig" command to change rpcserver settings in-flight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is much easier to reason about the rpcserver if we don't allow changing its basic settings while it is already running. What does it mean to change the TCP port it is listening on ("rpcport") if it's already running? It is even problematic to change the rpcpassword: care needs to be taken to already update it for the current server. (ref https://github.com/spesmilo/electrum/issues/6762) This commit disallows changing all of the "rpc*" config variables if the daemon is already running. --- Simultaneously, it also ensures rpc_password is always set and auth cannot be disabled. Previously if there was a daemon running, and the user ran `$ electrum setconfig rpcpassword ""` that would leave the RPC unauthenticated for the current session. However next time the daemon restarted, get_rpc_credentials would see the unset password and generate one. I think this was the worst of both worlds: - we did not really allow removing the rpc password, except for the current session, and - perhaps unexpectedly, we would generate a new password on daemon restart Instead now we explicitly make sure the RPC server can never get into a state where it does not have a password set. Based on a report by `Zuzana Kotásková <36777@mail.vsfs.cz>` --- electrum/commands.py | 17 +++++++++++++---- electrum/daemon.py | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index dca66ed7e..021041b77 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -397,10 +397,19 @@ class Commands(Logger): def _setconfig(self, key, value): value = self._setconfig_normalize_value(key, value) - if self.daemon and key == SimpleConfig.RPC_USERNAME.key(): - self.daemon.commands_server.rpc_user = value - if self.daemon and key == SimpleConfig.RPC_PASSWORD.key(): - self.daemon.commands_server.rpc_password = value + if self.daemon and key in ( + SimpleConfig.RPC_USERNAME.key(), + SimpleConfig.RPC_PASSWORD.key(), + SimpleConfig.RPC_HOST.key(), + SimpleConfig.RPC_PORT.key(), + SimpleConfig.RPC_SOCKET_TYPE.key(), + SimpleConfig.RPC_SOCKET_FILEPATH.key(), + ): + raise UserFacingException( + "error: RPC server settings cannot be changed for already running daemon. " + "Stop the daemon first, and run 'setconfig' in --offline mode. " + "\nFor example: '$ electrum -o setconfig rpcport 7777'." + ) if Plugins.is_plugin_enabler_config_key(key): self.config.set_key(key, value) else: diff --git a/electrum/daemon.py b/electrum/daemon.py index 818b32786..ceab53cf3 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -180,6 +180,7 @@ def wait_until_daemon_becomes_ready(*, config: SimpleConfig, timeout=5) -> bool: def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]: rpc_user = config.RPC_USERNAME or None rpc_password = config.RPC_PASSWORD or None + # note: we explicitly forbid empty/unset password, and will generate one now instead if rpc_user is None or rpc_password is None: rpc_user = 'user' bits = 128 @@ -219,9 +220,8 @@ class AuthenticatedServer(Logger): self._methods[name] = f async def authenticate(self, headers): - if self.rpc_password == '': - # RPC authentication is disabled - return + if not self.rpc_password: + raise Exception('Server RPC password is unset. This should not happen.') auth_string = headers.get('Authorization', None) if auth_string is None: raise AuthenticationInvalidOrMissing('CredentialsMissing')