summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorDavid Baker <dbkr@matrix.org>2014-09-03 18:25:17 +0100
committerDavid Baker <dbkr@matrix.org>2014-09-03 18:25:17 +0100
commitd72ce4da64416eb10224e6a6b55aecdd27967b6b (patch)
treeec86c93b1ebaf0ebb11f9e9d2a5377f608ab98b2 /synapse
parentMake registering and logging in with a threepid work in the webclient. (diff)
parentFix ban path (diff)
downloadsynapse-d72ce4da64416eb10224e6a6b55aecdd27967b6b.tar.xz
Merge branch 'develop' of github.com:matrix-org/synapse into develop
Conflicts:
	synapse/http/client.py
Diffstat (limited to 'synapse')
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/api/__init__.py2
-rw-r--r--synapse/api/auth.py2
-rw-r--r--synapse/api/constants.py2
-rw-r--r--synapse/api/errors.py2
-rw-r--r--synapse/api/events/__init__.py2
-rw-r--r--synapse/api/events/factory.py2
-rw-r--r--synapse/api/events/room.py5
-rw-r--r--synapse/api/ratelimiting.py2
-rw-r--r--synapse/api/urls.py2
-rw-r--r--synapse/app/__init__.py2
-rwxr-xr-xsynapse/app/homeserver.py32
-rw-r--r--synapse/config/__init__.py2
-rw-r--r--synapse/config/_base.py2
-rw-r--r--synapse/config/database.py2
-rw-r--r--synapse/config/homeserver.py5
-rw-r--r--synapse/config/logger.py2
-rw-r--r--synapse/config/ratelimiting.py2
-rw-r--r--synapse/config/repository.py39
-rw-r--r--synapse/config/server.py16
-rw-r--r--synapse/config/tls.py2
-rw-r--r--synapse/crypto/__init__.py2
-rw-r--r--synapse/crypto/context_factory.py2
-rw-r--r--synapse/crypto/keyclient.py2
-rw-r--r--synapse/crypto/keyserver.py2
-rw-r--r--synapse/crypto/resource/__init__.py2
-rw-r--r--synapse/crypto/resource/key.py2
-rw-r--r--synapse/federation/__init__.py2
-rw-r--r--synapse/federation/pdu_codec.py2
-rw-r--r--synapse/federation/persistence.py2
-rw-r--r--synapse/federation/replication.py3
-rw-r--r--synapse/federation/transport.py2
-rw-r--r--synapse/federation/units.py2
-rw-r--r--synapse/handlers/__init__.py2
-rw-r--r--synapse/handlers/_base.py2
-rw-r--r--synapse/handlers/directory.py19
-rw-r--r--synapse/handlers/events.py6
-rw-r--r--synapse/handlers/federation.py2
-rw-r--r--synapse/handlers/login.py2
-rw-r--r--synapse/handlers/message.py9
-rw-r--r--synapse/handlers/presence.py118
-rw-r--r--synapse/handlers/profile.py2
-rw-r--r--synapse/handlers/register.py2
-rw-r--r--synapse/handlers/room.py2
-rw-r--r--synapse/handlers/typing.py2
-rw-r--r--synapse/http/__init__.py2
-rw-r--r--synapse/http/client.py26
-rw-r--r--synapse/http/content_repository.py206
-rw-r--r--synapse/http/endpoint.py2
-rw-r--r--synapse/http/server.py165
-rw-r--r--synapse/notifier.py9
-rw-r--r--synapse/rest/__init__.py2
-rw-r--r--synapse/rest/base.py2
-rw-r--r--synapse/rest/directory.py24
-rw-r--r--synapse/rest/events.py2
-rw-r--r--synapse/rest/initial_sync.py2
-rw-r--r--synapse/rest/login.py2
-rw-r--r--synapse/rest/presence.py41
-rw-r--r--synapse/rest/profile.py8
-rw-r--r--synapse/rest/register.py2
-rw-r--r--synapse/rest/room.py9
-rw-r--r--synapse/rest/transactions.py2
-rw-r--r--synapse/server.py2
-rw-r--r--synapse/state.py14
-rw-r--r--synapse/storage/__init__.py2
-rw-r--r--synapse/storage/_base.py12
-rw-r--r--synapse/storage/directory.py2
-rw-r--r--synapse/storage/feedback.py2
-rw-r--r--synapse/storage/keys.py2
-rw-r--r--synapse/storage/pdu.py2
-rw-r--r--synapse/storage/presence.py2
-rw-r--r--synapse/storage/profile.py2
-rw-r--r--synapse/storage/registration.py2
-rw-r--r--synapse/storage/room.py2
-rw-r--r--synapse/storage/roommember.py4
-rw-r--r--synapse/storage/schema/delta/v2.sql168
-rw-r--r--synapse/storage/schema/edge_pdus.sql2
-rw-r--r--synapse/storage/schema/im.sql2
-rw-r--r--synapse/storage/schema/keys.sql2
-rw-r--r--synapse/storage/schema/pdu.sql2
-rw-r--r--synapse/storage/schema/presence.sql2
-rw-r--r--synapse/storage/schema/profiles.sql2
-rw-r--r--synapse/storage/schema/room_aliases.sql2
-rw-r--r--synapse/storage/schema/transactions.sql2
-rw-r--r--synapse/storage/schema/users.sql2
-rw-r--r--synapse/storage/stream.py2
-rw-r--r--synapse/storage/transactions.py2
-rw-r--r--synapse/streams/__init__.py2
-rw-r--r--synapse/streams/config.py2
-rw-r--r--synapse/streams/events.py2
-rw-r--r--synapse/types.py2
-rw-r--r--synapse/util/__init__.py2
-rw-r--r--synapse/util/async.py2
-rw-r--r--synapse/util/distributor.py19
-rw-r--r--synapse/util/jsonobject.py2
-rw-r--r--synapse/util/lockutils.py2
-rw-r--r--synapse/util/logutils.py2
-rw-r--r--synapse/util/stringutils.py2
98 files changed, 757 insertions, 350 deletions
diff --git a/synapse/__init__.py b/synapse/__init__.py
index b45cf47b56..564d789645 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/__init__.py b/synapse/api/__init__.py
index 2216c0f1ca..9bff9ec169 100644
--- a/synapse/api/__init__.py
+++ b/synapse/api/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 54ecbe5b3a..b4eda3df01 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index 668ffa07ca..fcef062fc9 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index ad79bc7ff9..84afe4fa37 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py
index 9502f5df8f..f95468fc65 100644
--- a/synapse/api/events/__init__.py
+++ b/synapse/api/events/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py
index 159728b2d2..a3b293e024 100644
--- a/synapse/api/events/factory.py
+++ b/synapse/api/events/factory.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py
index f6d3c59a9a..33f0f0cb99 100644
--- a/synapse/api/events/room.py
+++ b/synapse/api/events/room.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -103,8 +103,7 @@ class FeedbackEvent(SynapseEvent):
     def get_content_template(self):
         return {
             "type": u"string",
-            "target_event_id": u"string",
-            "msg_sender_id": u"string"
+            "target_event_id": u"string"
         }
 
 
diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py
index b9b6a9de1a..b25358090f 100644
--- a/synapse/api/ratelimiting.py
+++ b/synapse/api/ratelimiting.py
@@ -1,4 +1,4 @@
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/api/urls.py b/synapse/api/urls.py
index 3d0b5de965..6314f31f7a 100644
--- a/synapse/api/urls.py
+++ b/synapse/api/urls.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/app/__init__.py b/synapse/app/__init__.py
index 2216c0f1ca..9bff9ec169 100644
--- a/synapse/app/__init__.py
+++ b/synapse/app/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index 8a7cd07fec..49cf928cc1 100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@ from twisted.enterprise import adbapi
 from twisted.web.resource import Resource
 from twisted.web.static import File
 from twisted.web.server import Site
-from synapse.http.server import JsonResource, RootRedirect, ContentRepoResource
+from synapse.http.server import JsonResource, RootRedirect
+from synapse.http.content_repository import ContentRepoResource
 from synapse.http.client import TwistedHttpClient
 from synapse.api.urls import (
     CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
@@ -74,7 +75,9 @@ class SynapseHomeServer(HomeServer):
         return File("webclient")  # TODO configurable?
 
     def build_resource_for_content_repo(self):
-        return ContentRepoResource(self, self.upload_dir, self.auth)
+        return ContentRepoResource(
+            self, self.upload_dir, self.auth, self.content_addr
+        )
 
     def build_db_pool(self):
         """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
@@ -90,20 +93,28 @@ class SynapseHomeServer(HomeServer):
             if row and row[0]:
                 user_version = row[0]
 
-                if user_version < SCHEMA_VERSION:
-                    # TODO(paul): add some kind of intelligent fixup here
-                    raise ValueError("Cannot use this database as the " +
-                        "schema version (%d) does not match (%d)" %
-                        (user_version, SCHEMA_VERSION)
+                if user_version > SCHEMA_VERSION:
+                    raise ValueError("Cannot use this database as it is too " +
+                        "new for the server to understand"
+                    )
+                elif user_version < SCHEMA_VERSION:
+                    logging.info("Upgrading database from version %d",
+                        user_version
                     )
 
+                    # Run every version since after the current version.
+                    for v in range(user_version + 1, SCHEMA_VERSION + 1):
+                        sql_script = read_schema("delta/v%d" % (v))
+                        c.executescript(sql_script)
+
+                    db_conn.commit()
+
             else:
                 for sql_loc in SCHEMAS:
                     sql_script = read_schema(sql_loc)
 
                     c.executescript(sql_script)
-                    db_conn.commit()
-
+                db_conn.commit()
                 c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
 
             c.close()
@@ -248,6 +259,7 @@ def setup():
         db_name=config.database_path,
         tls_context_factory=tls_context_factory,
         config=config,
+        content_addr=config.content_addr,
     )
 
     hs.register_servlets()
diff --git a/synapse/config/__init__.py b/synapse/config/__init__.py
index fe8a073cd3..f9811bfa04 100644
--- a/synapse/config/__init__.py
+++ b/synapse/config/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/config/_base.py b/synapse/config/_base.py
index 4b445806da..7dc68230bd 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/config/database.py b/synapse/config/database.py
index edf2361914..460445f15d 100644
--- a/synapse/config/database.py
+++ b/synapse/config/database.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index a9aa4c735c..76e2cdeddd 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,9 +18,10 @@ from .server import ServerConfig
 from .logger import LoggingConfig
 from .database import DatabaseConfig
 from .ratelimiting import RatelimitConfig
+from .repository import ContentRepositoryConfig
 
 class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
-                       RatelimitConfig):
+                       RatelimitConfig, ContentRepositoryConfig):
     pass
 
 if __name__=='__main__':
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index 8db6621ae8..56cd095433 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py
index ee572e1fbf..f126782b8d 100644
--- a/synapse/config/ratelimiting.py
+++ b/synapse/config/ratelimiting.py
@@ -1,4 +1,4 @@
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/config/repository.py b/synapse/config/repository.py
new file mode 100644
index 0000000000..407c8d6c24
--- /dev/null
+++ b/synapse/config/repository.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 matrix.org
+#
+# 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
+import os
+
+class ContentRepositoryConfig(Config):
+    def __init__(self, args):
+        super(ContentRepositoryConfig, self).__init__(args)
+        self.max_upload_size = self.parse_size(args.max_upload_size)
+
+    def parse_size(self, string):
+        sizes = {"K": 1024, "M": 1024 * 1024}
+        size = 1
+        suffix = string[-1]
+        if suffix in sizes:
+            string = string[:-1]
+            size = sizes[suffix]
+        return int(string) * size
+
+    @classmethod
+    def add_arguments(cls, parser):
+        super(ContentRepositoryConfig, cls).add_arguments(parser)
+        db_group = parser.add_argument_group("content_repository")
+        db_group.add_argument(
+            "--max-upload-size", default="1M"
+        )
diff --git a/synapse/config/server.py b/synapse/config/server.py
index 36143e3c9c..516e4cf882 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -32,6 +32,14 @@ class ServerConfig(Config):
         self.webclient = True
         self.manhole = args.manhole
 
+        if not args.content_addr:
+            host = args.server_name
+            if ':' not in host:
+                host  = "%s:%d" % (host, args.bind_port)
+            args.content_addr = "https://%s" % (host,)
+
+        self.content_addr = args.content_addr
+
     @classmethod
     def add_arguments(cls, parser):
         super(ServerConfig, cls).add_arguments(parser)
@@ -50,13 +58,16 @@ class ServerConfig(Config):
                                   help="Local interface to listen on")
         server_group.add_argument("-D", "--daemonize", action='store_true',
                                   help="Daemonize the home server")
-        server_group.add_argument('--pid-file', default="hs.pid",
+        server_group.add_argument('--pid-file', default="homeserver.pid",
                                   help="When running as a daemon, the file to"
                                   " store the pid in")
         server_group.add_argument("--manhole", metavar="PORT", dest="manhole",
                                   type=int,
                                   help="Turn on the twisted telnet manhole"
                                   " service on the given port.")
+        server_group.add_argument("--content-addr", default=None,
+                                  help="The host and scheme to use for the "
+                                  "content repository")
 
     def read_signing_key(self, signing_key_path):
         signing_key_base64 = self.read_file(signing_key_path, "signing_key")
@@ -77,3 +88,4 @@ class ServerConfig(Config):
             with open(args.signing_key_path, "w") as signing_key_file:
                 key = nacl.signing.SigningKey.generate()
                 signing_key_file.write(encode_base64(key.encode()))
+
diff --git a/synapse/config/tls.py b/synapse/config/tls.py
index 16f6f3aba6..72d5518a89 100644
--- a/synapse/config/tls.py
+++ b/synapse/config/tls.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/__init__.py b/synapse/crypto/__init__.py
index 2216c0f1ca..9bff9ec169 100644
--- a/synapse/crypto/__init__.py
+++ b/synapse/crypto/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/context_factory.py b/synapse/crypto/context_factory.py
index 344f2dd218..f86bd19255 100644
--- a/synapse/crypto/context_factory.py
+++ b/synapse/crypto/context_factory.py
@@ -1,4 +1,4 @@
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/keyclient.py b/synapse/crypto/keyclient.py
index e615866b68..c11df5c529 100644
--- a/synapse/crypto/keyclient.py
+++ b/synapse/crypto/keyclient.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/keyserver.py b/synapse/crypto/keyserver.py
index 3d80a0e660..a23484dbae 100644
--- a/synapse/crypto/keyserver.py
+++ b/synapse/crypto/keyserver.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/resource/__init__.py b/synapse/crypto/resource/__init__.py
index 2216c0f1ca..9bff9ec169 100644
--- a/synapse/crypto/resource/__init__.py
+++ b/synapse/crypto/resource/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/crypto/resource/key.py b/synapse/crypto/resource/key.py
index 6aecd2b95f..48d14b9f4a 100644
--- a/synapse/crypto/resource/key.py
+++ b/synapse/crypto/resource/key.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/federation/__init__.py b/synapse/federation/__init__.py
index b15e7cf941..1351b68fd6 100644
--- a/synapse/federation/__init__.py
+++ b/synapse/federation/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/federation/pdu_codec.py b/synapse/federation/pdu_codec.py
index adc166c564..cef61108dd 100644
--- a/synapse/federation/pdu_codec.py
+++ b/synapse/federation/pdu_codec.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/federation/persistence.py b/synapse/federation/persistence.py
index 4cf72b2e42..de36a80e41 100644
--- a/synapse/federation/persistence.py
+++ b/synapse/federation/persistence.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py
index cadf574b3b..e12510017f 100644
--- a/synapse/federation/replication.py
+++ b/synapse/federation/replication.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -628,7 +628,6 @@ class _TransactionQueue(object):
 
             for deferred in deferreds:
                 deferred.errback(e)
-                yield deferred
 
         finally:
             # We want to be *very* sure we delete this after we stop processing
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index 50c3df4a5d..6e62ae7c74 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/federation/units.py b/synapse/federation/units.py
index b468f70546..9740431279 100644
--- a/synapse/federation/units.py
+++ b/synapse/federation/units.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/__init__.py b/synapse/handlers/__init__.py
index b2208b26c3..5308e2c8e1 100644
--- a/synapse/handlers/__init__.py
+++ b/synapse/handlers/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py
index 57429ad2ef..9989fe8670 100644
--- a/synapse/handlers/_base.py
+++ b/synapse/handlers/_base.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
index 7c89150d99..1b9e831fc0 100644
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ from twisted.internet import defer
 from ._base import BaseHandler
 
 from synapse.api.errors import SynapseError
+from synapse.http.client import HttpClient
 
 import logging
 
@@ -36,7 +37,7 @@ class DirectoryHandler(BaseHandler):
         )
 
     @defer.inlineCallbacks
-    def create_association(self, room_alias, room_id, servers):
+    def create_association(self, room_alias, room_id, servers=None):
         # TODO(erikj): Do auth.
 
         if not room_alias.is_mine:
@@ -47,6 +48,12 @@ class DirectoryHandler(BaseHandler):
 
         # TODO(erikj): Check if there is a current association.
 
+        if not servers:
+            servers = yield self.store.get_joined_hosts_for_room(room_id)
+
+        if not servers:
+            raise SynapseError(400, "Failed to get server list")
+
         yield self.store.create_room_alias_association(
             room_alias,
             room_id,
@@ -68,7 +75,10 @@ class DirectoryHandler(BaseHandler):
             result = yield self.federation.make_query(
                 destination=room_alias.domain,
                 query_type="directory",
-                args={"room_alias": room_alias.to_string()},
+                args={
+                    "room_alias": room_alias.to_string(),
+                    HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
+                }
             )
 
             if result and "room_id" in result and "servers" in result:
@@ -79,6 +89,9 @@ class DirectoryHandler(BaseHandler):
             defer.returnValue({})
             return
 
+        extra_servers = yield self.store.get_joined_hosts_for_room(room_id)
+        servers = list(set(extra_servers) | set(servers))
+
         defer.returnValue({
             "room_id": room_id,
             "servers": servers,
diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py
index 980a169b25..fd24a11fb8 100644
--- a/synapse/handlers/events.py
+++ b/synapse/handlers/events.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -126,5 +126,7 @@ class EventHandler(BaseHandler):
             defer.returnValue(None)
             return
 
-        yield self.auth.check(event, raises=True)
+        if hasattr(event, "room_id"):
+            yield self.auth.check_joined_room(event.room_id, user.to_string())
+
         defer.returnValue(event)
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index eac110419c..84059d8033 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py
index 0220fa0604..6ee7ce5a2d 100644
--- a/synapse/handlers/login.py
+++ b/synapse/handlers/login.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index c9e3c4e451..dad2bbd1a4 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -142,7 +142,12 @@ class MessageHandler(BaseRoomHandler):
             SynapseError if something went wrong.
         """
 
-        snapshot = yield self.store.snapshot_room(event.room_id, event.user_id)
+        snapshot = yield self.store.snapshot_room(
+            event.room_id,
+            event.user_id,
+            state_type=event.type,
+            state_key=event.state_key,
+        )
 
         yield self.auth.check(event, snapshot, raises=True)
 
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 9bfceda88a..c79bb6ff76 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -155,19 +155,18 @@ class PresenceHandler(BaseHandler):
         if observer_user == observed_user:
             defer.returnValue(True)
 
-        allowed_by_subscription = yield self.store.is_presence_visible(
+        if (yield self.store.user_rooms_intersect(
+            [u.to_string() for u in observer_user, observed_user]
+        )):
+            defer.returnValue(True)
+
+        if (yield self.store.is_presence_visible(
             observed_localpart=observed_user.localpart,
             observer_userid=observer_user.to_string(),
-        )
-
-        if allowed_by_subscription:
+        )):
             defer.returnValue(True)
 
-        share_room = yield self.store.do_users_share_a_room(
-            [observer_user, observed_user]
-        )
-
-        defer.returnValue(share_room)
+        defer.returnValue(False)
 
     @defer.inlineCallbacks
     def get_state(self, target_user, auth_user):
@@ -181,7 +180,7 @@ class PresenceHandler(BaseHandler):
             state = yield self.store.get_presence_state(target_user.localpart)
             if "mtime" in state:
                 del state["mtime"]
-            state["presence"] = state["state"]
+            state["presence"] = state.pop("state")
 
             if target_user in self._user_cachemap:
                 state["last_active"] = (
@@ -208,21 +207,17 @@ class PresenceHandler(BaseHandler):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
-            raise AuthError(400, "Cannot set another user's displayname")
+            raise AuthError(400, "Cannot set another user's presence")
 
         if "status_msg" not in state:
             state["status_msg"] = None
 
         for k in state.keys():
-            if k not in ("presence", "state", "status_msg"):
+            if k not in ("presence", "status_msg"):
                 raise SynapseError(
                     400, "Unexpected presence state key '%s'" % (k,)
                 )
 
-        # Handle legacy "state" key for now
-        if "state" in state:
-            state["presence"] = state.pop("state")
-
         if state["presence"] not in self.STATE_LEVELS:
             raise SynapseError(400, "'%s' is not a valid presence state" %
                 state["presence"]
@@ -601,7 +596,7 @@ class PresenceHandler(BaseHandler):
         if state is None:
             state = yield self.store.get_presence_state(user.localpart)
             del state["mtime"]
-            state["presence"] = state["state"]
+            state["presence"] = state.pop("state")
 
             if user in self._user_cachemap:
                 state["last_active"] = (
@@ -622,8 +617,6 @@ class PresenceHandler(BaseHandler):
             "user_id": user.to_string(),
         }
         user_state.update(**state)
-        if "state" in user_state and "presence" not in user_state:
-            user_state["presence"] = user_state["state"]
 
         yield self.federation.send_edu(
             destination=destination,
@@ -655,21 +648,12 @@ class PresenceHandler(BaseHandler):
             state = dict(push)
             del state["user_id"]
 
-            if "presence" in state:
-                # all is OK
-                pass
-            elif "state" in state:
-                # Legacy handling
-                state["presence"] = state["state"]
-            else:
+            if "presence" not in state:
                 logger.warning("Received a presence 'push' EDU from %s without"
-                    + " either a 'presence' or 'state' key", origin
+                    + " a 'presence' key", origin
                 )
                 continue
 
-            if "state" in state:
-                del state["state"]
-
             if "last_active_ago" in state:
                 state["last_active"] = int(
                     self.clock.time_msec() - state.pop("last_active_ago")
@@ -773,15 +757,52 @@ class PresenceEventSource(object):
         self.hs = hs
         self.clock = hs.get_clock()
 
+    @defer.inlineCallbacks
+    def is_visible(self, observer_user, observed_user):
+        if observer_user == observed_user:
+            defer.returnValue(True)
+
+        presence = self.hs.get_handlers().presence_handler
+
+        if (yield presence.store.user_rooms_intersect(
+            [u.to_string() for u in observer_user, observed_user]
+        )):
+            defer.returnValue(True)
+
+        if observed_user.is_mine:
+            pushmap = presence._local_pushmap
+
+            defer.returnValue(
+                observed_user.localpart in pushmap and
+                observer_user in pushmap[observed_user.localpart]
+            )
+        else:
+            recvmap = presence._remote_recvmap
+
+            defer.returnValue(
+                observed_user in recvmap and
+                observer_user in recvmap[observed_user]
+            )
+
+    @defer.inlineCallbacks
     def get_new_events_for_user(self, user, from_key, limit):
         from_key = int(from_key)
 
+        observer_user = user
+
         presence = self.hs.get_handlers().presence_handler
         cachemap = presence._user_cachemap
 
-        # TODO(paul): limit, and filter by visibility
-        updates = [(k, cachemap[k]) for k in cachemap
-                   if from_key < cachemap[k].serial]
+        updates = []
+        # TODO(paul): use a DeferredList ? How to limit concurrency.
+        for observed_user in cachemap.keys():
+            if not (from_key < cachemap[observed_user].serial):
+                continue
+
+            if (yield self.is_visible(observer_user, observed_user)):
+                updates.append((observed_user, cachemap[observed_user]))
+
+        # TODO(paul): limit
 
         if updates:
             clock = self.clock
@@ -789,20 +810,23 @@ class PresenceEventSource(object):
             latest_serial = max([x[1].serial for x in updates])
             data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
 
-            return ((data, latest_serial))
+            defer.returnValue((data, latest_serial))
         else:
-            return (([], presence._user_cachemap_latest_serial))
+            defer.returnValue(([], presence._user_cachemap_latest_serial))
 
     def get_current_key(self):
         presence = self.hs.get_handlers().presence_handler
         return presence._user_cachemap_latest_serial
 
+    @defer.inlineCallbacks
     def get_pagination_rows(self, user, pagination_config, key):
         # TODO (erikj): Does this make sense? Ordering?
 
         from_token = pagination_config.from_token
         to_token = pagination_config.to_token
 
+        observer_user = user
+
         from_key = int(from_token.presence_key)
 
         if to_token:
@@ -813,7 +837,17 @@ class PresenceEventSource(object):
         presence = self.hs.get_handlers().presence_handler
         cachemap = presence._user_cachemap
 
-        # TODO(paul): limit, and filter by visibility
+        updates = []
+        # TODO(paul): use a DeferredList ? How to limit concurrency.
+        for observed_user in cachemap.keys():
+            if not (to_key < cachemap[observed_user].serial < from_key):
+                continue
+
+            if (yield self.is_visible(observer_user, observed_user)):
+                updates.append((observed_user, cachemap[observed_user]))
+
+        # TODO(paul): limit
+
         updates = [(k, cachemap[k]) for k in cachemap
                    if to_key < cachemap[k].serial < from_key]
 
@@ -831,13 +865,13 @@ class PresenceEventSource(object):
             next_token = next_token.copy_and_replace(
                 "presence_key", earliest_serial
             )
-            return ((data, next_token))
+            defer.returnValue((data, next_token))
         else:
             if not to_token:
                 to_token = from_token.copy_and_replace(
                     "presence_key", 0
                 )
-            return (([], to_token))
+            defer.returnValue(([], to_token))
 
 
 class UserPresenceCache(object):
@@ -851,7 +885,6 @@ class UserPresenceCache(object):
 
     def update(self, state, serial):
         assert("mtime_age" not in state)
-        assert("state" not in state)
 
         self.state.update(state)
         # Delete keys that are now 'None'
@@ -869,11 +902,6 @@ class UserPresenceCache(object):
     def get_state(self):
         # clone it so caller can't break our cache
         state = dict(self.state)
-
-        # Legacy handling
-        if "presence" in state:
-            state["state"] = state["presence"]
-
         return state
 
     def make_event(self, user, clock):
diff --git a/synapse/handlers/profile.py b/synapse/handlers/profile.py
index 6799132054..023d8c0cf2 100644
--- a/synapse/handlers/profile.py
+++ b/synapse/handlers/profile.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index d56d2663d7..bee052274f 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 58afced8f5..64956c320c 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index 3268427ecd..0ca4e5c31e 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/http/__init__.py b/synapse/http/__init__.py
index 2216c0f1ca..9bff9ec169 100644
--- a/synapse/http/__init__.py
+++ b/synapse/http/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/http/client.py b/synapse/http/client.py
index 5a5e40a26e..ebf1aa47c4 100644
--- a/synapse/http/client.py
+++ b/synapse/http/client.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
 
 
 from twisted.internet import defer, reactor
+from twisted.internet.error import DNSLookupError
 from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer
 from twisted.web.http_headers import Headers
 
@@ -23,7 +24,7 @@ from synapse.util.async import sleep
 
 from syutil.jsonutil import encode_canonical_json
 
-from synapse.api.errors import CodeMessageException
+from synapse.api.errors import CodeMessageException, SynapseError
 
 from StringIO import StringIO
 
@@ -45,6 +46,7 @@ _destination_mappings = {
 class HttpClient(object):
     """ Interface for talking json over http
     """
+    RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
 
     def put_json(self, destination, path, data):
         """ Sends the specifed json data using PUT
@@ -144,13 +146,23 @@ class TwistedHttpClient(HttpClient):
             destination = _destination_mappings[destination]
 
         logger.debug("get_json args: %s", args)
+
+        retry_on_dns_fail = True
+        if HttpClient.RETRY_DNS_LOOKUP_FAILURES in args:
+            # FIXME: This isn't ideal, but the interface exposed in get_json
+            # isn't comprehensive enough to give caller's any control over
+            # their connection mechanics.
+            retry_on_dns_fail = args.pop(HttpClient.RETRY_DNS_LOOKUP_FAILURES)
+
         query_bytes = urllib.urlencode(args, True)
+        logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
 
         response = yield self._create_request(
             destination.encode("ascii"),
             "GET",
             path.encode("ascii"),
-            query_bytes=query_bytes
+            query_bytes=query_bytes,
+            retry_on_dns_fail=retry_on_dns_fail
         )
 
         body = yield readBody(response)
@@ -179,7 +191,8 @@ class TwistedHttpClient(HttpClient):
 
     @defer.inlineCallbacks
     def _create_request(self, destination, method, path_bytes, param_bytes=b"",
-                        query_bytes=b"", producer=None, headers_dict={}):
+                        query_bytes=b"", producer=None, headers_dict={},
+                        retry_on_dns_fail=True):
         """ Creates and sends a request to the given url
         """
         headers_dict[b"User-Agent"] = [b"Synapse"]
@@ -218,6 +231,11 @@ class TwistedHttpClient(HttpClient):
                 logger.debug("Got response to %s", method)
                 break
             except Exception as e:
+                if not retry_on_dns_fail and isinstance(e, DNSLookupError):
+                    logger.warn("DNS Lookup failed to %s with %s", destination,
+                                e)
+                    raise SynapseError(400, "Domain specified not found.")
+
                 logger.exception("Got error in _create_request")
                 _print_ex(e)
 
diff --git a/synapse/http/content_repository.py b/synapse/http/content_repository.py
new file mode 100644
index 0000000000..7dd4a859f8
--- /dev/null
+++ b/synapse/http/content_repository.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 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 .server import respond_with_json_bytes
+
+from synapse.util.stringutils import random_string
+from synapse.api.errors import (
+    cs_exception, SynapseError, CodeMessageException, Codes, cs_error
+)
+
+from twisted.protocols.basic import FileSender
+from twisted.web import server, resource
+from twisted.internet import defer
+
+import base64
+import json
+import logging
+import os
+import re
+
+logger = logging.getLogger(__name__)
+
+
+class ContentRepoResource(resource.Resource):
+    """Provides file uploading and downloading.
+
+    Uploads are POSTed to wherever this Resource is linked to. This resource
+    returns a "content token" which can be used to GET this content again. The
+    token is typically a path, but it may not be. Tokens can expire, be one-time
+    uses, etc.
+
+    In this case, the token is a path to the file and contains 3 interesting
+    sections:
+        - User ID base64d (for namespacing content to each user)
+        - random 24 char string
+        - Content type base64d (so we can return it when clients GET it)
+
+    """
+    isLeaf = True
+
+    def __init__(self, hs, directory, auth, external_addr):
+        resource.Resource.__init__(self)
+        self.hs = hs
+        self.directory = directory
+        self.auth = auth
+        self.external_addr = external_addr.rstrip('/')
+        self.max_upload_size = hs.config.max_upload_size
+
+        if not os.path.isdir(self.directory):
+            os.mkdir(self.directory)
+            logger.info("ContentRepoResource : Created %s directory.",
+                        self.directory)
+
+    @defer.inlineCallbacks
+    def map_request_to_name(self, request):
+        # auth the user
+        auth_user = yield self.auth.get_user_by_req(request)
+
+        # namespace all file uploads on the user
+        prefix = base64.urlsafe_b64encode(
+            auth_user.to_string()
+        ).replace('=', '')
+
+        # use a random string for the main portion
+        main_part = random_string(24)
+
+        # suffix with a file extension if we can make one. This is nice to
+        # provide a hint to clients on the file information. We will also reuse
+        # this info to spit back the content type to the client.
+        suffix = ""
+        if request.requestHeaders.hasHeader("Content-Type"):
+            content_type = request.requestHeaders.getRawHeaders(
+                "Content-Type")[0]
+            suffix = "." + base64.urlsafe_b64encode(content_type)
+            if (content_type.split("/")[0].lower() in
+                    ["image", "video", "audio"]):
+                file_ext = content_type.split("/")[-1]
+                # be a little paranoid and only allow a-z
+                file_ext = re.sub("[^a-z]", "", file_ext)
+                suffix += "." + file_ext
+
+        file_name = prefix + main_part + suffix
+        file_path = os.path.join(self.directory, file_name)
+        logger.info("User %s is uploading a file to path %s",
+                    auth_user.to_string(),
+                    file_path)
+
+        # keep trying to make a non-clashing file, with a sensible max attempts
+        attempts = 0
+        while os.path.exists(file_path):
+            main_part = random_string(24)
+            file_name = prefix + main_part + suffix
+            file_path = os.path.join(self.directory, file_name)
+            attempts += 1
+            if attempts > 25:  # really? Really?
+                raise SynapseError(500, "Unable to create file.")
+
+        defer.returnValue(file_path)
+
+    def render_GET(self, request):
+        # no auth here on purpose, to allow anyone to view, even across home
+        # servers.
+
+        # TODO: A little crude here, we could do this better.
+        filename = request.path.split('/')[-1]
+        # be paranoid
+        filename = re.sub("[^0-9A-z.-_]", "", filename)
+
+        file_path = self.directory + "/" + filename
+
+        logger.debug("Searching for %s", file_path)
+
+        if os.path.isfile(file_path):
+            # filename has the content type
+            base64_contentype = filename.split(".")[1]
+            content_type = base64.urlsafe_b64decode(base64_contentype)
+            logger.info("Sending file %s", file_path)
+            f = open(file_path, 'rb')
+            request.setHeader('Content-Type', content_type)
+            d = FileSender().beginFileTransfer(f, request)
+
+            # after the file has been sent, clean up and finish the request
+            def cbFinished(ignored):
+                f.close()
+                request.finish()
+            d.addCallback(cbFinished)
+        else:
+            respond_with_json_bytes(
+                request,
+                404,
+                json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
+                send_cors=True)
+
+        return server.NOT_DONE_YET
+
+    def render_POST(self, request):
+        self._async_render(request)
+        return server.NOT_DONE_YET
+
+    def render_OPTIONS(self, request):
+        respond_with_json_bytes(request, 200, {}, send_cors=True)
+        return server.NOT_DONE_YET
+
+    @defer.inlineCallbacks
+    def _async_render(self, request):
+        try:
+            # TODO: The checks here are a bit late. The content will have
+            # already been uploaded to a tmp file at this point
+            content_length = request.getHeader("Content-Length")
+            if content_length is None:
+                raise SynapseError(
+                    msg="Request must specify a Content-Length", code=400
+                )
+            if int(content_length) > self.max_upload_size:
+                raise SynapseError(
+                    msg="Upload request body is too large",
+                    code=413,
+                )
+
+            fname = yield self.map_request_to_name(request)
+
+            # TODO I have a suspcious feeling this is just going to block
+            with open(fname, "wb") as f:
+                f.write(request.content.read())
+
+
+            # FIXME (erikj): These should use constants.
+            file_name = os.path.basename(fname)
+            # FIXME: we can't assume what the public mounted path of the repo is
+            # ...plus self-signed SSL won't work to remote clients anyway
+            # ...and we can't assume that it's SSL anyway, as we might want to
+            # server it via the non-SSL listener...
+            url = "%s/_matrix/content/%s" % (
+                self.external_addr, file_name
+            )
+
+            respond_with_json_bytes(request, 200,
+                                    json.dumps({"content_token": url}),
+                                    send_cors=True)
+
+        except CodeMessageException as e:
+            logger.exception(e)
+            respond_with_json_bytes(request, e.code,
+                                    json.dumps(cs_exception(e)))
+        except Exception as e:
+            logger.error("Failed to store file: %s" % e)
+            respond_with_json_bytes(
+                request,
+                500,
+                json.dumps({"error": "Internal server error"}),
+                send_cors=True)
+
+
+
diff --git a/synapse/http/endpoint.py b/synapse/http/endpoint.py
index 6c1fdcb853..7018ee3458 100644
--- a/synapse/http/endpoint.py
+++ b/synapse/http/endpoint.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/http/server.py b/synapse/http/server.py
index 74c220e869..8d419c02dd 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,22 +18,16 @@ from syutil.jsonutil import (
     encode_canonical_json, encode_pretty_printed_json
 )
 from synapse.api.errors import (
-    cs_exception, SynapseError, CodeMessageException, Codes, cs_error
+    cs_exception, SynapseError, CodeMessageException
 )
-from synapse.util.stringutils import random_string
 
 from twisted.internet import defer, reactor
-from twisted.protocols.basic import FileSender
 from twisted.web import server, resource
 from twisted.web.server import NOT_DONE_YET
 from twisted.web.util import redirectTo
 
-import base64
 import collections
-import json
 import logging
-import os
-import re
 
 logger = logging.getLogger(__name__)
 
@@ -198,161 +192,6 @@ class RootRedirect(resource.Resource):
         return resource.Resource.getChild(self, name, request)
 
 
-class ContentRepoResource(resource.Resource):
-    """Provides file uploading and downloading.
-
-    Uploads are POSTed to wherever this Resource is linked to. This resource
-    returns a "content token" which can be used to GET this content again. The
-    token is typically a path, but it may not be. Tokens can expire, be one-time
-    uses, etc.
-
-    In this case, the token is a path to the file and contains 3 interesting
-    sections:
-        - User ID base64d (for namespacing content to each user)
-        - random 24 char string
-        - Content type base64d (so we can return it when clients GET it)
-
-    """
-    isLeaf = True
-
-    def __init__(self, hs, directory, auth):
-        resource.Resource.__init__(self)
-        self.hs = hs
-        self.directory = directory
-        self.auth = auth
-
-        if not os.path.isdir(self.directory):
-            os.mkdir(self.directory)
-            logger.info("ContentRepoResource : Created %s directory.",
-                        self.directory)
-
-    @defer.inlineCallbacks
-    def map_request_to_name(self, request):
-        # auth the user
-        auth_user = yield self.auth.get_user_by_req(request)
-
-        # namespace all file uploads on the user
-        prefix = base64.urlsafe_b64encode(
-            auth_user.to_string()
-        ).replace('=', '')
-
-        # use a random string for the main portion
-        main_part = random_string(24)
-
-        # suffix with a file extension if we can make one. This is nice to
-        # provide a hint to clients on the file information. We will also reuse
-        # this info to spit back the content type to the client.
-        suffix = ""
-        if request.requestHeaders.hasHeader("Content-Type"):
-            content_type = request.requestHeaders.getRawHeaders(
-                "Content-Type")[0]
-            suffix = "." + base64.urlsafe_b64encode(content_type)
-            if (content_type.split("/")[0].lower() in
-                    ["image", "video", "audio"]):
-                file_ext = content_type.split("/")[-1]
-                # be a little paranoid and only allow a-z
-                file_ext = re.sub("[^a-z]", "", file_ext)
-                suffix += "." + file_ext
-
-        file_name = prefix + main_part + suffix
-        file_path = os.path.join(self.directory, file_name)
-        logger.info("User %s is uploading a file to path %s",
-                    auth_user.to_string(),
-                    file_path)
-
-        # keep trying to make a non-clashing file, with a sensible max attempts
-        attempts = 0
-        while os.path.exists(file_path):
-            main_part = random_string(24)
-            file_name = prefix + main_part + suffix
-            file_path = os.path.join(self.directory, file_name)
-            attempts += 1
-            if attempts > 25:  # really? Really?
-                raise SynapseError(500, "Unable to create file.")
-
-        defer.returnValue(file_path)
-
-    def render_GET(self, request):
-        # no auth here on purpose, to allow anyone to view, even across home
-        # servers.
-
-        # TODO: A little crude here, we could do this better.
-        filename = request.path.split('/')[-1]
-        # be paranoid
-        filename = re.sub("[^0-9A-z.-_]", "", filename)
-
-        file_path = self.directory + "/" + filename
-
-        logger.debug("Searching for %s", file_path)
-
-        if os.path.isfile(file_path):
-            # filename has the content type
-            base64_contentype = filename.split(".")[1]
-            content_type = base64.urlsafe_b64decode(base64_contentype)
-            logger.info("Sending file %s", file_path)
-            f = open(file_path, 'rb')
-            request.setHeader('Content-Type', content_type)
-            d = FileSender().beginFileTransfer(f, request)
-
-            # after the file has been sent, clean up and finish the request
-            def cbFinished(ignored):
-                f.close()
-                request.finish()
-            d.addCallback(cbFinished)
-        else:
-            respond_with_json_bytes(
-                request,
-                404,
-                json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
-                send_cors=True)
-
-        return server.NOT_DONE_YET
-
-    def render_POST(self, request):
-        self._async_render(request)
-        return server.NOT_DONE_YET
-
-    def render_OPTIONS(self, request):
-        respond_with_json_bytes(request, 200, {}, send_cors=True)
-        return server.NOT_DONE_YET
-
-    @defer.inlineCallbacks
-    def _async_render(self, request):
-        try:
-            fname = yield self.map_request_to_name(request)
-
-            # TODO I have a suspcious feeling this is just going to block
-            with open(fname, "wb") as f:
-                f.write(request.content.read())
-
-
-            # FIXME (erikj): These should use constants.
-            file_name = os.path.basename(fname)
-            # FIXME: we can't assume what the public mounted path of the repo is
-            # ...plus self-signed SSL won't work to remote clients anyway
-            # ...and we can't assume that it's SSL anyway, as we might want to
-            # server it via the non-SSL listener...
-            url = "https://%s/_matrix/content/%s" % (
-                self.hs.domain_with_port, file_name
-            )
-
-            respond_with_json_bytes(request, 200,
-                                    json.dumps({"content_token": url}),
-                                    send_cors=True)
-
-        except CodeMessageException as e:
-            logger.exception(e)
-            respond_with_json_bytes(request, e.code,
-                                    json.dumps(cs_exception(e)))
-        except Exception as e:
-            logger.error("Failed to store file: %s" % e)
-            respond_with_json_bytes(
-                request,
-                500,
-                json.dumps({"error": "Internal server error"}),
-                send_cors=True)
-
-
 def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
                             response_code_message=None):
     """Sends encoded JSON in response to the given request.
diff --git a/synapse/notifier.py b/synapse/notifier.py
index 3260aa744f..5b02c71d1e 100644
--- a/synapse/notifier.py
+++ b/synapse/notifier.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -167,7 +167,12 @@ class Notifier(object):
                 )
 
         def eb(failure):
-            logger.exception("Failed to notify listener", failure)
+            logger.error("Failed to notify listener",
+                exc_info=(
+                    failure.type,
+                    failure.value,
+                    failure.getTracebackObject())
+            )
 
         yield defer.DeferredList(
             [notify(l).addErrback(eb) for l in listeners]
diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index f33024e72a..ed785cfbd5 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/base.py b/synapse/rest/base.py
index e855d293e5..2e8e3fa7d4 100644
--- a/synapse/rest/base.py
+++ b/synapse/rest/base.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/directory.py b/synapse/rest/directory.py
index dc347652a0..18df7c8d8b 100644
--- a/synapse/rest/directory.py
+++ b/synapse/rest/directory.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 from twisted.internet import defer
 
+from synapse.api.errors import SynapseError, Codes
 from base import RestServlet, client_path_pattern
 
 import json
@@ -44,8 +45,10 @@ class ClientDirectoryServer(RestServlet):
 
     @defer.inlineCallbacks
     def on_PUT(self, request, room_alias):
-        # TODO(erikj): Exceptions
-        content = json.loads(request.content.read())
+        content = _parse_json(request)
+        if not "room_id" in content:
+            raise SynapseError(400, "Missing room_id key",
+                               errcode=Codes.BAD_JSON)
 
         logger.debug("Got content: %s", content)
 
@@ -54,7 +57,7 @@ class ClientDirectoryServer(RestServlet):
         logger.debug("Got room name: %s", room_alias.to_string())
 
         room_id = content["room_id"]
-        servers = content["servers"]
+        servers = content["servers"] if "servers" in content else None
 
         logger.debug("Got room_id: %s", room_id)
         logger.debug("Got servers: %s", servers)
@@ -68,7 +71,20 @@ class ClientDirectoryServer(RestServlet):
             yield dir_handler.create_association(
                 room_alias, room_id, servers
             )
+        except SynapseError as e:
+            raise e
         except:
             logger.exception("Failed to create association")
 
         defer.returnValue((200, {}))
+
+
+def _parse_json(request):
+    try:
+        content = json.loads(request.content.read())
+        if type(content) != dict:
+            raise SynapseError(400, "Content must be a JSON object.",
+                               errcode=Codes.NOT_JSON)
+        return content
+    except ValueError:
+        raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
diff --git a/synapse/rest/events.py b/synapse/rest/events.py
index 2e7563d14b..7fde143200 100644
--- a/synapse/rest/events.py
+++ b/synapse/rest/events.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/initial_sync.py b/synapse/rest/initial_sync.py
index d18c4c0f60..a1cb442256 100644
--- a/synapse/rest/initial_sync.py
+++ b/synapse/rest/initial_sync.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/login.py b/synapse/rest/login.py
index 99e4f10aac..c7bf901c8e 100644
--- a/synapse/rest/login.py
+++ b/synapse/rest/login.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/presence.py b/synapse/rest/presence.py
index bce3943542..7fc8ce4404 100644
--- a/synapse/rest/presence.py
+++ b/synapse/rest/presence.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,11 +17,12 @@
 """
 from twisted.internet import defer
 
+from synapse.api.errors import SynapseError
 from base import RestServlet, client_path_pattern
 
 import json
 import logging
-
+import urllib
 
 logger = logging.getLogger(__name__)
 
@@ -32,6 +33,7 @@ class PresenceStatusRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         state = yield self.handlers.presence_handler.get_state(
@@ -42,25 +44,26 @@ class PresenceStatusRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         state = {}
         try:
             content = json.loads(request.content.read())
 
-            # Legacy handling
-            if "state" in content:
-                state["presence"] = content.pop("state")
-            else:
-                state["presence"] = content.pop("presence")
+            state["presence"] = content.pop("presence")
 
             if "status_msg" in content:
                 state["status_msg"] = content.pop("status_msg")
+                if not isinstance(state["status_msg"], basestring):
+                    raise SynapseError(400, "status_msg must be a string.")
 
             if content:
                 raise KeyError()
+        except SynapseError as e:
+            raise e
         except:
-            defer.returnValue((400, "Unable to parse state"))
+            raise SynapseError(400, "Unable to parse state")
 
         yield self.handlers.presence_handler.set_state(
             target_user=user, auth_user=auth_user, state=state)
@@ -77,13 +80,14 @@ class PresenceListRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         if not user.is_mine:
-            defer.returnValue((400, "User not hosted on this Home Server"))
+            raise SynapseError(400, "User not hosted on this Home Server")
 
         if auth_user != user:
-            defer.returnValue((400, "Cannot get another user's presence list"))
+            raise SynapseError(400, "Cannot get another user's presence list")
 
         presence = yield self.handlers.presence_handler.get_presence_list(
             observer_user=user, accepted=True)
@@ -97,31 +101,40 @@ class PresenceListRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_POST(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         if not user.is_mine:
-            defer.returnValue((400, "User not hosted on this Home Server"))
+            raise SynapseError(400, "User not hosted on this Home Server")
 
         if auth_user != user:
-            defer.returnValue((
-                400, "Cannot modify another user's presence list"))
+            raise SynapseError(
+                400, "Cannot modify another user's presence list")
 
         try:
             content = json.loads(request.content.read())
         except:
             logger.exception("JSON parse error")
-            defer.returnValue((400, "Unable to parse content"))
+            raise SynapseError(400, "Unable to parse content")
 
         deferreds = []
 
         if "invite" in content:
             for u in content["invite"]:
+                if not isinstance(u, basestring):
+                    raise SynapseError(400, "Bad invite value.")
+                if len(u) == 0:
+                    continue
                 invited_user = self.hs.parse_userid(u)
                 deferreds.append(self.handlers.presence_handler.send_invite(
                     observer_user=user, observed_user=invited_user))
 
         if "drop" in content:
             for u in content["drop"]:
+                if not isinstance(u, basestring):
+                    raise SynapseError(400, "Bad drop value.")
+                if len(u) == 0:
+                    continue
                 dropped_user = self.hs.parse_userid(u)
                 deferreds.append(self.handlers.presence_handler.drop(
                     observer_user=user, observed_user=dropped_user))
diff --git a/synapse/rest/profile.py b/synapse/rest/profile.py
index 06076667c7..2e17f87fa1 100644
--- a/synapse/rest/profile.py
+++ b/synapse/rest/profile.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ from twisted.internet import defer
 from base import RestServlet, client_path_pattern
 
 import json
+import urllib
 
 
 class ProfileDisplaynameRestServlet(RestServlet):
@@ -26,6 +27,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         displayname = yield self.handlers.profile_handler.get_displayname(
@@ -37,6 +39,7 @@ class ProfileDisplaynameRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         try:
@@ -59,6 +62,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         avatar_url = yield self.handlers.profile_handler.get_avatar_url(
@@ -70,6 +74,7 @@ class ProfileAvatarURLRestServlet(RestServlet):
     @defer.inlineCallbacks
     def on_PUT(self, request, user_id):
         auth_user = yield self.auth.get_user_by_req(request)
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         try:
@@ -92,6 +97,7 @@ class ProfileRestServlet(RestServlet):
 
     @defer.inlineCallbacks
     def on_GET(self, request, user_id):
+        user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
         displayname = yield self.handlers.profile_handler.get_displayname(
diff --git a/synapse/rest/register.py b/synapse/rest/register.py
index 27ab7f182b..b8de3b250d 100644
--- a/synapse/rest/register.py
+++ b/synapse/rest/register.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index d76a2f5cd4..308b447090 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -388,7 +388,7 @@ class RoomMembershipRestServlet(RestServlet):
     def register(self, http_server):
         # /rooms/$roomid/[invite|join|leave]
         PATTERN = ("/rooms/(?P<room_id>[^/]*)/" +
-            "(?P<membership_action>join|invite|leave|ban)")
+            "(?P<membership_action>join|invite|leave|ban|kick)")
         register_txn_path(self, PATTERN, http_server)
 
     @defer.inlineCallbacks
@@ -399,11 +399,14 @@ class RoomMembershipRestServlet(RestServlet):
 
         # target user is you unless it is an invite
         state_key = user.to_string()
-        if membership_action in ["invite", "ban"]:
+        if membership_action in ["invite", "ban", "kick"]:
             if "user_id" not in content:
                 raise SynapseError(400, "Missing user_id key.")
             state_key = content["user_id"]
 
+            if membership_action == "kick":
+                membership_action = "leave"
+
         event = self.event_factory.create_event(
             etype=RoomMemberEvent.TYPE,
             content={"membership": unicode(membership_action)},
diff --git a/synapse/rest/transactions.py b/synapse/rest/transactions.py
index b8aa1ef11c..e06dcc8c57 100644
--- a/synapse/rest/transactions.py
+++ b/synapse/rest/transactions.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/server.py b/synapse/server.py
index 35e311a47d..83368ea5a7 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/state.py b/synapse/state.py
index e1a1a159bb..36d8210eb5 100644
--- a/synapse/state.py
+++ b/synapse/state.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -179,6 +179,18 @@ class StateHandler(object):
                 key=lambda x: x.depth
             )
 
+            if not hasattr(missing_prev, "prev_state_id"):
+                # FIXME Hmm
+                # temporary fallback
+                for algo in conflict_res:
+                    new_res, curr_res = algo(new_branch, current_branch)
+
+                    if new_res < curr_res:
+                        defer.returnValue(False)
+                    elif new_res > curr_res:
+                        defer.returnValue(True)
+                return
+
             pdu_id = missing_prev.prev_state_id
             origin = missing_prev.prev_state_origin
 
diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py
index aadaab06e7..d97014f4da 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py
index 33d56f47ce..bae50e7d1f 100644
--- a/synapse/storage/_base.py
+++ b/synapse/storage/_base.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -79,19 +79,21 @@ class SQLBaseStore(object):
     # "Simple" SQL API methods that operate on a single table with no JOINs,
     # no complex WHERE clauses, just a dict of values for columns.
 
-    def _simple_insert(self, table, values):
+    def _simple_insert(self, table, values, or_replace=False):
         """Executes an INSERT query on the named table.
 
         Args:
             table : string giving the table name
             values : dict of new column names and values for them
+            or_replace : bool; if True performs an INSERT OR REPLACE
         """
         return self._db_pool.runInteraction(
-            self._simple_insert_txn, table, values,
+            self._simple_insert_txn, table, values, or_replace=or_replace
         )
 
-    def _simple_insert_txn(self, txn, table, values):
-        sql = "INSERT INTO %s (%s) VALUES(%s)" % (
+    def _simple_insert_txn(self, txn, table, values, or_replace=False):
+        sql = "%s INTO %s (%s) VALUES(%s)" % (
+            ("INSERT OR REPLACE" if or_replace else "INSERT"),
             table,
             ", ".join(k for k in values),
             ", ".join("?" for k in values)
diff --git a/synapse/storage/directory.py b/synapse/storage/directory.py
index b22ce02f3f..bf55449253 100644
--- a/synapse/storage/directory.py
+++ b/synapse/storage/directory.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/feedback.py b/synapse/storage/feedback.py
index bac3dea955..8a18617188 100644
--- a/synapse/storage/feedback.py
+++ b/synapse/storage/feedback.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/keys.py b/synapse/storage/keys.py
index 4d19b9f641..5a38c3e8f2 100644
--- a/synapse/storage/keys.py
+++ b/synapse/storage/keys.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/pdu.py b/synapse/storage/pdu.py
index 9fd44f2454..0bf97e37ee 100644
--- a/synapse/storage/pdu.py
+++ b/synapse/storage/pdu.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/presence.py b/synapse/storage/presence.py
index a529104f4d..71b2bb084d 100644
--- a/synapse/storage/presence.py
+++ b/synapse/storage/presence.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/profile.py b/synapse/storage/profile.py
index 91dd565033..7e1fdd9d88 100644
--- a/synapse/storage/profile.py
+++ b/synapse/storage/profile.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py
index b1e4196435..fd762bc643 100644
--- a/synapse/storage/registration.py
+++ b/synapse/storage/registration.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/room.py b/synapse/storage/room.py
index 01ae190316..017169ce00 100644
--- a/synapse/storage/room.py
+++ b/synapse/storage/room.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py
index 2746126e85..75c9a60101 100644
--- a/synapse/storage/roommember.py
+++ b/synapse/storage/roommember.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -165,7 +165,7 @@ class RoomMemberStore(SQLBaseStore):
         defer.returnValue(results)
 
     @defer.inlineCallbacks
-    def do_users_share_a_room(self, user_list):
+    def user_rooms_intersect(self, user_list):
         """ Checks whether a list of users share a room.
         """
         user_list_clause = " OR ".join(["m.user_id = ?"] * len(user_list))
diff --git a/synapse/storage/schema/delta/v2.sql b/synapse/storage/schema/delta/v2.sql
new file mode 100644
index 0000000000..73b140465e
--- /dev/null
+++ b/synapse/storage/schema/delta/v2.sql
@@ -0,0 +1,168 @@
+/* Copyright 2014 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.
+ */
+
+CREATE TABLE IF NOT EXISTS events(
+    stream_ordering INTEGER PRIMARY KEY AUTOINCREMENT,
+    topological_ordering INTEGER NOT NULL,
+    event_id TEXT NOT NULL,
+    type TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    content TEXT NOT NULL,
+    unrecognized_keys TEXT,
+    processed BOOL NOT NULL,
+    outlier BOOL NOT NULL,
+    CONSTRAINT ev_uniq UNIQUE (event_id)
+);
+
+CREATE INDEX IF NOT EXISTS events_event_id ON events (event_id);
+CREATE INDEX IF NOT EXISTS events_stream_ordering ON events (stream_ordering);
+CREATE INDEX IF NOT EXISTS events_topological_ordering ON events (topological_ordering);
+CREATE INDEX IF NOT EXISTS events_room_id ON events (room_id);
+
+CREATE TABLE IF NOT EXISTS state_events(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    type TEXT NOT NULL,
+    state_key TEXT NOT NULL,
+    prev_state TEXT
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS state_events_event_id ON state_events (event_id);
+CREATE INDEX IF NOT EXISTS state_events_room_id ON state_events (room_id);
+CREATE INDEX IF NOT EXISTS state_events_type ON state_events (type);
+CREATE INDEX IF NOT EXISTS state_events_state_key ON state_events (state_key);
+
+
+CREATE TABLE IF NOT EXISTS current_state_events(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    type TEXT NOT NULL,
+    state_key TEXT NOT NULL,
+    CONSTRAINT curr_uniq UNIQUE (room_id, type, state_key) ON CONFLICT REPLACE
+);
+
+CREATE INDEX IF NOT EXISTS curr_events_event_id ON current_state_events (event_id);
+CREATE INDEX IF NOT EXISTS current_state_events_room_id ON current_state_events (room_id);
+CREATE INDEX IF NOT EXISTS current_state_events_type ON current_state_events (type);
+CREATE INDEX IF NOT EXISTS current_state_events_state_key ON current_state_events (state_key);
+
+CREATE TABLE IF NOT EXISTS room_memberships(
+    event_id TEXT NOT NULL,
+    user_id TEXT NOT NULL,
+    sender TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    membership TEXT NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS room_memberships_event_id ON room_memberships (event_id);
+CREATE INDEX IF NOT EXISTS room_memberships_room_id ON room_memberships (room_id);
+CREATE INDEX IF NOT EXISTS room_memberships_user_id ON room_memberships (user_id);
+
+CREATE TABLE IF NOT EXISTS feedback(
+    event_id TEXT NOT NULL,
+    feedback_type TEXT,
+    target_event_id TEXT,
+    sender TEXT,
+    room_id TEXT
+);
+
+CREATE TABLE IF NOT EXISTS topics(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    topic TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS room_names(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    name TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS rooms(
+    room_id TEXT PRIMARY KEY NOT NULL,
+    is_public INTEGER,
+    creator TEXT
+);
+
+CREATE TABLE IF NOT EXISTS room_join_rules(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    join_rule TEXT NOT NULL
+);
+CREATE INDEX IF NOT EXISTS room_join_rules_event_id ON room_join_rules(event_id);
+CREATE INDEX IF NOT EXISTS room_join_rules_room_id ON room_join_rules(room_id);
+
+
+CREATE TABLE IF NOT EXISTS room_power_levels(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    user_id TEXT NOT NULL,
+    level INTEGER NOT NULL
+);
+CREATE INDEX IF NOT EXISTS room_power_levels_event_id ON room_power_levels(event_id);
+CREATE INDEX IF NOT EXISTS room_power_levels_room_id ON room_power_levels(room_id);
+CREATE INDEX IF NOT EXISTS room_power_levels_room_user ON room_power_levels(room_id, user_id);
+
+
+CREATE TABLE IF NOT EXISTS room_default_levels(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    level INTEGER NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS room_default_levels_event_id ON room_default_levels(event_id);
+CREATE INDEX IF NOT EXISTS room_default_levels_room_id ON room_default_levels(room_id);
+
+
+CREATE TABLE IF NOT EXISTS room_add_state_levels(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    level INTEGER NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS room_add_state_levels_event_id ON room_add_state_levels(event_id);
+CREATE INDEX IF NOT EXISTS room_add_state_levels_room_id ON room_add_state_levels(room_id);
+
+
+CREATE TABLE IF NOT EXISTS room_send_event_levels(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    level INTEGER NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS room_send_event_levels_event_id ON room_send_event_levels(event_id);
+CREATE INDEX IF NOT EXISTS room_send_event_levels_room_id ON room_send_event_levels(room_id);
+
+
+CREATE TABLE IF NOT EXISTS room_ops_levels(
+    event_id TEXT NOT NULL,
+    room_id TEXT NOT NULL,
+    ban_level INTEGER,
+    kick_level INTEGER
+);
+
+CREATE INDEX IF NOT EXISTS room_ops_levels_event_id ON room_ops_levels(event_id);
+CREATE INDEX IF NOT EXISTS room_ops_levels_room_id ON room_ops_levels(room_id);
+
+
+CREATE TABLE IF NOT EXISTS room_hosts(
+    room_id TEXT NOT NULL,
+    host TEXT NOT NULL,
+    CONSTRAINT room_hosts_uniq UNIQUE (room_id, host) ON CONFLICT IGNORE
+);
+
+CREATE INDEX IF NOT EXISTS room_hosts_room_id ON room_hosts (room_id);
+
+PRAGMA user_version = 2;
diff --git a/synapse/storage/schema/edge_pdus.sql b/synapse/storage/schema/edge_pdus.sql
index 17b3c52f0d..8a00868065 100644
--- a/synapse/storage/schema/edge_pdus.sql
+++ b/synapse/storage/schema/edge_pdus.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql
index dbefbbda31..6ffea51310 100644
--- a/synapse/storage/schema/im.sql
+++ b/synapse/storage/schema/im.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/keys.sql b/synapse/storage/schema/keys.sql
index 45cdbcecae..706a1a03ff 100644
--- a/synapse/storage/schema/keys.sql
+++ b/synapse/storage/schema/keys.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/pdu.sql b/synapse/storage/schema/pdu.sql
index ca3de005e9..16e111a56c 100644
--- a/synapse/storage/schema/pdu.sql
+++ b/synapse/storage/schema/pdu.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/presence.sql b/synapse/storage/schema/presence.sql
index b1081d3aab..595b3b5a69 100644
--- a/synapse/storage/schema/presence.sql
+++ b/synapse/storage/schema/presence.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/profiles.sql b/synapse/storage/schema/profiles.sql
index 1092d7672c..58209f1af0 100644
--- a/synapse/storage/schema/profiles.sql
+++ b/synapse/storage/schema/profiles.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/room_aliases.sql b/synapse/storage/schema/room_aliases.sql
index d72b79e6ed..9191016814 100644
--- a/synapse/storage/schema/room_aliases.sql
+++ b/synapse/storage/schema/room_aliases.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/transactions.sql b/synapse/storage/schema/transactions.sql
index 4b1a2368f6..88e3e4e04d 100644
--- a/synapse/storage/schema/transactions.sql
+++ b/synapse/storage/schema/transactions.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/schema/users.sql b/synapse/storage/schema/users.sql
index 46b60297cb..2519702971 100644
--- a/synapse/storage/schema/users.sql
+++ b/synapse/storage/schema/users.sql
@@ -1,4 +1,4 @@
-/* Copyright 2014 matrix.org
+/* Copyright 2014 OpenMarket Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py
index 0b78222827..2cb0067a67 100644
--- a/synapse/storage/stream.py
+++ b/synapse/storage/stream.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/storage/transactions.py b/synapse/storage/transactions.py
index a277e4971a..7467e1035b 100644
--- a/synapse/storage/transactions.py
+++ b/synapse/storage/transactions.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/streams/__init__.py b/synapse/streams/__init__.py
index fe8a073cd3..f9811bfa04 100644
--- a/synapse/streams/__init__.py
+++ b/synapse/streams/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/streams/config.py b/synapse/streams/config.py
index 01bab568ff..6483ce2e25 100644
--- a/synapse/streams/config.py
+++ b/synapse/streams/config.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/streams/events.py b/synapse/streams/events.py
index 08d6e6f733..41715436b0 100644
--- a/synapse/streams/events.py
+++ b/synapse/streams/events.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/types.py b/synapse/types.py
index 1a9dceabf5..c51bc8e4f2 100644
--- a/synapse/types.py
+++ b/synapse/types.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py
index 3ea431a7f9..c9a73b0413 100644
--- a/synapse/util/__init__.py
+++ b/synapse/util/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/async.py b/synapse/util/async.py
index ebbdc00ae1..647ea6142c 100644
--- a/synapse/util/async.py
+++ b/synapse/util/async.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/distributor.py b/synapse/util/distributor.py
index 9605d7d1b9..1de50e049f 100644
--- a/synapse/util/distributor.py
+++ b/synapse/util/distributor.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -32,7 +32,9 @@ class Distributor(object):
       model will do for today.
     """
 
-    def __init__(self):
+    def __init__(self, suppress_failures=True):
+        self.suppress_failures = suppress_failures
+
         self.signals = {}
         self.pre_registration = {}
 
@@ -40,7 +42,9 @@ class Distributor(object):
         if name in self.signals:
             raise KeyError("%r already has a signal named %s" % (self, name))
 
-        self.signals[name] = Signal(name)
+        self.signals[name] = Signal(name,
+            suppress_failures=self.suppress_failures,
+        )
 
         if name in self.pre_registration:
             signal = self.signals[name]
@@ -74,8 +78,9 @@ class Signal(object):
     method into all of the observers.
     """
 
-    def __init__(self, name):
+    def __init__(self, name, suppress_failures):
         self.name = name
+        self.suppress_failures = suppress_failures
         self.observers = []
 
     def observe(self, observer):
@@ -104,6 +109,10 @@ class Signal(object):
                         failure.type,
                         failure.value,
                         failure.getTracebackObject()))
+                if not self.suppress_failures:
+                    raise failure
             deferreds.append(d.addErrback(eb))
 
-        return defer.DeferredList(deferreds)
+        return defer.DeferredList(
+            deferreds, fireOnOneErrback=not self.suppress_failures
+        )
diff --git a/synapse/util/jsonobject.py b/synapse/util/jsonobject.py
index e2840b59f9..6c99705747 100644
--- a/synapse/util/jsonobject.py
+++ b/synapse/util/jsonobject.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/lockutils.py b/synapse/util/lockutils.py
index d0bb50d035..3a84c09db4 100644
--- a/synapse/util/lockutils.py
+++ b/synapse/util/lockutils.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/logutils.py b/synapse/util/logutils.py
index 3d7c46ae44..fadf0bd510 100644
--- a/synapse/util/logutils.py
+++ b/synapse/util/logutils.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/synapse/util/stringutils.py b/synapse/util/stringutils.py
index e1b0796e56..8767e437dd 100644
--- a/synapse/util/stringutils.py
+++ b/synapse/util/stringutils.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2014 matrix.org
+# Copyright 2014 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.