1# Copyright 2014-2021 The Matrix.org Foundation C.I.C. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import argparse 16import itertools 17import logging 18import os.path 19import re 20import urllib.parse 21from textwrap import indent 22from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union 23 24import attr 25import yaml 26from netaddr import AddrFormatError, IPNetwork, IPSet 27 28from twisted.conch.ssh.keys import Key 29 30from synapse.api.room_versions import KNOWN_ROOM_VERSIONS 31from synapse.types import JsonDict 32from synapse.util.module_loader import load_module 33from synapse.util.stringutils import parse_and_validate_server_name 34 35from ._base import Config, ConfigError 36from ._util import validate_config 37 38logger = logging.Logger(__name__) 39 40# by default, we attempt to listen on both '::' *and* '0.0.0.0' because some OSes 41# (Windows, macOS, other BSD/Linux where net.ipv6.bindv6only is set) will only listen 42# on IPv6 when '::' is set. 43# 44# We later check for errors when binding to 0.0.0.0 and ignore them if :: is also in 45# in the list. 46DEFAULT_BIND_ADDRESSES = ["::", "0.0.0.0"] 47 48 49def _6to4(network: IPNetwork) -> IPNetwork: 50 """Convert an IPv4 network into a 6to4 IPv6 network per RFC 3056.""" 51 52 # 6to4 networks consist of: 53 # * 2002 as the first 16 bits 54 # * The first IPv4 address in the network hex-encoded as the next 32 bits 55 # * The new prefix length needs to include the bits from the 2002 prefix. 56 hex_network = hex(network.first)[2:] 57 hex_network = ("0" * (8 - len(hex_network))) + hex_network 58 return IPNetwork( 59 "2002:%s:%s::/%d" 60 % ( 61 hex_network[:4], 62 hex_network[4:], 63 16 + network.prefixlen, 64 ) 65 ) 66 67 68def generate_ip_set( 69 ip_addresses: Optional[Iterable[str]], 70 extra_addresses: Optional[Iterable[str]] = None, 71 config_path: Optional[Iterable[str]] = None, 72) -> IPSet: 73 """ 74 Generate an IPSet from a list of IP addresses or CIDRs. 75 76 Additionally, for each IPv4 network in the list of IP addresses, also 77 includes the corresponding IPv6 networks. 78 79 This includes: 80 81 * IPv4-Compatible IPv6 Address (see RFC 4291, section 2.5.5.1) 82 * IPv4-Mapped IPv6 Address (see RFC 4291, section 2.5.5.2) 83 * 6to4 Address (see RFC 3056, section 2) 84 85 Args: 86 ip_addresses: An iterable of IP addresses or CIDRs. 87 extra_addresses: An iterable of IP addresses or CIDRs. 88 config_path: The path in the configuration for error messages. 89 90 Returns: 91 A new IP set. 92 """ 93 result = IPSet() 94 for ip in itertools.chain(ip_addresses or (), extra_addresses or ()): 95 try: 96 network = IPNetwork(ip) 97 except AddrFormatError as e: 98 raise ConfigError( 99 "Invalid IP range provided: %s." % (ip,), config_path 100 ) from e 101 result.add(network) 102 103 # It is possible that these already exist in the set, but that's OK. 104 if ":" not in str(network): 105 result.add(IPNetwork(network).ipv6(ipv4_compatible=True)) 106 result.add(IPNetwork(network).ipv6(ipv4_compatible=False)) 107 result.add(_6to4(network)) 108 109 return result 110 111 112# IP ranges that are considered private / unroutable / don't make sense. 113DEFAULT_IP_RANGE_BLACKLIST = [ 114 # Localhost 115 "127.0.0.0/8", 116 # Private networks. 117 "10.0.0.0/8", 118 "172.16.0.0/12", 119 "192.168.0.0/16", 120 # Carrier grade NAT. 121 "100.64.0.0/10", 122 # Address registry. 123 "192.0.0.0/24", 124 # Link-local networks. 125 "169.254.0.0/16", 126 # Formerly used for 6to4 relay. 127 "192.88.99.0/24", 128 # Testing networks. 129 "198.18.0.0/15", 130 "192.0.2.0/24", 131 "198.51.100.0/24", 132 "203.0.113.0/24", 133 # Multicast. 134 "224.0.0.0/4", 135 # Localhost 136 "::1/128", 137 # Link-local addresses. 138 "fe80::/10", 139 # Unique local addresses. 140 "fc00::/7", 141 # Testing networks. 142 "2001:db8::/32", 143 # Multicast. 144 "ff00::/8", 145 # Site-local addresses 146 "fec0::/10", 147] 148 149DEFAULT_ROOM_VERSION = "6" 150 151ROOM_COMPLEXITY_TOO_GREAT = ( 152 "Your homeserver is unable to join rooms this large or complex. " 153 "Please speak to your server administrator, or upgrade your instance " 154 "to join this room." 155) 156 157METRICS_PORT_WARNING = """\ 158The metrics_port configuration option is deprecated in Synapse 0.31 in favour of 159a listener. Please see 160https://matrix-org.github.io/synapse/latest/metrics-howto.html 161on how to configure the new listener. 162--------------------------------------------------------------------------------""" 163 164 165KNOWN_LISTENER_TYPES = { 166 "http", 167 "metrics", 168 "manhole", 169 "replication", 170} 171 172KNOWN_RESOURCES = { 173 "client", 174 "consent", 175 "federation", 176 "keys", 177 "media", 178 "metrics", 179 "openid", 180 "replication", 181 "static", 182 "webclient", 183} 184 185 186@attr.s(frozen=True) 187class HttpResourceConfig: 188 names: List[str] = attr.ib( 189 factory=list, 190 validator=attr.validators.deep_iterable(attr.validators.in_(KNOWN_RESOURCES)), # type: ignore 191 ) 192 compress: bool = attr.ib( 193 default=False, 194 validator=attr.validators.optional(attr.validators.instance_of(bool)), # type: ignore[arg-type] 195 ) 196 197 198@attr.s(slots=True, frozen=True, auto_attribs=True) 199class HttpListenerConfig: 200 """Object describing the http-specific parts of the config of a listener""" 201 202 x_forwarded: bool = False 203 resources: List[HttpResourceConfig] = attr.ib(factory=list) 204 additional_resources: Dict[str, dict] = attr.ib(factory=dict) 205 tag: Optional[str] = None 206 207 208@attr.s(slots=True, frozen=True, auto_attribs=True) 209class ListenerConfig: 210 """Object describing the configuration of a single listener.""" 211 212 port: int = attr.ib(validator=attr.validators.instance_of(int)) 213 bind_addresses: List[str] 214 type: str = attr.ib(validator=attr.validators.in_(KNOWN_LISTENER_TYPES)) 215 tls: bool = False 216 217 # http_options is only populated if type=http 218 http_options: Optional[HttpListenerConfig] = None 219 220 221@attr.s(slots=True, frozen=True, auto_attribs=True) 222class ManholeConfig: 223 """Object describing the configuration of the manhole""" 224 225 username: str = attr.ib(validator=attr.validators.instance_of(str)) 226 password: str = attr.ib(validator=attr.validators.instance_of(str)) 227 priv_key: Optional[Key] 228 pub_key: Optional[Key] 229 230 231@attr.s(frozen=True) 232class LimitRemoteRoomsConfig: 233 enabled: bool = attr.ib(validator=attr.validators.instance_of(bool), default=False) 234 complexity: Union[float, int] = attr.ib( 235 validator=attr.validators.instance_of( 236 (float, int) # type: ignore[arg-type] # noqa 237 ), 238 default=1.0, 239 ) 240 complexity_error: str = attr.ib( 241 validator=attr.validators.instance_of(str), 242 default=ROOM_COMPLEXITY_TOO_GREAT, 243 ) 244 admins_can_join: bool = attr.ib( 245 validator=attr.validators.instance_of(bool), default=False 246 ) 247 248 249class ServerConfig(Config): 250 section = "server" 251 252 def read_config(self, config, **kwargs): 253 self.server_name = config["server_name"] 254 self.server_context = config.get("server_context", None) 255 256 try: 257 parse_and_validate_server_name(self.server_name) 258 except ValueError as e: 259 raise ConfigError(str(e)) 260 261 self.pid_file = self.abspath(config.get("pid_file")) 262 self.web_client_location = config.get("web_client_location", None) 263 self.soft_file_limit = config.get("soft_file_limit", 0) 264 self.daemonize = config.get("daemonize") 265 self.print_pidfile = config.get("print_pidfile") 266 self.user_agent_suffix = config.get("user_agent_suffix") 267 self.use_frozen_dicts = config.get("use_frozen_dicts", False) 268 self.serve_server_wellknown = config.get("serve_server_wellknown", False) 269 270 # Whether we should serve a "client well-known": 271 # (a) at .well-known/matrix/client on our client HTTP listener 272 # (b) in the response to /login 273 # 274 # ... which together help ensure that clients use our public_baseurl instead of 275 # whatever they were told by the user. 276 # 277 # For the sake of backwards compatibility with existing installations, this is 278 # True if public_baseurl is specified explicitly, and otherwise False. (The 279 # reasoning here is that we have no way of knowing that the default 280 # public_baseurl is actually correct for existing installations - many things 281 # will not work correctly, but that's (probably?) better than sending clients 282 # to a completely broken URL. 283 self.serve_client_wellknown = False 284 285 public_baseurl = config.get("public_baseurl") 286 if public_baseurl is None: 287 public_baseurl = f"https://{self.server_name}/" 288 logger.info("Using default public_baseurl %s", public_baseurl) 289 else: 290 self.serve_client_wellknown = True 291 if public_baseurl[-1] != "/": 292 public_baseurl += "/" 293 self.public_baseurl = public_baseurl 294 295 # check that public_baseurl is valid 296 try: 297 splits = urllib.parse.urlsplit(self.public_baseurl) 298 except Exception as e: 299 raise ConfigError(f"Unable to parse URL: {e}", ("public_baseurl",)) 300 if splits.scheme not in ("https", "http"): 301 raise ConfigError( 302 f"Invalid scheme '{splits.scheme}': only https and http are supported" 303 ) 304 if splits.query or splits.fragment: 305 raise ConfigError( 306 "public_baseurl cannot contain query parameters or a #-fragment" 307 ) 308 309 # Whether to enable user presence. 310 presence_config = config.get("presence") or {} 311 self.use_presence = presence_config.get("enabled") 312 if self.use_presence is None: 313 self.use_presence = config.get("use_presence", True) 314 315 # Custom presence router module 316 # This is the legacy way of configuring it (the config should now be put in the modules section) 317 self.presence_router_module_class = None 318 self.presence_router_config = None 319 presence_router_config = presence_config.get("presence_router") 320 if presence_router_config: 321 ( 322 self.presence_router_module_class, 323 self.presence_router_config, 324 ) = load_module(presence_router_config, ("presence", "presence_router")) 325 326 # Whether to update the user directory or not. This should be set to 327 # false only if we are updating the user directory in a worker 328 self.update_user_directory = config.get("update_user_directory", True) 329 330 # whether to enable the media repository endpoints. This should be set 331 # to false if the media repository is running as a separate endpoint; 332 # doing so ensures that we will not run cache cleanup jobs on the 333 # master, potentially causing inconsistency. 334 self.enable_media_repo = config.get("enable_media_repo", True) 335 336 # Whether to require authentication to retrieve profile data (avatars, 337 # display names) of other users through the client API. 338 self.require_auth_for_profile_requests = config.get( 339 "require_auth_for_profile_requests", False 340 ) 341 342 # Whether to require sharing a room with a user to retrieve their 343 # profile data 344 self.limit_profile_requests_to_users_who_share_rooms = config.get( 345 "limit_profile_requests_to_users_who_share_rooms", 346 False, 347 ) 348 349 # Whether to retrieve and display profile data for a user when they 350 # are invited to a room 351 self.include_profile_data_on_invite = config.get( 352 "include_profile_data_on_invite", True 353 ) 354 355 if "restrict_public_rooms_to_local_users" in config and ( 356 "allow_public_rooms_without_auth" in config 357 or "allow_public_rooms_over_federation" in config 358 ): 359 raise ConfigError( 360 "Can't use 'restrict_public_rooms_to_local_users' if" 361 " 'allow_public_rooms_without_auth' and/or" 362 " 'allow_public_rooms_over_federation' is set." 363 ) 364 365 # Check if the legacy "restrict_public_rooms_to_local_users" flag is set. This 366 # flag is now obsolete but we need to check it for backward-compatibility. 367 if config.get("restrict_public_rooms_to_local_users", False): 368 self.allow_public_rooms_without_auth = False 369 self.allow_public_rooms_over_federation = False 370 else: 371 # If set to 'true', removes the need for authentication to access the server's 372 # public rooms directory through the client API, meaning that anyone can 373 # query the room directory. Defaults to 'false'. 374 self.allow_public_rooms_without_auth = config.get( 375 "allow_public_rooms_without_auth", False 376 ) 377 # If set to 'true', allows any other homeserver to fetch the server's public 378 # rooms directory via federation. Defaults to 'false'. 379 self.allow_public_rooms_over_federation = config.get( 380 "allow_public_rooms_over_federation", False 381 ) 382 383 default_room_version = config.get("default_room_version", DEFAULT_ROOM_VERSION) 384 385 # Ensure room version is a str 386 default_room_version = str(default_room_version) 387 388 if default_room_version not in KNOWN_ROOM_VERSIONS: 389 raise ConfigError( 390 "Unknown default_room_version: %s, known room versions: %s" 391 % (default_room_version, list(KNOWN_ROOM_VERSIONS.keys())) 392 ) 393 394 # Get the actual room version object rather than just the identifier 395 self.default_room_version = KNOWN_ROOM_VERSIONS[default_room_version] 396 397 # whether to enable search. If disabled, new entries will not be inserted 398 # into the search tables and they will not be indexed. Users will receive 399 # errors when attempting to search for messages. 400 self.enable_search = config.get("enable_search", True) 401 402 self.filter_timeline_limit = config.get("filter_timeline_limit", 100) 403 404 # Whether we should block invites sent to users on this server 405 # (other than those sent by local server admins) 406 self.block_non_admin_invites = config.get("block_non_admin_invites", False) 407 408 # Options to control access by tracking MAU 409 self.limit_usage_by_mau = config.get("limit_usage_by_mau", False) 410 self.max_mau_value = 0 411 if self.limit_usage_by_mau: 412 self.max_mau_value = config.get("max_mau_value", 0) 413 self.mau_stats_only = config.get("mau_stats_only", False) 414 415 self.mau_limits_reserved_threepids = config.get( 416 "mau_limit_reserved_threepids", [] 417 ) 418 419 self.mau_trial_days = config.get("mau_trial_days", 0) 420 self.mau_limit_alerting = config.get("mau_limit_alerting", True) 421 422 # How long to keep redacted events in the database in unredacted form 423 # before redacting them. 424 redaction_retention_period = config.get("redaction_retention_period", "7d") 425 if redaction_retention_period is not None: 426 self.redaction_retention_period: Optional[int] = self.parse_duration( 427 redaction_retention_period 428 ) 429 else: 430 self.redaction_retention_period = None 431 432 # How long to keep entries in the `users_ips` table. 433 user_ips_max_age = config.get("user_ips_max_age", "28d") 434 if user_ips_max_age is not None: 435 self.user_ips_max_age: Optional[int] = self.parse_duration(user_ips_max_age) 436 else: 437 self.user_ips_max_age = None 438 439 # Options to disable HS 440 self.hs_disabled = config.get("hs_disabled", False) 441 self.hs_disabled_message = config.get("hs_disabled_message", "") 442 443 # Admin uri to direct users at should their instance become blocked 444 # due to resource constraints 445 self.admin_contact = config.get("admin_contact", None) 446 447 ip_range_blacklist = config.get( 448 "ip_range_blacklist", DEFAULT_IP_RANGE_BLACKLIST 449 ) 450 451 # Attempt to create an IPSet from the given ranges 452 453 # Always blacklist 0.0.0.0, :: 454 self.ip_range_blacklist = generate_ip_set( 455 ip_range_blacklist, ["0.0.0.0", "::"], config_path=("ip_range_blacklist",) 456 ) 457 458 self.ip_range_whitelist = generate_ip_set( 459 config.get("ip_range_whitelist", ()), config_path=("ip_range_whitelist",) 460 ) 461 # The federation_ip_range_blacklist is used for backwards-compatibility 462 # and only applies to federation and identity servers. 463 if "federation_ip_range_blacklist" in config: 464 # Always blacklist 0.0.0.0, :: 465 self.federation_ip_range_blacklist = generate_ip_set( 466 config["federation_ip_range_blacklist"], 467 ["0.0.0.0", "::"], 468 config_path=("federation_ip_range_blacklist",), 469 ) 470 # 'federation_ip_range_whitelist' was never a supported configuration option. 471 self.federation_ip_range_whitelist = None 472 else: 473 # No backwards-compatiblity requrired, as federation_ip_range_blacklist 474 # is not given. Default to ip_range_blacklist and ip_range_whitelist. 475 self.federation_ip_range_blacklist = self.ip_range_blacklist 476 self.federation_ip_range_whitelist = self.ip_range_whitelist 477 478 # (undocumented) option for torturing the worker-mode replication a bit, 479 # for testing. The value defines the number of milliseconds to pause before 480 # sending out any replication updates. 481 self.replication_torture_level = config.get("replication_torture_level") 482 483 # Whether to require a user to be in the room to add an alias to it. 484 # Defaults to True. 485 self.require_membership_for_aliases = config.get( 486 "require_membership_for_aliases", True 487 ) 488 489 # Whether to allow per-room membership profiles through the send of membership 490 # events with profile information that differ from the target's global profile. 491 self.allow_per_room_profiles = config.get("allow_per_room_profiles", True) 492 493 self.listeners = [parse_listener_def(x) for x in config.get("listeners", [])] 494 495 # no_tls is not really supported any more, but let's grandfather it in 496 # here. 497 if config.get("no_tls", False): 498 l2 = [] 499 for listener in self.listeners: 500 if listener.tls: 501 logger.info( 502 "Ignoring TLS-enabled listener on port %i due to no_tls", 503 listener.port, 504 ) 505 else: 506 l2.append(listener) 507 self.listeners = l2 508 509 if not self.web_client_location: 510 _warn_if_webclient_configured(self.listeners) 511 512 self.gc_thresholds = read_gc_thresholds(config.get("gc_thresholds", None)) 513 self.gc_seconds = self.read_gc_intervals(config.get("gc_min_interval", None)) 514 515 self.limit_remote_rooms = LimitRemoteRoomsConfig( 516 **(config.get("limit_remote_rooms") or {}) 517 ) 518 519 bind_port = config.get("bind_port") 520 if bind_port: 521 if config.get("no_tls", False): 522 raise ConfigError("no_tls is incompatible with bind_port") 523 524 self.listeners = [] 525 bind_host = config.get("bind_host", "") 526 gzip_responses = config.get("gzip_responses", True) 527 528 http_options = HttpListenerConfig( 529 resources=[ 530 HttpResourceConfig(names=["client"], compress=gzip_responses), 531 HttpResourceConfig(names=["federation"]), 532 ], 533 ) 534 535 self.listeners.append( 536 ListenerConfig( 537 port=bind_port, 538 bind_addresses=[bind_host], 539 tls=True, 540 type="http", 541 http_options=http_options, 542 ) 543 ) 544 545 unsecure_port = config.get("unsecure_port", bind_port - 400) 546 if unsecure_port: 547 self.listeners.append( 548 ListenerConfig( 549 port=unsecure_port, 550 bind_addresses=[bind_host], 551 tls=False, 552 type="http", 553 http_options=http_options, 554 ) 555 ) 556 557 manhole = config.get("manhole") 558 if manhole: 559 self.listeners.append( 560 ListenerConfig( 561 port=manhole, 562 bind_addresses=["127.0.0.1"], 563 type="manhole", 564 ) 565 ) 566 567 manhole_settings = config.get("manhole_settings") or {} 568 validate_config( 569 _MANHOLE_SETTINGS_SCHEMA, manhole_settings, ("manhole_settings",) 570 ) 571 572 manhole_username = manhole_settings.get("username", "matrix") 573 manhole_password = manhole_settings.get("password", "rabbithole") 574 manhole_priv_key_path = manhole_settings.get("ssh_priv_key_path") 575 manhole_pub_key_path = manhole_settings.get("ssh_pub_key_path") 576 577 manhole_priv_key = None 578 if manhole_priv_key_path is not None: 579 try: 580 manhole_priv_key = Key.fromFile(manhole_priv_key_path) 581 except Exception as e: 582 raise ConfigError( 583 f"Failed to read manhole private key file {manhole_priv_key_path}" 584 ) from e 585 586 manhole_pub_key = None 587 if manhole_pub_key_path is not None: 588 try: 589 manhole_pub_key = Key.fromFile(manhole_pub_key_path) 590 except Exception as e: 591 raise ConfigError( 592 f"Failed to read manhole public key file {manhole_pub_key_path}" 593 ) from e 594 595 self.manhole_settings = ManholeConfig( 596 username=manhole_username, 597 password=manhole_password, 598 priv_key=manhole_priv_key, 599 pub_key=manhole_pub_key, 600 ) 601 602 metrics_port = config.get("metrics_port") 603 if metrics_port: 604 logger.warning(METRICS_PORT_WARNING) 605 606 self.listeners.append( 607 ListenerConfig( 608 port=metrics_port, 609 bind_addresses=[config.get("metrics_bind_host", "127.0.0.1")], 610 type="http", 611 http_options=HttpListenerConfig( 612 resources=[HttpResourceConfig(names=["metrics"])] 613 ), 614 ) 615 ) 616 617 self.cleanup_extremities_with_dummy_events = config.get( 618 "cleanup_extremities_with_dummy_events", True 619 ) 620 621 # The number of forward extremities in a room needed to send a dummy event. 622 self.dummy_events_threshold = config.get("dummy_events_threshold", 10) 623 624 self.enable_ephemeral_messages = config.get("enable_ephemeral_messages", False) 625 626 # Inhibits the /requestToken endpoints from returning an error that might leak 627 # information about whether an e-mail address is in use or not on this 628 # homeserver, and instead return a 200 with a fake sid if this kind of error is 629 # met, without sending anything. 630 # This is a compromise between sending an email, which could be a spam vector, 631 # and letting the client know which email address is bound to an account and 632 # which one isn't. 633 self.request_token_inhibit_3pid_errors = config.get( 634 "request_token_inhibit_3pid_errors", 635 False, 636 ) 637 638 # List of users trialing the new experimental default push rules. This setting is 639 # not included in the sample configuration file on purpose as it's a temporary 640 # hack, so that some users can trial the new defaults without impacting every 641 # user on the homeserver. 642 users_new_default_push_rules: list = ( 643 config.get("users_new_default_push_rules") or [] 644 ) 645 if not isinstance(users_new_default_push_rules, list): 646 raise ConfigError("'users_new_default_push_rules' must be a list") 647 648 # Turn the list into a set to improve lookup speed. 649 self.users_new_default_push_rules: set = set(users_new_default_push_rules) 650 651 # Whitelist of domain names that given next_link parameters must have 652 next_link_domain_whitelist: Optional[List[str]] = config.get( 653 "next_link_domain_whitelist" 654 ) 655 656 self.next_link_domain_whitelist: Optional[Set[str]] = None 657 if next_link_domain_whitelist is not None: 658 if not isinstance(next_link_domain_whitelist, list): 659 raise ConfigError("'next_link_domain_whitelist' must be a list") 660 661 # Turn the list into a set to improve lookup speed. 662 self.next_link_domain_whitelist = set(next_link_domain_whitelist) 663 664 templates_config = config.get("templates") or {} 665 if not isinstance(templates_config, dict): 666 raise ConfigError("The 'templates' section must be a dictionary") 667 668 self.custom_template_directory: Optional[str] = templates_config.get( 669 "custom_template_directory" 670 ) 671 if self.custom_template_directory is not None and not isinstance( 672 self.custom_template_directory, str 673 ): 674 raise ConfigError("'custom_template_directory' must be a string") 675 676 def has_tls_listener(self) -> bool: 677 return any(listener.tls for listener in self.listeners) 678 679 def generate_config_section( 680 self, 681 server_name, 682 data_dir_path, 683 open_private_ports, 684 listeners, 685 config_dir_path, 686 **kwargs, 687 ): 688 ip_range_blacklist = "\n".join( 689 " # - '%s'" % ip for ip in DEFAULT_IP_RANGE_BLACKLIST 690 ) 691 692 _, bind_port = parse_and_validate_server_name(server_name) 693 if bind_port is not None: 694 unsecure_port = bind_port - 400 695 else: 696 bind_port = 8448 697 unsecure_port = 8008 698 699 pid_file = os.path.join(data_dir_path, "homeserver.pid") 700 701 # Bring DEFAULT_ROOM_VERSION into the local-scope for use in the 702 # default config string 703 default_room_version = DEFAULT_ROOM_VERSION 704 secure_listeners = [] 705 unsecure_listeners = [] 706 private_addresses = ["::1", "127.0.0.1"] 707 if listeners: 708 for listener in listeners: 709 if listener["tls"]: 710 secure_listeners.append(listener) 711 else: 712 # If we don't want open ports we need to bind the listeners 713 # to some address other than 0.0.0.0. Here we chose to use 714 # localhost. 715 # If the addresses are already bound we won't overwrite them 716 # however. 717 if not open_private_ports: 718 listener.setdefault("bind_addresses", private_addresses) 719 720 unsecure_listeners.append(listener) 721 722 secure_http_bindings = indent( 723 yaml.dump(secure_listeners), " " * 10 724 ).lstrip() 725 726 unsecure_http_bindings = indent( 727 yaml.dump(unsecure_listeners), " " * 10 728 ).lstrip() 729 730 if not unsecure_listeners: 731 unsecure_http_bindings = ( 732 """- port: %(unsecure_port)s 733 tls: false 734 type: http 735 x_forwarded: true""" 736 % locals() 737 ) 738 739 if not open_private_ports: 740 unsecure_http_bindings += ( 741 "\n bind_addresses: ['::1', '127.0.0.1']" 742 ) 743 744 unsecure_http_bindings += """ 745 746 resources: 747 - names: [client, federation] 748 compress: false""" 749 750 if listeners: 751 # comment out this block 752 unsecure_http_bindings = "#" + re.sub( 753 "\n {10}", 754 lambda match: match.group(0) + "#", 755 unsecure_http_bindings, 756 ) 757 758 if not secure_listeners: 759 secure_http_bindings = ( 760 """#- port: %(bind_port)s 761 # type: http 762 # tls: true 763 # resources: 764 # - names: [client, federation]""" 765 % locals() 766 ) 767 768 return ( 769 """\ 770 ## Server ## 771 772 # The public-facing domain of the server 773 # 774 # The server_name name will appear at the end of usernames and room addresses 775 # created on this server. For example if the server_name was example.com, 776 # usernames on this server would be in the format @user:example.com 777 # 778 # In most cases you should avoid using a matrix specific subdomain such as 779 # matrix.example.com or synapse.example.com as the server_name for the same 780 # reasons you wouldn't use user@email.example.com as your email address. 781 # See https://matrix-org.github.io/synapse/latest/delegate.html 782 # for information on how to host Synapse on a subdomain while preserving 783 # a clean server_name. 784 # 785 # The server_name cannot be changed later so it is important to 786 # configure this correctly before you start Synapse. It should be all 787 # lowercase and may contain an explicit port. 788 # Examples: matrix.org, localhost:8080 789 # 790 server_name: "%(server_name)s" 791 792 # When running as a daemon, the file to store the pid in 793 # 794 pid_file: %(pid_file)s 795 796 # The absolute URL to the web client which /_matrix/client will redirect 797 # to if 'webclient' is configured under the 'listeners' configuration. 798 # 799 # This option can be also set to the filesystem path to the web client 800 # which will be served at /_matrix/client/ if 'webclient' is configured 801 # under the 'listeners' configuration, however this is a security risk: 802 # https://github.com/matrix-org/synapse#security-note 803 # 804 #web_client_location: https://riot.example.com/ 805 806 # The public-facing base URL that clients use to access this Homeserver (not 807 # including _matrix/...). This is the same URL a user might enter into the 808 # 'Custom Homeserver URL' field on their client. If you use Synapse with a 809 # reverse proxy, this should be the URL to reach Synapse via the proxy. 810 # Otherwise, it should be the URL to reach Synapse's client HTTP listener (see 811 # 'listeners' below). 812 # 813 # Defaults to 'https://<server_name>/'. 814 # 815 #public_baseurl: https://example.com/ 816 817 # Uncomment the following to tell other servers to send federation traffic on 818 # port 443. 819 # 820 # By default, other servers will try to reach our server on port 8448, which can 821 # be inconvenient in some environments. 822 # 823 # Provided 'https://<server_name>/' on port 443 is routed to Synapse, this 824 # option configures Synapse to serve a file at 825 # 'https://<server_name>/.well-known/matrix/server'. This will tell other 826 # servers to send traffic to port 443 instead. 827 # 828 # See https://matrix-org.github.io/synapse/latest/delegate.html for more 829 # information. 830 # 831 # Defaults to 'false'. 832 # 833 #serve_server_wellknown: true 834 835 # Set the soft limit on the number of file descriptors synapse can use 836 # Zero is used to indicate synapse should set the soft limit to the 837 # hard limit. 838 # 839 #soft_file_limit: 0 840 841 # Presence tracking allows users to see the state (e.g online/offline) 842 # of other local and remote users. 843 # 844 presence: 845 # Uncomment to disable presence tracking on this homeserver. This option 846 # replaces the previous top-level 'use_presence' option. 847 # 848 #enabled: false 849 850 # Whether to require authentication to retrieve profile data (avatars, 851 # display names) of other users through the client API. Defaults to 852 # 'false'. Note that profile data is also available via the federation 853 # API, unless allow_profile_lookup_over_federation is set to false. 854 # 855 #require_auth_for_profile_requests: true 856 857 # Uncomment to require a user to share a room with another user in order 858 # to retrieve their profile information. Only checked on Client-Server 859 # requests. Profile requests from other servers should be checked by the 860 # requesting server. Defaults to 'false'. 861 # 862 #limit_profile_requests_to_users_who_share_rooms: true 863 864 # Uncomment to prevent a user's profile data from being retrieved and 865 # displayed in a room until they have joined it. By default, a user's 866 # profile data is included in an invite event, regardless of the values 867 # of the above two settings, and whether or not the users share a server. 868 # Defaults to 'true'. 869 # 870 #include_profile_data_on_invite: false 871 872 # If set to 'true', removes the need for authentication to access the server's 873 # public rooms directory through the client API, meaning that anyone can 874 # query the room directory. Defaults to 'false'. 875 # 876 #allow_public_rooms_without_auth: true 877 878 # If set to 'true', allows any other homeserver to fetch the server's public 879 # rooms directory via federation. Defaults to 'false'. 880 # 881 #allow_public_rooms_over_federation: true 882 883 # The default room version for newly created rooms. 884 # 885 # Known room versions are listed here: 886 # https://matrix.org/docs/spec/#complete-list-of-room-versions 887 # 888 # For example, for room version 1, default_room_version should be set 889 # to "1". 890 # 891 #default_room_version: "%(default_room_version)s" 892 893 # The GC threshold parameters to pass to `gc.set_threshold`, if defined 894 # 895 #gc_thresholds: [700, 10, 10] 896 897 # The minimum time in seconds between each GC for a generation, regardless of 898 # the GC thresholds. This ensures that we don't do GC too frequently. 899 # 900 # A value of `[1s, 10s, 30s]` indicates that a second must pass between consecutive 901 # generation 0 GCs, etc. 902 # 903 # Defaults to `[1s, 10s, 30s]`. 904 # 905 #gc_min_interval: [0.5s, 30s, 1m] 906 907 # Set the limit on the returned events in the timeline in the get 908 # and sync operations. The default value is 100. -1 means no upper limit. 909 # 910 # Uncomment the following to increase the limit to 5000. 911 # 912 #filter_timeline_limit: 5000 913 914 # Whether room invites to users on this server should be blocked 915 # (except those sent by local server admins). The default is False. 916 # 917 #block_non_admin_invites: true 918 919 # Room searching 920 # 921 # If disabled, new messages will not be indexed for searching and users 922 # will receive errors when searching for messages. Defaults to enabled. 923 # 924 #enable_search: false 925 926 # Prevent outgoing requests from being sent to the following blacklisted IP address 927 # CIDR ranges. If this option is not specified then it defaults to private IP 928 # address ranges (see the example below). 929 # 930 # The blacklist applies to the outbound requests for federation, identity servers, 931 # push servers, and for checking key validity for third-party invite events. 932 # 933 # (0.0.0.0 and :: are always blacklisted, whether or not they are explicitly 934 # listed here, since they correspond to unroutable addresses.) 935 # 936 # This option replaces federation_ip_range_blacklist in Synapse v1.25.0. 937 # 938 # Note: The value is ignored when an HTTP proxy is in use 939 # 940 #ip_range_blacklist: 941%(ip_range_blacklist)s 942 943 # List of IP address CIDR ranges that should be allowed for federation, 944 # identity servers, push servers, and for checking key validity for 945 # third-party invite events. This is useful for specifying exceptions to 946 # wide-ranging blacklisted target IP ranges - e.g. for communication with 947 # a push server only visible in your network. 948 # 949 # This whitelist overrides ip_range_blacklist and defaults to an empty 950 # list. 951 # 952 #ip_range_whitelist: 953 # - '192.168.1.1' 954 955 # List of ports that Synapse should listen on, their purpose and their 956 # configuration. 957 # 958 # Options for each listener include: 959 # 960 # port: the TCP port to bind to 961 # 962 # bind_addresses: a list of local addresses to listen on. The default is 963 # 'all local interfaces'. 964 # 965 # type: the type of listener. Normally 'http', but other valid options are: 966 # 'manhole' (see https://matrix-org.github.io/synapse/latest/manhole.html), 967 # 'metrics' (see https://matrix-org.github.io/synapse/latest/metrics-howto.html), 968 # 'replication' (see https://matrix-org.github.io/synapse/latest/workers.html). 969 # 970 # tls: set to true to enable TLS for this listener. Will use the TLS 971 # key/cert specified in tls_private_key_path / tls_certificate_path. 972 # 973 # x_forwarded: Only valid for an 'http' listener. Set to true to use the 974 # X-Forwarded-For header as the client IP. Useful when Synapse is 975 # behind a reverse-proxy. 976 # 977 # resources: Only valid for an 'http' listener. A list of resources to host 978 # on this port. Options for each resource are: 979 # 980 # names: a list of names of HTTP resources. See below for a list of 981 # valid resource names. 982 # 983 # compress: set to true to enable HTTP compression for this resource. 984 # 985 # additional_resources: Only valid for an 'http' listener. A map of 986 # additional endpoints which should be loaded via dynamic modules. 987 # 988 # Valid resource names are: 989 # 990 # client: the client-server API (/_matrix/client), and the synapse admin 991 # API (/_synapse/admin). Also implies 'media' and 'static'. 992 # 993 # consent: user consent forms (/_matrix/consent). 994 # See https://matrix-org.github.io/synapse/latest/consent_tracking.html. 995 # 996 # federation: the server-server API (/_matrix/federation). Also implies 997 # 'media', 'keys', 'openid' 998 # 999 # keys: the key discovery API (/_matrix/keys). 1000 # 1001 # media: the media API (/_matrix/media). 1002 # 1003 # metrics: the metrics interface. 1004 # See https://matrix-org.github.io/synapse/latest/metrics-howto.html. 1005 # 1006 # openid: OpenID authentication. 1007 # 1008 # replication: the HTTP replication API (/_synapse/replication). 1009 # See https://matrix-org.github.io/synapse/latest/workers.html. 1010 # 1011 # static: static resources under synapse/static (/_matrix/static). (Mostly 1012 # useful for 'fallback authentication'.) 1013 # 1014 # webclient: A web client. Requires web_client_location to be set. 1015 # 1016 listeners: 1017 # TLS-enabled listener: for when matrix traffic is sent directly to synapse. 1018 # 1019 # Disabled by default. To enable it, uncomment the following. (Note that you 1020 # will also need to give Synapse a TLS key and certificate: see the TLS section 1021 # below.) 1022 # 1023 %(secure_http_bindings)s 1024 1025 # Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy 1026 # that unwraps TLS. 1027 # 1028 # If you plan to use a reverse proxy, please see 1029 # https://matrix-org.github.io/synapse/latest/reverse_proxy.html. 1030 # 1031 %(unsecure_http_bindings)s 1032 1033 # example additional_resources: 1034 # 1035 #additional_resources: 1036 # "/_matrix/my/custom/endpoint": 1037 # module: my_module.CustomRequestHandler 1038 # config: {} 1039 1040 # Turn on the twisted ssh manhole service on localhost on the given 1041 # port. 1042 # 1043 #- port: 9000 1044 # bind_addresses: ['::1', '127.0.0.1'] 1045 # type: manhole 1046 1047 # Connection settings for the manhole 1048 # 1049 manhole_settings: 1050 # The username for the manhole. This defaults to 'matrix'. 1051 # 1052 #username: manhole 1053 1054 # The password for the manhole. This defaults to 'rabbithole'. 1055 # 1056 #password: mypassword 1057 1058 # The private and public SSH key pair used to encrypt the manhole traffic. 1059 # If these are left unset, then hardcoded and non-secret keys are used, 1060 # which could allow traffic to be intercepted if sent over a public network. 1061 # 1062 #ssh_priv_key_path: %(config_dir_path)s/id_rsa 1063 #ssh_pub_key_path: %(config_dir_path)s/id_rsa.pub 1064 1065 # Forward extremities can build up in a room due to networking delays between 1066 # homeservers. Once this happens in a large room, calculation of the state of 1067 # that room can become quite expensive. To mitigate this, once the number of 1068 # forward extremities reaches a given threshold, Synapse will send an 1069 # org.matrix.dummy_event event, which will reduce the forward extremities 1070 # in the room. 1071 # 1072 # This setting defines the threshold (i.e. number of forward extremities in the 1073 # room) at which dummy events are sent. The default value is 10. 1074 # 1075 #dummy_events_threshold: 5 1076 1077 1078 ## Homeserver blocking ## 1079 1080 # How to reach the server admin, used in ResourceLimitError 1081 # 1082 #admin_contact: 'mailto:admin@server.com' 1083 1084 # Global blocking 1085 # 1086 #hs_disabled: false 1087 #hs_disabled_message: 'Human readable reason for why the HS is blocked' 1088 1089 # Monthly Active User Blocking 1090 # 1091 # Used in cases where the admin or server owner wants to limit to the 1092 # number of monthly active users. 1093 # 1094 # 'limit_usage_by_mau' disables/enables monthly active user blocking. When 1095 # enabled and a limit is reached the server returns a 'ResourceLimitError' 1096 # with error type Codes.RESOURCE_LIMIT_EXCEEDED 1097 # 1098 # 'max_mau_value' is the hard limit of monthly active users above which 1099 # the server will start blocking user actions. 1100 # 1101 # 'mau_trial_days' is a means to add a grace period for active users. It 1102 # means that users must be active for this number of days before they 1103 # can be considered active and guards against the case where lots of users 1104 # sign up in a short space of time never to return after their initial 1105 # session. 1106 # 1107 # 'mau_limit_alerting' is a means of limiting client side alerting 1108 # should the mau limit be reached. This is useful for small instances 1109 # where the admin has 5 mau seats (say) for 5 specific people and no 1110 # interest increasing the mau limit further. Defaults to True, which 1111 # means that alerting is enabled 1112 # 1113 #limit_usage_by_mau: false 1114 #max_mau_value: 50 1115 #mau_trial_days: 2 1116 #mau_limit_alerting: false 1117 1118 # If enabled, the metrics for the number of monthly active users will 1119 # be populated, however no one will be limited. If limit_usage_by_mau 1120 # is true, this is implied to be true. 1121 # 1122 #mau_stats_only: false 1123 1124 # Sometimes the server admin will want to ensure certain accounts are 1125 # never blocked by mau checking. These accounts are specified here. 1126 # 1127 #mau_limit_reserved_threepids: 1128 # - medium: 'email' 1129 # address: 'reserved_user@example.com' 1130 1131 # Used by phonehome stats to group together related servers. 1132 #server_context: context 1133 1134 # Resource-constrained homeserver settings 1135 # 1136 # When this is enabled, the room "complexity" will be checked before a user 1137 # joins a new remote room. If it is above the complexity limit, the server will 1138 # disallow joining, or will instantly leave. 1139 # 1140 # Room complexity is an arbitrary measure based on factors such as the number of 1141 # users in the room. 1142 # 1143 limit_remote_rooms: 1144 # Uncomment to enable room complexity checking. 1145 # 1146 #enabled: true 1147 1148 # the limit above which rooms cannot be joined. The default is 1.0. 1149 # 1150 #complexity: 0.5 1151 1152 # override the error which is returned when the room is too complex. 1153 # 1154 #complexity_error: "This room is too complex." 1155 1156 # allow server admins to join complex rooms. Default is false. 1157 # 1158 #admins_can_join: true 1159 1160 # Whether to require a user to be in the room to add an alias to it. 1161 # Defaults to 'true'. 1162 # 1163 #require_membership_for_aliases: false 1164 1165 # Whether to allow per-room membership profiles through the send of membership 1166 # events with profile information that differ from the target's global profile. 1167 # Defaults to 'true'. 1168 # 1169 #allow_per_room_profiles: false 1170 1171 # How long to keep redacted events in unredacted form in the database. After 1172 # this period redacted events get replaced with their redacted form in the DB. 1173 # 1174 # Defaults to `7d`. Set to `null` to disable. 1175 # 1176 #redaction_retention_period: 28d 1177 1178 # How long to track users' last seen time and IPs in the database. 1179 # 1180 # Defaults to `28d`. Set to `null` to disable clearing out of old rows. 1181 # 1182 #user_ips_max_age: 14d 1183 1184 # Inhibits the /requestToken endpoints from returning an error that might leak 1185 # information about whether an e-mail address is in use or not on this 1186 # homeserver. 1187 # Note that for some endpoints the error situation is the e-mail already being 1188 # used, and for others the error is entering the e-mail being unused. 1189 # If this option is enabled, instead of returning an error, these endpoints will 1190 # act as if no error happened and return a fake session ID ('sid') to clients. 1191 # 1192 #request_token_inhibit_3pid_errors: true 1193 1194 # A list of domains that the domain portion of 'next_link' parameters 1195 # must match. 1196 # 1197 # This parameter is optionally provided by clients while requesting 1198 # validation of an email or phone number, and maps to a link that 1199 # users will be automatically redirected to after validation 1200 # succeeds. Clients can make use this parameter to aid the validation 1201 # process. 1202 # 1203 # The whitelist is applied whether the homeserver or an 1204 # identity server is handling validation. 1205 # 1206 # The default value is no whitelist functionality; all domains are 1207 # allowed. Setting this value to an empty list will instead disallow 1208 # all domains. 1209 # 1210 #next_link_domain_whitelist: ["matrix.org"] 1211 1212 # Templates to use when generating email or HTML page contents. 1213 # 1214 templates: 1215 # Directory in which Synapse will try to find template files to use to generate 1216 # email or HTML page contents. 1217 # If not set, or a file is not found within the template directory, a default 1218 # template from within the Synapse package will be used. 1219 # 1220 # See https://matrix-org.github.io/synapse/latest/templates.html for more 1221 # information about using custom templates. 1222 # 1223 #custom_template_directory: /path/to/custom/templates/ 1224 """ 1225 % locals() 1226 ) 1227 1228 def read_arguments(self, args: argparse.Namespace) -> None: 1229 if args.manhole is not None: 1230 self.manhole = args.manhole 1231 if args.daemonize is not None: 1232 self.daemonize = args.daemonize 1233 if args.print_pidfile is not None: 1234 self.print_pidfile = args.print_pidfile 1235 1236 @staticmethod 1237 def add_arguments(parser: argparse.ArgumentParser) -> None: 1238 server_group = parser.add_argument_group("server") 1239 server_group.add_argument( 1240 "-D", 1241 "--daemonize", 1242 action="store_true", 1243 default=None, 1244 help="Daemonize the homeserver", 1245 ) 1246 server_group.add_argument( 1247 "--print-pidfile", 1248 action="store_true", 1249 default=None, 1250 help="Print the path to the pidfile just before daemonizing", 1251 ) 1252 server_group.add_argument( 1253 "--manhole", 1254 metavar="PORT", 1255 dest="manhole", 1256 type=int, 1257 help="Turn on the twisted telnet manhole service on the given port.", 1258 ) 1259 1260 def read_gc_intervals(self, durations: Any) -> Optional[Tuple[float, float, float]]: 1261 """Reads the three durations for the GC min interval option, returning seconds.""" 1262 if durations is None: 1263 return None 1264 1265 try: 1266 if len(durations) != 3: 1267 raise ValueError() 1268 return ( 1269 self.parse_duration(durations[0]) / 1000, 1270 self.parse_duration(durations[1]) / 1000, 1271 self.parse_duration(durations[2]) / 1000, 1272 ) 1273 except Exception: 1274 raise ConfigError( 1275 "Value of `gc_min_interval` must be a list of three durations if set" 1276 ) 1277 1278 1279def is_threepid_reserved( 1280 reserved_threepids: List[JsonDict], threepid: JsonDict 1281) -> bool: 1282 """Check the threepid against the reserved threepid config 1283 Args: 1284 reserved_threepids: List of reserved threepids 1285 threepid: The threepid to test for 1286 1287 Returns: 1288 Is the threepid undertest reserved_user 1289 """ 1290 1291 for tp in reserved_threepids: 1292 if threepid["medium"] == tp["medium"] and threepid["address"] == tp["address"]: 1293 return True 1294 return False 1295 1296 1297def read_gc_thresholds( 1298 thresholds: Optional[List[Any]], 1299) -> Optional[Tuple[int, int, int]]: 1300 """Reads the three integer thresholds for garbage collection. Ensures that 1301 the thresholds are integers if thresholds are supplied. 1302 """ 1303 if thresholds is None: 1304 return None 1305 try: 1306 assert len(thresholds) == 3 1307 return int(thresholds[0]), int(thresholds[1]), int(thresholds[2]) 1308 except Exception: 1309 raise ConfigError( 1310 "Value of `gc_threshold` must be a list of three integers if set" 1311 ) 1312 1313 1314def parse_listener_def(listener: Any) -> ListenerConfig: 1315 """parse a listener config from the config file""" 1316 listener_type = listener["type"] 1317 1318 port = listener.get("port") 1319 if not isinstance(port, int): 1320 raise ConfigError("Listener configuration is lacking a valid 'port' option") 1321 1322 tls = listener.get("tls", False) 1323 1324 bind_addresses = listener.get("bind_addresses", []) 1325 bind_address = listener.get("bind_address") 1326 # if bind_address was specified, add it to the list of addresses 1327 if bind_address: 1328 bind_addresses.append(bind_address) 1329 1330 # if we still have an empty list of addresses, use the default list 1331 if not bind_addresses: 1332 if listener_type == "metrics": 1333 # the metrics listener doesn't support IPv6 1334 bind_addresses.append("0.0.0.0") 1335 else: 1336 bind_addresses.extend(DEFAULT_BIND_ADDRESSES) 1337 1338 http_config = None 1339 if listener_type == "http": 1340 http_config = HttpListenerConfig( 1341 x_forwarded=listener.get("x_forwarded", False), 1342 resources=[ 1343 HttpResourceConfig(**res) for res in listener.get("resources", []) 1344 ], 1345 additional_resources=listener.get("additional_resources", {}), 1346 tag=listener.get("tag"), 1347 ) 1348 1349 return ListenerConfig(port, bind_addresses, listener_type, tls, http_config) 1350 1351 1352NO_MORE_WEB_CLIENT_WARNING = """ 1353Synapse no longer includes a web client. To enable a web client, configure 1354web_client_location. To remove this warning, remove 'webclient' from the 'listeners' 1355configuration. 1356""" 1357 1358 1359def _warn_if_webclient_configured(listeners: Iterable[ListenerConfig]) -> None: 1360 for listener in listeners: 1361 if not listener.http_options: 1362 continue 1363 for res in listener.http_options.resources: 1364 for name in res.names: 1365 if name == "webclient": 1366 logger.warning(NO_MORE_WEB_CLIENT_WARNING) 1367 return 1368 1369 1370_MANHOLE_SETTINGS_SCHEMA = { 1371 "type": "object", 1372 "properties": { 1373 "username": {"type": "string"}, 1374 "password": {"type": "string"}, 1375 "ssh_priv_key_path": {"type": "string"}, 1376 "ssh_pub_key_path": {"type": "string"}, 1377 }, 1378} 1379