From 7c2f8881a955a509a4137119128974c4a58a9b88 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 21 Jun 2019 23:39:08 +0100 Subject: [PATCH 1/3] Ensure that all config options have sensible defaults This will enable us to skip the unintuitive behaviour where the generated config and default config are the same thing. --- synapse/config/_base.py | 10 +++++----- synapse/config/key.py | 22 +++++++++++++++++----- synapse/config/logger.py | 2 +- synapse/config/repository.py | 6 ++++-- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/synapse/config/_base.py b/synapse/config/_base.py index 21d110c82..20778973d 100644 --- a/synapse/config/_base.py +++ b/synapse/config/_base.py @@ -354,8 +354,8 @@ class Config(object): config_file.write("# vim:ft=yaml\n\n") config_file.write(config_str) - config = yaml.safe_load(config_str) - obj.invoke_all("generate_files", config) + config_dict = yaml.safe_load(config_str) + obj.generate_missing_files(config_dict, config_dir_path) print( ( @@ -390,7 +390,7 @@ class Config(object): ) if generate_missing_configs: - obj.generate_missing_files(config_dict) + obj.generate_missing_files(config_dict, config_dir_path) return None obj.parse_config_dict( @@ -466,8 +466,8 @@ class Config(object): data_dir_path=data_dir_path, ) - def generate_missing_files(self, config_dict): - self.invoke_all("generate_files", config_dict) + def generate_missing_files(self, config_dict, config_dir_path): + self.invoke_all("generate_files", config_dict, config_dir_path) def find_config_files(search_paths): diff --git a/synapse/config/key.py b/synapse/config/key.py index e58638f70..5ec465b19 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -65,13 +65,18 @@ class TrustedKeyServer(object): class KeyConfig(Config): - def read_config(self, config, **kwargs): + def read_config(self, config, config_dir_path, **kwargs): # the signing key can be specified inline or in a separate file if "signing_key" in config: self.signing_key = read_signing_keys([config["signing_key"]]) else: - self.signing_key_path = config["signing_key_path"] - self.signing_key = self.read_signing_key(self.signing_key_path) + signing_key_path = config.get("signing_key_path") + if signing_key_path is None: + signing_key_path = os.path.join( + config_dir_path, config["server_name"] + ".signing.key" + ) + + self.signing_key = self.read_signing_key(signing_key_path) self.old_signing_keys = self.read_old_signing_keys( config.get("old_signing_keys", {}) @@ -237,8 +242,15 @@ class KeyConfig(Config): ) return keys - def generate_files(self, config): - signing_key_path = config["signing_key_path"] + def generate_files(self, config, config_dir_path): + if "signing_key" in config: + return + + signing_key_path = config.get("signing_key_path") + if signing_key_path is None: + signing_key_path = os.path.join( + config_dir_path, config["server_name"] + ".signing.key" + ) if not self.path_exists(signing_key_path): print("Generating signing key file %s" % (signing_key_path,)) diff --git a/synapse/config/logger.py b/synapse/config/logger.py index 153a13751..3e05f49ab 100644 --- a/synapse/config/logger.py +++ b/synapse/config/logger.py @@ -133,7 +133,7 @@ class LoggingConfig(Config): help="Do not redirect stdout/stderr to the log", ) - def generate_files(self, config): + def generate_files(self, config, config_dir_path): log_config = config.get("log_config") if log_config and not os.path.exists(log_config): log_file = self.abspath("homeserver.log") diff --git a/synapse/config/repository.py b/synapse/config/repository.py index 15a19e091..43e6c4921 100644 --- a/synapse/config/repository.py +++ b/synapse/config/repository.py @@ -91,7 +91,9 @@ class ContentRepositoryConfig(Config): self.max_image_pixels = self.parse_size(config.get("max_image_pixels", "32M")) self.max_spider_size = self.parse_size(config.get("max_spider_size", "10M")) - self.media_store_path = self.ensure_directory(config["media_store_path"]) + self.media_store_path = self.ensure_directory( + config.get("media_store_path", "media_store") + ) backup_media_store_path = config.get("backup_media_store_path") @@ -148,7 +150,7 @@ class ContentRepositoryConfig(Config): (provider_class, parsed_config, wrapper_config) ) - self.uploads_path = self.ensure_directory(config["uploads_path"]) + self.uploads_path = self.ensure_directory(config.get("uploads_path", "uploads")) self.dynamic_thumbnails = config.get("dynamic_thumbnails", False) self.thumbnail_requirements = parse_thumbnail_requirements( config.get("thumbnail_sizes", DEFAULT_THUMBNAIL_SIZES) From 16b52642e274054a070fb494684547a77790c22b Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Sat, 22 Jun 2019 00:00:20 +0100 Subject: [PATCH 2/3] Don't load the generated config as the default. It's too confusing. --- synapse/config/_base.py | 96 +++++++---------------- synapse/config/api.py | 2 +- synapse/config/appservice.py | 2 +- synapse/config/captcha.py | 2 +- synapse/config/cas.py | 2 +- synapse/config/consent_config.py | 2 +- synapse/config/database.py | 2 +- synapse/config/emailconfig.py | 2 +- synapse/config/groups.py | 2 +- synapse/config/jwt_config.py | 2 +- synapse/config/key.py | 2 +- synapse/config/logger.py | 2 +- synapse/config/metrics.py | 2 +- synapse/config/password.py | 2 +- synapse/config/password_auth_providers.py | 2 +- synapse/config/push.py | 2 +- synapse/config/ratelimiting.py | 2 +- synapse/config/registration.py | 2 +- synapse/config/repository.py | 2 +- synapse/config/room_directory.py | 2 +- synapse/config/saml2_config.py | 2 +- synapse/config/server.py | 2 +- synapse/config/server_notices_config.py | 2 +- synapse/config/spam_checker.py | 2 +- synapse/config/stats.py | 2 +- synapse/config/third_party_event_rules.py | 2 +- synapse/config/tls.py | 4 +- synapse/config/user_directory.py | 2 +- synapse/config/voip.py | 2 +- 29 files changed, 60 insertions(+), 94 deletions(-) diff --git a/synapse/config/_base.py b/synapse/config/_base.py index 20778973d..8757416a6 100644 --- a/synapse/config/_base.py +++ b/synapse/config/_base.py @@ -136,11 +136,6 @@ class Config(object): with open(file_path) as file_stream: return file_stream.read() - @staticmethod - def read_config_file(file_path): - with open(file_path) as file_stream: - return yaml.safe_load(file_stream) - def invoke_all(self, name, *args, **kargs): results = [] for cls in type(self).mro(): @@ -158,9 +153,8 @@ class Config(object): ): """Build a default configuration file - This is used both when the user explicitly asks us to generate a config file - (eg with --generate_config), and before loading the config at runtime (to give - a base which the config files override) + This is used when the user explicitly asks us to generate a config file + (eg with --generate_config). Args: config_dir_path (str): The path where the config files are kept. Used to @@ -182,10 +176,10 @@ class Config(object): Returns: str: the yaml config file """ - default_config = "\n\n".join( + return "\n\n".join( dedent(conf) for conf in self.invoke_all( - "default_config", + "generate_config_section", config_dir_path=config_dir_path, data_dir_path=data_dir_path, server_name=server_name, @@ -194,8 +188,6 @@ class Config(object): ) ) - return default_config - @classmethod def load_config(cls, description, argv): """Parse the commandline and config files @@ -240,9 +232,7 @@ class Config(object): config_dir_path = os.path.abspath(config_dir_path) data_dir_path = os.getcwd() - config_dict = obj.read_config_files( - config_files, config_dir_path=config_dir_path, data_dir_path=data_dir_path - ) + config_dict = read_config_files(config_files) obj.parse_config_dict( config_dict, config_dir_path=config_dir_path, data_dir_path=data_dir_path ) @@ -385,10 +375,7 @@ class Config(object): obj.invoke_all("add_arguments", parser) args = parser.parse_args(remaining_args) - config_dict = obj.read_config_files( - config_files, config_dir_path=config_dir_path, data_dir_path=data_dir_path - ) - + config_dict = read_config_files(config_files) if generate_missing_configs: obj.generate_missing_files(config_dict, config_dir_path) return None @@ -400,53 +387,6 @@ class Config(object): return obj - def read_config_files(self, config_files, config_dir_path, data_dir_path): - """Read the config files into a dict - - Args: - config_files (iterable[str]): A list of the config files to read - - config_dir_path (str): The path where the config files are kept. Used to - create filenames for things like the log config and the signing key. - - data_dir_path (str): The path where the data files are kept. Used to create - filenames for things like the database and media store. - - Returns: dict - """ - # first we read the config files into a dict - specified_config = {} - for config_file in config_files: - yaml_config = self.read_config_file(config_file) - specified_config.update(yaml_config) - - # not all of the options have sensible defaults in code, so we now need to - # generate a default config file suitable for the specified server name... - if "server_name" not in specified_config: - raise ConfigError(MISSING_SERVER_NAME) - server_name = specified_config["server_name"] - config_string = self.generate_config( - config_dir_path=config_dir_path, - data_dir_path=data_dir_path, - server_name=server_name, - generate_secrets=False, - ) - - # ... and read it into a base config dict ... - config = yaml.safe_load(config_string) - - # ... and finally, overlay it with the actual configuration. - config.pop("log_config") - config.update(specified_config) - - if "report_stats" not in config: - raise ConfigError( - MISSING_REPORT_STATS_CONFIG_INSTRUCTIONS - + "\n" - + MISSING_REPORT_STATS_SPIEL - ) - return config - def parse_config_dict(self, config_dict, config_dir_path, data_dir_path): """Read the information from the config dict into this Config object. @@ -470,6 +410,30 @@ class Config(object): self.invoke_all("generate_files", config_dict, config_dir_path) +def read_config_files(config_files): + """Read the config files into a dict + + Args: + config_files (iterable[str]): A list of the config files to read + + Returns: dict + """ + specified_config = {} + for config_file in config_files: + with open(config_file) as file_stream: + yaml_config = yaml.safe_load(file_stream) + specified_config.update(yaml_config) + + if "server_name" not in specified_config: + raise ConfigError(MISSING_SERVER_NAME) + + if "report_stats" not in specified_config: + raise ConfigError( + MISSING_REPORT_STATS_CONFIG_INSTRUCTIONS + "\n" + MISSING_REPORT_STATS_SPIEL + ) + return specified_config + + def find_config_files(search_paths): """Finds config files using a list of search paths. If a path is a file then that file path is added to the list. If a search path is a directory diff --git a/synapse/config/api.py b/synapse/config/api.py index d9eff9ae1..dddea79a8 100644 --- a/synapse/config/api.py +++ b/synapse/config/api.py @@ -30,7 +30,7 @@ class ApiConfig(Config): ], ) - def default_config(cls, **kwargs): + def generate_config_section(cls, **kwargs): return """\ ## API Configuration ## diff --git a/synapse/config/appservice.py b/synapse/config/appservice.py index b74cebfca..8387ff680 100644 --- a/synapse/config/appservice.py +++ b/synapse/config/appservice.py @@ -34,7 +34,7 @@ class AppServiceConfig(Config): self.notify_appservices = config.get("notify_appservices", True) self.track_appservice_user_ips = config.get("track_appservice_user_ips", False) - def default_config(cls, **kwargs): + def generate_config_section(cls, **kwargs): return """\ # A list of application service config files to use # diff --git a/synapse/config/captcha.py b/synapse/config/captcha.py index a08b08570..8dac8152c 100644 --- a/synapse/config/captcha.py +++ b/synapse/config/captcha.py @@ -28,7 +28,7 @@ class CaptchaConfig(Config): "https://www.recaptcha.net/recaptcha/api/siteverify", ) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ ## Captcha ## # See docs/CAPTCHA_SETUP for full details of configuring this. diff --git a/synapse/config/cas.py b/synapse/config/cas.py index a5f044995..ebe34d933 100644 --- a/synapse/config/cas.py +++ b/synapse/config/cas.py @@ -35,7 +35,7 @@ class CasConfig(Config): self.cas_service_url = None self.cas_required_attributes = {} - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # Enable CAS for registration and login. # diff --git a/synapse/config/consent_config.py b/synapse/config/consent_config.py index 6fd493168..94916f3a4 100644 --- a/synapse/config/consent_config.py +++ b/synapse/config/consent_config.py @@ -111,5 +111,5 @@ class ConsentConfig(Config): "policy_name", "Privacy Policy" ) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return DEFAULT_CONFIG diff --git a/synapse/config/database.py b/synapse/config/database.py index c8963e276..bcb2089dd 100644 --- a/synapse/config/database.py +++ b/synapse/config/database.py @@ -38,7 +38,7 @@ class DatabaseConfig(Config): self.set_databasepath(config.get("database_path")) - def default_config(self, data_dir_path, **kwargs): + def generate_config_section(self, data_dir_path, **kwargs): database_path = os.path.join(data_dir_path, "homeserver.db") return ( """\ diff --git a/synapse/config/emailconfig.py b/synapse/config/emailconfig.py index 07df7b717..cf39936da 100644 --- a/synapse/config/emailconfig.py +++ b/synapse/config/emailconfig.py @@ -214,7 +214,7 @@ class EmailConfig(Config): if not os.path.isfile(p): raise ConfigError("Unable to find email template file %s" % (p,)) - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # Enable sending emails for password resets, notification events or # account expiry notices diff --git a/synapse/config/groups.py b/synapse/config/groups.py index d11f4d3b9..2a522b5f4 100644 --- a/synapse/config/groups.py +++ b/synapse/config/groups.py @@ -21,7 +21,7 @@ class GroupsConfig(Config): self.enable_group_creation = config.get("enable_group_creation", False) self.group_creation_prefix = config.get("group_creation_prefix", "") - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ # Uncomment to allow non-server-admin users to create groups on this server # diff --git a/synapse/config/jwt_config.py b/synapse/config/jwt_config.py index a2c97dea9..36d87cef0 100644 --- a/synapse/config/jwt_config.py +++ b/synapse/config/jwt_config.py @@ -41,7 +41,7 @@ class JWTConfig(Config): self.jwt_secret = None self.jwt_algorithm = None - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ # The JWT needs to contain a globally unique "sub" (subject) claim. # diff --git a/synapse/config/key.py b/synapse/config/key.py index 5ec465b19..8fc74f9cd 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -122,7 +122,7 @@ class KeyConfig(Config): # falsification of values self.form_secret = config.get("form_secret", None) - def default_config( + def generate_config_section( self, config_dir_path, server_name, generate_secrets=False, **kwargs ): base_key_name = os.path.join(config_dir_path, server_name) diff --git a/synapse/config/logger.py b/synapse/config/logger.py index 3e05f49ab..931aec41c 100644 --- a/synapse/config/logger.py +++ b/synapse/config/logger.py @@ -80,7 +80,7 @@ class LoggingConfig(Config): self.log_config = self.abspath(config.get("log_config")) self.log_file = self.abspath(config.get("log_file")) - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): log_config = os.path.join(config_dir_path, server_name + ".log.config") return ( """\ diff --git a/synapse/config/metrics.py b/synapse/config/metrics.py index 6af82e132..369844196 100644 --- a/synapse/config/metrics.py +++ b/synapse/config/metrics.py @@ -40,7 +40,7 @@ class MetricsConfig(Config): "sentry.dsn field is required when sentry integration is enabled" ) - def default_config(self, report_stats=None, **kwargs): + def generate_config_section(self, report_stats=None, **kwargs): res = """\ ## Metrics ### diff --git a/synapse/config/password.py b/synapse/config/password.py index 300b67f23..598f84fc0 100644 --- a/synapse/config/password.py +++ b/synapse/config/password.py @@ -28,7 +28,7 @@ class PasswordConfig(Config): self.password_enabled = password_config.get("enabled", True) self.password_pepper = password_config.get("pepper", "") - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """\ password_config: # Uncomment to disable password login diff --git a/synapse/config/password_auth_providers.py b/synapse/config/password_auth_providers.py index 8ffefd263..788c39c9f 100644 --- a/synapse/config/password_auth_providers.py +++ b/synapse/config/password_auth_providers.py @@ -46,7 +46,7 @@ class PasswordAuthProviderConfig(Config): self.password_providers.append((provider_class, provider_config)) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ #password_providers: # - module: "ldap_auth_provider.LdapAuthProvider" diff --git a/synapse/config/push.py b/synapse/config/push.py index 99d15e446..1b932722a 100644 --- a/synapse/config/push.py +++ b/synapse/config/push.py @@ -42,7 +42,7 @@ class PushConfig(Config): ) self.push_include_content = not redact_content - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # Clients requesting push notifications can either have the body of # the message sent in the notification poke along with other details diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py index b03047f2b..8c587f3fd 100644 --- a/synapse/config/ratelimiting.py +++ b/synapse/config/ratelimiting.py @@ -80,7 +80,7 @@ class RatelimitConfig(Config): "federation_rr_transactions_per_room_per_second", 50 ) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ ## Ratelimiting ## diff --git a/synapse/config/registration.py b/synapse/config/registration.py index 6d8a2df29..4a59e6ec9 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -85,7 +85,7 @@ class RegistrationConfig(Config): "disable_msisdn_registration", False ) - def default_config(self, generate_secrets=False, **kwargs): + def generate_config_section(self, generate_secrets=False, **kwargs): if generate_secrets: registration_shared_secret = 'registration_shared_secret: "%s"' % ( random_string_with_symbols(50), diff --git a/synapse/config/repository.py b/synapse/config/repository.py index 43e6c4921..80a628d9b 100644 --- a/synapse/config/repository.py +++ b/synapse/config/repository.py @@ -190,7 +190,7 @@ class ContentRepositoryConfig(Config): self.url_preview_url_blacklist = config.get("url_preview_url_blacklist", ()) - def default_config(self, data_dir_path, **kwargs): + def generate_config_section(self, data_dir_path, **kwargs): media_store = os.path.join(data_dir_path, "media_store") uploads_path = os.path.join(data_dir_path, "uploads") diff --git a/synapse/config/room_directory.py b/synapse/config/room_directory.py index 24223db7a..a92693017 100644 --- a/synapse/config/room_directory.py +++ b/synapse/config/room_directory.py @@ -46,7 +46,7 @@ class RoomDirectoryConfig(Config): _RoomDirectoryRule("room_list_publication_rules", {"action": "allow"}) ] - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # Uncomment to disable searching the public room list. When disabled # blocks searching local and remote room lists for local and remote diff --git a/synapse/config/saml2_config.py b/synapse/config/saml2_config.py index d86cf0e6e..872a1ba93 100644 --- a/synapse/config/saml2_config.py +++ b/synapse/config/saml2_config.py @@ -61,7 +61,7 @@ class SAML2Config(Config): }, } - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """\ # Enable SAML2 for registration and login. Uses pysaml2. # diff --git a/synapse/config/server.py b/synapse/config/server.py index 1e58b2e91..2f5d1a6ae 100644 --- a/synapse/config/server.py +++ b/synapse/config/server.py @@ -307,7 +307,7 @@ class ServerConfig(Config): def has_tls_listener(self): return any(l["tls"] for l in self.listeners) - def default_config(self, server_name, data_dir_path, **kwargs): + def generate_config_section(self, server_name, data_dir_path, **kwargs): _, bind_port = parse_and_validate_server_name(server_name) if bind_port is not None: unsecure_port = bind_port - 400 diff --git a/synapse/config/server_notices_config.py b/synapse/config/server_notices_config.py index 05110c17a..eaac3d73b 100644 --- a/synapse/config/server_notices_config.py +++ b/synapse/config/server_notices_config.py @@ -78,5 +78,5 @@ class ServerNoticesConfig(Config): # todo: i18n self.server_notices_room_name = c.get("room_name", "Server Notices") - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return DEFAULT_CONFIG diff --git a/synapse/config/spam_checker.py b/synapse/config/spam_checker.py index 1968003cb..e40797ab5 100644 --- a/synapse/config/spam_checker.py +++ b/synapse/config/spam_checker.py @@ -26,7 +26,7 @@ class SpamCheckerConfig(Config): if provider is not None: self.spam_checker = load_module(provider) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ #spam_checker: # module: "my_custom_project.SuperSpamChecker" diff --git a/synapse/config/stats.py b/synapse/config/stats.py index 73a87c73f..b518a3ed9 100644 --- a/synapse/config/stats.py +++ b/synapse/config/stats.py @@ -42,7 +42,7 @@ class StatsConfig(Config): / 1000 ) - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # Local statistics collection. Used in populating the room directory. # diff --git a/synapse/config/third_party_event_rules.py b/synapse/config/third_party_event_rules.py index 1bedd607b..b3431441b 100644 --- a/synapse/config/third_party_event_rules.py +++ b/synapse/config/third_party_event_rules.py @@ -26,7 +26,7 @@ class ThirdPartyRulesConfig(Config): if provider is not None: self.third_party_event_rules = load_module(provider) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ # Server admins can define a Python module that implements extra rules for # allowing or denying incoming events. In order to work, this module needs to diff --git a/synapse/config/tls.py b/synapse/config/tls.py index 9a66e8cc4..8fcf80141 100644 --- a/synapse/config/tls.py +++ b/synapse/config/tls.py @@ -217,7 +217,9 @@ class TlsConfig(Config): if sha256_fingerprint not in sha256_fingerprints: self.tls_fingerprints.append({"sha256": sha256_fingerprint}) - def default_config(self, config_dir_path, server_name, data_dir_path, **kwargs): + def generate_config_section( + self, config_dir_path, server_name, data_dir_path, **kwargs + ): base_key_name = os.path.join(config_dir_path, server_name) tls_certificate_path = base_key_name + ".tls.crt" diff --git a/synapse/config/user_directory.py b/synapse/config/user_directory.py index 0665dc3fc..f6313e17d 100644 --- a/synapse/config/user_directory.py +++ b/synapse/config/user_directory.py @@ -33,7 +33,7 @@ class UserDirectoryConfig(Config): "search_all_users", False ) - def default_config(self, config_dir_path, server_name, **kwargs): + def generate_config_section(self, config_dir_path, server_name, **kwargs): return """ # User Directory configuration # diff --git a/synapse/config/voip.py b/synapse/config/voip.py index 01e0cb2e2..2ca0e1cf7 100644 --- a/synapse/config/voip.py +++ b/synapse/config/voip.py @@ -26,7 +26,7 @@ class VoipConfig(Config): ) self.turn_allow_guests = config.get("turn_allow_guests", True) - def default_config(self, **kwargs): + def generate_config_section(self, **kwargs): return """\ ## TURN ## From c7832945498b45f0ae2a0b3c00c2e08254aed4e0 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Sat, 22 Jun 2019 00:15:01 +0100 Subject: [PATCH 3/3] changelog --- changelog.d/5523.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5523.bugfix diff --git a/changelog.d/5523.bugfix b/changelog.d/5523.bugfix new file mode 100644 index 000000000..26acd367a --- /dev/null +++ b/changelog.d/5523.bugfix @@ -0,0 +1 @@ +Fix a regression where homeservers on private IP addresses were incorrectly blacklisted. \ No newline at end of file