mirror of
https://github.com/matrix-construct/construct
synced 2024-11-09 19:41:22 +01:00
404 lines
9.8 KiB
C++
404 lines
9.8 KiB
C++
// Matrix Construct
|
|
//
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
|
|
//
|
|
// 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.
|
|
|
|
ircd::mapi::header
|
|
IRCD_MODULE
|
|
{
|
|
"Matrix Room Server Access Control List"
|
|
};
|
|
|
|
//
|
|
// vm hookfn's
|
|
//
|
|
|
|
namespace ircd::m
|
|
{
|
|
static void on_changed_room_server_acl(const m::event &, m::vm::eval &);
|
|
static void on_check_room_server_acl(const m::event &, m::vm::eval &);
|
|
|
|
extern m::hookfn<m::vm::eval &> changed_room_server_acl;
|
|
extern m::hookfn<m::vm::eval &> check_room_server_acl;
|
|
}
|
|
|
|
decltype(ircd::m::check_room_server_acl)
|
|
ircd::m::check_room_server_acl
|
|
{
|
|
on_check_room_server_acl,
|
|
{
|
|
{ "_site", "vm.access" },
|
|
}
|
|
};
|
|
|
|
void
|
|
ircd::m::on_check_room_server_acl(const event &event,
|
|
vm::eval &)
|
|
{
|
|
if(!m::room::server_acl::enable_write)
|
|
return;
|
|
|
|
if(!m::room::server_acl::check(at<"room_id"_>(event), at<"origin"_>(event)))
|
|
throw m::ACCESS_DENIED
|
|
{
|
|
"Server '%s' denied by room %s access control list.",
|
|
at<"origin"_>(event),
|
|
at<"room_id"_>(event),
|
|
};
|
|
}
|
|
|
|
decltype(ircd::m::changed_room_server_acl)
|
|
ircd::m::changed_room_server_acl
|
|
{
|
|
on_changed_room_server_acl,
|
|
{
|
|
{ "_site", "vm.notify" },
|
|
{ "type", "m.room.server_acl" },
|
|
}
|
|
};
|
|
|
|
void
|
|
ircd::m::on_changed_room_server_acl(const event &event,
|
|
vm::eval &)
|
|
{
|
|
log::info
|
|
{
|
|
m::log, "%s changed server access control list in %s [%s]",
|
|
json::get<"sender"_>(event),
|
|
json::get<"room_id"_>(event),
|
|
string_view{event.event_id},
|
|
};
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ircd/m/room/server_acl.h
|
|
//
|
|
|
|
/// Coarse control over whether ACL's are considered during the vm::eval of an
|
|
/// event, ACL's will be checked against the event's origin during processing
|
|
/// of the event, regardless of how the event was received, fetched, etc. The
|
|
/// m::vm options may dictate further detailed behavior (hard-fail, soft-
|
|
/// fail, auth integration, etc). This is the principal configuration option
|
|
/// for effecting the server access control list functionality. Though this
|
|
/// conf item is independent of other conf items in this module, setting it
|
|
/// to false denudes the core functionality.
|
|
///
|
|
/// Setting this to true is *stricter* than the official specification and
|
|
/// fixes several vulnerabilities for bypassing ACL's. This also applies to
|
|
/// both PDU's and EDU's, and is agnostic to the method or endpoint by which
|
|
/// this server obtained the event. This departs from the specification.
|
|
///
|
|
/// This option has no effect on the room::server_acl interface itself, it is
|
|
/// available for the callsite to check independently before using the iface.
|
|
decltype(ircd::m::room::server_acl::enable_write)
|
|
IRCD_MODULE_EXPORT_DATA
|
|
ircd::m::room::server_acl::enable_write
|
|
{
|
|
{ "name", "ircd.m.room.server_acl.enable.write" },
|
|
{ "default", true },
|
|
};
|
|
|
|
/// Coarse control over whether ACL's apply to endpoints considered
|
|
/// non-modifying/passive to the room. If false, ACL's are not checked on
|
|
/// endpoints which have no visible effects to the federation; this can
|
|
/// increase performance.
|
|
///
|
|
/// Setting this option to false relaxes the list of endpoints covered by ACL's
|
|
/// and departs from the official specification.
|
|
///
|
|
/// This option has no effect on the room::server_acl interface itself, it is
|
|
/// available for the callsite to check independently before using the iface.
|
|
decltype(ircd::m::room::server_acl::enable_read)
|
|
IRCD_MODULE_EXPORT_DATA
|
|
ircd::m::room::server_acl::enable_read
|
|
{
|
|
{ "name", "ircd.m.room.server_acl.enable.read" },
|
|
{ "default", false },
|
|
};
|
|
|
|
/// Coarse control over whether ACL's are considered for event fetching. If
|
|
/// true, events originating from an ACL'ed server will not be fetched, nor
|
|
/// will an ACL'ed server be queried by the fetch unit for any event. Note that
|
|
/// this cannot fully apply for newer event_id's without hostparts, but the
|
|
/// fetch unit may discard such events for an ACL'ed server after receiving.
|
|
///
|
|
/// Setting this to true is *stricter* than the official specification, which
|
|
/// is vulnerable to "bouncing" around ACL's.
|
|
/// (see: https://github.com/maubot/bouncybot)
|
|
///
|
|
/// This option has no effect on the room::server_acl interface itself, it is
|
|
/// available for the callsite to check independently before using the iface.
|
|
decltype(ircd::m::room::server_acl::enable_fetch)
|
|
IRCD_MODULE_EXPORT_DATA
|
|
ircd::m::room::server_acl::enable_fetch
|
|
{
|
|
{ "name", "ircd.m.room.server_acl.enable.fetch" },
|
|
{ "default", true },
|
|
};
|
|
|
|
/// Coarse control over whether ACL's are considered when this server
|
|
/// transmits transactions to the participants in a room. If true, transactions
|
|
/// with all contained PDU's and EDU's will not be sent to ACL'ed servers.
|
|
///
|
|
/// Setting this to true is *stricter* than the official specification, which
|
|
/// leaks all transmissions to ACL'ed servers.
|
|
///
|
|
/// This option has no effect on the room::server_acl interface itself, it is
|
|
/// available for the callsite to check independently before using the iface.
|
|
decltype(ircd::m::room::server_acl::enable_send)
|
|
IRCD_MODULE_EXPORT_DATA
|
|
ircd::m::room::server_acl::enable_send
|
|
{
|
|
{ "name", "ircd.m.room.server_acl.enable.send" },
|
|
{ "default", true },
|
|
};
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::check(const m::room::id &room_id,
|
|
const net::hostport &server)
|
|
noexcept try
|
|
{
|
|
const server_acl server_acl
|
|
{
|
|
room_id
|
|
};
|
|
|
|
return server_acl(server);
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::critical
|
|
{
|
|
log, "Failed to check server_acl for '%s' in %s :%s",
|
|
string(server),
|
|
string_view{room_id},
|
|
e.what()
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// server_acl::server_acl
|
|
//
|
|
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::server_acl(const m::room &room,
|
|
const event::idx &event_idx)
|
|
:room
|
|
{
|
|
room
|
|
}
|
|
,event_idx
|
|
{
|
|
!event_idx?
|
|
room.get(std::nothrow, "m.room.server_acl", ""):
|
|
event_idx
|
|
}
|
|
{
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::operator()(const net::hostport &server)
|
|
const
|
|
{
|
|
bool ret;
|
|
const auto closure{[this, &server, &ret]
|
|
(const json::object &content)
|
|
{
|
|
// Set the content reference here so only one actual IO is made to
|
|
// fetch the m.room.server_acl content for all queries.
|
|
const scope_restore this_content
|
|
{
|
|
this->content, content
|
|
};
|
|
|
|
ret = this->check(server);
|
|
}};
|
|
|
|
return !view(closure) || ret;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::match(const string_view &prop,
|
|
const net::hostport &remote)
|
|
const
|
|
{
|
|
// Spec sez when comparing against the server ACLs, the suspect server's
|
|
// port number must not be considered.
|
|
const string_view &server
|
|
{
|
|
net::host(remote)
|
|
};
|
|
|
|
return !for_each(prop, [&server]
|
|
(const string_view &expression)
|
|
{
|
|
const globular_match match
|
|
{
|
|
expression
|
|
};
|
|
|
|
// return false to break on match.
|
|
return match(server)? false : true;
|
|
});
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::has(const string_view &prop,
|
|
const string_view &expr)
|
|
const
|
|
{
|
|
return !for_each(prop, [&expr]
|
|
(const string_view &_expr)
|
|
{
|
|
// false to break on match
|
|
return _expr == expr? false : true;
|
|
});
|
|
}
|
|
|
|
int
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::getbool(const string_view &prop)
|
|
const
|
|
{
|
|
int ret(-1);
|
|
view([&ret, &prop]
|
|
(const json::object &object)
|
|
{
|
|
const string_view &value
|
|
{
|
|
object[prop]
|
|
};
|
|
|
|
if(value == json::literal_true)
|
|
ret = 1;
|
|
else if(value == json::literal_false)
|
|
ret = 0;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::has(const string_view &prop)
|
|
const
|
|
{
|
|
bool ret{false};
|
|
view([&ret, &prop]
|
|
(const json::object &object)
|
|
{
|
|
ret = object.has(prop);
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::count(const string_view &prop)
|
|
const
|
|
{
|
|
size_t ret(0);
|
|
for_each(prop, [&ret]
|
|
(const string_view &)
|
|
{
|
|
++ret;
|
|
return true;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::for_each(const string_view &prop,
|
|
const closure_bool &closure)
|
|
const
|
|
{
|
|
bool ret{true};
|
|
view([&ret, &closure, &prop]
|
|
(const json::object &content)
|
|
{
|
|
const json::array &list
|
|
{
|
|
content[prop]
|
|
};
|
|
|
|
if(!list || json::type(list, std::nothrow) != json::ARRAY)
|
|
return;
|
|
|
|
for(auto it(begin(list)); it != end(list) && ret; ++it)
|
|
{
|
|
if(json::type(*it, json::strict, std::nothrow) != json::STRING)
|
|
continue;
|
|
|
|
if(!closure(json::string(*it)))
|
|
ret = false;
|
|
}
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::exists()
|
|
const
|
|
{
|
|
return content || event_idx;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::check(const net::hostport &server)
|
|
const
|
|
{
|
|
// c2s 13.29.1 rules
|
|
|
|
// 1. If there is no m.room.server_acl event in the room state, allow.
|
|
if(!exists())
|
|
return true;
|
|
|
|
// 2. If the server name is an IP address (v4 or v6) literal, and
|
|
// allow_ip_literals is present and false, deny.
|
|
if(getbool("allow_ip_literals") == false)
|
|
if(rfc3986::valid(std::nothrow, rfc3986::parser::ip_address, net::host(server)))
|
|
return false;
|
|
|
|
// 3. If the server name matches an entry in the deny list, deny.
|
|
if(match("deny", server))
|
|
return false;
|
|
|
|
// 4. If the server name matches an entry in the allow list, allow.
|
|
if(match("allow", server))
|
|
return true;
|
|
|
|
// 5. Otherwise, deny.
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::room::server_acl::view(const view_closure &closure)
|
|
const
|
|
{
|
|
if(content)
|
|
{
|
|
closure(content);
|
|
return true;
|
|
}
|
|
|
|
return event_idx && m::get(std::nothrow, event_idx, "content", closure);
|
|
}
|