mirror of
https://github.com/matrix-construct/construct
synced 2024-12-26 07:23:53 +01:00
ircd:Ⓜ️:room: Add server_acl interface and protocol module.
This commit is contained in:
parent
8aa67ccb48
commit
a44e491054
5 changed files with 430 additions and 0 deletions
|
@ -109,6 +109,7 @@ struct ircd::m::room
|
|||
struct power;
|
||||
struct aliases;
|
||||
struct stats;
|
||||
struct server_acl;
|
||||
|
||||
using id = m::id::room;
|
||||
using alias = m::id::room_alias;
|
||||
|
@ -195,6 +196,7 @@ struct ircd::m::room
|
|||
#include "power.h"
|
||||
#include "aliases.h"
|
||||
#include "stats.h"
|
||||
#include "server_acl.h"
|
||||
|
||||
inline ircd::m::room::operator
|
||||
const ircd::m::room::id &()
|
||||
|
|
76
include/ircd/m/room/server_acl.h
Normal file
76
include/ircd/m/room/server_acl.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
// 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.
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_ROOM_SERVER_ACL_H
|
||||
|
||||
/// Interface to the server access control lists
|
||||
///
|
||||
/// This interface focuses specifically on the state event type
|
||||
/// `m.room.server_acl` which allows for access control at server scope. This
|
||||
/// is necessary because access controls via `m.room.member` operate at the
|
||||
/// scope of individual `state_key` cells in the room state, thus lacking the
|
||||
/// ability to assert control over cells which do not yet exist. In other
|
||||
/// words, for example, this prevent a server from generating new users to
|
||||
/// evade bans set on other users.
|
||||
///
|
||||
/// Our implementation is keyed on the `origin` field of an event as well as
|
||||
/// the `origin` field of an m::request depending on the callsite and options.
|
||||
/// If the `origin` field is not available (it is possibly slated to be phased
|
||||
/// out) expect this interface to fall back to the `sender` hostpart.
|
||||
///
|
||||
struct ircd::m::room::server_acl
|
||||
{
|
||||
using closure_bool = std::function<bool (const string_view &)>;
|
||||
using view_closure = std::function<void (const json::object &)>;
|
||||
|
||||
static conf::item<bool> enable_write; // request origin / event origin
|
||||
static conf::item<bool> enable_read; // request origin
|
||||
static conf::item<bool> enable_fetch; // request destination / event origin
|
||||
static conf::item<bool> enable_send; // request destination
|
||||
|
||||
m::room room;
|
||||
json::object content;
|
||||
event::idx event_idx {0};
|
||||
|
||||
bool view(const view_closure &) const;
|
||||
|
||||
public:
|
||||
bool exists() const;
|
||||
|
||||
// Iterate a list property; spec properties are "allow" and "deny".
|
||||
bool for_each(const string_view &prop, const closure_bool &) const;
|
||||
size_t count(const string_view &prop) const;
|
||||
bool has(const string_view &prop) const;
|
||||
|
||||
// Get top-level boolean value returning 1 or 0; -1 for not found.
|
||||
int getbool(const string_view &prop) const;
|
||||
|
||||
// Test if *exact string* is listed in property list; not expr match.
|
||||
bool has(const string_view &prop, const string_view &expr) const;
|
||||
|
||||
// Test if string is expression-matched in property list.
|
||||
bool match(const string_view &prop, const string_view &server) const;
|
||||
|
||||
// Test if server passes or fails the ACL; this factors matching in
|
||||
// "allow", "deny" and "allow_ip_literals" per the input with any default.
|
||||
bool operator()(const string_view &server) const;
|
||||
|
||||
server_acl(const m::room &, const event::idx &acl_event_idx = 0);
|
||||
server_acl(const m::room &, const json::object &content);
|
||||
server_acl() = default;
|
||||
};
|
||||
|
||||
inline
|
||||
ircd::m::room::server_acl::server_acl(const m::room &room,
|
||||
const json::object &content)
|
||||
:room{room}
|
||||
,content{content}
|
||||
{}
|
|
@ -124,6 +124,7 @@ m_room_canonical_alias_la_SOURCES = m_room_canonical_alias.cc
|
|||
m_room_aliases_la_SOURCES = m_room_aliases.cc
|
||||
m_room_message_la_SOURCES = m_room_message.cc
|
||||
m_room_power_levels_la_SOURCES = m_room_power_levels.cc
|
||||
m_room_server_acl_la_SOURCES = m_room_server_acl.cc
|
||||
|
||||
m_module_LTLIBRARIES = \
|
||||
m_noop.la \
|
||||
|
@ -146,6 +147,7 @@ m_module_LTLIBRARIES = \
|
|||
m_room_aliases.la \
|
||||
m_room_message.la \
|
||||
m_room_power_levels.la \
|
||||
m_room_server_acl.la \
|
||||
###
|
||||
|
||||
###############################################################################
|
||||
|
|
|
@ -7931,6 +7931,35 @@ console_cmd__room__alias__cache__del(opt &out, const string_view &line)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
console_cmd__room__server_acl(opt &out, const string_view &line)
|
||||
{
|
||||
const params param{line, " ",
|
||||
{
|
||||
"room_id", "server"
|
||||
}};
|
||||
|
||||
const auto &room_id
|
||||
{
|
||||
m::room_id(param.at("room_id"))
|
||||
};
|
||||
|
||||
const m::room::server_acl server_acl
|
||||
{
|
||||
room_id
|
||||
};
|
||||
|
||||
const bool allowed
|
||||
{
|
||||
server_acl(param.at("server"))
|
||||
};
|
||||
|
||||
out << (allowed? "ALLOWED"_sv : "DENIED"_sv)
|
||||
<< std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
console_cmd__room__members(opt &out, const string_view &line)
|
||||
{
|
||||
|
|
321
modules/m_room_server_acl.cc
Normal file
321
modules/m_room_server_acl.cc
Normal file
|
@ -0,0 +1,321 @@
|
|||
// 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"
|
||||
};
|
||||
|
||||
//
|
||||
// Notify hook
|
||||
//
|
||||
|
||||
namespace ircd::m
|
||||
{
|
||||
static void on_changed_room_server_acl(const m::event &, m::vm::eval &);
|
||||
extern m::hookfn<m::vm::eval &> changed_room_server_acl;
|
||||
}
|
||||
|
||||
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),
|
||||
json::get<"event_id"_>(event)
|
||||
};
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 },
|
||||
};
|
||||
|
||||
//
|
||||
// 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 string_view &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_remote, 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::match(const string_view &prop,
|
||||
const string_view &server)
|
||||
const
|
||||
{
|
||||
return !for_each(prop, [&server]
|
||||
(const string_view &expression)
|
||||
{
|
||||
const gmatch 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::view(const view_closure &closure)
|
||||
const
|
||||
{
|
||||
if(content)
|
||||
{
|
||||
closure(content);
|
||||
return true;
|
||||
}
|
||||
|
||||
return event_idx && m::get(std::nothrow, event_idx, "content", closure);
|
||||
}
|
Loading…
Reference in a new issue