// 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. namespace ircd::m::vm { extern m::hookfn conform_check_event_id; extern m::hookfn conform_check_origin; extern m::hookfn conform_check_size; extern m::hookfn conform_report; } /// Check if event_id is sufficient for the room version. decltype(ircd::m::vm::conform_check_event_id) ircd::m::vm::conform_check_event_id { { { "_site", "vm.conform" } }, [](const m::event &event, eval &eval) { // Don't care about EDU's on this hook if(!event.event_id) return; // Conditions for when we don't care if the event_id conforms. This // hook only cares if the event_id is sufficient for the version, and // we don't care about the early matrix versions with mxids here. const bool unaffected { !eval.room_version || eval.room_version == "0" || eval.room_version == "1" || eval.room_version == "2" }; if(eval.room_version == "3") if(!event::id::v3::is(event.event_id)) throw error { fault::INVALID, "Event ID %s is not sufficient for version 3 room.", string_view{event.event_id} }; // note: we check v4 format for all other room versions, including "4" if(!unaffected && eval.room_version != "3") if(!event::id::v4::is(event.event_id)) throw error { fault::INVALID, "Event ID %s in a version %s room is not a version 4 Event ID.", string_view{event.event_id}, eval.room_version, }; } }; /// Check if an eval with a copts structure (indicating this server is /// creating the event) has an origin set to !my_host(). decltype(ircd::m::vm::conform_check_origin) ircd::m::vm::conform_check_origin { { { "_site", "vm.conform" } }, [](const m::event &event, eval &eval) { if(eval.opts && !eval.opts->conforming) return; if(unlikely(eval.copts && !my_host(at<"origin"_>(event)))) throw error { fault::INVALID, "Issuing event for origin: %s", at<"origin"_>(event) }; } }; /// Check if an event originating from this server exceeds maximum size. decltype(ircd::m::vm::conform_check_size) ircd::m::vm::conform_check_size { { { "_site", "vm.conform" }, }, [](const m::event &event, eval &eval) { const size_t &event_size { serialized(event) }; if(event_size > size_t(event::max_size)) throw m::BAD_JSON { "Event is %zu bytes which is larger than the maximum %zu bytes", event_size, size_t(event::max_size) }; } }; /// Check if an event originating from this server exceeds maximum size. decltype(ircd::m::vm::conform_report) ircd::m::vm::conform_report { { { "_site", "vm.conform" } }, [](const m::event &event, eval &eval) { assert(eval.opts); auto &opts(*eval.opts); // When opts.conformed is set the report is already generated if(opts.conformed) { eval.report = opts.report; return; } // Generate the report here. eval.report = event::conforms { event, opts.non_conform.report }; // When opts.conforming is false a bad report is not an error. if(!opts.conforming) return; const bool redacted { // redacted hint given in options opts.redacted != -1? bool(opts.redacted): // assume unredacted when user requires content opts.require_content? false: // assume redacted when hash mismatch already allowed (opts.non_conform.has(event::conforms::MISMATCH_HASHES))? true: // assume no redaction for hash match (!eval.report.has(event::conforms::MISMATCH_HASHES))? false: // make query bool(m::redacted(event.event_id)) }; auto report { eval.report };; // Allow content hash to fail on redacted events. if(redacted) report.del(event::conforms::MISMATCH_HASHES); // Otherwise this will kill the eval if(!report.clean()) throw error { fault::INVALID, "Non-conforming event: %s", string(report) }; } }; namespace ircd::m { constexpr size_t event_conforms_num{num_of()}; extern const std::array event_conforms_reflects; } decltype(ircd::m::event_conforms_reflects) ircd::m::event_conforms_reflects { "INVALID_OR_MISSING_EVENT_ID", "INVALID_OR_MISSING_ROOM_ID", "INVALID_OR_MISSING_SENDER_ID", "MISSING_TYPE", "INVALID_TYPE", "MISSING_ORIGIN", "INVALID_ORIGIN", "INVALID_STATE_KEY", "INVALID_OR_MISSING_REDACTS_ID", "MISSING_CONTENT_MEMBERSHIP", "INVALID_CONTENT_MEMBERSHIP", "MISSING_MEMBER_STATE_KEY", "INVALID_MEMBER_STATE_KEY", "MISSING_PREV_EVENTS", "MISSING_AUTH_EVENTS", "DEPTH_NEGATIVE", "DEPTH_ZERO", "MISSING_SIGNATURES", "MISSING_ORIGIN_SIGNATURE", "MISMATCH_ORIGIN_SENDER", "MISMATCH_CREATE_SENDER", "MISMATCH_ALIASES_STATE_KEY", "SELF_REDACTS", "SELF_PREV_EVENT", "SELF_AUTH_EVENT", "DUP_PREV_EVENT", "DUP_AUTH_EVENT", "MISMATCH_EVENT_ID", "MISSING_HASHES", "MISMATCH_HASHES", }; std::ostream & ircd::m::operator<<(std::ostream &s, const event::conforms &conforms) { thread_local char buf[1024]; s << conforms.string(buf); return s; } ircd::string_view ircd::m::reflect(const event::conforms::code &code) try { return event_conforms_reflects.at(code); } catch(const std::out_of_range &e) { return "??????"_sv; } ircd::m::event::conforms::code ircd::m::event::conforms::reflect(const string_view &name) { const auto it { std::find(begin(event_conforms_reflects), end(event_conforms_reflects), name) }; if(it == end(event_conforms_reflects)) throw std::out_of_range { "There is no event::conforms code by that name." }; return code(std::distance(begin(event_conforms_reflects), it)); } ircd::m::event::conforms::conforms(const event &e, const uint64_t &skip) :conforms{e} { report &= ~skip; } ircd::m::event::conforms::conforms(const event &e) try :report{0} { if(!e.event_id) set(INVALID_OR_MISSING_EVENT_ID); if(defined(json::get<"event_id"_>(e))) if(!valid(m::id::EVENT, json::get<"event_id"_>(e))) set(INVALID_OR_MISSING_EVENT_ID); if(!has(INVALID_OR_MISSING_EVENT_ID)) if(!m::check_id(e)) set(MISMATCH_EVENT_ID); if(empty(json::get<"hashes"_>(e))) set(MISSING_HASHES); if(!has(MISMATCH_HASHES) && !has(MISSING_HASHES)) if(!m::verify_hash(e)) set(MISMATCH_HASHES); if(!valid(m::id::ROOM, json::get<"room_id"_>(e))) set(INVALID_OR_MISSING_ROOM_ID); if(!valid(m::id::USER, json::get<"sender"_>(e))) set(INVALID_OR_MISSING_SENDER_ID); if(empty(json::get<"type"_>(e))) set(MISSING_TYPE); if(json::get<"type"_>(e).size() > event::TYPE_MAX_SIZE) set(INVALID_TYPE); if(empty(json::get<"origin"_>(e))) set(MISSING_ORIGIN); if(json::get<"origin"_>(e).size() > event::ORIGIN_MAX_SIZE) set(INVALID_ORIGIN); if(!rfc3986::valid_remote(std::nothrow, json::get<"origin"_>(e))) set(INVALID_ORIGIN); if(json::get<"state_key"_>(e).size() > event::STATE_KEY_MAX_SIZE) set(INVALID_STATE_KEY); if(empty(json::get<"signatures"_>(e))) set(MISSING_SIGNATURES); if(empty(json::object{json::get<"signatures"_>(e).get(json::get<"origin"_>(e))})) set(MISSING_ORIGIN_SIGNATURE); if(!has(INVALID_OR_MISSING_SENDER_ID)) if(json::get<"origin"_>(e) != m::id::user{json::get<"sender"_>(e)}.host()) set(MISMATCH_ORIGIN_SENDER); if(json::get<"type"_>(e) == "m.room.create") if(m::room::id(json::get<"room_id"_>(e)).host() != m::user::id(json::get<"sender"_>(e)).host()) set(MISMATCH_CREATE_SENDER); if(json::get<"type"_>(e) == "m.room.aliases") if(m::user::id(json::get<"sender"_>(e)).host() != json::get<"state_key"_>(e)) set(MISMATCH_ALIASES_STATE_KEY); if(json::get<"type"_>(e) == "m.room.redaction") if(!valid(m::id::EVENT, json::get<"redacts"_>(e))) set(INVALID_OR_MISSING_REDACTS_ID); if(json::get<"redacts"_>(e)) if(json::get<"redacts"_>(e) == e.event_id) set(SELF_REDACTS); if(json::get<"type"_>(e) == "m.room.member") if(empty(unquote(json::get<"content"_>(e).get("membership")))) set(MISSING_CONTENT_MEMBERSHIP); if(json::get<"type"_>(e) == "m.room.member") if(!all_of(unquote(json::get<"content"_>(e).get("membership")))) set(INVALID_CONTENT_MEMBERSHIP); if(json::get<"type"_>(e) == "m.room.member") if(empty(json::get<"state_key"_>(e))) set(MISSING_MEMBER_STATE_KEY); if(json::get<"type"_>(e) == "m.room.member") if(!valid(m::id::USER, json::get<"state_key"_>(e))) set(INVALID_MEMBER_STATE_KEY); if(json::get<"type"_>(e) != "m.room.create") if(empty(json::get<"prev_events"_>(e))) set(MISSING_PREV_EVENTS); if(json::get<"type"_>(e) != "m.room.create") if(empty(json::get<"auth_events"_>(e))) set(MISSING_AUTH_EVENTS); if(json::get<"depth"_>(e) != json::undefined_number && json::get<"depth"_>(e) < 0) set(DEPTH_NEGATIVE); if(json::get<"type"_>(e) != "m.room.create") if(json::get<"depth"_>(e) == 0) set(DEPTH_ZERO); const event::prev prev{e}; if(json::get<"event_id"_>(e)) { for(size_t i(0); i < prev.auth_events_count(); ++i) if(prev.auth_event(i) == json::get<"event_id"_>(e)) set(SELF_AUTH_EVENT); for(size_t i(0); i < prev.prev_events_count(); ++i) if(prev.prev_event(i) == json::get<"event_id"_>(e)) set(SELF_PREV_EVENT); } for(size_t i(0); i < prev.auth_events_count(); ++i) { const auto &[event_id, ref_hash] { prev.auth_events(i) }; for(size_t j(0); j < prev.auth_events_count(); ++j) if(i != j) if(event_id == prev.auth_event(j)) set(DUP_AUTH_EVENT); } for(size_t i(0); i < prev.prev_events_count(); ++i) { const auto &[event_id, ref_hash] { prev.prev_events(i) }; for(size_t j(0); j < prev.prev_events_count(); ++j) if(i != j) if(event_id == prev.prev_event(j)) set(DUP_PREV_EVENT); } } catch(const std::exception &_e) { log::error { log, "Unable to complete conformity check :%s", _e.what(), }; throw; } void ircd::m::event::conforms::operator|=(const code &code) & { set(code); } void ircd::m::event::conforms::del(const code &code) { report &= ~(1UL << code); } void ircd::m::event::conforms::set(const code &code) { report |= (1UL << code); } ircd::string_view ircd::m::event::conforms::string(const mutable_buffer &out) const { mutable_buffer buf{out}; for(uint64_t i(0); i < num_of(); ++i) { if(!has(code(i))) continue; if(begin(buf) != begin(out)) consume(buf, copy(buf, ' ')); consume(buf, copy(buf, m::reflect(code(i)))); } return { data(out), begin(buf) }; } bool ircd::m::event::conforms::has(const code &code) const { return report & (1UL << code); } bool ircd::m::event::conforms::has(const uint &code) const { return (report & (1UL << code)) == code; } bool ircd::m::event::conforms::operator!() const { return clean(); } ircd::m::event::conforms::operator bool() const { return !clean(); } bool ircd::m::event::conforms::clean() const { return report == 0; }