diff --git a/synapse/config/_base.py b/synapse/config/_base.py
index 8a75c48733..c18e0bdbb8 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -14,6 +14,7 @@
# limitations under the License.
import argparse
+import errno
import os
import yaml
import sys
@@ -26,6 +27,16 @@ class ConfigError(Exception):
class Config(object):
+ stats_reporting_begging_spiel = (
+ "We would really appreciate it if you could help our project out by"
+ " reporting anonymized usage statistics from your homeserver. Only very"
+ " basic aggregate data (e.g. number of users) will be reported, but it"
+ " helps us to track the growth of the Matrix community, and helps us to"
+ " make Matrix a success, as well as to convince other networks that they"
+ " should peer with us."
+ "\nThank you."
+ )
+
@staticmethod
def parse_size(value):
if isinstance(value, int) or isinstance(value, long):
@@ -81,8 +92,11 @@ class Config(object):
@classmethod
def ensure_directory(cls, dir_path):
dir_path = cls.abspath(dir_path)
- if not os.path.exists(dir_path):
+ try:
os.makedirs(dir_path)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
if not os.path.isdir(dir_path):
raise ConfigError(
"%s is not a directory" % (dir_path,)
@@ -111,11 +125,14 @@ class Config(object):
results.append(getattr(cls, name)(self, *args, **kargs))
return results
- def generate_config(self, config_dir_path, server_name):
+ def generate_config(self, config_dir_path, server_name, report_stats=None):
default_config = "# vim:ft=yaml\n"
default_config += "\n\n".join(dedent(conf) for conf in self.invoke_all(
- "default_config", config_dir_path, server_name
+ "default_config",
+ config_dir_path=config_dir_path,
+ server_name=server_name,
+ report_stats=report_stats,
))
config = yaml.load(default_config)
@@ -140,6 +157,12 @@ class Config(object):
help="Generate a config file for the server name"
)
config_parser.add_argument(
+ "--report-stats",
+ action="store",
+ help="Stuff",
+ choices=["yes", "no"]
+ )
+ config_parser.add_argument(
"--generate-keys",
action="store_true",
help="Generate any missing key files then exit"
@@ -189,6 +212,11 @@ class Config(object):
config_files.append(config_path)
if config_args.generate_config:
+ if config_args.report_stats is None:
+ config_parser.error(
+ "Please specify either --report-stats=yes or --report-stats=no\n\n" +
+ cls.stats_reporting_begging_spiel
+ )
if not config_files:
config_parser.error(
"Must supply a config file.\nA config file can be automatically"
@@ -211,7 +239,9 @@ class Config(object):
os.makedirs(config_dir_path)
with open(config_path, "wb") as config_file:
config_bytes, config = obj.generate_config(
- config_dir_path, server_name
+ config_dir_path=config_dir_path,
+ server_name=server_name,
+ report_stats=(config_args.report_stats == "yes"),
)
obj.invoke_all("generate_files", config)
config_file.write(config_bytes)
@@ -261,9 +291,20 @@ class Config(object):
specified_config.update(yaml_config)
server_name = specified_config["server_name"]
- _, config = obj.generate_config(config_dir_path, server_name)
+ _, config = obj.generate_config(
+ config_dir_path=config_dir_path,
+ server_name=server_name
+ )
config.pop("log_config")
config.update(specified_config)
+ if "report_stats" not in config:
+ sys.stderr.write(
+ "Please opt in or out of reporting anonymized homeserver usage "
+ "statistics, by setting the report_stats key in your config file "
+ " ( " + config_path + " ) " +
+ "to either True or False.\n\n" +
+ Config.stats_reporting_begging_spiel + "\n")
+ sys.exit(1)
if generate_keys:
obj.invoke_all("generate_files", config)
diff --git a/synapse/config/appservice.py b/synapse/config/appservice.py
index 38f41933b7..b8d301995e 100644
--- a/synapse/config/appservice.py
+++ b/synapse/config/appservice.py
@@ -20,7 +20,7 @@ class AppServiceConfig(Config):
def read_config(self, config):
self.app_service_config_files = config.get("app_service_config_files", [])
- def default_config(cls, config_dir_path, server_name):
+ def default_config(cls, **kwargs):
return """\
# A list of application service config file to use
app_service_config_files: []
diff --git a/synapse/config/captcha.py b/synapse/config/captcha.py
index 15a132b4e3..dd92fcd0dc 100644
--- a/synapse/config/captcha.py
+++ b/synapse/config/captcha.py
@@ -24,7 +24,7 @@ class CaptchaConfig(Config):
self.captcha_bypass_secret = config.get("captcha_bypass_secret")
self.recaptcha_siteverify_api = config["recaptcha_siteverify_api"]
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, **kwargs):
return """\
## Captcha ##
diff --git a/synapse/config/cas.py b/synapse/config/cas.py
new file mode 100644
index 0000000000..326e405841
--- /dev/null
+++ b/synapse/config/cas.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ._base import Config
+
+
+class CasConfig(Config):
+ """Cas Configuration
+
+ cas_server_url: URL of CAS server
+ """
+
+ def read_config(self, config):
+ cas_config = config.get("cas_config", None)
+ if cas_config:
+ self.cas_enabled = cas_config.get("enabled", True)
+ self.cas_server_url = cas_config["server_url"]
+ self.cas_service_url = cas_config["service_url"]
+ self.cas_required_attributes = cas_config.get("required_attributes", {})
+ else:
+ self.cas_enabled = False
+ self.cas_server_url = None
+ self.cas_service_url = None
+ self.cas_required_attributes = {}
+
+ def default_config(self, config_dir_path, server_name, **kwargs):
+ return """
+ # Enable CAS for registration and login.
+ #cas_config:
+ # enabled: true
+ # server_url: "https://cas-server.com"
+ # service_url: "https://homesever.domain.com:8448"
+ # #required_attributes:
+ # # name: value
+ """
diff --git a/synapse/config/database.py b/synapse/config/database.py
index f0611e8884..baeda8f300 100644
--- a/synapse/config/database.py
+++ b/synapse/config/database.py
@@ -45,7 +45,7 @@ class DatabaseConfig(Config):
self.set_databasepath(config.get("database_path"))
- def default_config(self, config, config_dir_path):
+ def default_config(self, **kwargs):
database_path = self.abspath("homeserver.db")
return """\
# Database configuration
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index d77f045406..4743e6abc5 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -26,12 +26,15 @@ from .metrics import MetricsConfig
from .appservice import AppServiceConfig
from .key import KeyConfig
from .saml2 import SAML2Config
+from .cas import CasConfig
+from .password import PasswordConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
VoipConfig, RegistrationConfig, MetricsConfig,
- AppServiceConfig, KeyConfig, SAML2Config, ):
+ AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
+ PasswordConfig,):
pass
diff --git a/synapse/config/key.py b/synapse/config/key.py
index 0494c0cb77..2c187065e5 100644
--- a/synapse/config/key.py
+++ b/synapse/config/key.py
@@ -13,14 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import os
from ._base import Config, ConfigError
-import syutil.crypto.signing_key
-from syutil.crypto.signing_key import (
- is_signing_algorithm_supported, decode_verify_key_bytes
-)
-from syutil.base64util import decode_base64
+
from synapse.util.stringutils import random_string
+from signedjson.key import (
+ generate_signing_key, is_signing_algorithm_supported,
+ decode_signing_key_base64, decode_verify_key_bytes,
+ read_signing_keys, write_signing_keys, NACL_ED25519
+)
+from unpaddedbase64 import decode_base64
+
+import os
class KeyConfig(Config):
@@ -37,7 +40,7 @@ class KeyConfig(Config):
config["perspectives"]
)
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, config_dir_path, server_name, **kwargs):
base_key_name = os.path.join(config_dir_path, server_name)
return """\
## Signing Keys ##
@@ -83,9 +86,7 @@ class KeyConfig(Config):
def read_signing_key(self, signing_key_path):
signing_keys = self.read_file(signing_key_path, "signing_key")
try:
- return syutil.crypto.signing_key.read_signing_keys(
- signing_keys.splitlines(True)
- )
+ return read_signing_keys(signing_keys.splitlines(True))
except Exception:
raise ConfigError(
"Error reading signing_key."
@@ -112,22 +113,18 @@ class KeyConfig(Config):
if not os.path.exists(signing_key_path):
with open(signing_key_path, "w") as signing_key_file:
key_id = "a_" + random_string(4)
- syutil.crypto.signing_key.write_signing_keys(
- signing_key_file,
- (syutil.crypto.signing_key.generate_signing_key(key_id),),
+ write_signing_keys(
+ signing_key_file, (generate_signing_key(key_id),),
)
else:
signing_keys = self.read_file(signing_key_path, "signing_key")
if len(signing_keys.split("\n")[0].split()) == 1:
# handle keys in the old format.
key_id = "a_" + random_string(4)
- key = syutil.crypto.signing_key.decode_signing_key_base64(
- syutil.crypto.signing_key.NACL_ED25519,
- key_id,
- signing_keys.split("\n")[0]
+ key = decode_signing_key_base64(
+ NACL_ED25519, key_id, signing_keys.split("\n")[0]
)
with open(signing_key_path, "w") as signing_key_file:
- syutil.crypto.signing_key.write_signing_keys(
- signing_key_file,
- (key,),
+ write_signing_keys(
+ signing_key_file, (key,),
)
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index fa542623b7..a13dc170c4 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -21,6 +21,8 @@ import logging.config
import yaml
from string import Template
import os
+import signal
+from synapse.util.debug import debug_deferreds
DEFAULT_LOG_CONFIG = Template("""
@@ -68,8 +70,10 @@ class LoggingConfig(Config):
self.verbosity = config.get("verbose", 0)
self.log_config = self.abspath(config.get("log_config"))
self.log_file = self.abspath(config.get("log_file"))
+ if config.get("full_twisted_stacktraces"):
+ debug_deferreds()
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, config_dir_path, server_name, **kwargs):
log_file = self.abspath("homeserver.log")
log_config = self.abspath(
os.path.join(config_dir_path, server_name + ".log.config")
@@ -83,6 +87,11 @@ class LoggingConfig(Config):
# A yaml python logging config file
log_config: "%(log_config)s"
+
+ # Stop twisted from discarding the stack traces of exceptions in
+ # deferreds by waiting a reactor tick before running a deferred's
+ # callbacks.
+ # full_twisted_stacktraces: true
""" % locals()
def read_arguments(self, args):
@@ -142,6 +151,19 @@ class LoggingConfig(Config):
handler = logging.handlers.RotatingFileHandler(
self.log_file, maxBytes=(1000 * 1000 * 100), backupCount=3
)
+
+ def sighup(signum, stack):
+ logger.info("Closing log file due to SIGHUP")
+ handler.doRollover()
+ logger.info("Opened new log file due to SIGHUP")
+
+ # TODO(paul): obviously this is a terrible mechanism for
+ # stealing SIGHUP, because it means no other part of synapse
+ # can use it instead. If we want to catch SIGHUP anywhere
+ # else as well, I'd suggest we find a nicer way to broadcast
+ # it around.
+ if getattr(signal, "SIGHUP"):
+ signal.signal(signal.SIGHUP, sighup)
else:
handler = logging.StreamHandler()
handler.setFormatter(formatter)
diff --git a/synapse/config/metrics.py b/synapse/config/metrics.py
index ae5a691527..825fec9a38 100644
--- a/synapse/config/metrics.py
+++ b/synapse/config/metrics.py
@@ -19,13 +19,15 @@ from ._base import Config
class MetricsConfig(Config):
def read_config(self, config):
self.enable_metrics = config["enable_metrics"]
+ self.report_stats = config.get("report_stats", None)
self.metrics_port = config.get("metrics_port")
self.metrics_bind_host = config.get("metrics_bind_host", "127.0.0.1")
- def default_config(self, config_dir_path, server_name):
- return """\
+ def default_config(self, report_stats=None, **kwargs):
+ suffix = "" if report_stats is None else "report_stats: %(report_stats)s\n"
+ return ("""\
## Metrics ###
# Enable collection and rendering of performance metrics
enable_metrics: False
- """
+ """ + suffix) % locals()
diff --git a/synapse/config/password.py b/synapse/config/password.py
new file mode 100644
index 0000000000..1a3e278472
--- /dev/null
+++ b/synapse/config/password.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ._base import Config
+
+
+class PasswordConfig(Config):
+ """Password login configuration
+ """
+
+ def read_config(self, config):
+ password_config = config.get("password_config", {})
+ self.password_enabled = password_config.get("enabled", True)
+
+ def default_config(self, config_dir_path, server_name, **kwargs):
+ return """
+ # Enable password for login.
+ password_config:
+ enabled: true
+ """
diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py
index 76d9970e5b..611b598ec7 100644
--- a/synapse/config/ratelimiting.py
+++ b/synapse/config/ratelimiting.py
@@ -27,7 +27,7 @@ class RatelimitConfig(Config):
self.federation_rc_reject_limit = config["federation_rc_reject_limit"]
self.federation_rc_concurrent = config["federation_rc_concurrent"]
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, **kwargs):
return """\
## Ratelimiting ##
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index 67e780864e..dca391f7af 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -32,9 +32,13 @@ class RegistrationConfig(Config):
)
self.registration_shared_secret = config.get("registration_shared_secret")
+ self.macaroon_secret_key = config.get("macaroon_secret_key")
+ self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
+ self.allow_guest_access = config.get("allow_guest_access", False)
- def default_config(self, config_dir, server_name):
+ def default_config(self, **kwargs):
registration_shared_secret = random_string_with_symbols(50)
+ macaroon_secret_key = random_string_with_symbols(50)
return """\
## Registration ##
@@ -44,6 +48,18 @@ class RegistrationConfig(Config):
# If set, allows registration by anyone who also has the shared
# secret, even if registration is otherwise disabled.
registration_shared_secret: "%(registration_shared_secret)s"
+
+ macaroon_secret_key: "%(macaroon_secret_key)s"
+
+ # Set the number of bcrypt rounds used to generate password hash.
+ # Larger numbers increase the work factor needed to generate the hash.
+ # The default number of rounds is 12.
+ bcrypt_rounds: 12
+
+ # Allows users to register as guests without a password/email/etc, and
+ # participate in rooms hosted on this server which have been made
+ # accessible to anonymous users.
+ allow_guest_access: False
""" % locals()
def add_arguments(self, parser):
diff --git a/synapse/config/repository.py b/synapse/config/repository.py
index 64644b9a7a..2fcf872449 100644
--- a/synapse/config/repository.py
+++ b/synapse/config/repository.py
@@ -60,7 +60,7 @@ class ContentRepositoryConfig(Config):
config["thumbnail_sizes"]
)
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, **kwargs):
media_store = self.default_path("media_store")
uploads_path = self.default_path("uploads")
return """
diff --git a/synapse/config/saml2.py b/synapse/config/saml2.py
index 1532036876..8d7f443021 100644
--- a/synapse/config/saml2.py
+++ b/synapse/config/saml2.py
@@ -33,7 +33,7 @@ class SAML2Config(Config):
def read_config(self, config):
saml2_config = config.get("saml2_config", None)
if saml2_config:
- self.saml2_enabled = True
+ self.saml2_enabled = saml2_config.get("enabled", True)
self.saml2_config_path = saml2_config["config_path"]
self.saml2_idp_redirect_url = saml2_config["idp_redirect_url"]
else:
@@ -41,7 +41,7 @@ class SAML2Config(Config):
self.saml2_config_path = None
self.saml2_idp_redirect_url = None
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, config_dir_path, server_name, **kwargs):
return """
# Enable SAML2 for registration and login. Uses pysaml2
# config_path: Path to the sp_conf.py configuration file
@@ -49,6 +49,7 @@ class SAML2Config(Config):
# the user back to /login/saml2 with proper info.
# See pysaml2 docs for format of config.
#saml2_config:
+ # enabled: true
# config_path: "%s/sp_conf.py"
# idp_redirect_url: "http://%s/idp"
""" % (config_dir_path, server_name)
diff --git a/synapse/config/server.py b/synapse/config/server.py
index a03e55c223..5c2d6bfeab 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -26,6 +26,7 @@ class ServerConfig(Config):
self.soft_file_limit = config["soft_file_limit"]
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", True)
self.listeners = config.get("listeners", [])
@@ -117,7 +118,7 @@ class ServerConfig(Config):
self.content_addr = content_addr
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, server_name, **kwargs):
if ":" in server_name:
bind_port = int(server_name.split(":")[1])
unsecure_port = bind_port - 400
diff --git a/synapse/config/tls.py b/synapse/config/tls.py
index 4751d39bc9..0ac2698293 100644
--- a/synapse/config/tls.py
+++ b/synapse/config/tls.py
@@ -42,7 +42,15 @@ class TlsConfig(Config):
config.get("tls_dh_params_path"), "tls_dh_params"
)
- def default_config(self, config_dir_path, server_name):
+ # This config option applies to non-federation HTTP clients
+ # (e.g. for talking to recaptcha, identity servers, and such)
+ # It should never be used in production, and is intended for
+ # use only when running tests.
+ self.use_insecure_ssl_client_just_for_testing_do_not_use = config.get(
+ "use_insecure_ssl_client_just_for_testing_do_not_use"
+ )
+
+ def default_config(self, config_dir_path, server_name, **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/voip.py b/synapse/config/voip.py
index a1707223d3..a093354ccd 100644
--- a/synapse/config/voip.py
+++ b/synapse/config/voip.py
@@ -22,7 +22,7 @@ class VoipConfig(Config):
self.turn_shared_secret = config["turn_shared_secret"]
self.turn_user_lifetime = self.parse_duration(config["turn_user_lifetime"])
- def default_config(self, config_dir_path, server_name):
+ def default_config(self, **kwargs):
return """\
## Turn ##
|