def read_config(self, config: JsonDict, **kwargs: Any) -> None: self.server_name = config["server_name"] self.server_context = config.get("server_context", None) try: parse_and_validate_server_name(self.server_name) except ValueError as e: raise ConfigError(str(e)) self.pid_file = self.abspath(config.get("pid_file")) self.soft_file_limit = config.get("soft_file_limit", 0) self.daemonize = bool(config.get("daemonize")) self.print_pidfile = bool(config.get("print_pidfile")) self.user_agent_suffix = config.get("user_agent_suffix") self.use_frozen_dicts = config.get("use_frozen_dicts", False) self.serve_server_wellknown = config.get("serve_server_wellknown", False) # Whether we should serve a "client well-known": # (a) at .well-known/matrix/client on our client HTTP listener # (b) in the response to /login # # ... which together help ensure that clients use our public_baseurl instead of # whatever they were told by the user. # # For the sake of backwards compatibility with existing installations, this is # True if public_baseurl is specified explicitly, and otherwise False. (The # reasoning here is that we have no way of knowing that the default # public_baseurl is actually correct for existing installations - many things # will not work correctly, but that's (probably?) better than sending clients # to a completely broken URL. self.serve_client_wellknown = False public_baseurl = config.get("public_baseurl") if public_baseurl is None: public_baseurl = f"https://{self.server_name}/" logger.info("Using default public_baseurl %s", public_baseurl) else: self.serve_client_wellknown = True if public_baseurl[-1] != "/": public_baseurl += "/" self.public_baseurl = public_baseurl # check that public_baseurl is valid try: splits = urllib.parse.urlsplit(self.public_baseurl) except Exception as e: raise ConfigError(f"Unable to parse URL: {e}", ("public_baseurl",)) if splits.scheme not in ("https", "http"): raise ConfigError( f"Invalid scheme '{splits.scheme}': only https and http are supported" ) if splits.query or splits.fragment: raise ConfigError( "public_baseurl cannot contain query parameters or a #-fragment" ) self.extra_well_known_client_content = config.get( "extra_well_known_client_content", {} ) if not isinstance(self.extra_well_known_client_content, dict): raise ConfigError( "extra_well_known_content must be a dictionary of key-value pairs" ) if "m.homeserver" in self.extra_well_known_client_content: raise ConfigError( "m.homeserver is not supported in extra_well_known_content, " "use public_baseurl in base config instead." ) if "m.identity_server" in self.extra_well_known_client_content: raise ConfigError( "m.identity_server is not supported in extra_well_known_content, " "use default_identity_server in base config instead." ) # Whether to enable user presence. presence_config = config.get("presence") or {} self.use_presence = presence_config.get("enabled") if self.use_presence is None: self.use_presence = config.get("use_presence", True) # Custom presence router module # This is the legacy way of configuring it (the config should now be put in the modules section) self.presence_router_module_class = None self.presence_router_config = None presence_router_config = presence_config.get("presence_router") if presence_router_config: ( self.presence_router_module_class, self.presence_router_config, ) = load_module(presence_router_config, ("presence", "presence_router")) # whether to enable the media repository endpoints. This should be set # to false if the media repository is running as a separate endpoint; # doing so ensures that we will not run cache cleanup jobs on the # master, potentially causing inconsistency. self.enable_media_repo = config.get("enable_media_repo", True) # Whether to require authentication to retrieve profile data (avatars, # display names) of other users through the client API. self.require_auth_for_profile_requests = config.get( "require_auth_for_profile_requests", False ) # Whether to require sharing a room with a user to retrieve their # profile data self.limit_profile_requests_to_users_who_share_rooms = config.get( "limit_profile_requests_to_users_who_share_rooms", False, ) # Whether to retrieve and display profile data for a user when they # are invited to a room self.include_profile_data_on_invite = config.get( "include_profile_data_on_invite", True ) if "restrict_public_rooms_to_local_users" in config and ( "allow_public_rooms_without_auth" in config or "allow_public_rooms_over_federation" in config ): raise ConfigError( "Can't use 'restrict_public_rooms_to_local_users' if" " 'allow_public_rooms_without_auth' and/or" " 'allow_public_rooms_over_federation' is set." ) # Check if the legacy "restrict_public_rooms_to_local_users" flag is set. This # flag is now obsolete but we need to check it for backward-compatibility. if config.get("restrict_public_rooms_to_local_users", False): self.allow_public_rooms_without_auth = False self.allow_public_rooms_over_federation = False else: # If set to 'true', removes the need for authentication to access the server's # public rooms directory through the client API, meaning that anyone can # query the room directory. Defaults to 'false'. self.allow_public_rooms_without_auth = config.get( "allow_public_rooms_without_auth", False ) # If set to 'true', allows any other homeserver to fetch the server's public # rooms directory via federation. Defaults to 'false'. self.allow_public_rooms_over_federation = config.get( "allow_public_rooms_over_federation", False ) default_room_version = config.get("default_room_version", DEFAULT_ROOM_VERSION) # Ensure room version is a str default_room_version = str(default_room_version) if default_room_version not in KNOWN_ROOM_VERSIONS: raise ConfigError( "Unknown default_room_version: %s, known room versions: %s" % (default_room_version, list(KNOWN_ROOM_VERSIONS.keys())) ) # Get the actual room version object rather than just the identifier self.default_room_version = KNOWN_ROOM_VERSIONS[default_room_version] # whether to enable search. If disabled, new entries will not be inserted # into the search tables and they will not be indexed. Users will receive # errors when attempting to search for messages. self.enable_search = config.get("enable_search", True) self.filter_timeline_limit = config.get("filter_timeline_limit", 100) # Whether we should block invites sent to users on this server # (other than those sent by local server admins) self.block_non_admin_invites = config.get("block_non_admin_invites", False) # Options to control access by tracking MAU self.limit_usage_by_mau = config.get("limit_usage_by_mau", False) self.max_mau_value = 0 if self.limit_usage_by_mau: self.max_mau_value = config.get("max_mau_value", 0) self.mau_stats_only = config.get("mau_stats_only", False) self.mau_limits_reserved_threepids = config.get( "mau_limit_reserved_threepids", [] ) self.mau_trial_days = config.get("mau_trial_days", 0) self.mau_appservice_trial_days = config.get("mau_appservice_trial_days", {}) self.mau_limit_alerting = config.get("mau_limit_alerting", True) # How long to keep redacted events in the database in unredacted form # before redacting them. redaction_retention_period = config.get("redaction_retention_period", "7d") if redaction_retention_period is not None: self.redaction_retention_period: Optional[int] = self.parse_duration( redaction_retention_period ) else: self.redaction_retention_period = None # How long to keep entries in the `users_ips` table. user_ips_max_age = config.get("user_ips_max_age", "28d") if user_ips_max_age is not None: self.user_ips_max_age: Optional[int] = self.parse_duration(user_ips_max_age) else: self.user_ips_max_age = None # Options to disable HS self.hs_disabled = config.get("hs_disabled", False) self.hs_disabled_message = config.get("hs_disabled_message", "") # Admin uri to direct users at should their instance become blocked # due to resource constraints self.admin_contact = config.get("admin_contact", None) ip_range_blacklist = config.get( "ip_range_blacklist", DEFAULT_IP_RANGE_BLACKLIST ) # Attempt to create an IPSet from the given ranges # Always blacklist 0.0.0.0, :: self.ip_range_blacklist = generate_ip_set( ip_range_blacklist, ["0.0.0.0", "::"], config_path=("ip_range_blacklist",) ) self.ip_range_whitelist = generate_ip_set( config.get("ip_range_whitelist", ()), config_path=("ip_range_whitelist",) ) # The federation_ip_range_blacklist is used for backwards-compatibility # and only applies to federation and identity servers. if "federation_ip_range_blacklist" in config: # Always blacklist 0.0.0.0, :: self.federation_ip_range_blacklist = generate_ip_set( config["federation_ip_range_blacklist"], ["0.0.0.0", "::"], config_path=("federation_ip_range_blacklist",), ) # 'federation_ip_range_whitelist' was never a supported configuration option. self.federation_ip_range_whitelist = None else: # No backwards-compatiblity requrired, as federation_ip_range_blacklist # is not given. Default to ip_range_blacklist and ip_range_whitelist. self.federation_ip_range_blacklist = self.ip_range_blacklist self.federation_ip_range_whitelist = self.ip_range_whitelist # (undocumented) option for torturing the worker-mode replication a bit, # for testing. The value defines the number of milliseconds to pause before # sending out any replication updates. self.replication_torture_level = config.get("replication_torture_level") # Whether to require a user to be in the room to add an alias to it. # Defaults to True. self.require_membership_for_aliases = config.get( "require_membership_for_aliases", True ) # Whether to allow per-room membership profiles through the send of membership # events with profile information that differ from the target's global profile. self.allow_per_room_profiles = config.get("allow_per_room_profiles", True) # The maximum size an avatar can have, in bytes. self.max_avatar_size = config.get("max_avatar_size") if self.max_avatar_size is not None: self.max_avatar_size = self.parse_size(self.max_avatar_size) # The MIME types allowed for an avatar. self.allowed_avatar_mimetypes = config.get("allowed_avatar_mimetypes") if self.allowed_avatar_mimetypes and not isinstance( self.allowed_avatar_mimetypes, list, ): raise ConfigError("allowed_avatar_mimetypes must be a list") self.listeners = [parse_listener_def(x) for x in config.get("listeners", [])] # no_tls is not really supported any more, but let's grandfather it in # here. if config.get("no_tls", False): l2 = [] for listener in self.listeners: if listener.tls: logger.info( "Ignoring TLS-enabled listener on port %i due to no_tls", listener.port, ) else: l2.append(listener) self.listeners = l2 self.web_client_location = config.get("web_client_location", None) # Non-HTTP(S) web client location is not supported. if self.web_client_location and not ( self.web_client_location.startswith("http://") or self.web_client_location.startswith("https://") ): raise ConfigError("web_client_location must point to a HTTP(S) URL.") self.gc_thresholds = read_gc_thresholds(config.get("gc_thresholds", None)) self.gc_seconds = self.read_gc_intervals(config.get("gc_min_interval", None)) self.limit_remote_rooms = LimitRemoteRoomsConfig( **(config.get("limit_remote_rooms") or {}) ) bind_port = config.get("bind_port") if bind_port: if config.get("no_tls", False): raise ConfigError("no_tls is incompatible with bind_port") self.listeners = [] bind_host = config.get("bind_host", "") gzip_responses = config.get("gzip_responses", True) http_options = HttpListenerConfig( resources=[ HttpResourceConfig(names=["client"], compress=gzip_responses), HttpResourceConfig(names=["federation"]), ], ) self.listeners.append( ListenerConfig( port=bind_port, bind_addresses=[bind_host], tls=True, type="http", http_options=http_options, ) ) unsecure_port = config.get("unsecure_port", bind_port - 400) if unsecure_port: self.listeners.append( ListenerConfig( port=unsecure_port, bind_addresses=[bind_host], tls=False, type="http", http_options=http_options, ) ) manhole = config.get("manhole") if manhole: self.listeners.append( ListenerConfig( port=manhole, bind_addresses=["127.0.0.1"], type="manhole", ) ) manhole_settings = config.get("manhole_settings") or {} validate_config( _MANHOLE_SETTINGS_SCHEMA, manhole_settings, ("manhole_settings",) ) manhole_username = manhole_settings.get("username", "matrix") manhole_password = manhole_settings.get("password", "rabbithole") manhole_priv_key_path = manhole_settings.get("ssh_priv_key_path") manhole_pub_key_path = manhole_settings.get("ssh_pub_key_path") manhole_priv_key = None if manhole_priv_key_path is not None: try: manhole_priv_key = Key.fromFile(manhole_priv_key_path) except Exception as e: raise ConfigError( f"Failed to read manhole private key file {manhole_priv_key_path}" ) from e manhole_pub_key = None if manhole_pub_key_path is not None: try: manhole_pub_key = Key.fromFile(manhole_pub_key_path) except Exception as e: raise ConfigError( f"Failed to read manhole public key file {manhole_pub_key_path}" ) from e self.manhole_settings = ManholeConfig( username=manhole_username, password=manhole_password, priv_key=manhole_priv_key, pub_key=manhole_pub_key, ) metrics_port = config.get("metrics_port") if metrics_port: logger.warning(METRICS_PORT_WARNING) self.listeners.append( ListenerConfig( port=metrics_port, bind_addresses=[config.get("metrics_bind_host", "127.0.0.1")], type="http", http_options=HttpListenerConfig( resources=[HttpResourceConfig(names=["metrics"])] ), ) ) self.cleanup_extremities_with_dummy_events = config.get( "cleanup_extremities_with_dummy_events", True ) # The number of forward extremities in a room needed to send a dummy event. self.dummy_events_threshold = config.get("dummy_events_threshold", 10) self.enable_ephemeral_messages = config.get("enable_ephemeral_messages", False) # Inhibits the /requestToken endpoints from returning an error that might leak # information about whether an e-mail address is in use or not on this # homeserver, and instead return a 200 with a fake sid if this kind of error is # met, without sending anything. # This is a compromise between sending an email, which could be a spam vector, # and letting the client know which email address is bound to an account and # which one isn't. self.request_token_inhibit_3pid_errors = config.get( "request_token_inhibit_3pid_errors", False, ) # Whitelist of domain names that given next_link parameters must have next_link_domain_whitelist: Optional[List[str]] = config.get( "next_link_domain_whitelist" ) self.next_link_domain_whitelist: Optional[Set[str]] = None if next_link_domain_whitelist is not None: if not isinstance(next_link_domain_whitelist, list): raise ConfigError("'next_link_domain_whitelist' must be a list") # Turn the list into a set to improve lookup speed. self.next_link_domain_whitelist = set(next_link_domain_whitelist) templates_config = config.get("templates") or {} if not isinstance(templates_config, dict): raise ConfigError("The 'templates' section must be a dictionary") self.custom_template_directory: Optional[str] = templates_config.get( "custom_template_directory" ) if self.custom_template_directory is not None and not isinstance( self.custom_template_directory, str ): raise ConfigError("'custom_template_directory' must be a string") self.use_account_validity_in_account_status: bool = ( config.get("use_account_validity_in_account_status") or False ) self.rooms_to_exclude_from_sync: List[str] = ( config.get("exclude_rooms_from_sync") or [] ) delete_stale_devices_after: Optional[str] = ( config.get("delete_stale_devices_after") or None ) if delete_stale_devices_after is not None: self.delete_stale_devices_after: Optional[int] = self.parse_duration( delete_stale_devices_after ) else: self.delete_stale_devices_after = None
def read_config(self, config, **kwargs): self.server_name = config["server_name"] self.server_context = config.get("server_context", None) try: parse_and_validate_server_name(self.server_name) except ValueError as e: raise ConfigError(str(e)) self.pid_file = self.abspath(config.get("pid_file")) self.web_client_location = config.get("web_client_location", None) self.soft_file_limit = config.get("soft_file_limit", 0) self.daemonize = config.get("daemonize") self.print_pidfile = config.get("print_pidfile") self.user_agent_suffix = config.get("user_agent_suffix") self.use_frozen_dicts = config.get("use_frozen_dicts", False) self.public_baseurl = config.get("public_baseurl") # Whether to send federation traffic out in this process. This only # applies to some federation traffic, and so shouldn't be used to # "disable" federation self.send_federation = config.get("send_federation", True) # Whether to enable user presence. self.use_presence = config.get("use_presence", True) # Whether to update the user directory or not. This should be set to # false only if we are updating the user directory in a worker self.update_user_directory = config.get("update_user_directory", True) # whether to enable the media repository endpoints. This should be set # to false if the media repository is running as a separate endpoint; # doing so ensures that we will not run cache cleanup jobs on the # master, potentially causing inconsistency. self.enable_media_repo = config.get("enable_media_repo", True) # Whether to require authentication to retrieve profile data (avatars, # display names) of other users through the client API. self.require_auth_for_profile_requests = config.get( "require_auth_for_profile_requests", False) if "restrict_public_rooms_to_local_users" in config and ( "allow_public_rooms_without_auth" in config or "allow_public_rooms_over_federation" in config): raise ConfigError( "Can't use 'restrict_public_rooms_to_local_users' if" " 'allow_public_rooms_without_auth' and/or" " 'allow_public_rooms_over_federation' is set.") # Check if the legacy "restrict_public_rooms_to_local_users" flag is set. This # flag is now obsolete but we need to check it for backward-compatibility. if config.get("restrict_public_rooms_to_local_users", False): self.allow_public_rooms_without_auth = False self.allow_public_rooms_over_federation = False else: # If set to 'False', requires authentication to access the server's public # rooms directory through the client API. Defaults to 'True'. self.allow_public_rooms_without_auth = config.get( "allow_public_rooms_without_auth", True) # If set to 'False', forbids any other homeserver to fetch the server's public # rooms directory via federation. Defaults to 'True'. self.allow_public_rooms_over_federation = config.get( "allow_public_rooms_over_federation", True) default_room_version = config.get("default_room_version", DEFAULT_ROOM_VERSION) # Ensure room version is a str default_room_version = str(default_room_version) if default_room_version not in KNOWN_ROOM_VERSIONS: raise ConfigError( "Unknown default_room_version: %s, known room versions: %s" % (default_room_version, list(KNOWN_ROOM_VERSIONS.keys()))) # Get the actual room version object rather than just the identifier self.default_room_version = KNOWN_ROOM_VERSIONS[default_room_version] # whether to enable search. If disabled, new entries will not be inserted # into the search tables and they will not be indexed. Users will receive # errors when attempting to search for messages. self.enable_search = config.get("enable_search", True) self.filter_timeline_limit = config.get("filter_timeline_limit", -1) # Whether we should block invites sent to users on this server # (other than those sent by local server admins) self.block_non_admin_invites = config.get("block_non_admin_invites", False) # Whether to enable experimental MSC1849 (aka relations) support self.experimental_msc1849_support_enabled = config.get( "experimental_msc1849_support_enabled", True) # Options to control access by tracking MAU self.limit_usage_by_mau = config.get("limit_usage_by_mau", False) self.max_mau_value = 0 if self.limit_usage_by_mau: self.max_mau_value = config.get("max_mau_value", 0) self.mau_stats_only = config.get("mau_stats_only", False) self.mau_limits_reserved_threepids = config.get( "mau_limit_reserved_threepids", []) self.mau_trial_days = config.get("mau_trial_days", 0) # Options to disable HS self.hs_disabled = config.get("hs_disabled", False) self.hs_disabled_message = config.get("hs_disabled_message", "") self.hs_disabled_limit_type = config.get("hs_disabled_limit_type", "") # Admin uri to direct users at should their instance become blocked # due to resource constraints self.admin_contact = config.get("admin_contact", None) # FIXME: federation_domain_whitelist needs sytests self.federation_domain_whitelist = None federation_domain_whitelist = config.get("federation_domain_whitelist", None) if federation_domain_whitelist is not None: # turn the whitelist into a hash for speed of lookup self.federation_domain_whitelist = {} for domain in federation_domain_whitelist: self.federation_domain_whitelist[domain] = True self.federation_ip_range_blacklist = config.get( "federation_ip_range_blacklist", []) # Attempt to create an IPSet from the given ranges try: self.federation_ip_range_blacklist = IPSet( self.federation_ip_range_blacklist) # Always blacklist 0.0.0.0, :: self.federation_ip_range_blacklist.update(["0.0.0.0", "::"]) except Exception as e: raise ConfigError("Invalid range(s) provided in " "federation_ip_range_blacklist: %s" % e) if self.public_baseurl is not None: if self.public_baseurl[-1] != "/": self.public_baseurl += "/" self.start_pushers = config.get("start_pushers", True) # (undocumented) option for torturing the worker-mode replication a bit, # for testing. The value defines the number of milliseconds to pause before # sending out any replication updates. self.replication_torture_level = config.get( "replication_torture_level") # Whether to require a user to be in the room to add an alias to it. # Defaults to True. self.require_membership_for_aliases = config.get( "require_membership_for_aliases", True) # Whether to allow per-room membership profiles through the send of membership # events with profile information that differ from the target's global profile. self.allow_per_room_profiles = config.get("allow_per_room_profiles", True) self.listeners = [] for listener in config.get("listeners", []): if not isinstance(listener.get("port", None), int): raise ConfigError( "Listener configuration is lacking a valid 'port' option") if listener.setdefault("tls", False): # no_tls is not really supported any more, but let's grandfather it in # here. if config.get("no_tls", False): logger.info( "Ignoring TLS-enabled listener on port %i due to no_tls" ) continue bind_address = listener.pop("bind_address", None) bind_addresses = listener.setdefault("bind_addresses", []) # if bind_address was specified, add it to the list of addresses if bind_address: bind_addresses.append(bind_address) # if we still have an empty list of addresses, use the default list if not bind_addresses: if listener["type"] == "metrics": # the metrics listener doesn't support IPv6 bind_addresses.append("0.0.0.0") else: bind_addresses.extend(DEFAULT_BIND_ADDRESSES) self.listeners.append(listener) if not self.web_client_location: _warn_if_webclient_configured(self.listeners) self.gc_thresholds = read_gc_thresholds( config.get("gc_thresholds", None)) bind_port = config.get("bind_port") if bind_port: if config.get("no_tls", False): raise ConfigError("no_tls is incompatible with bind_port") self.listeners = [] bind_host = config.get("bind_host", "") gzip_responses = config.get("gzip_responses", True) self.listeners.append({ "port": bind_port, "bind_addresses": [bind_host], "tls": True, "type": "http", "resources": [ { "names": ["client"], "compress": gzip_responses }, { "names": ["federation"], "compress": False }, ], }) unsecure_port = config.get("unsecure_port", bind_port - 400) if unsecure_port: self.listeners.append({ "port": unsecure_port, "bind_addresses": [bind_host], "tls": False, "type": "http", "resources": [ { "names": ["client"], "compress": gzip_responses }, { "names": ["federation"], "compress": False }, ], }) manhole = config.get("manhole") if manhole: self.listeners.append({ "port": manhole, "bind_addresses": ["127.0.0.1"], "type": "manhole", "tls": False, }) metrics_port = config.get("metrics_port") if metrics_port: logger.warn(( "The metrics_port configuration option is deprecated in Synapse 0.31 " "in favour of a listener. Please see " "http://github.com/matrix-org/synapse/blob/master/docs/metrics-howto.rst" " on how to configure the new listener.")) self.listeners.append({ "port": metrics_port, "bind_addresses": [config.get("metrics_bind_host", "127.0.0.1")], "tls": False, "type": "http", "resources": [{ "names": ["metrics"], "compress": False }], }) _check_resource_config(self.listeners) # An experimental option to try and periodically clean up extremities # by sending dummy events. self.cleanup_extremities_with_dummy_events = config.get( "cleanup_extremities_with_dummy_events", False)
def read_config(self, config, **kwargs): self.server_name = config["server_name"] self.server_context = config.get("server_context", None) try: parse_and_validate_server_name(self.server_name) except ValueError as e: raise ConfigError(str(e)) self.pid_file = self.abspath(config.get("pid_file")) self.web_client_location = config.get("web_client_location", None) self.soft_file_limit = config.get("soft_file_limit", 0) self.daemonize = config.get("daemonize") self.print_pidfile = config.get("print_pidfile") self.user_agent_suffix = config.get("user_agent_suffix") self.use_frozen_dicts = config.get("use_frozen_dicts", False) self.public_baseurl = config.get("public_baseurl") # Whether to send federation traffic out in this process. This only # applies to some federation traffic, and so shouldn't be used to # "disable" federation self.send_federation = config.get("send_federation", True) # Whether to enable user presence. self.use_presence = config.get("use_presence", True) # Whether to update the user directory or not. This should be set to # false only if we are updating the user directory in a worker self.update_user_directory = config.get("update_user_directory", True) # whether to enable the media repository endpoints. This should be set # to false if the media repository is running as a separate endpoint; # doing so ensures that we will not run cache cleanup jobs on the # master, potentially causing inconsistency. self.enable_media_repo = config.get("enable_media_repo", True) # Whether to require authentication to retrieve profile data (avatars, # display names) of other users through the client API. self.require_auth_for_profile_requests = config.get( "require_auth_for_profile_requests", False ) # Whether to require sharing a room with a user to retrieve their # profile data self.limit_profile_requests_to_users_who_share_rooms = config.get( "limit_profile_requests_to_users_who_share_rooms", False, ) if "restrict_public_rooms_to_local_users" in config and ( "allow_public_rooms_without_auth" in config or "allow_public_rooms_over_federation" in config ): raise ConfigError( "Can't use 'restrict_public_rooms_to_local_users' if" " 'allow_public_rooms_without_auth' and/or" " 'allow_public_rooms_over_federation' is set." ) # Check if the legacy "restrict_public_rooms_to_local_users" flag is set. This # flag is now obsolete but we need to check it for backward-compatibility. if config.get("restrict_public_rooms_to_local_users", False): self.allow_public_rooms_without_auth = False self.allow_public_rooms_over_federation = False else: # If set to 'true', removes the need for authentication to access the server's # public rooms directory through the client API, meaning that anyone can # query the room directory. Defaults to 'false'. self.allow_public_rooms_without_auth = config.get( "allow_public_rooms_without_auth", False ) # If set to 'true', allows any other homeserver to fetch the server's public # rooms directory via federation. Defaults to 'false'. self.allow_public_rooms_over_federation = config.get( "allow_public_rooms_over_federation", False ) default_room_version = config.get("default_room_version", DEFAULT_ROOM_VERSION) # Ensure room version is a str default_room_version = str(default_room_version) if default_room_version not in KNOWN_ROOM_VERSIONS: raise ConfigError( "Unknown default_room_version: %s, known room versions: %s" % (default_room_version, list(KNOWN_ROOM_VERSIONS.keys())) ) # Get the actual room version object rather than just the identifier self.default_room_version = KNOWN_ROOM_VERSIONS[default_room_version] # whether to enable search. If disabled, new entries will not be inserted # into the search tables and they will not be indexed. Users will receive # errors when attempting to search for messages. self.enable_search = config.get("enable_search", True) self.filter_timeline_limit = config.get("filter_timeline_limit", -1) # Whether we should block invites sent to users on this server # (other than those sent by local server admins) self.block_non_admin_invites = config.get("block_non_admin_invites", False) # Whether to enable experimental MSC1849 (aka relations) support self.experimental_msc1849_support_enabled = config.get( "experimental_msc1849_support_enabled", True ) # Options to control access by tracking MAU self.limit_usage_by_mau = config.get("limit_usage_by_mau", False) self.max_mau_value = 0 if self.limit_usage_by_mau: self.max_mau_value = config.get("max_mau_value", 0) self.mau_stats_only = config.get("mau_stats_only", False) self.mau_limits_reserved_threepids = config.get( "mau_limit_reserved_threepids", [] ) self.mau_trial_days = config.get("mau_trial_days", 0) self.mau_limit_alerting = config.get("mau_limit_alerting", True) # How long to keep redacted events in the database in unredacted form # before redacting them. redaction_retention_period = config.get("redaction_retention_period", "7d") if redaction_retention_period is not None: self.redaction_retention_period = self.parse_duration( redaction_retention_period ) else: self.redaction_retention_period = None # How long to keep entries in the `users_ips` table. user_ips_max_age = config.get("user_ips_max_age", "28d") if user_ips_max_age is not None: self.user_ips_max_age = self.parse_duration(user_ips_max_age) else: self.user_ips_max_age = None # Options to disable HS self.hs_disabled = config.get("hs_disabled", False) self.hs_disabled_message = config.get("hs_disabled_message", "") # Admin uri to direct users at should their instance become blocked # due to resource constraints self.admin_contact = config.get("admin_contact", None) # FIXME: federation_domain_whitelist needs sytests self.federation_domain_whitelist = None # type: Optional[dict] federation_domain_whitelist = config.get("federation_domain_whitelist", None) if federation_domain_whitelist is not None: # turn the whitelist into a hash for speed of lookup self.federation_domain_whitelist = {} for domain in federation_domain_whitelist: self.federation_domain_whitelist[domain] = True self.federation_ip_range_blacklist = config.get( "federation_ip_range_blacklist", [] ) # Attempt to create an IPSet from the given ranges try: self.federation_ip_range_blacklist = IPSet( self.federation_ip_range_blacklist ) # Always blacklist 0.0.0.0, :: self.federation_ip_range_blacklist.update(["0.0.0.0", "::"]) except Exception as e: raise ConfigError( "Invalid range(s) provided in federation_ip_range_blacklist: %s" % e ) if self.public_baseurl is not None: if self.public_baseurl[-1] != "/": self.public_baseurl += "/" self.start_pushers = config.get("start_pushers", True) # (undocumented) option for torturing the worker-mode replication a bit, # for testing. The value defines the number of milliseconds to pause before # sending out any replication updates. self.replication_torture_level = config.get("replication_torture_level") # Whether to require a user to be in the room to add an alias to it. # Defaults to True. self.require_membership_for_aliases = config.get( "require_membership_for_aliases", True ) # Whether to allow per-room membership profiles through the send of membership # events with profile information that differ from the target's global profile. self.allow_per_room_profiles = config.get("allow_per_room_profiles", True) retention_config = config.get("retention") if retention_config is None: retention_config = {} self.retention_enabled = retention_config.get("enabled", False) retention_default_policy = retention_config.get("default_policy") if retention_default_policy is not None: self.retention_default_min_lifetime = retention_default_policy.get( "min_lifetime" ) if self.retention_default_min_lifetime is not None: self.retention_default_min_lifetime = self.parse_duration( self.retention_default_min_lifetime ) self.retention_default_max_lifetime = retention_default_policy.get( "max_lifetime" ) if self.retention_default_max_lifetime is not None: self.retention_default_max_lifetime = self.parse_duration( self.retention_default_max_lifetime ) if ( self.retention_default_min_lifetime is not None and self.retention_default_max_lifetime is not None and ( self.retention_default_min_lifetime > self.retention_default_max_lifetime ) ): raise ConfigError( "The default retention policy's 'min_lifetime' can not be greater" " than its 'max_lifetime'" ) else: self.retention_default_min_lifetime = None self.retention_default_max_lifetime = None if self.retention_enabled: logger.info( "Message retention policies support enabled with the following default" " policy: min_lifetime = %s ; max_lifetime = %s", self.retention_default_min_lifetime, self.retention_default_max_lifetime, ) self.retention_allowed_lifetime_min = retention_config.get( "allowed_lifetime_min" ) if self.retention_allowed_lifetime_min is not None: self.retention_allowed_lifetime_min = self.parse_duration( self.retention_allowed_lifetime_min ) self.retention_allowed_lifetime_max = retention_config.get( "allowed_lifetime_max" ) if self.retention_allowed_lifetime_max is not None: self.retention_allowed_lifetime_max = self.parse_duration( self.retention_allowed_lifetime_max ) if ( self.retention_allowed_lifetime_min is not None and self.retention_allowed_lifetime_max is not None and self.retention_allowed_lifetime_min > self.retention_allowed_lifetime_max ): raise ConfigError( "Invalid retention policy limits: 'allowed_lifetime_min' can not be" " greater than 'allowed_lifetime_max'" ) self.retention_purge_jobs = [] # type: List[Dict[str, Optional[int]]] for purge_job_config in retention_config.get("purge_jobs", []): interval_config = purge_job_config.get("interval") if interval_config is None: raise ConfigError( "A retention policy's purge jobs configuration must have the" " 'interval' key set." ) interval = self.parse_duration(interval_config) shortest_max_lifetime = purge_job_config.get("shortest_max_lifetime") if shortest_max_lifetime is not None: shortest_max_lifetime = self.parse_duration(shortest_max_lifetime) longest_max_lifetime = purge_job_config.get("longest_max_lifetime") if longest_max_lifetime is not None: longest_max_lifetime = self.parse_duration(longest_max_lifetime) if ( shortest_max_lifetime is not None and longest_max_lifetime is not None and shortest_max_lifetime > longest_max_lifetime ): raise ConfigError( "A retention policy's purge jobs configuration's" " 'shortest_max_lifetime' value can not be greater than its" " 'longest_max_lifetime' value." ) self.retention_purge_jobs.append( { "interval": interval, "shortest_max_lifetime": shortest_max_lifetime, "longest_max_lifetime": longest_max_lifetime, } ) if not self.retention_purge_jobs: self.retention_purge_jobs = [ { "interval": self.parse_duration("1d"), "shortest_max_lifetime": None, "longest_max_lifetime": None, } ] self.listeners = [] # type: List[dict] for listener in config.get("listeners", []): if not isinstance(listener.get("port", None), int): raise ConfigError( "Listener configuration is lacking a valid 'port' option" ) if listener.setdefault("tls", False): # no_tls is not really supported any more, but let's grandfather it in # here. if config.get("no_tls", False): logger.info( "Ignoring TLS-enabled listener on port %i due to no_tls" ) continue bind_address = listener.pop("bind_address", None) bind_addresses = listener.setdefault("bind_addresses", []) # if bind_address was specified, add it to the list of addresses if bind_address: bind_addresses.append(bind_address) # if we still have an empty list of addresses, use the default list if not bind_addresses: if listener["type"] == "metrics": # the metrics listener doesn't support IPv6 bind_addresses.append("0.0.0.0") else: bind_addresses.extend(DEFAULT_BIND_ADDRESSES) self.listeners.append(listener) if not self.web_client_location: _warn_if_webclient_configured(self.listeners) self.gc_thresholds = read_gc_thresholds(config.get("gc_thresholds", None)) @attr.s class LimitRemoteRoomsConfig(object): enabled = attr.ib( validator=attr.validators.instance_of(bool), default=False ) complexity = attr.ib( validator=attr.validators.instance_of( (float, int) # type: ignore[arg-type] # noqa ), default=1.0, ) complexity_error = attr.ib( validator=attr.validators.instance_of(str), default=ROOM_COMPLEXITY_TOO_GREAT, ) self.limit_remote_rooms = LimitRemoteRoomsConfig( **config.get("limit_remote_rooms", {}) ) bind_port = config.get("bind_port") if bind_port: if config.get("no_tls", False): raise ConfigError("no_tls is incompatible with bind_port") self.listeners = [] bind_host = config.get("bind_host", "") gzip_responses = config.get("gzip_responses", True) self.listeners.append( { "port": bind_port, "bind_addresses": [bind_host], "tls": True, "type": "http", "resources": [ {"names": ["client"], "compress": gzip_responses}, {"names": ["federation"], "compress": False}, ], } ) unsecure_port = config.get("unsecure_port", bind_port - 400) if unsecure_port: self.listeners.append( { "port": unsecure_port, "bind_addresses": [bind_host], "tls": False, "type": "http", "resources": [ {"names": ["client"], "compress": gzip_responses}, {"names": ["federation"], "compress": False}, ], } ) manhole = config.get("manhole") if manhole: self.listeners.append( { "port": manhole, "bind_addresses": ["127.0.0.1"], "type": "manhole", "tls": False, } ) metrics_port = config.get("metrics_port") if metrics_port: logger.warning(METRICS_PORT_WARNING) self.listeners.append( { "port": metrics_port, "bind_addresses": [config.get("metrics_bind_host", "127.0.0.1")], "tls": False, "type": "http", "resources": [{"names": ["metrics"], "compress": False}], } ) _check_resource_config(self.listeners) self.cleanup_extremities_with_dummy_events = config.get( "cleanup_extremities_with_dummy_events", True ) # The number of forward extremities in a room needed to send a dummy event. self.dummy_events_threshold = config.get("dummy_events_threshold", 10) self.enable_ephemeral_messages = config.get("enable_ephemeral_messages", False) # Inhibits the /requestToken endpoints from returning an error that might leak # information about whether an e-mail address is in use or not on this # homeserver, and instead return a 200 with a fake sid if this kind of error is # met, without sending anything. # This is a compromise between sending an email, which could be a spam vector, # and letting the client know which email address is bound to an account and # which one isn't. self.request_token_inhibit_3pid_errors = config.get( "request_token_inhibit_3pid_errors", False, )