// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 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. The // full license for this software is available in the LICENSE file. decltype(ircd::m::log) ircd::m::log { "matrix", 'm' }; decltype(ircd::m::listeners) ircd::m::listeners {}; // // init // ircd::conf::item me_online_status_msg { { "name", "ircd.me.online.status_msg" }, { "default", "Wanna chat? IRCd at your service!" } }; ircd::conf::item me_offline_status_msg { { "name", "ircd.me.offline.status_msg" }, { "default", "Catch ya on the flip side..." } }; struct ircd::m::init::modules { modules(const json::object &); ~modules() noexcept; }; struct ircd::m::init::listeners { listeners(const json::object &); ~listeners() noexcept; }; // // init::init // ircd::m::init::init() try :config { ircd::conf::config } ,_self { this->config } ,_keys { this->config } ,modules{[this] { auto ret{std::make_unique(config)}; //TODO: XXX // Because s_conf is loaded before other modules are loaded, all items // have not registered at that time. s_conf reads the !conf room on load // but each conf::item does not read from the conf room itself when it // loads after that. Instead they start with their "default" value. This // isn't good, but for the time being we can trigger a rehash here. import rehash_conf { "s_conf", "rehash_conf" }; // if(rehash_conf) // rehash_conf(); if(db::sequence(*dbs::events) == 0) bootstrap(); return ret; }()} ,listeners { std::make_unique(config) } { presence::set(me, "online", me_online_status_msg); } catch(const m::error &e) { log.error("%s %s", e.what(), e.content); } catch(const std::exception &e) { log.error("%s", e.what()); } ircd::m::init::~init() noexcept try { listeners.reset(nullptr); presence::set(me, "offline", me_offline_status_msg); } catch(const m::error &e) { log.critical("%s %s", e.what(), e.content); ircd::terminate(); } void ircd::m::init::close() { listeners.reset(nullptr); } ircd::m::init::modules::modules(const json::object &config) try { if(ircd::noautomod) { log::warning { "Not loading modules because noautomod flag is set. " "You may still load modules manually." }; return; } // Manually load first modules m::modules.emplace("vm"s, "vm"s); m::modules.emplace("vm_fetch"s, "vm_fetch"s); // The order of these prefixes will be the loading order. Order of // specific modules within a prefix is not determined here. static const string_view prefixes[] { "s_", "m_", "key_", "media_", "client_", "federation_" }; // Load modules by prefix. for(const auto &prefix : prefixes) for(const auto &name : mods::available()) if(startswith(name, prefix)) m::modules.emplace(name, name); // Manually load last modules m::modules.emplace("webroot"s, "webroot"s); } catch(...) { this->~modules(); } ircd::m::init::modules::~modules() noexcept { m::modules.clear(); } namespace ircd::m { static void init_listener(const json::object &config, const string_view &name, const json::object &conf); } ircd::m::init::listeners::listeners(const json::object &config) try { if(ircd::nolisten) { log::warning { "Not listening on any addresses because nolisten flag is set." }; return; } const json::array listeners { config[{"ircd", "listen"}] }; for(const auto &name : listeners) try { const json::object &opts { config.at({"listen", unquote(name)}) }; if(!opts.has("tmp_dh_path")) throw user_error { "Listener %s requires a 'tmp_dh_path' in the config. We do not" " create this yet. Try `openssl dhparam -outform PEM -out dh512.pem 512`", name }; init_listener(config, unquote(name), opts); } catch(const json::not_found &e) { throw ircd::user_error { "Failed to find configuration block for listener %s", name }; } } catch(...) { this->~listeners(); } ircd::m::init::listeners::~listeners() noexcept { m::listeners.clear(); } static void ircd::m::init_listener(const json::object &config, const string_view &name, const json::object &opts) { m::listeners.emplace_back(name, opts); } void ircd::m::init::bootstrap() { assert(dbs::events); assert(db::sequence(*dbs::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..." ); if(me.user_id.hostname() == "localhost") log::warning { "The ircd.origin is configured to localhost. This is probably not" " what you want. To fix this now, you will have to remove the " " database and start over." }; create(user::users, me.user_id); create(my_room, me.user_id); create(me.user_id); me.activate(); join(my_room, me.user_id); send(my_room, me.user_id, "m.room.name", "", { { "name", "IRCd's Room" } }); send(my_room, me.user_id, "m.room.topic", "", { { "topic", "The daemon's den." } }); send(user::users, me.user_id, "m.room.name", "", { { "name", "Users" } }); create(user::tokens, me.user_id); send(user::tokens, me.user_id, "m.room.name", "", { { "name", "User Tokens" } }); } /////////////////////////////////////////////////////////////////////////////// // // m/self.h // // // my user // ircd::m::user::id::buf ircd_user_id { "ircd", "localhost" // gets replaced after conf init }; ircd::m::user ircd::m::me { ircd_user_id }; // // my room // ircd::m::room::id::buf ircd_room_id { "ircd", "localhost" // replaced after conf init }; ircd::m::room ircd::m::my_room { ircd_room_id }; // // my node // ircd::m::node::id::buf ircd_node_id { "", "localhost" // replaced after conf init }; ircd::m::node ircd::m::my_node { ircd_node_id }; bool ircd::m::self::host(const string_view &s) { return s == host(); } ircd::string_view ircd::m::self::host() { return me.user_id.host(); } // // init // ircd::m::self::init::init(const json::object &config) { const string_view &origin_name { unquote(config.get({"ircd", "origin"}, "localhost")) }; ircd_user_id = {"ircd", origin_name}; m::me = {ircd_user_id}; ircd_room_id = {"ircd", origin_name}; m::my_room = {ircd_room_id}; ircd_node_id = {"", origin_name}; m::my_node = {ircd_node_id}; if(origin_name == "localhost") log::warning { "The ircd.origin is configured or has defaulted to 'localhost'" }; } ircd::m::self::init::~init() noexcept { } /////////////////////////////////////////////////////////////////////////////// // // m/vm.h // decltype(ircd::m::vm::log) ircd::m::vm::log { "vm", 'v' }; decltype(ircd::m::vm::accept) ircd::m::vm::accept {}; decltype(ircd::m::vm::current_sequence) ircd::m::vm::current_sequence {}; decltype(ircd::m::vm::default_opts) ircd::m::vm::default_opts {}; decltype(ircd::m::vm::default_copts) ircd::m::vm::default_copts {}; // // Eval // // Processes any event from any place from any time and does whatever is // necessary to validate, reject, learn from new information, ignore old // information and advance the state of IRCd as best as possible. /// Instance list linkage for all of the evaluations. template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list {}; decltype(ircd::m::vm::eval::id_ctr) ircd::m::vm::eval::id_ctr {}; // // eval::eval // ircd::m::vm::eval::eval(const room &room, json::iov &event, const json::iov &content) :eval{} { operator()(room, event, content); } ircd::m::vm::eval::eval(json::iov &event, const json::iov &content, const vm::copts &opts) :eval{opts} { operator()(event, content); } ircd::m::vm::eval::eval(const event &event, const vm::opts &opts) :eval{opts} { operator()(event); } ircd::m::vm::eval::eval(const vm::copts &opts) :opts{&opts} ,copts{&opts} { } ircd::m::vm::eval::eval(const vm::opts &opts) :opts{&opts} { } ircd::m::vm::eval::~eval() noexcept { } ircd::m::vm::eval::operator const event::id::buf &() const { return event_id; } /// /// Figure 1: /// in . <-- injection /// ===:::::::==// /// | ||||||| // <-- these functions /// | \\|// //| /// | ||| // | | acceleration /// | |||// | | /// | |||/ | | /// | ||| | V /// | !!! | /// | * | <----- nozzle /// | ///|||\\\ | /// |/|/|/|\|\|\| <---- propagation cone /// _/|/|/|/|\|\|\|\_ /// out /// /// Inject a new event in a room originating from this server. /// enum ircd::m::vm::fault ircd::m::vm::eval::operator()(const room &room, json::iov &event, const json::iov &contents) { using prototype = fault (eval &, const m::room &, json::iov &, const json::iov &); static import function { "vm", "eval__commit_room" }; return function(*this, room, event, contents); } /// Inject a new event originating from this server. /// enum ircd::m::vm::fault ircd::m::vm::eval::operator()(json::iov &event, const json::iov &contents) { using prototype = fault (eval &, json::iov &, const json::iov &); static import function { "vm", "eval__commit" }; return function(*this, event, contents); } enum ircd::m::vm::fault ircd::m::vm::eval::operator()(const event &event) { using prototype = fault (eval &, const m::event &); static import function { "vm", "eval__event" }; return function(*this, event); } const uint64_t & ircd::m::vm::sequence(const eval &eval) { return eval.sequence; } uint64_t ircd::m::vm::retired_sequence() { event::id::buf event_id; return retired_sequence(event_id); } uint64_t ircd::m::vm::retired_sequence(event::id::buf &event_id) { static constexpr auto column_idx { json::indexof() }; auto &column { dbs::event_column.at(column_idx) }; const auto it { column.rbegin() }; if(!it) { // If this iterator is invalid the events db should // be completely fresh. assert(db::sequence(*dbs::events) == 0); return 0; } const auto &ret { byte_view(it->first) }; event_id = it->second; return ret; } ircd::string_view ircd::m::vm::reflect(const enum fault &code) { switch(code) { case fault::ACCEPT: return "ACCEPT"; case fault::EXISTS: return "EXISTS"; case fault::INVALID: return "INVALID"; case fault::DEBUGSTEP: return "DEBUGSTEP"; case fault::BREAKPOINT: return "BREAKPOINT"; case fault::GENERAL: return "GENERAL"; case fault::EVENT: return "EVENT"; case fault::STATE: return "STATE"; case fault::INTERRUPT: return "INTERRUPT"; } return "??????"; } // // vm::phase // /// Instance list linkage for all of the vm phases template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list {}; ircd::m::vm::phase::phase(const string_view &name, decltype(function) function) :name{name} ,function{std::move(function)} { } void ircd::m::vm::phase::operator()(eval &eval) { auto theirs(eval.phase); eval.phase = this; const unwind _{[&eval, &theirs] { eval.phase = theirs; }}; if(likely(function)) function(eval); } // // accepted // ircd::m::vm::accepted::accepted(const m::event &event, const vm::opts *const &opts, const event::conforms *const &report) :m::event{event} ,context{ctx::current} ,opts{opts} ,report{report} { } /////////////////////////////////////////////////////////////////////////////// // // m/keys.h // bool ircd::m::verify(const m::keys &keys) { using prototype = bool (const m::keys &) noexcept; static import function { "key_keys", "verify__keys" }; return function(keys); } // // m::self // ircd::ed25519::sk ircd::m::self::secret_key {}; ircd::ed25519::pk ircd::m::self::public_key {}; std::string ircd::m::self::public_key_b64 {}; std::string ircd::m::self::public_key_id {}; std::string ircd::m::self::tls_cert_der {}; std::string ircd::m::self::tls_cert_der_sha256_b64 {}; // // keys // void ircd::m::keys::get(const string_view &server_name, const closure &closure) { return get(server_name, "", closure); } void ircd::m::keys::get(const string_view &server_name, const string_view &key_id, const closure &closure_) { using prototype = void (const string_view &, const string_view &, const closure &); static import function { "key_keys", "get__keys" }; return function(server_name, key_id, closure_); } bool ircd::m::keys::query(const string_view &query_server, const queries &queries_, const closure_bool &closure) { using prototype = bool (const string_view &, const queries &, const closure_bool &); static import function { "key_keys", "query__keys" }; return function(query_server, queries_, closure); } // // init // ircd::m::keys::init::init(const json::object &config) :config{config} { certificate(); signing(); } ircd::m::keys::init::~init() noexcept { } void ircd::m::keys::init::certificate() { const string_view origin { unquote(this->config.at({"ircd", "origin"})) }; const json::object config { this->config.at({"origin", origin}) }; const std::string private_key_file { unquote(config.get("ssl_private_key_pem_path")) }; const std::string public_key_file { unquote(config.get("ssl_public_key_pem_path", private_key_file + ".pub")) }; if(!private_key_file) throw user_error { "You must specify an SSL private key file at" " origin.[%s].ssl_private_pem_path even if you do not have one;" " it will be created there.", origin }; if(!fs::exists(private_key_file)) { log::warning { "Failed to find certificate private key @ `%s'; creating...", private_key_file }; openssl::genrsa(private_key_file, public_key_file); } const std::string cert_file { unquote(config.get("ssl_certificate_pem_path")) }; if(!cert_file) throw user_error { "You must specify an SSL certificate file path in the config at" " origin.[%s].ssl_certificate_pem_path even if you do not have one;" " it will be created there.", origin }; if(!fs::exists(cert_file)) { if(!this->config.has({"certificate", origin, "subject"})) throw user_error { "Failed to find SSL certificate @ `%s'. Additionally, no" " certificate.[%s].subject was found in the conf to generate one.", cert_file, origin }; log::warning { "Failed to find SSL certificate @ `%s'; creating for '%s'...", cert_file, origin }; const unique_buffer buf { 1_MiB }; const json::strung opts{json::members { { "private_key_pem_path", private_key_file }, { "public_key_pem_path", public_key_file }, { "subject", this->config.get({"certificate", origin, "subject"}) } }}; const auto cert { openssl::genX509_rsa(buf, opts) }; fs::overwrite(cert_file, cert); } const auto cert_pem { fs::read(cert_file) }; const unique_buffer der_buf { 8_KiB }; const auto cert_der { openssl::cert2d(der_buf, cert_pem) }; const fixed_buffer hash { sha256{cert_der} }; self::tls_cert_der_sha256_b64 = { b64encode_unpadded(hash) }; log.info("Certificate `%s' :PEM %zu bytes; DER %zu bytes; sha256b64 %s", cert_file, cert_pem.size(), ircd::size(cert_der), self::tls_cert_der_sha256_b64); thread_local char print_buf[8_KiB]; log.info("Certificate `%s' :%s", cert_file, openssl::print_subject(print_buf, cert_pem)); } void ircd::m::keys::init::signing() { const string_view origin { unquote(this->config.at({"ircd", "origin"})) }; const json::object config { this->config.at({"origin", unquote(origin)}) }; const std::string sk_file { unquote(config.get("ed25519_private_key_path")) }; if(!sk_file) throw user_error { "Failed to find ed25519 secret key path at" " origin.[%s].ed25519_private_key_path in config. If you do not" " have a private key, specify a path for one to be created.", origin }; if(fs::exists(sk_file)) log.info("Using ed25519 secret key @ `%s'", sk_file); else log.notice("Creating ed25519 secret key @ `%s'", sk_file); self::secret_key = ed25519::sk { sk_file, &self::public_key }; self::public_key_b64 = b64encode_unpadded(self::public_key); const fixed_buffer hash { sha256{self::public_key} }; const auto public_key_hash_b58 { b58encode(hash) }; static const auto trunc_size{8}; self::public_key_id = fmt::snstringf { BUFSIZE, "ed25519:%s", trunc(public_key_hash_b58, trunc_size) }; log.info("Current key is '%s' and the public key is: %s", self::public_key_id, self::public_key_b64); } /////////////////////////////////////////////////////////////////////////////// // // m/visible.h // bool ircd::m::visible(const event::id &event_id, const string_view &mxid) { m::room::id::buf room_id { get(event_id, "room_id", room_id) }; const m::event event { { "event_id", event_id }, { "room_id", room_id } }; return visible(event, mxid); } bool ircd::m::visible(const event &event, const string_view &mxid) { using prototype = bool (const m::event &, const string_view &); static import function { "m_room_history_visibility", "visible" }; return function(event, mxid); } /////////////////////////////////////////////////////////////////////////////// // // m/receipt.h // ircd::m::event::id::buf ircd::m::receipt::read(const id::room &room_id, const id::user &user_id, const id::event &event_id) { return read(room_id, user_id, event_id, ircd::time()); } ircd::m::event::id::buf ircd::m::receipt::read(const id::room &room_id, const id::user &user_id, const id::event &event_id, const time_t &ms) { using prototype = event::id::buf (const id::room &, const id::user &, const id::event &, const time_t &); static import function { "client_rooms", "commit__m_receipt_m_read" }; return function(room_id, user_id, event_id, ms); } /////////////////////////////////////////////////////////////////////////////// // // m/typing.h // ircd::m::event::id::buf ircd::m::typing::set(const m::typing &object) { using prototype = event::id::buf (const m::typing &); static import function { "client_rooms", "commit__m_typing" }; return function(object); } /////////////////////////////////////////////////////////////////////////////// // // m/presence.h // ircd::m::presence::presence(const user &user, const mutable_buffer &buf) :presence { get(user, buf) } { } ircd::m::event::id::buf ircd::m::presence::set(const user &user, const string_view &presence, const string_view &status_msg) { return set(m::presence { { "user_id", user.user_id }, { "presence", presence }, { "status_msg", status_msg }, }); } ircd::m::event::id::buf ircd::m::presence::set(const presence &object) { using prototype = event::id::buf (const presence &); static import function { "m_presence", "commit__m_presence" }; return function(object); } ircd::json::object ircd::m::presence::get(const user &user, const mutable_buffer &buffer) { json::object ret; get(std::nothrow, user, [&ret, &buffer] (const json::object &object) { ret = { data(buffer), copy(buffer, object) }; }); return ret; } void ircd::m::presence::get(const user &user, const closure &closure) { get(user, [&closure] (const m::event &event, const json::object &content) { closure(content); }); } void ircd::m::presence::get(const user &user, const event_closure &closure) { if(!get(std::nothrow, user, closure)) throw m::NOT_FOUND { "No presence found for %s", string_view{user.user_id} }; } bool ircd::m::presence::get(std::nothrow_t, const user &user, const closure &closure) { return get(std::nothrow, user, [&closure] (const m::event &event, const json::object &content) { closure(content); }); } bool ircd::m::presence::get(std::nothrow_t, const user &user, const event_closure &closure) { using prototype = bool (std::nothrow_t, const m::user &, const event_closure &); static import function { "m_presence", "get__m_presence" }; return function(std::nothrow, user, closure); } bool ircd::m::presence::valid_state(const string_view &state) { using prototype = bool (const string_view &); static import function { "m_presence", "presence_valid_state" }; return function(state); } /////////////////////////////////////////////////////////////////////////////// // // m/node.h // /// ID of the room which indexes all nodes (an instance of the room is /// provided below). const ircd::m::room::id::buf nodes_room_id { "nodes", ircd::my_host() }; /// The nodes room is the database of all nodes. It primarily serves as an /// indexing mechanism and for top-level node related keys. /// const ircd::m::room ircd::m::nodes { nodes_room_id }; ircd::m::node ircd::m::create(const id::node &node_id, const json::members &args) { using prototype = node (const id::node &, const json::members &); static import function { "s_node", "create_node" }; assert(node_id); return function(node_id, args); } bool ircd::m::exists(const node::id &node_id) { using prototype = bool (const node::id &); static import function { "s_node", "exists__nodeid" }; return function(node_id); } bool ircd::m::my(const node &node) { return my(node.node_id); } // // node // void ircd::m::node::key(const string_view &key_id, const ed25519_closure &closure) const { key(key_id, key_closure{[&closure] (const string_view &keyb64) { const ed25519::pk pk { [&keyb64](auto &buf) { b64decode(buf, unquote(keyb64)); } }; closure(pk); }}); } void ircd::m::node::key(const string_view &key_id, const key_closure &closure) const { const auto &server_name { node_id.hostname() }; m::keys::get(server_name, key_id, [&closure, &key_id] (const json::object &keys) { const json::object &vks { keys.at("verify_keys") }; const json::object &vkk { vks.at(key_id) }; const string_view &key { vkk.at("key") }; closure(key); }); } /// Generates a node-room ID into buffer; see room_id() overload. ircd::m::id::room::buf ircd::m::node::room_id() const { ircd::m::id::room::buf buf; return buf.assigned(room_id(buf)); } /// This generates a room mxid for the "node's room" essentially serving as /// a database mechanism for this specific node. This room_id is a hash of /// the node's full mxid. /// ircd::m::id::room ircd::m::node::room_id(const mutable_buffer &buf) const { assert(!empty(node_id)); const sha256::buf hash { sha256{node_id} }; char b58[size(hash) * 2]; return { buf, b58encode(b58, hash), my_host() }; } // // node::room // ircd::m::node::room::room(const m::node::id &node_id) :room{m::node{node_id}} {} ircd::m::node::room::room(const m::node &node) :node{node} ,room_id{node.room_id()} { static_cast(*this) = room_id; } /////////////////////////////////////////////////////////////////////////////// // // m/events.h // bool ircd::m::events::rfor_each(const event::idx &start, const event_filter &filter, const closure_bool &closure) { auto limit { json::get<"limit"_>(filter)?: 32L }; return rfor_each(start, [&filter, &closure, &limit] (const event::idx &event_idx, const m::event &event) -> bool { if(!match(filter, event)) return true; if(!closure(event_idx, event)) return false; return --limit; }); } bool ircd::m::events::rfor_each(const event::idx &start, const closure_bool &closure) { event::fetch event; return rfor_each(start, id_closure_bool{[&event, &closure] (const event::idx &event_idx, const event::id &event_id) { if(!seek(event, event_idx, std::nothrow)) return true; return closure(event_idx, event); }}); } bool ircd::m::events::rfor_each(const event::idx &start, const id_closure_bool &closure) { static const db::gopts opts { db::get::NO_CACHE }; static constexpr auto column_idx { json::indexof() }; auto &column { dbs::event_column.at(column_idx) }; if(start == uint64_t(-1)) { for(auto it(column.rbegin(opts)); it; ++it) if(!closure(byte_view(it->first), it->second)) return false; return true; } auto it { column.lower_bound(byte_view(start), opts) }; for(; it; --it) if(!closure(byte_view(it->first), it->second)) return false; return true; } bool ircd::m::events::for_each(const event::idx &start, const event_filter &filter, const closure_bool &closure) { auto limit { json::get<"limit"_>(filter)?: 32L }; return for_each(start, [&filter, &closure, &limit] (const event::idx &event_idx, const m::event &event) -> bool { if(!match(filter, event)) return true; if(!closure(event_idx, event)) return false; return --limit; }); } bool ircd::m::events::for_each(const event::idx &start, const closure_bool &closure) { event::fetch event; return for_each(start, id_closure_bool{[&event, &closure] (const event::idx &event_idx, const event::id &event_id) { if(!seek(event, event_idx, std::nothrow)) return true; return closure(event_idx, event); }}); } bool ircd::m::events::for_each(const event::idx &start, const id_closure_bool &closure) { static const db::gopts opts { db::get::NO_CACHE }; static constexpr auto column_idx { json::indexof() }; auto &column { dbs::event_column.at(column_idx) }; auto it { start > 0? column.lower_bound(byte_view(start), opts): column.begin(opts) }; for(; it; ++it) if(!closure(byte_view(it->first), it->second)) return false; return true; } /////////////////////////////////////////////////////////////////////////////// // // m/filter.h // //TODO: globular expression //TODO: tribool for contains_url; we currently ignore the false value. bool ircd::m::match(const room_event_filter &filter, const event &event) { if(json::get<"contains_url"_>(filter) == true) if(!at<"content"_>(event).has("url")) return false; for(const auto &room_id : json::get<"not_rooms"_>(filter)) if(at<"room_id"_>(event) == unquote(room_id)) return false; if(empty(json::get<"rooms"_>(filter))) return match(event_filter{filter}, event); for(const auto &room_id : json::get<"rooms"_>(filter)) if(at<"room_id"_>(event) == unquote(room_id)) return match(event_filter{filter}, event); return false; } //TODO: globular expression bool ircd::m::match(const event_filter &filter, const event &event) { for(const auto &type : json::get<"not_types"_>(filter)) if(at<"type"_>(event) == unquote(type)) return false; for(const auto &sender : json::get<"not_senders"_>(filter)) if(at<"sender"_>(event) == unquote(sender)) return false; if(empty(json::get<"senders"_>(filter)) && empty(json::get<"types"_>(filter))) return true; if(empty(json::get<"senders"_>(filter))) { for(const auto &type : json::get<"types"_>(filter)) if(at<"type"_>(event) == unquote(type)) return true; return false; } if(empty(json::get<"types"_>(filter))) { for(const auto &sender : json::get<"senders"_>(filter)) if(at<"sender"_>(event) == unquote(sender)) return true; return false; } return true; } // // filter // ircd::m::filter::filter(const user &user, const string_view &filter_id, const mutable_buffer &buf) { get(user, filter_id, [this, &buf] (const json::object &filter) { const size_t len { copy(buf, string_view{filter}) }; new (this) m::filter { json::object { data(buf), len } }; }); } // // room_filter // ircd::m::room_filter::room_filter(const mutable_buffer &buf, const json::members &members) :super_type::tuple { json::stringify(mutable_buffer{buf}, members) } { } // // room_event_filter // ircd::m::room_event_filter::room_event_filter(const mutable_buffer &buf, const json::members &members) :super_type::tuple { json::stringify(mutable_buffer{buf}, members) } { } // // event_filter // ircd::m::event_filter::event_filter(const mutable_buffer &buf, const json::members &members) :super_type::tuple { json::stringify(mutable_buffer{buf}, members) } { } /////////////////////////////////////////////////////////////////////////////// // // m/rooms.h // void ircd::m::rooms::for_each(const user &user, const user::rooms::closure &closure) { const m::user::rooms rooms{user}; rooms.for_each(closure); } void ircd::m::rooms::for_each(const user &user, const user::rooms::closure_bool &closure) { const m::user::rooms rooms{user}; rooms.for_each(closure); } void ircd::m::rooms::for_each(const user &user, const string_view &membership, const user::rooms::closure &closure) { const m::user::rooms rooms{user}; rooms.for_each(membership, closure); } void ircd::m::rooms::for_each(const user &user, const string_view &membership, const user::rooms::closure_bool &closure) { const m::user::rooms rooms{user}; rooms.for_each(membership, closure); } void ircd::m::rooms::for_each(const room::closure &closure) { for_each(room::closure_bool{[&closure] (const room &room) { closure(room); return true; }}); } void ircd::m::rooms::for_each(const room::closure_bool &closure) { for_each(room::id::closure_bool{[&closure] (const room::id &room_id) { return closure(room_id); }}); } void ircd::m::rooms::for_each(const room::id::closure &closure) { for_each(room::id::closure_bool{[&closure] (const room::id &room_id) { closure(room_id); return true; }}); } void ircd::m::rooms::for_each(const room::id::closure_bool &closure) { const room::state state { my_room }; state.test("ircd.room", room::state::keys_bool{[&closure] (const string_view &key) { return !closure(key); }}); } /////////////////////////////////////////////////////////////////////////////// // // m/user.h // /// ID of the room which indexes all users (an instance of the room is /// provided below). const ircd::m::room::id::buf users_room_id { "users", ircd::my_host() }; /// The users room is the database of all users. It primarily serves as an /// indexing mechanism and for top-level user related keys. Accounts /// registered on this server will be among state events in this room. /// Users do not have access to this room, it is used internally. /// ircd::m::room ircd::m::user::users { users_room_id }; /// ID of the room which stores ephemeral tokens (an instance of the room is /// provided below). const ircd::m::room::id::buf tokens_room_id { "tokens", ircd::my_host() }; /// The tokens room serves as a key-value lookup for various tokens to /// users, etc. It primarily serves to store access tokens for users. This /// is a separate room from the users room because in the future it may /// have an optimized configuration as well as being more easily cleared. /// ircd::m::room ircd::m::user::tokens { tokens_room_id }; ircd::m::user ircd::m::create(const id::user &user_id, const json::members &contents) { using prototype = user (const id::user &, const json::members &); static import function { "client_user", "user_create" }; return function(user_id, contents); } bool ircd::m::exists(const user::id &user_id) { return user::users.has("ircd.user", user_id); } bool ircd::m::exists(const user &user) { return exists(user.user_id); } bool ircd::m::my(const user &user) { return my(user.user_id); } // // user // /// Generates a user-room ID into buffer; see room_id() overload. ircd::m::id::room::buf ircd::m::user::room_id() const { ircd::m::id::room::buf buf; return buf.assigned(room_id(buf)); } /// This generates a room mxid for the "user's room" essentially serving as /// a database mechanism for this specific user. This room_id is a hash of /// the user's full mxid. /// ircd::m::id::room ircd::m::user::room_id(const mutable_buffer &buf) const { assert(!empty(user_id)); const ripemd160::buf hash { ripemd160{user_id} }; char b58[size(hash) * 2]; return { buf, b58encode(b58, hash), my_host() }; } ircd::string_view ircd::m::user::gen_access_token(const mutable_buffer &buf) { static const size_t token_max{32}; static const auto &token_dict{rand::dict::alpha}; const mutable_buffer out { data(buf), std::min(token_max, size(buf)) }; return rand::string(token_dict, out); } ircd::m::event::id::buf ircd::m::user::activate() { using prototype = event::id::buf (const m::user &); static import function { "client_account", "activate__user" }; return function(*this); } ircd::m::event::id::buf ircd::m::user::deactivate() { using prototype = event::id::buf (const m::user &); static import function { "client_account", "deactivate__user" }; return function(*this); } bool ircd::m::user::is_active() const { using prototype = bool (const m::user &); static import function { "client_account", "is_active__user" }; return function(*this); } ircd::m::event::id::buf ircd::m::user::filter(const json::object &filter, const mutable_buffer &idbuf) { using prototype = event::id::buf (const m::user &, const json::object &, const mutable_buffer &); static import function { "client_user", "filter_set" }; return function(*this, filter, idbuf); } std::string ircd::m::user::filter(const string_view &filter_id) const { std::string ret; filter(filter_id, [&ret] (const json::object &filter) { ret.assign(data(filter), size(filter)); }); return ret; } std::string ircd::m::user::filter(std::nothrow_t, const string_view &filter_id) const { std::string ret; filter(std::nothrow, filter_id, [&ret] (const json::object &filter) { ret.assign(data(filter), size(filter)); }); return ret; } void ircd::m::user::filter(const string_view &filter_id, const filter_closure &closure) const { if(!filter(std::nothrow, filter_id, closure)) throw m::NOT_FOUND { "Filter '%s' not found", filter_id }; } bool ircd::m::user::filter(std::nothrow_t, const string_view &filter_id, const filter_closure &closure) const { using prototype = bool (std::nothrow_t, const m::user &, const string_view &, const filter_closure &); static import function { "client_user", "filter_get" }; return function(std::nothrow, *this, filter_id, closure); } ircd::m::event::id::buf ircd::m::user::account_data(const m::user &sender, const string_view &type, const json::object &val) { using prototype = event::id::buf (const m::user &, const m::user &, const string_view &, const json::object &); static import function { "client_user", "account_data_set" }; return function(*this, sender, type, val); } ircd::json::object ircd::m::user::account_data(const mutable_buffer &out, const string_view &type) const { json::object ret; account_data(std::nothrow, type, [&out, &ret] (const json::object &val) { ret = string_view { data(out), copy(out, val) }; }); return ret; } bool ircd::m::user::account_data(std::nothrow_t, const string_view &type, const account_data_closure &closure) const try { account_data(type, closure); return true; } catch(const std::exception &e) { return false; } void ircd::m::user::account_data(const string_view &type, const account_data_closure &closure) const { using prototype = void (const m::user &, const string_view &, const account_data_closure &); static import function { "client_user", "account_data_get" }; return function(*this, type, closure); } ircd::m::event::id::buf ircd::m::user::profile(const m::user &sender, const string_view &key, const string_view &val) { using prototype = event::id::buf (const m::user &, const m::user &, const string_view &, const string_view &); static import function { "client_profile", "profile_set" }; return function(*this, sender, key, val); } ircd::string_view ircd::m::user::profile(const mutable_buffer &out, const string_view &key) const { string_view ret; profile(std::nothrow, key, [&out, &ret] (const string_view &val) { ret = { data(out), copy(out, val) }; }); return ret; } bool ircd::m::user::profile(std::nothrow_t, const string_view &key, const profile_closure &closure) const try { profile(key, closure); return true; } catch(const std::exception &e) { return false; } void ircd::m::user::profile(const string_view &key, const profile_closure &closure) const { using prototype = void (const m::user &, const string_view &, const profile_closure &); static import function { "client_profile", "profile_get" }; return function(*this, key, closure); } ircd::m::event::id::buf ircd::m::user::password(const string_view &password) { using prototype = event::id::buf (const m::user::id &, const string_view &) noexcept; static import function { "client_account", "set_password" }; return function(user_id, password); } bool ircd::m::user::is_password(const string_view &password) const noexcept try { using prototype = bool (const m::user::id &, const string_view &) noexcept; static import function { "client_account", "is_password" }; return function(user_id, password); } catch(const std::exception &e) { log::critical { "user::is_password(): %s %s", string_view{user_id}, e.what() }; return false; } // // user::room // ircd::m::user::room::room(const m::user::id &user_id, const vm::copts *const &copts, const event::fetch::opts *const &fopts) :room { m::user{user_id}, copts, fopts } { } ircd::m::user::room::room(const m::user &user, const vm::copts *const &copts, const event::fetch::opts *const &fopts) :user{user} ,room_id{user.room_id()} { static_cast(*this) = { room_id, copts, fopts }; } // // user::rooms // ircd::m::user::rooms::rooms(const m::user &user) :user_room{user} { } size_t ircd::m::user::rooms::count() const { size_t ret{0}; for_each([&ret] (const m::room &, const string_view &membership) { ++ret; }); return ret; } size_t ircd::m::user::rooms::count(const string_view &membership) const { size_t ret{0}; for_each(membership, [&ret] (const m::room &, const string_view &membership) { ++ret; }); return ret; } void ircd::m::user::rooms::for_each(const closure &closure) const { for_each(closure_bool{[&closure] (const m::room &room, const string_view &membership) { closure(room, membership); return true; }}); } void ircd::m::user::rooms::for_each(const closure_bool &closure) const { for_each(string_view{}, closure); } void ircd::m::user::rooms::for_each(const string_view &membership, const closure &closure) const { for_each(membership, closure_bool{[&closure] (const m::room &room, const string_view &membership) { closure(room, membership); return true; }}); } void ircd::m::user::rooms::for_each(const string_view &membership, const closure_bool &closure) const { // Setup the list of event fields to fetch for the closure static const event::keys keys { event::keys::include { "event_id", "state_key", "content", } }; const m::event::fetch::opts fopts { keys, user_room.fopts? user_room.fopts->gopts : db::gopts{} }; const m::room::state state { user_room, &fopts }; state.test("ircd.member", [&membership, &closure] (const m::event &event) { const string_view &membership_ { unquote(at<"content"_>(event).at("membership")) }; if(membership && membership_ != membership) return false; const m::room::id &room_id { at<"state_key"_>(event) }; return !closure(room_id, membership); }); } // // user::rooms::origins // ircd::m::user::rooms::origins::origins(const m::user &user) :user{user} { } void ircd::m::user::rooms::origins::for_each(const closure &closure) const { for_each(string_view{}, closure); } void ircd::m::user::rooms::origins::for_each(const closure_bool &closure) const { for_each(string_view{}, closure); } void ircd::m::user::rooms::origins::for_each(const string_view &membership, const closure &closure) const { for_each(membership, closure_bool{[&closure] (const string_view &origin) { closure(origin); return true; }}); } void ircd::m::user::rooms::origins::for_each(const string_view &membership, const closure_bool &closure) const { const m::user::rooms rooms { user }; std::set> seen; rooms.for_each(membership, rooms::closure_bool{[&closure, &seen] (const m::room &room, const string_view &membership) { const m::room::origins origins{room}; return !origins.test([&closure, &seen] (const string_view &origin) { const auto it { seen.lower_bound(origin) }; if(it != end(seen) && *it == origin) return false; seen.emplace_hint(it, std::string{origin}); return !closure(origin); }); }}); } // // user::mitsein // ircd::m::user::mitsein::mitsein(const m::user &user) :user{user} { } size_t ircd::m::user::mitsein::count(const string_view &membership) const { size_t ret{0}; for_each(membership, [&ret](const m::user &) { ++ret; }); return ret; } size_t ircd::m::user::mitsein::count(const m::user &user, const string_view &membership) const { size_t ret{0}; for_each(user, membership, [&ret](const m::room &, const string_view &) { ++ret; }); return ret; } void ircd::m::user::mitsein::for_each(const closure &closure) const { for_each(string_view{}, closure); } void ircd::m::user::mitsein::for_each(const closure_bool &closure) const { for_each(string_view{}, closure); } void ircd::m::user::mitsein::for_each(const string_view &membership, const closure &closure) const { for_each(membership, closure_bool{[&closure] (const m::user &user) { closure(user); return true; }}); } void ircd::m::user::mitsein::for_each(const string_view &membership, const closure_bool &closure) const { const m::user::rooms rooms { user }; // here we gooooooo :/ ///TODO: ideal: db schema ///TODO: minimally: custom alloc? std::set> seen; rooms.for_each(membership, rooms::closure_bool{[&membership, &closure, &seen] (const m::room &room, const string_view &) { const m::room::members members{room}; return !members.test(membership, [&seen, &closure] (const m::event &event) { const auto &other { at<"state_key"_>(event) }; const auto it { seen.lower_bound(other) }; if(it != end(seen) && *it == other) return false; seen.emplace_hint(it, std::string{other}); return !closure(m::user{other}); }); }}); } void ircd::m::user::mitsein::for_each(const m::user &user, const rooms::closure &closure) const { for_each(user, string_view{}, closure); } void ircd::m::user::mitsein::for_each(const m::user &user, const rooms::closure_bool &closure) const { for_each(user, string_view{}, closure); } void ircd::m::user::mitsein::for_each(const m::user &user, const string_view &membership, const rooms::closure &closure) const { for_each(user, membership, rooms::closure_bool{[&membership, &closure] (const m::room &room, const string_view &) { closure(room, membership); return true; }}); } void ircd::m::user::mitsein::for_each(const m::user &user, const string_view &membership, const rooms::closure_bool &closure) const { const m::user::rooms our_rooms{this->user}; const m::user::rooms their_rooms{user}; const bool use_our { our_rooms.count() <= their_rooms.count() }; const m::user::rooms &rooms { use_our? our_rooms : their_rooms }; const string_view &test_key { use_our? user.user_id : this->user.user_id }; rooms.for_each(membership, rooms::closure_bool{[&membership, &closure, &test_key] (const m::room &room, const string_view &) { if(!room.has("m.room.member", test_key)) return true; return closure(room, membership); }}); } /////////////////////////////////////////////////////////////////////////////// // // m/room.h // ircd::m::room ircd::m::create(const id::room &room_id, const id::user &creator, const string_view &type) { using prototype = room (const id::room &, const id::user &, const string_view &); static import function { "client_createroom", "createroom__type" }; return function(room_id, creator, type); } ircd::m::room ircd::m::create(const id::room &room_id, const id::user &creator, const id::room &parent, const string_view &type) { using prototype = room (const id::room &, const id::user &, const id::room &, const string_view &); static import function { "client_createroom", "createroom__parent_type" }; return function(room_id, creator, parent, type); } ircd::m::event::id::buf ircd::m::join(const id::room_alias &room_alias, const id::user &user_id) { using prototype = event::id::buf (const id::room_alias &, const id::user &); static import function { "client_rooms", "join__alias_user" }; return function(room_alias, user_id); } ircd::m::event::id::buf ircd::m::join(const room &room, const id::user &user_id) { using prototype = event::id::buf (const m::room &, const id::user &); static import function { "client_rooms", "join__room_user" }; return function(room, user_id); } ircd::m::event::id::buf ircd::m::leave(const room &room, const id::user &user_id) { using prototype = event::id::buf (const m::room &, const id::user &); static import function { "client_rooms", "leave__room_user" }; return function(room, user_id); } ircd::m::event::id::buf ircd::m::invite(const room &room, const id::user &target, const id::user &sender) { using prototype = event::id::buf (const m::room &, const id::user &, const id::user &); static import function { "client_rooms", "invite__room_user" }; return function(room, target, sender); } ircd::m::event::id::buf ircd::m::redact(const room &room, const id::user &sender, const id::event &event_id, const string_view &reason) { using prototype = event::id::buf (const m::room &, const id::user &, const id::event &, const string_view &); static import function { "client_rooms", "redact__" }; return function(room, sender, event_id, reason); } ircd::m::event::id::buf ircd::m::notice(const room &room, const string_view &body) { return message(room, me.user_id, body, "m.notice"); } ircd::m::event::id::buf ircd::m::notice(const room &room, const m::id::user &sender, const string_view &body) { return message(room, sender, body, "m.notice"); } ircd::m::event::id::buf ircd::m::msghtml(const room &room, const m::id::user &sender, const string_view &html, const string_view &alt, const string_view &msgtype) { return message(room, sender, { { "msgtype", msgtype }, { "format", "org.matrix.custom.html" }, { "body", { alt?: html, json::STRING } }, { "formatted_body", { html, json::STRING } }, }); } ircd::m::event::id::buf ircd::m::message(const room &room, const m::id::user &sender, const string_view &body, const string_view &msgtype) { return message(room, sender, { { "body", { body, json::STRING } }, { "msgtype", { msgtype, json::STRING } }, }); } ircd::m::event::id::buf ircd::m::message(const room &room, const m::id::user &sender, const json::members &contents) { return send(room, sender, "m.room.message", contents); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const string_view &state_key, const json::members &contents) { json::iov _content; json::iov::push content[contents.size()]; return send(room, sender, type, state_key, make_iov(_content, content, contents.size(), contents)); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const string_view &state_key, const json::object &contents) { json::iov _content; json::iov::push content[contents.size()]; return send(room, sender, type, state_key, make_iov(_content, content, contents.size(), contents)); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const string_view &state_key, const json::iov &content) { using prototype = event::id::buf (const m::room &, const id::user &, const string_view &, const string_view &, const json::iov &); static import function { "client_rooms", "state__iov" }; return function(room, sender, type, state_key, content); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const json::members &contents) { json::iov _content; json::iov::push content[contents.size()]; return send(room, sender, type, make_iov(_content, content, contents.size(), contents)); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const json::object &contents) { json::iov _content; json::iov::push content[contents.size()]; return send(room, sender, type, make_iov(_content, content, contents.size(), contents)); } ircd::m::event::id::buf ircd::m::send(const room &room, const m::id::user &sender, const string_view &type, const json::iov &content) { using prototype = event::id::buf (const m::room &, const id::user &, const string_view &, const json::iov &); static import function { "client_rooms", "send__iov" }; return function(room, sender, type, content); } ircd::m::event::id::buf ircd::m::commit(const room &room, json::iov &event, const json::iov &contents) { vm::copts opts { room.copts? *room.copts: vm::default_copts }; // Some functionality on this server may create an event on behalf // of remote users. It's safe for us to mask this here, but eval'ing // this event in any replay later will require special casing. opts.non_conform |= event::conforms::MISMATCH_ORIGIN_SENDER; // Stupid protocol workaround opts.non_conform |= event::conforms::MISSING_PREV_STATE; // Don't need this here opts.verify = false; vm::eval eval { opts }; eval(room, event, contents); return eval.event_id; } ircd::m::id::room::buf ircd::m::room_id(const id::room_alias &room_alias) { char buf[256]; return room_id(buf, room_alias); } ircd::m::id::room::buf ircd::m::room_id(const string_view &room_id_or_alias) { char buf[256]; return room_id(buf, room_id_or_alias); } ircd::m::id::room ircd::m::room_id(const mutable_buffer &out, const string_view &room_id_or_alias) { switch(m::sigil(room_id_or_alias)) { case id::ROOM: return id::room{out, room_id_or_alias}; default: return room_id(out, id::room_alias{room_id_or_alias}); } } ircd::m::id::room ircd::m::room_id(const mutable_buffer &out, const id::room_alias &room_alias) { using prototype = id::room (const mutable_buffer &, const id::room_alias &); static import function { "client_directory_room", "room_id__room_alias" }; return function(out, room_alias); } bool ircd::m::exists(const id::room_alias &room_alias, const bool &remote_query) { using prototype = bool (const id::room_alias, const bool &); static import function { "client_directory_room", "room_alias_exists" }; return function(room_alias, remote_query); } /////////////////////////////////////////////////////////////////////////////// // // m/txn.h // /// Returns the serial size of the JSON this txn would consume. Note: this /// creates a json::iov involving a timestamp to figure out the total size /// of the txn. When the user creates the actual txn a different timestamp /// is created which may be a different size. Consider using the lower-level /// create(closure) or add some pad to be sure. /// size_t ircd::m::txn::serialized(const array &pdu, const array &edu, const array &pdu_failure) { size_t ret; const auto closure{[&ret] (const json::iov &iov) { ret = json::serialized(iov); }}; create(closure, pdu, edu, pdu_failure); return ret; } /// Stringifies a txn from the inputs into the returned std::string /// std::string ircd::m::txn::create(const array &pdu, const array &edu, const array &pdu_failure) { std::string ret; const auto closure{[&ret] (const json::iov &iov) { ret = json::strung(iov); }}; create(closure, pdu, edu, pdu_failure); return ret; } /// Stringifies a txn from the inputs into the buffer /// ircd::string_view ircd::m::txn::create(const mutable_buffer &buf, const array &pdu, const array &edu, const array &pdu_failure) { string_view ret; const auto closure{[&buf, &ret] (const json::iov &iov) { ret = json::stringify(mutable_buffer{buf}, iov); }}; create(closure, pdu, edu, pdu_failure); return ret; } /// Forms a txn from the inputs into a json::iov and presents that iov /// to the user's closure. /// void ircd::m::txn::create(const closure &closure, const array &pdu, const array &edu, const array &pdu_failure) { using ircd::size; json::iov iov; const json::iov::push push[] { { iov, { "origin", my_host() }}, { iov, { "origin_server_ts", ircd::time() }}, }; const json::iov::add _pdus { iov, !empty(pdu), { "pdus", [&pdu]() -> json::value { return { data(pdu), size(pdu) }; } } }; const json::iov::add _edus { iov, !empty(edu), { "edus", [&edu]() -> json::value { return { data(edu), size(edu) }; } } }; const json::iov::add _pdu_failures { iov, !empty(pdu_failure), { "pdu_failures", [&pdu_failure]() -> json::value { return { data(pdu_failure), size(pdu_failure) }; } } }; closure(iov); } ircd::string_view ircd::m::txn::create_id(const mutable_buffer &out, const string_view &txn) { const sha256::buf hash { sha256{txn} }; const string_view txnid { b58encode(out, hash) }; return txnid; } /////////////////////////////////////////////////////////////////////////////// // // m/hook.h // // Internal utils namespace ircd::m { static bool _hook_match(const m::event &matching, const m::event &); static void _hook_fix_state_key(const json::members &, json::member &); static void _hook_fix_room_id(const json::members &, json::member &); static void _hook_fix_sender(const json::members &, json::member &); static json::strung _hook_make_feature(const json::members &); } // // hook::maps // struct ircd::m::hook::maps { std::multimap origin; std::multimap room_id; std::multimap sender; std::multimap state_key; std::multimap type; std::vector always; size_t match(const event &match, const std::function &) const; size_t add(base &hook, const event &matching); size_t del(base &hook, const event &matching); maps(); ~maps() noexcept; }; ircd::m::hook::maps::maps() { } ircd::m::hook::maps::~maps() noexcept { } size_t ircd::m::hook::maps::add(base &hook, const event &matching) { size_t ret{0}; const auto map{[&hook, &ret] (auto &map, const string_view &value) { map.emplace(value, &hook); ++ret; }}; if(json::get<"origin"_>(matching)) map(origin, at<"origin"_>(matching)); if(json::get<"room_id"_>(matching)) map(room_id, at<"room_id"_>(matching)); if(json::get<"sender"_>(matching)) map(sender, at<"sender"_>(matching)); if(json::get<"state_key"_>(matching)) map(state_key, at<"state_key"_>(matching)); if(json::get<"type"_>(matching)) map(type, at<"type"_>(matching)); // Hook had no mappings which means it will match everything. // We don't increment the matcher count for this case. if(!ret) always.emplace_back(&hook); return ret; } size_t ircd::m::hook::maps::del(base &hook, const event &matching) { size_t ret{0}; const auto unmap{[&hook, &ret] (auto &map, const string_view &key) { auto pit{map.equal_range(key)}; for(; pit.first != pit.second; ++pit.first) if(pit.first->second == &hook) { ++ret; return map.erase(pit.first); } assert(0); return end(map); }}; // Unconditional attempt to remove from always. std::remove(begin(always), end(always), &hook); if(json::get<"origin"_>(matching)) unmap(origin, at<"origin"_>(matching)); if(json::get<"room_id"_>(matching)) unmap(room_id, at<"room_id"_>(matching)); if(json::get<"sender"_>(matching)) unmap(sender, at<"sender"_>(matching)); if(json::get<"state_key"_>(matching)) unmap(state_key, at<"state_key"_>(matching)); if(json::get<"type"_>(matching)) unmap(type, at<"type"_>(matching)); return ret; } size_t ircd::m::hook::maps::match(const event &event, const std::function &callback) const { std::set matching { begin(always), end(always) }; const auto site_match{[&matching] (auto &map, const string_view &key) { auto pit{map.equal_range(key)}; for(; pit.first != pit.second; ++pit.first) matching.emplace(pit.first->second); }}; if(json::get<"origin"_>(event)) site_match(origin, at<"origin"_>(event)); if(json::get<"room_id"_>(event)) site_match(room_id, at<"room_id"_>(event)); if(json::get<"sender"_>(event)) site_match(sender, at<"sender"_>(event)); if(json::get<"type"_>(event)) site_match(type, at<"type"_>(event)); if(json::get<"state_key"_>(event)) site_match(state_key, at<"state_key"_>(event)); auto it(begin(matching)); while(it != end(matching)) { const base &hook(**it); if(!_hook_match(hook.matching, event)) it = matching.erase(it); else ++it; } size_t ret{0}; for(auto it(begin(matching)); it != end(matching); ++it, ++ret) if(!callback(**it)) return ret; return ret; } // // hook::base // /// Instance list linkage for all hooks template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list {}; /// Primary hook ctor ircd::m::hook::base::base(const json::members &members) try :_feature { _hook_make_feature(members) } ,feature { _feature } ,matching { feature } { site *site; if((site = find_site())) site->add(*this); } catch(...) { if(!registered) throw; auto *const site(find_site()); assert(site != nullptr); site->del(*this); } ircd::m::hook::base::~base() noexcept { if(!registered) return; auto *const site(find_site()); assert(site != nullptr); site->del(*this); } ircd::m::hook::base::site * ircd::m::hook::base::find_site() const { const auto &site_name { this->site_name() }; if(!site_name) return nullptr; for(auto *const &site : m::hook::base::site::list) if(site->name() == site_name) return site; return nullptr; } ircd::string_view ircd::m::hook::base::site_name() const try { return unquote(feature.at("_site")); } catch(const std::out_of_range &e) { throw assertive { "Hook %p must name a '_site' to register with.", this }; } // // hook::site // /// Instance list linkage for all hook sites template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list {}; // // hook::site::site // ircd::m::hook::base::site::site(const json::members &members) :_feature { members } ,feature { _feature } ,maps { std::make_unique() } { for(const auto &site : list) if(site->name() == name() && site != this) throw error { "Hook site '%s' already registered at %p", name(), site }; // Find and register all of the orphan hooks which were constructed before // this site was constructed. for(auto *const &hook : m::hook::base::list) if(hook->site_name() == name()) add(*hook); } ircd::m::hook::base::site::~site() noexcept { const std::vector hooks { begin(this->hooks), end(this->hooks) }; for(auto *const hook : hooks) del(*hook); } void ircd::m::hook::base::site::match(const event &event, const std::function &callback) { maps->match(event, callback); } bool ircd::m::hook::base::site::add(base &hook) { assert(!hook.registered); assert(hook.site_name() == name()); assert(hook.matchers == 0); if(!hooks.emplace(&hook).second) { log::warning { "Hook %p already registered to site %s", &hook, name() }; return false; } assert(maps); const size_t matched { maps->add(hook, hook.matching) }; hook.matchers = matched; hook.registered = true; matchers += matched; ++count; return true; } bool ircd::m::hook::base::site::del(base &hook) { assert(hook.registered); assert(hook.site_name() == name()); const size_t matched { maps->del(hook, hook.matching) }; const auto erased { hooks.erase(&hook) }; hook.matchers -= matched; hook.registered = false; matchers -= matched; --count; assert(hook.matchers == 0); assert(erased); return true; } ircd::string_view ircd::m::hook::base::site::name() const try { return unquote(feature.at("name")); } catch(const std::out_of_range &e) { throw assertive { "Hook site %p requires a name", this }; } // // hook // ircd::m::hook::hook::hook(const json::members &feature, decltype(function) function) :base{feature} ,function{std::move(function)} { } ircd::m::hook::hook::hook(decltype(function) function, const json::members &feature) :base{feature} ,function{std::move(function)} { } ircd::m::hook::site::site(const json::members &feature) :base::site{feature} { } void ircd::m::hook::site::operator()(const event &event) { match(event, [this, &event] (base &base) { call(dynamic_cast &>(base), event); return true; }); } void ircd::m::hook::site::call(hook &hfn, const event &event) try { ++hfn.calls; hfn.function(event); } catch(const std::exception &e) { log::critical { "Unhandled hookfn(%p) %s error :%s", &hfn, string_view{hfn.feature}, e.what() }; } // // hook internal // /// Internal interface which manipulates the initializer supplied by the /// developer to the hook to create the proper JSON output. i.e They supply /// a "room_id" of "!config" which has no hostname, that is added here /// depending on my_host() in the deployment runtime... /// ircd::json::strung ircd::m::_hook_make_feature(const json::members &members) { const ctx::critical_assertion ca; std::vector copy { begin(members), end(members) }; for(auto &member : copy) switch(hash(member.first)) { case hash("room_id"): _hook_fix_room_id(members, member); continue; case hash("sender"): _hook_fix_sender(members, member); continue; case hash("state_key"): _hook_fix_state_key(members, member); continue; } return { copy.data(), copy.data() + copy.size() }; } void ircd::m::_hook_fix_sender(const json::members &members, json::member &member) { // Rewrite the sender if the supplied input has no hostname if(valid_local_only(id::USER, member.second)) { assert(my_host()); thread_local char buf[256]; member.second = id::user { buf, member.second, my_host() }; } validate(id::USER, member.second); } void ircd::m::_hook_fix_room_id(const json::members &members, json::member &member) { // Rewrite the room_id if the supplied input has no hostname if(valid_local_only(id::ROOM, member.second)) { assert(my_host()); thread_local char buf[256]; member.second = id::room { buf, member.second, my_host() }; } validate(id::ROOM, member.second); } void ircd::m::_hook_fix_state_key(const json::members &members, json::member &member) { const bool is_member_event { end(members) != std::find_if(begin(members), end(members), [] (const auto &member) { return member.first == "type" && member.second == "m.room.member"; }) }; // Rewrite the sender if the supplied input has no hostname if(valid_local_only(id::USER, member.second)) { assert(my_host()); thread_local char buf[256]; member.second = id::user { buf, member.second, my_host() }; } validate(id::USER, member.second); } bool ircd::m::_hook_match(const m::event &matching, const m::event &event) { if(json::get<"origin"_>(matching)) if(at<"origin"_>(matching) != json::get<"origin"_>(event)) return false; if(json::get<"room_id"_>(matching)) if(at<"room_id"_>(matching) != json::get<"room_id"_>(event)) return false; if(json::get<"sender"_>(matching)) if(at<"sender"_>(matching) != json::get<"sender"_>(event)) return false; if(json::get<"type"_>(matching)) if(at<"type"_>(matching) != json::get<"type"_>(event)) return false; if(json::get<"state_key"_>(matching)) if(at<"state_key"_>(matching) != json::get<"state_key"_>(event)) return false; if(json::get<"membership"_>(matching)) if(at<"membership"_>(matching) != json::get<"membership"_>(event)) return false; if(json::get<"content"_>(matching)) if(json::get<"type"_>(event) == "m.room.message") if(at<"content"_>(matching).has("msgtype")) if(at<"content"_>(matching).get("msgtype") != json::get<"content"_>(event).get("msgtype")) return false; return true; } /////////////////////////////////////////////////////////////////////////////// // // m/import.h // decltype(ircd::m::modules) ircd::m::modules {}; /////////////////////////////////////////////////////////////////////////////// // // m/error.h // thread_local char ircd::m::error::fmtbuf[768] {}; ircd::m::error::error() :http::error{http::INTERNAL_SERVER_ERROR} {} ircd::m::error::error(std::string c) :http::error{http::INTERNAL_SERVER_ERROR, std::move(c)} {} ircd::m::error::error(const http::code &c) :http::error{c, std::string{}} {} ircd::m::error::error(const http::code &c, const json::members &members) :http::error{c, json::strung{members}} {} ircd::m::error::error(const http::code &c, const json::iov &iov) :http::error{c, json::strung{iov}} {} ircd::m::error::error(const http::code &c, const json::object &object) :http::error{c, std::string{object}} {}