0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-02 18:18:56 +02:00
construct/ircd/m/m.cc

957 lines
20 KiB
C++
Raw Normal View History

2018-02-04 03:22:01 +01:00
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 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.
2017-11-16 02:37:09 +01:00
2017-11-30 20:44:23 +01:00
#include <ircd/m/m.h>
2017-11-16 02:37:09 +01:00
namespace ircd::m
{
struct log::log log
{
"matrix", 'm'
};
std::map<std::string, ircd::module> modules;
std::list<ircd::net::listener> listeners;
2017-11-16 02:37:09 +01:00
static void leave_ircd_room();
static void join_ircd_room();
}
const ircd::m::user::id::buf
ircd_user_id
{
"ircd", ircd::my_host() //TODO: hostname
};
ircd::m::user
ircd::m::me
{
ircd_user_id
};
const ircd::m::room::id::buf
ircd_room_id
{
"ircd", ircd::my_host()
};
ircd::m::room
ircd::m::my_room
{
ircd_room_id
};
const ircd::m::room::id::buf
control_room_id
{
"control", ircd::my_host()
};
ircd::m::room
ircd::m::control
{
control_room_id
};
//
// init
//
ircd::m::init::init()
try
2018-03-02 09:35:02 +01:00
:config
2017-11-16 02:37:09 +01:00
{
2018-03-02 09:35:02 +01:00
ircd::conf::config //TODO: X
2017-11-16 02:37:09 +01:00
}
,_keys
2017-11-16 02:37:09 +01:00
{
2018-03-02 09:35:02 +01:00
this->config
2017-11-16 02:37:09 +01:00
}
{
modules();
if(db::sequence(*dbs::events) == 0)
bootstrap();
2017-11-16 02:37:09 +01:00
join_ircd_room();
listeners();
2017-11-16 02:37:09 +01:00
}
catch(const m::error &e)
{
log.error("%s %s", e.what(), e.content);
throw;
}
catch(const std::exception &e)
{
log.error("%s", e.what());
throw;
2017-11-16 02:37:09 +01:00
}
ircd::m::init::~init()
noexcept try
{
leave_ircd_room();
m::listeners.clear();
m::modules.clear();
2017-11-16 02:37:09 +01:00
}
catch(const m::error &e)
{
log.critical("%s %s", e.what(), e.content);
ircd::terminate();
2017-11-16 02:37:09 +01:00
}
void
ircd::m::init::modules()
{
const string_view prefixes[]
{
"m_", "client_", "key_", "federation_", "media_"
};
m::modules.emplace("conf"s, "conf"s);
for(const auto &name : mods::available())
if(startswith_any(name, std::begin(prefixes), std::end(prefixes)))
m::modules.emplace(name, name);
m::modules.emplace("root"s, "root"s);
}
namespace ircd::m
{
2018-03-02 09:35:02 +01:00
static void init_listener(const json::object &config, const json::object &opts, const string_view &bindaddr);
static void init_listener(const json::object &config, const json::object &opts);
}
void
ircd::m::init::listeners()
{
const json::array listeners
{
2018-03-02 09:35:02 +01:00
config["listeners"]
};
if(m::listeners.empty())
2018-03-02 09:35:02 +01:00
init_listener(config, {});
else
for(const json::object opts : listeners)
2018-03-02 09:35:02 +01:00
init_listener(config, opts);
}
static void
2018-03-02 09:35:02 +01:00
ircd::m::init_listener(const json::object &config,
const json::object &opts)
{
const json::array binds
{
opts["bind_addresses"]
};
if(binds.empty())
2018-03-02 09:35:02 +01:00
init_listener(config, opts, "0.0.0.0");
else
for(const auto &bindaddr : binds)
2018-03-02 09:35:02 +01:00
init_listener(config, opts, unquote(bindaddr));
}
static void
2018-03-02 09:35:02 +01:00
ircd::m::init_listener(const json::object &config,
const json::object &opts,
const string_view &host)
{
const json::array resources
{
opts["resources"]
};
// resources has multiple names with different configs which are being
// ignored :-/
const std::string name{"Matrix"s};
// Translate synapse options to our options (which reflect asio::ssl)
const json::strung options{json::members
{
2018-03-02 09:35:02 +01:00
{ "name", name },
{ "host", host },
{ "port", opts.get("port", 8448L) },
{ "ssl_certificate_file_pem", config["tls_certificate_path"] },
{ "ssl_private_key_file_pem", config["tls_private_key_path"] },
{ "ssl_tmp_dh_file", config["tls_dh_params_path"] },
}};
m::listeners.emplace_back(options);
2017-11-16 02:37:09 +01:00
}
void
ircd::m::init::bootstrap()
2017-11-16 02:37:09 +01:00
{
assert(dbs::events);
assert(db::sequence(*dbs::events) == 0);
2017-11-16 02:37:09 +01:00
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..."
);
create(user::users, me.user_id);
me.activate();
2017-11-16 02:37:09 +01:00
create(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." }
});
2017-11-16 02:37:09 +01:00
create(control, me.user_id);
join(control, me.user_id);
send(control, me.user_id, "m.room.name", "",
{
{ "name", "Control Room" }
});
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" }
});
_keys.bootstrap();
2017-11-16 02:37:09 +01:00
notice(control, me.user_id, "Welcome to the control room.");
notice(control, me.user_id, "I am the daemon. You can talk to me in this room by highlighting me.");
2017-11-16 02:37:09 +01:00
}
bool
ircd::m::self::host(const string_view &s)
{
return s == host();
}
ircd::string_view
ircd::m::self::host()
{
return "zemos.net"; //me.user_id.host();
}
void
ircd::m::join_ircd_room()
try
{
static conf::item<std::string> online_status_msg
{
{ "name", "m.ircd.online.status_msg" },
{ "default", "Wanna chat? IRCd at your service!" }
};
presence::set(me, "online", online_status_msg);
join(my_room, me.user_id);
}
catch(const m::ALREADY_MEMBER &e)
{
log.warning("IRCd did not shut down correctly...");
}
void
ircd::m::leave_ircd_room()
{
static conf::item<std::string> offline_status_msg
{
{ "name", "m.ircd.offline.status_msg" },
{ "default", "Catch ya on the flip side..." }
};
leave(my_room, me.user_id);
presence::set(me, "offline", offline_status_msg);
}
///////////////////////////////////////////////////////////////////////////////
//
// 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<prototype> function
{
"m_presence", "presence_set"
};
return function(object);
}
ircd::json::object
ircd::m::presence::get(const user &user,
const mutable_buffer &buffer)
{
using prototype = json::object (const m::user &, const mutable_buffer &);
static import<prototype> function
{
"m_presence", "presence_get"
};
return function(user, buffer);
}
bool
ircd::m::presence::valid_state(const string_view &state)
{
using prototype = bool (const string_view &);
static import<prototype> function
{
"m_presence", "presence_valid_state"
};
return function(state);
}
ircd::m::event::id::buf
ircd::m::user::presence(const string_view &presence,
const string_view &status_msg)
{
using prototype = event::id::buf (const id &, const string_view &, const string_view &);
static import<prototype> function
{
"client_presence", "set__user_presence_status"
};
return function(user_id, presence, status_msg);
}
///////////////////////////////////////////////////////////////////////////////
//
// 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<prototype> 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<prototype> function
{
"client_createroom", "createroom__parent_type"
};
return function(room_id, creator, parent, type);
}
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<prototype> 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<prototype> function
{
"client_rooms", "leave__room_user"
};
return function(room, user_id);
}
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<prototype> 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<prototype> 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.count()];
return send(room, sender, type, make_iov(_content, content, contents.count(), 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<prototype> 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)
{
using prototype = event::id::buf (const m::room &, json::iov &, const json::iov &);
static import<prototype> function
{
"client_rooms", "commit__iov_iov"
};
return function(room, event, contents);
}
2018-02-26 08:24:12 +01:00
ircd::m::id::room::buf
ircd::m::room_id(const id::room_alias &room_alias)
{
2018-02-28 07:37:10 +01:00
char buf[256];
return room_id(buf, room_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<prototype> function
{
"client_directory_room", "room_id__room_alias"
};
return function(out, room_alias);
}
2018-02-26 08:24:12 +01:00
///////////////////////////////////////////////////////////////////////////////
//
// m/hook.h
//
/// Alternative hook ctor simply allowing the the function argument
/// first and description after.
ircd::m::hook::hook(decltype(function) function,
const json::members &members)
:hook
{
members, std::move(function)
}
{
}
/// Primary hook ctor
2018-02-26 08:24:12 +01:00
ircd::m::hook::hook(const json::members &members,
decltype(function) function)
try
:_feature{[&members]() -> json::strung
2018-02-26 08:24:12 +01:00
{
const ctx::critical_assertion ca;
std::vector<json::member> copy
{
begin(members), end(members)
};
for(auto &member : copy) switch(hash(member.first))
{
case hash("room_id"):
{
// 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);
continue;
}
case hash("sender"):
{
// 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);
continue;
}
}
return { copy.data(), copy.data() + copy.size() };
}()}
2018-02-26 08:24:12 +01:00
,feature
{
_feature
}
,matching
{
feature
}
2018-02-26 08:24:12 +01:00
,function
{
std::move(function)
}
,registered
{
list.add(*this)
}
{
}
catch(...)
{
if(registered)
list.del(*this);
}
ircd::m::hook::~hook()
noexcept
{
if(registered)
list.del(*this);
}
ircd::string_view
ircd::m::hook::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
};
}
bool
ircd::m::hook::match(const m::event &event)
const
{
if(json::get<"origin"_>(matching))
if(at<"origin"_>(matching) != at<"origin"_>(event))
return false;
if(json::get<"room_id"_>(matching))
if(at<"room_id"_>(matching) != at<"room_id"_>(event))
return false;
if(json::get<"sender"_>(matching))
if(at<"sender"_>(matching) != at<"sender"_>(event))
return false;
if(json::get<"type"_>(matching))
if(at<"type"_>(matching) != at<"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(at<"type"_>(event) == "m.room.message")
if(at<"content"_>(matching).has("msgtype"))
if(at<"content"_>(matching).get("msgtype") != at<"content"_>(event).get("msgtype"))
return false;
return true;
}
2018-02-26 08:24:12 +01:00
//
// hook::site
//
ircd::m::hook::site::site(const json::members &members)
try
:_feature
{
members
}
,feature
{
_feature
}
,registered
{
list.add(*this)
}
{
}
catch(...)
{
if(registered)
list.del(*this);
}
ircd::m::hook::site::~site()
noexcept
{
if(registered)
list.del(*this);
}
void
ircd::m::hook::site::operator()(const event &event)
{
std::set<hook *> matching; //TODO: allocator
const auto site_match{[&matching]
2018-02-26 08:24:12 +01:00
(auto &map, const string_view &key)
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
matching.emplace(pit.first->second);
}};
site_match(origin, at<"origin"_>(event));
site_match(room_id, at<"room_id"_>(event));
site_match(sender, at<"sender"_>(event));
site_match(type, at<"type"_>(event));
2018-02-26 08:24:12 +01:00
if(json::get<"state_key"_>(event))
site_match(state_key, at<"state_key"_>(event));
auto it(begin(matching));
while(it != end(matching))
{
const hook &hook(**it);
if(!hook.match(event))
it = matching.erase(it);
else
++it;
}
2018-02-26 08:24:12 +01:00
for(const auto &hook : matching)
hook->function(event);
}
bool
ircd::m::hook::site::add(hook &hook)
{
if(json::get<"origin"_>(hook.matching))
origin.emplace(at<"origin"_>(hook.matching), &hook);
2018-02-26 08:24:12 +01:00
if(json::get<"room_id"_>(hook.matching))
room_id.emplace(at<"room_id"_>(hook.matching), &hook);
2018-02-26 08:24:12 +01:00
if(json::get<"sender"_>(hook.matching))
sender.emplace(at<"sender"_>(hook.matching), &hook);
2018-02-26 08:24:12 +01:00
if(json::get<"state_key"_>(hook.matching))
state_key.emplace(at<"state_key"_>(hook.matching), &hook);
2018-02-26 08:24:12 +01:00
if(json::get<"type"_>(hook.matching))
type.emplace(at<"type"_>(hook.matching), &hook);
2018-02-26 08:24:12 +01:00
++count;
2018-02-26 08:24:12 +01:00
return true;
}
bool
ircd::m::hook::site::del(hook &hook)
{
const auto unmap{[&hook]
(auto &map, const string_view &key)
{
auto pit{map.equal_range(key)};
for(; pit.first != pit.second; ++pit.first)
if(pit.first->second == &hook)
return map.erase(pit.first);
assert(0);
}};
if(json::get<"origin"_>(hook.matching))
unmap(origin, at<"origin"_>(hook.matching));
2018-02-26 08:24:12 +01:00
if(json::get<"room_id"_>(hook.matching))
unmap(room_id, at<"room_id"_>(hook.matching));
2018-02-26 08:24:12 +01:00
if(json::get<"sender"_>(hook.matching))
unmap(sender, at<"sender"_>(hook.matching));
2018-02-26 08:24:12 +01:00
if(json::get<"state_key"_>(hook.matching))
unmap(state_key, at<"state_key"_>(hook.matching));
2018-02-26 08:24:12 +01:00
if(json::get<"type"_>(hook.matching))
unmap(type, at<"type"_>(hook.matching));
2018-02-26 08:24:12 +01:00
--count;
2018-02-26 08:24:12 +01:00
return true;
}
ircd::string_view
ircd::m::hook::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::list
//
decltype(ircd::m::hook::list)
ircd::m::hook::list
{};
bool
ircd::m::hook::list::del(hook &hook)
try
{
const auto site(at(hook.site_name()));
assert(site != nullptr);
return site->add(hook);
}
catch(const std::out_of_range &e)
{
log::critical
{
"Tried to unregister hook(%p) from missing hook::site '%s'",
&hook,
hook.site_name()
};
assert(0);
return false;
}
bool
ircd::m::hook::list::add(hook &hook)
try
{
const auto site(at(hook.site_name()));
assert(site != nullptr);
return site->add(hook);
}
catch(const std::out_of_range &e)
{
throw error
{
"No hook::site named '%s' is registered...", hook.site_name()
};
}
bool
ircd::m::hook::list::del(site &site)
{
return erase(site.name());
}
bool
ircd::m::hook::list::add(site &site)
{
const auto iit
{
emplace(site.name(), &site)
};
if(unlikely(!iit.second))
throw error
{
"Hook site name '%s' already in use", site.name()
};
return true;
}