/* * Copyright (C) 2016 Charybdis Development Team * Copyright (C) 2016 Jason Volk * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include namespace ircd::m { struct log::log log { "matrix", 'm' }; std::map modules; std::list listeners; static void leave_ircd_room(); static void join_ircd_room(); } const ircd::m::user::id::buf ircd_user_id { "ircd", ircd::my_host() //TODO: hostname }; ircd::m::user ircd::m::me { ircd_user_id }; const ircd::m::room::id::buf ircd_room_id { "ircd", ircd::my_host() }; ircd::m::room ircd::m::my_room { ircd_room_id }; const ircd::m::room::id::buf control_room_id { "control", ircd::my_host() }; ircd::m::room ircd::m::control { control_room_id }; // // init // ircd::m::init::init() try :conf { ircd::conf } ,_keys { conf } { modules(); if(db::sequence(*event::events) == 0) bootstrap(); listeners(); join_ircd_room(); } catch(const m::error &e) { log.error("%s %s", e.what(), e.content); throw; } catch(const std::exception &e) { log.error("%s", e.what()); throw; } ircd::m::init::~init() noexcept try { leave_ircd_room(); m::listeners.clear(); vm::fronts.map.clear(); m::modules.clear(); } catch(const m::error &e) { log.critical("%s %s", e.what(), e.content); ircd::terminate(); } void ircd::m::init::modules() { const string_view prefixes[] { "m_", "client_", "key_", "federation_", "media_" }; for(const auto &name : mods::available()) if(startswith_any(name, std::begin(prefixes), std::end(prefixes))) m::modules.emplace(name, name); m::modules.emplace("root.so"s, "root.so"s); } namespace ircd::m { static void init_listener(const json::object &conf, const json::object &opts, const string_view &bindaddr); static void init_listener(const json::object &conf, const json::object &opts); } void ircd::m::init::listeners() { const json::array listeners { conf["listeners"] }; if(m::listeners.empty()) init_listener(conf, {}); else for(const json::object opts : listeners) init_listener(conf, opts); } static void ircd::m::init_listener(const json::object &conf, const json::object &opts) { const json::array binds { opts["bind_addresses"] }; if(binds.empty()) init_listener(conf, opts, "0.0.0.0"); else for(const auto &bindaddr : binds) init_listener(conf, opts, unquote(bindaddr)); } static void ircd::m::init_listener(const json::object &conf, const json::object &opts, const string_view &host) { const json::array resources { opts["resources"] }; // resources has multiple names with different configs which are being // ignored :-/ const std::string name{"Matrix"s}; // Translate synapse options to our options (which reflect asio::ssl) const json::strung options{json::members { { "name", name }, { "host", host }, { "port", opts.get("port", 8448) }, { "ssl_certificate_file_pem", conf["tls_certificate_path"] }, { "ssl_private_key_file_pem", conf["tls_private_key_path"] }, { "ssl_tmp_dh_file", conf["tls_dh_params_path"] }, }}; m::listeners.emplace_back(options); } void ircd::m::init::bootstrap() { assert(event::events); assert(db::sequence(*event::events) == 0); ircd::log::notice ( "This appears to be your first time running IRCd because the events " "database is empty. I will be bootstrapping it with initial events now..." ); create(my_room, me.user_id); send(my_room, me.user_id, "m.room.name", "", { { "name", "IRCd's Room" } }); create(control, me.user_id); send(control, me.user_id, "m.room.name", "", { { "name", "Control Room" } }); create(user::accounts, me.user_id); join(user::accounts, me.user_id); send(user::accounts, me.user_id, "m.room.name", "", { { "name", "User Accounts" } }); create(user::sessions, me.user_id); send(user::sessions, me.user_id, "m.room.name", "", { { "name", "User Sessions" } }); create(filter::filters, me.user_id); send(filter::filters, me.user_id, "m.room.name", "", { { "name", "User Filters Database" } }); _keys.bootstrap(); message(control, me.user_id, "Welcome to the control room."); message(control, me.user_id, "I am the daemon. You can talk to me in this room by highlighting me."); } bool ircd::m::self::host(const string_view &s) { return s == host(); } ircd::string_view ircd::m::self::host() { return "zemos.net"; //me.user_id.host(); } void ircd::m::join_ircd_room() try { join(my_room, me.user_id); my_room.get([](const auto &event) { std::cout << "mr G: " << event << std::endl; }); my_room.prev([](const auto &event) { std::cout << "mr P: " << event << std::endl; }); } catch(const m::ALREADY_MEMBER &e) { log.warning("IRCd did not shut down correctly..."); } void ircd::m::leave_ircd_room() { leave(my_room, me.user_id); } // // dbs // namespace ircd::m::dbs { std::map modules; std::map> databases; void init_modules(); void init_databases(); } ircd::m::dbs::init::init() { init_modules(); init_databases(); ircd::m::event::events = databases.at("events").get(); } ircd::m::dbs::init::~init() noexcept { ircd::m::event::events = nullptr; databases.clear(); modules.clear(); } void ircd::m::dbs::init_databases() { for(const auto &pair : modules) { const auto &name(pair.first); const auto dbname(mods::unpostfixed(name)); const std::string shortname(lstrip(dbname, "db_")); const std::string symname(shortname + "_database"s); databases.emplace(shortname, import_shared { dbname, symname }); } } void ircd::m::dbs::init_modules() { for(const auto &name : mods::available()) if(startswith(name, "db_")) modules.emplace(name, name); } /////////////////////////////////////////////////////////////////////////////// // // m/filter.h // const ircd::m::room::id::buf filters_room_id { "filters", ircd::my_host() }; ircd::m::room ircd::m::filter::filters { filters_room_id }; ircd::m::filter::filter(const string_view &filter_id, const mutable_buffer &buf) { const m::vm::query query { { "room_id", filters.room_id }, { "type", "ircd.filter" }, { "state_key", filter_id }, }; size_t len{0}; m::vm::test(query, [&buf, &len] (const auto &event) { len = copy(buf, json::get<"content"_>(event)); return true; }); new (this) filter{json::object{buf}}; } size_t ircd::m::filter::size(const string_view &filter_id) { const m::vm::query query { { "room_id", filters.room_id }, { "type", "ircd.filter" }, { "state_key", filter_id }, }; size_t ret{0}; m::vm::test(query, [&ret] (const auto &event) { const string_view content { json::get<"content"_>(event) }; ret = content.size(); return true; }); return ret; } /////////////////////////////////////////////////////////////////////////////// // // m/user.h // const ircd::m::room::id::buf accounts_room_id { "accounts", ircd::my_host() }; ircd::m::room ircd::m::user::accounts { accounts_room_id }; const ircd::m::room::id::buf sessions_room_id { "sessions", ircd::my_host() }; ircd::m::room ircd::m::user::sessions { sessions_room_id }; /// Register the user by joining them to the accounts room. /// /// The content of the join event may store keys including the registration /// options. Once this call completes the join was successful and the user is /// registered, otherwise throws. void ircd::m::user::activate(const json::members &contents) try { json::iov content; json::iov::push push[] { { content, { "membership", "join" }}, }; size_t i(0); json::iov::push _content[contents.size()]; for(const auto &member : contents) new (_content + i++) json::iov::push(content, member); send(accounts, me.user_id, "ircd.user", user_id, content); join(control, user_id); } catch(const m::ALREADY_MEMBER &e) { throw m::error { http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." }; } void ircd::m::user::deactivate(const json::members &contents) { json::iov content; json::iov::push push[] { { content, { "membership", "leave" }}, }; size_t i(0); json::iov::push _content[contents.size()]; for(const auto &member : contents) new (_content + i++) json::iov::push(content, member); send(accounts, me.user_id, "ircd.user", user_id, content); } void ircd::m::user::password(const string_view &password) try { //TODO: ADD SALT char b64[64]; uint8_t hash[32]; sha256{hash, const_buffer{password}}; const auto digest{b64encode_unpadded(b64, hash)}; send(accounts, me.user_id, "ircd.password", user_id, { { "sha256", digest } }); } catch(const m::ALREADY_MEMBER &e) { throw m::error { http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." }; } bool ircd::m::user::is_password(const string_view &supplied_password) const { const vm::query member_event { { "room_id", accounts.room_id }, { "type", "ircd.password" }, { "state_key", user_id }, }; //TODO: ADD SALT char b64[64]; uint8_t hash[32]; sha256{hash, const_buffer{supplied_password}}; const auto supplied_hash{b64encode_unpadded(b64, hash)}; const vm::query correct_password{[&supplied_hash] (const auto &event) { const json::object &content { json::at<"content"_>(event) }; const auto &correct_hash { unquote(content.at("sha256")) }; return supplied_hash == correct_hash; }}; const auto query { member_event && correct_password }; return m::vm::test(member_event && correct_password); } bool ircd::m::user::is_active() const { bool ret{false}; accounts.get("ircd.user", user_id, [&ret] (const auto &event) { const json::object &content { at<"content"_>(event) }; ret = unquote(content.at("membership")) == "join"; }); return ret; }