// 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. using namespace ircd; mapi::header IRCD_MODULE { "federation send" }; m::resource send_resource { "/_matrix/federation/v1/send/", { "federation send", resource::DIRECTORY, } }; conf::item allow_self { { "name", "ircd.federation.send.allow_self" }, { "default", "false" }, }; conf::item eval_max_per_node { { "name", "ircd.federation.send.eval.max_per_node" }, { "default", 1L }, }; void handle_edu(client &client, const m::resource::request::object &request, const string_view &txn_id, const m::edu &edu) { m::event event; json::get<"origin"_>(event) = request.origin; json::get<"origin_server_ts"_>(event) = at<"origin_server_ts"_>(request); json::get<"content"_>(event) = at<"content"_>(edu); json::get<"type"_>(event) = at<"edu_type"_>(edu); json::get<"depth"_>(event) = json::undefined_number; m::vm::opts vmopts; vmopts.node_id = request.origin; vmopts.txn_id = txn_id; vmopts.edu = true; vmopts.notify_clients = false; vmopts.notify_servers = false; m::vm::eval eval { event, vmopts }; } void handle_pdus(client &client, const m::resource::request::object &request, const string_view &txn_id, const json::array &pdus) { m::vm::opts vmopts; vmopts.warnlog = 0; vmopts.infolog_accept = true; vmopts.nothrows = -1U; vmopts.nothrows &= ~m::vm::fault::INTERRUPT; vmopts.node_id = request.origin; vmopts.txn_id = txn_id; vmopts.fetch_state = false; m::vm::eval eval { pdus, vmopts }; } void handle_pdu_failure(client &client, const m::resource::request::object &request, const string_view &txn_id, const json::object &pdu_failure) { log::error { m::log, "%s :%s | (pdu_failure) %s", txn_id, at<"origin"_>(request), pdu_failure.get("sender", string_view{"*"}), string_view{pdu_failure} }; } m::resource::response handle_put(client &client, const m::resource::request::object &request) { if(request.parv.size() < 1) throw m::NEED_MORE_PARAMS { "txn_id path parameter required" }; char txn_id_buf[128]; const auto txn_id { url::decode(txn_id_buf, request.parv[0]) }; const string_view &origin { json::at<"origin"_>(request) }; const json::array &edus { json::get<"edus"_>(request) }; const json::array &pdus { json::get<"pdus"_>(request) }; const json::array &pdu_failures { json::get<"pdu_failures"_>(request) }; log::debug { m::log, "%s :%s | %s --> edus:%zu pdus:%zu errors:%zu", txn_id, origin, string(remote(client)), edus.count(), pdus.count(), pdu_failures.count() }; if(origin && origin != request.origin) throw m::ACCESS_DENIED { "txn[%s] originating from '%s' not accepted when relayed by '%s'", txn_id, origin, request.origin, }; // Don't accept sends to ourself for whatever reason (i.e a 127.0.0.1 // leaked into the target list). This should be a 500 so it's not // considered success or cached as failure by the sender's state. if(unlikely(my_host(request.origin)) && !bool(allow_self)) throw m::error { "M_SEND_TO_SELF", "Tried to send %s from myself to myself.", txn_id }; size_t evals{0}; const bool txn_in_progress { !m::vm::eval::for_each([&txn_id, &request, &evals] (const auto &eval) { assert(eval.opts); const bool match_node { eval.opts->node_id == request.origin }; const bool match_txn { match_node && eval.opts->txn_id == txn_id }; evals += match_node; return !match_txn; // false to break; for_each() returns false }) }; if(txn_in_progress) return m::resource::response { client, http::ACCEPTED }; if(evals >= size_t(eval_max_per_node)) return m::resource::response { client, http::TOO_MANY_REQUESTS }; for(const auto &pdu_failure : pdu_failures) handle_pdu_failure(client, request, txn_id, pdu_failure); handle_pdus(client, request, txn_id, pdus); for(const json::object &edu : edus) handle_edu(client, request, txn_id, edu); return m::resource::response { client, http::OK }; } m::resource::method method_put { send_resource, "PUT", handle_put, { method_put.VERIFY_ORIGIN, // Coarse timeout 90s, //TODO: conf // Payload maximum 4_MiB // larger = HTTP 413 //TODO: conf } };