mirror of
https://github.com/matrix-construct/construct
synced 2024-11-27 01:02:46 +01:00
1208 lines
21 KiB
C++
1208 lines
21 KiB
C++
// 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.
|
|
|
|
namespace ircd::m
|
|
{
|
|
static void bootstrap_event_vector(homeserver &);
|
|
static void bootstrap(homeserver &);
|
|
static void signon(homeserver &), signoff(homeserver &) noexcept;
|
|
|
|
extern conf::item<std::string> online_status_msg;
|
|
extern conf::item<std::string> offline_status_msg;
|
|
}
|
|
|
|
// Linkage for the container of all active clients for iteration purposes.
|
|
template<>
|
|
decltype(ircd::util::instance_multimap<ircd::string_view, ircd::m::homeserver, std::less<>>::map)
|
|
ircd::util::instance_multimap<ircd::string_view, ircd::m::homeserver, std::less<>>::map
|
|
{};
|
|
|
|
[[gnu::hot]]
|
|
ircd::m::user::id
|
|
ircd::m::me()
|
|
{
|
|
auto &my
|
|
{
|
|
m::my()
|
|
};
|
|
|
|
return my.self;
|
|
}
|
|
|
|
[[gnu::hot]]
|
|
ircd::m::user::id
|
|
ircd::m::me(const string_view &origin)
|
|
{
|
|
auto &my
|
|
{
|
|
m::my(origin)
|
|
};
|
|
|
|
return my.self;
|
|
}
|
|
|
|
[[gnu::hot]]
|
|
ircd::m::homeserver &
|
|
ircd::m::my()
|
|
{
|
|
if(unlikely(!homeserver::primary))
|
|
throw m::NOT_A_HOMESERVER
|
|
{
|
|
"I do not host any homeserver here."
|
|
};
|
|
|
|
return *homeserver::primary;
|
|
}
|
|
|
|
[[gnu::hot]]
|
|
ircd::m::homeserver &
|
|
ircd::m::my(const string_view &name)
|
|
{
|
|
const auto &it
|
|
{
|
|
homeserver::map.find(name)
|
|
};
|
|
|
|
if(unlikely(it == end(homeserver::map)))
|
|
throw m::NOT_MY_HOMESERVER
|
|
{
|
|
"I do not host any '%s' homeserver here.",
|
|
name,
|
|
};
|
|
|
|
const auto &ptr
|
|
{
|
|
it->second
|
|
};
|
|
|
|
assert(ptr);
|
|
return *ptr;
|
|
}
|
|
|
|
bool
|
|
ircd::m::myself(const m::user::id &user_id)
|
|
{
|
|
return !for_each([&user_id]
|
|
(const auto &homeserver)
|
|
{
|
|
return homeserver.self != user_id;
|
|
});
|
|
}
|
|
|
|
bool
|
|
ircd::m::my_origin(const string_view &origin)
|
|
{
|
|
return !for_each([&origin]
|
|
(const auto &homeserver)
|
|
{
|
|
return m::origin(homeserver) != origin;
|
|
});
|
|
}
|
|
|
|
bool
|
|
ircd::m::for_each(const std::function<bool (homeserver &)> &closure)
|
|
{
|
|
for(auto &[name, hs_p] : homeserver::map)
|
|
if(!closure(*hs_p))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
const ircd::ed25519::sk &
|
|
ircd::m::secret_key(const homeserver &homeserver)
|
|
{
|
|
assert(homeserver.key);
|
|
return homeserver.key->secret_key;
|
|
}
|
|
|
|
ircd::string_view
|
|
ircd::m::public_key_id(const homeserver &homeserver)
|
|
{
|
|
assert(homeserver.key);
|
|
return homeserver.key->public_key_id;
|
|
}
|
|
|
|
bool
|
|
ircd::m::server_name(const homeserver &homeserver,
|
|
const string_view &server_name)
|
|
{
|
|
return server_name == m::server_name(homeserver);
|
|
}
|
|
|
|
bool
|
|
ircd::m::origin(const homeserver &homeserver,
|
|
const string_view &origin)
|
|
{
|
|
return origin == m::origin(homeserver);
|
|
}
|
|
|
|
ircd::string_view
|
|
ircd::m::server_name(const homeserver &homeserver)
|
|
{
|
|
assert(homeserver.opts);
|
|
return homeserver.opts->server_name;
|
|
}
|
|
|
|
ircd::string_view
|
|
ircd::m::origin(const homeserver &homeserver)
|
|
{
|
|
assert(homeserver.opts);
|
|
return homeserver.opts->origin;
|
|
}
|
|
|
|
//
|
|
// homeserver::homeserver
|
|
//
|
|
|
|
namespace ircd::m
|
|
{
|
|
std::unique_ptr<fetch::init> _fetch;
|
|
std::unique_ptr<vm::init> _vm;
|
|
}
|
|
|
|
decltype(ircd::m::homeserver::primary)
|
|
ircd::m::homeserver::primary;
|
|
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::homeserver *
|
|
ircd::m::homeserver::init(const struct opts *const opts)
|
|
try
|
|
{
|
|
assert(opts);
|
|
rfc3986::valid_host(opts->origin);
|
|
rfc3986::valid_host(opts->server_name);
|
|
|
|
return new homeserver
|
|
{
|
|
opts
|
|
};
|
|
}
|
|
catch(const m::error &e)
|
|
{
|
|
assert(opts);
|
|
// Don't rethrow m::error so the catcher doesn't depend on
|
|
// RTTI/personality from this shlib after it unloads.
|
|
throw ircd::error
|
|
{
|
|
"Failed to initialize homeserver %s of %s :%s :%s",
|
|
opts->server_name,
|
|
opts->origin,
|
|
e.errcode(),
|
|
e.errstr(),
|
|
};
|
|
}
|
|
|
|
void
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::homeserver::fini(homeserver *const homeserver)
|
|
noexcept
|
|
{
|
|
delete homeserver;
|
|
}
|
|
|
|
//
|
|
// homeserver::homeserver::homeserver
|
|
//
|
|
|
|
IRCD_MODULE_EXPORT
|
|
ircd::m::homeserver::homeserver(const struct opts *const &opts)
|
|
try
|
|
:instance_multimap
|
|
{
|
|
string_view{opts->origin}
|
|
}
|
|
,opts{[this, &opts]
|
|
{
|
|
primary = primary?: this; //TODO: xxx
|
|
return opts;
|
|
}()}
|
|
,key
|
|
{
|
|
std::make_unique<struct key>(*opts)
|
|
}
|
|
,database
|
|
{
|
|
std::make_shared<dbs::init>(opts->server_name)
|
|
}
|
|
,self
|
|
{
|
|
"ircd", opts->origin
|
|
}
|
|
,conf
|
|
{
|
|
std::make_unique<struct conf>(*opts)
|
|
}
|
|
,modules
|
|
{
|
|
begin(matrix::module_names), end(matrix::module_names)
|
|
}
|
|
{
|
|
if(ircd::mods::autoload)
|
|
for(const auto &name : modules)
|
|
mods::imports.emplace(std::string{name}, name);
|
|
|
|
if(conf && !ircd::defaults)
|
|
conf->load();
|
|
|
|
_fetch = std::make_unique<fetch::init>();
|
|
_vm = std::make_unique<vm::init>();
|
|
const unwind_exceptional exceptional{[]
|
|
{
|
|
_fetch.reset(nullptr);
|
|
_vm.reset(nullptr);
|
|
}};
|
|
|
|
if(dbs::events && sequence(*dbs::events) == 0)
|
|
{
|
|
if(opts->bootstrap_vector_path)
|
|
bootstrap_event_vector(*this);
|
|
else
|
|
bootstrap(*this);
|
|
}
|
|
|
|
if(key && !key->verify_keys.empty())
|
|
m::keys::cache::set(key->verify_keys);
|
|
|
|
signon(*this);
|
|
mods::imports.emplace("net_dns_cache"s, "net_dns_cache"s);
|
|
m::init::backfill::init();
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::logf
|
|
{
|
|
log, log::level::CRITICAL,
|
|
"Failed to start server '%s' on network '%s' :%s",
|
|
opts->server_name,
|
|
opts->origin,
|
|
e.what(),
|
|
};
|
|
}
|
|
|
|
ircd::m::homeserver::~homeserver()
|
|
noexcept try
|
|
{
|
|
///TODO: XXX primary
|
|
server::init::interrupt();
|
|
client::terminate_all(); //TODO: XXX
|
|
server::init::close();
|
|
client::close_all();
|
|
m::init::backfill::fini();
|
|
client::wait_all();
|
|
server::init::wait();
|
|
m::sync::pool.join();
|
|
|
|
if(_vm)
|
|
signoff(*this);
|
|
|
|
///TODO: XXX primary
|
|
mods::imports.erase("net_dns_cache"s);
|
|
_fetch.reset(nullptr);
|
|
_vm.reset(nullptr);
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::critical
|
|
{
|
|
log, "Homeserver shutdown failed :%s",
|
|
e.what()
|
|
};
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// homeserver modules
|
|
//
|
|
|
|
ircd::m::homeserver::modules::~modules()
|
|
noexcept
|
|
{
|
|
for(auto rit(std::rbegin(*this)); rit != std::rend(*this); ++rit)
|
|
mods::imports.erase(*rit);
|
|
}
|
|
|
|
//
|
|
// homeserver::key
|
|
//
|
|
|
|
namespace ircd::m
|
|
{
|
|
extern conf::item<std::string> ed25519_key_dir;
|
|
}
|
|
|
|
decltype(ircd::m::ed25519_key_dir)
|
|
ircd::m::ed25519_key_dir
|
|
{
|
|
{ "name", "ircd.keys.ed25519_key_dir" },
|
|
{ "default", fs::cwd() },
|
|
};
|
|
|
|
ircd::m::homeserver::key::key(const struct opts &opts)
|
|
:secret_key_path{[&opts]
|
|
() -> std::string
|
|
{
|
|
const std::string path_parts[]
|
|
{
|
|
std::string{ed25519_key_dir},
|
|
std::string{opts.origin} + ".ed25519"s,
|
|
};
|
|
|
|
const std::string sk_file
|
|
{
|
|
ircd::string(fs::PATH_MAX_LEN, [&](const mutable_buffer &buf)
|
|
{
|
|
return fs::path(buf, path_parts);
|
|
})
|
|
};
|
|
|
|
if(!fs::exists(sk_file) && !ircd::write_avoid)
|
|
log::notice
|
|
{
|
|
m::log, "Creating ed25519 secret key @ `%s'", sk_file
|
|
};
|
|
|
|
return sk_file;
|
|
}()}
|
|
,secret_key
|
|
{
|
|
secret_key_path, &public_key
|
|
}
|
|
,public_key_b64
|
|
{
|
|
ircd::string(96, [this](const mutable_buffer &buf)
|
|
{
|
|
return b64encode_unpadded(buf, public_key);
|
|
})
|
|
}
|
|
,public_key_id
|
|
{
|
|
"ed25519:"s + std::string{trunc(public_key_b64, 8)}
|
|
}
|
|
,verify_keys{[this, &opts]
|
|
() -> std::string
|
|
{
|
|
const json::strung verify_keys
|
|
{
|
|
json::members
|
|
{
|
|
{ public_key_id, json::member
|
|
{
|
|
"key", public_key_b64
|
|
}}
|
|
}
|
|
};
|
|
|
|
const time_t ts
|
|
{
|
|
//TODO: XXX
|
|
ircd::time<milliseconds>() + (1000 * 60 * 60 * 24 * 7)
|
|
};
|
|
|
|
m::keys key;
|
|
json::get<"server_name"_>(key) = opts.origin;
|
|
json::get<"old_verify_keys"_>(key) = "{}";
|
|
json::get<"verify_keys"_>(key) = verify_keys;
|
|
json::get<"valid_until_ts"_>(key) = ts;
|
|
json::strung ret
|
|
{
|
|
key
|
|
};
|
|
|
|
const ed25519::sig sig
|
|
{
|
|
secret_key.sign(const_buffer(ret))
|
|
};
|
|
|
|
char buf[2][512];
|
|
const json::object sigs
|
|
{
|
|
json::stringify(mutable_buffer(buf[0]), json::members
|
|
{
|
|
{ opts.origin, json::member
|
|
{
|
|
public_key_id, b64encode_unpadded(buf[1], sig)
|
|
}}
|
|
})
|
|
};
|
|
|
|
json::get<"signatures"_>(key) = sigs;
|
|
ret = json::strung
|
|
{
|
|
key
|
|
};
|
|
|
|
return std::move(ret);
|
|
}()}
|
|
{
|
|
log::info
|
|
{
|
|
m::log, "Secret key for %s at `%s'. Public key is %s identified as '%s'",
|
|
opts.origin,
|
|
secret_key_path,
|
|
public_key_b64,
|
|
public_key_id,
|
|
};
|
|
}
|
|
|
|
//
|
|
// homeserver::conf
|
|
//
|
|
|
|
namespace ircd::m
|
|
{
|
|
static bool load_conf_item(const event &);
|
|
static bool load_conf_item(const event::idx &);
|
|
static size_t load_conf_items(const room &, const string_view &prefix);
|
|
|
|
static void handle_conf_room_hook(const event &, vm::eval &);
|
|
static void handle_item_init(const room &, conf::item<> &);
|
|
}
|
|
|
|
//
|
|
// homeserver::conf::conf
|
|
//
|
|
|
|
ircd::m::homeserver::conf::conf(const struct opts &opts)
|
|
:room_id
|
|
{
|
|
"conf", opts.origin
|
|
}
|
|
,room
|
|
{
|
|
room_id
|
|
}
|
|
,item_init
|
|
{
|
|
ircd::conf::on_init, [this](ircd::conf::item<> &item)
|
|
{
|
|
handle_item_init(room, item);
|
|
}
|
|
}
|
|
,conf_updated
|
|
{
|
|
handle_conf_room_hook,
|
|
{
|
|
{ "_site", "vm.effect" },
|
|
{ "room_id", room_id },
|
|
{ "type", "ircd.conf.item" },
|
|
}
|
|
}
|
|
{
|
|
for(const auto &[key, item] : ircd::conf::items) try
|
|
{
|
|
assert(item);
|
|
if(item->set_cb)
|
|
item->set_cb();
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::error
|
|
{
|
|
"Failed to initialize conf item '%s' :%s",
|
|
key,
|
|
e.what()
|
|
};
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
ircd::m::homeserver::conf::store(const string_view &prefix,
|
|
const bool &force)
|
|
const
|
|
{
|
|
size_t ret(0);
|
|
for(const auto &[key, item] : ircd::conf::items) try
|
|
{
|
|
if(prefix && !startswith(key, prefix))
|
|
continue;
|
|
|
|
// Conf items marked with a persist=false property are not written
|
|
// to the conf room; regardless of force=true
|
|
if(!item->feature.get("persist", true))
|
|
continue;
|
|
|
|
thread_local char buf[4_KiB];
|
|
const auto &val
|
|
{
|
|
item->get(buf)
|
|
};
|
|
|
|
bool dup{false}, exists{false};
|
|
if(!force)
|
|
get(key, [&exists, &dup, &val](const string_view &val_)
|
|
{
|
|
exists = true;
|
|
dup = val == val_;
|
|
});
|
|
|
|
// No reason to store the same value
|
|
if(!force && dup)
|
|
continue;
|
|
|
|
const json::string &default_value
|
|
{
|
|
item->feature["default"]
|
|
};
|
|
|
|
// When the !conf room has nothing for a key, and this store request
|
|
// is asking us to write the default value, that is rejected here.
|
|
if(!force && !exists && val == default_value)
|
|
continue;
|
|
|
|
set(key, val);
|
|
ret++;
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::error
|
|
{
|
|
"Failed to create conf item '%s' :%s",
|
|
key,
|
|
e.what()
|
|
};
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t
|
|
ircd::m::homeserver::conf::load(const string_view &prefix)
|
|
const
|
|
{
|
|
return load_conf_items(room, prefix);
|
|
}
|
|
|
|
size_t
|
|
ircd::m::homeserver::conf::defaults(const string_view &prefix)
|
|
const
|
|
{
|
|
size_t ret(0);
|
|
for(const auto &[key, item] : ircd::conf::items)
|
|
{
|
|
if(prefix && !startswith(key, prefix))
|
|
continue;
|
|
|
|
assert(item);
|
|
const json::string &default_value
|
|
{
|
|
item->feature["default"]
|
|
};
|
|
|
|
ret += ircd::conf::set(key, default_value);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ircd::m::event::id::buf
|
|
ircd::m::homeserver::conf::set(const string_view &key,
|
|
const string_view &val)
|
|
const
|
|
{
|
|
// Branch for conf items that do not persist. We don't send a message to
|
|
// the conf room to update them; the value is put directly into the item.
|
|
if(ircd::conf::exists(key) && !ircd::conf::persists(key))
|
|
{
|
|
ircd::conf::set(key, val);
|
|
return {};
|
|
}
|
|
|
|
const m::user::id::buf sender
|
|
{
|
|
"ircd", room_id.hostname()
|
|
};
|
|
|
|
return send(room, sender, "ircd.conf.item", key, json::members
|
|
{
|
|
{ "value", val }
|
|
});
|
|
}
|
|
|
|
bool
|
|
ircd::m::homeserver::conf::get(const string_view &key,
|
|
const std::function<void (const string_view &)> &closure)
|
|
const
|
|
{
|
|
const auto &event_idx
|
|
{
|
|
room.get("ircd.conf.item", key)
|
|
};
|
|
|
|
return m::get(std::nothrow, event_idx, "content", [&closure]
|
|
(const json::object &content)
|
|
{
|
|
const json::string &value
|
|
{
|
|
content["value"]
|
|
};
|
|
|
|
closure(value);
|
|
});
|
|
}
|
|
|
|
void
|
|
ircd::m::handle_item_init(const room &room,
|
|
conf::item<> &item)
|
|
{
|
|
const auto event_idx
|
|
{
|
|
room.get(std::nothrow, "ircd.conf.item", item.name)
|
|
};
|
|
|
|
if(!event_idx)
|
|
return;
|
|
|
|
load_conf_item(event_idx);
|
|
}
|
|
|
|
void
|
|
ircd::m::handle_conf_room_hook(const event &event,
|
|
vm::eval &eval)
|
|
{
|
|
if(!homeserver::primary)
|
|
return;
|
|
|
|
assert(homeserver::primary->conf);
|
|
const m::room::id &primary_room
|
|
{
|
|
homeserver::primary->conf->room_id
|
|
};
|
|
|
|
// Only the primary homeserver controls the global conf items.
|
|
if(json::get<"room_id"_>(event) != primary_room)
|
|
return;
|
|
|
|
load_conf_item(event);
|
|
}
|
|
|
|
size_t
|
|
ircd::m::load_conf_items(const m::room &room,
|
|
const string_view &prefix)
|
|
{
|
|
const m::room::state state
|
|
{
|
|
room
|
|
};
|
|
|
|
state.for_each("ircd.conf.item", [&prefix]
|
|
(const auto &, const auto &state_key, const auto &event_idx)
|
|
{
|
|
static const m::event::fetch::opts fopts
|
|
{
|
|
m::event::keys::include { "content", "state_key" }
|
|
};
|
|
|
|
if(prefix && !startswith(state_key, prefix))
|
|
return true;
|
|
|
|
m::prefetch(event_idx, fopts);
|
|
return true;
|
|
});
|
|
|
|
size_t ret(0);
|
|
state.for_each("ircd.conf.item", [&ret, &prefix]
|
|
(const auto &, const auto &state_key, const auto &event_idx)
|
|
{
|
|
if(prefix && !startswith(state_key, prefix))
|
|
return true;
|
|
|
|
if(!conf::exists(state_key))
|
|
return true;
|
|
|
|
ret += load_conf_item(event_idx);
|
|
return true;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
ircd::m::load_conf_item(const m::event::idx &event_idx)
|
|
{
|
|
static const m::event::fetch::opts fopts
|
|
{
|
|
m::event::keys::include { "content", "state_key" }
|
|
};
|
|
|
|
const m::event::fetch event
|
|
{
|
|
std::nothrow, event_idx, fopts
|
|
};
|
|
|
|
return event.valid && load_conf_item(event);
|
|
}
|
|
|
|
bool
|
|
ircd::m::load_conf_item(const m::event &event)
|
|
try
|
|
{
|
|
const auto &key
|
|
{
|
|
at<"state_key"_>(event)
|
|
};
|
|
|
|
const auto &content
|
|
{
|
|
at<"content"_>(event)
|
|
};
|
|
|
|
const json::string &value
|
|
{
|
|
content.get("value")
|
|
};
|
|
|
|
// Conf items marked with a persist=false property are not read from
|
|
// the conf room into the item, even if the value exists in the room.
|
|
if(conf::exists(key) && !conf::persists(key))
|
|
return false;
|
|
|
|
log::debug
|
|
{
|
|
"Updating conf [%s] => [%s]", key, value
|
|
};
|
|
|
|
ircd::conf::set(key, value);
|
|
return true;
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::error
|
|
{
|
|
"Failed to set conf item '%s' :%s",
|
|
json::get<"state_key"_>(event),
|
|
e.what()
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// signon/signoff greetings
|
|
//
|
|
|
|
decltype(ircd::m::online_status_msg)
|
|
ircd::m::online_status_msg
|
|
{
|
|
{ "name", "ircd.me.online.status_msg" },
|
|
{ "default", "Wanna chat? IRCd at your service!" }
|
|
};
|
|
|
|
decltype(ircd::m::offline_status_msg)
|
|
ircd::m::offline_status_msg
|
|
{
|
|
{ "name", "ircd.me.offline.status_msg" },
|
|
{ "default", "Catch ya on the flip side..." }
|
|
};
|
|
|
|
void
|
|
ircd::m::signon(homeserver &homeserver)
|
|
{
|
|
if(!ircd::write_avoid && vm::sequence::retired != 0)
|
|
presence::set(homeserver.self, "online", online_status_msg);
|
|
}
|
|
|
|
void
|
|
ircd::m::signoff(homeserver &homeserver)
|
|
noexcept try
|
|
{
|
|
if(!std::uncaught_exceptions() && !ircd::write_avoid && vm::sequence::retired != 0)
|
|
presence::set(homeserver.self, "offline", offline_status_msg);
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::error
|
|
{
|
|
log, "homeserver signoff failure :%s",
|
|
e.what(),
|
|
};
|
|
}
|
|
catch(...)
|
|
{
|
|
log::critical
|
|
{
|
|
log, "Unknown error during homeserver signoff"
|
|
};
|
|
}
|
|
|
|
//
|
|
// bootstrap_vector
|
|
//
|
|
|
|
void
|
|
ircd::m::bootstrap_event_vector(homeserver &homeserver)
|
|
try
|
|
{
|
|
const string_view &path
|
|
{
|
|
homeserver.opts->bootstrap_vector_path
|
|
};
|
|
|
|
fs::fd::opts fileopts(std::ios::in);
|
|
fileopts.sequential = true;
|
|
const fs::fd file
|
|
{
|
|
path, fileopts
|
|
};
|
|
|
|
log::notice
|
|
{
|
|
log, "Bootstrapping database from event vector @ `%s'",
|
|
path,
|
|
};
|
|
|
|
const unique_mutable_buffer mb
|
|
{
|
|
4_MiB
|
|
};
|
|
|
|
const_buffer buf;
|
|
fs::read_opts ropts;
|
|
size_t reads(0), events(0);
|
|
while(!!(buf = read(file, mb, ropts)))
|
|
{
|
|
++reads;
|
|
const json::vector vec{buf};
|
|
const size_t _events(events);
|
|
for(auto it(begin(vec)); it != end(vec); ) try
|
|
{
|
|
ropts.offset += size(string_view(*it));
|
|
const m::event event{*it};
|
|
|
|
++events;
|
|
++it;
|
|
}
|
|
catch(const json::parse_error &)
|
|
{
|
|
if(_events == events)
|
|
throw;
|
|
else
|
|
break;
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::error
|
|
{
|
|
log, "Bootstrap event %zu :%s",
|
|
events,
|
|
e.what()
|
|
};
|
|
}
|
|
|
|
char pbuf[48];
|
|
log::info
|
|
{
|
|
log, "Bootstrapping progress %zu events; %s in %zu reads...",
|
|
events,
|
|
pretty(pbuf, iec(ropts.offset)),
|
|
reads,
|
|
};
|
|
}
|
|
|
|
char pbuf[48];
|
|
log::info
|
|
{
|
|
log, "Bootstrapped %zu events in %s from %zu reads of `%s'",
|
|
events,
|
|
pretty(pbuf, iec(ropts.offset)),
|
|
reads,
|
|
path,
|
|
};
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::logf
|
|
{
|
|
log, log::level::CRITICAL,
|
|
"Failed to bootstrap server '%s' on network '%s' :%s",
|
|
server_name(homeserver),
|
|
origin(homeserver),
|
|
e.what(),
|
|
};
|
|
|
|
throw ircd::error
|
|
{
|
|
"bootstrap %s :%s",
|
|
server_name(homeserver),
|
|
e.what(),
|
|
};
|
|
}
|
|
|
|
void
|
|
ircd::m::bootstrap(homeserver &homeserver)
|
|
try
|
|
{
|
|
assert(dbs::events);
|
|
assert(db::sequence(*dbs::events) == 0);
|
|
|
|
assert(homeserver.self);
|
|
const m::user::id &my_id
|
|
{
|
|
homeserver.self
|
|
};
|
|
|
|
if(my_id.hostname() == "localhost")
|
|
log::warning
|
|
{
|
|
"The server's name is configured to localhost. This is probably not what you want."
|
|
};
|
|
|
|
m::user me
|
|
{
|
|
my_id
|
|
};
|
|
|
|
if(!exists(me))
|
|
{
|
|
create(me);
|
|
me.activate();
|
|
}
|
|
|
|
const m::node my_node
|
|
{
|
|
origin(homeserver)
|
|
};
|
|
|
|
const m::node::room node_room
|
|
{
|
|
my_node
|
|
};
|
|
|
|
if(!exists(node_room))
|
|
create(node_room, me);
|
|
|
|
const m::room::id::buf my_room_id
|
|
{
|
|
"ircd", origin(homeserver)
|
|
};
|
|
|
|
const m::room my_room
|
|
{
|
|
my_room_id
|
|
};
|
|
|
|
if(!exists(my_room))
|
|
create(my_room, me);
|
|
|
|
if(!membership(my_room, me, "join"))
|
|
join(my_room, me);
|
|
|
|
if(!my_room.has("m.room.name", ""))
|
|
send(my_room, me, "m.room.name", "",
|
|
{
|
|
{ "name", "IRCd's Room" }
|
|
});
|
|
|
|
if(!my_room.has("m.room.topic", ""))
|
|
send(my_room, me, "m.room.topic", "",
|
|
{
|
|
{ "topic", "The daemon's den." }
|
|
});
|
|
|
|
const m::room::id::buf conf_room_id
|
|
{
|
|
"conf", origin(homeserver)
|
|
};
|
|
|
|
const m::room conf_room
|
|
{
|
|
conf_room_id
|
|
};
|
|
|
|
if(!exists(conf_room))
|
|
create(conf_room, me);
|
|
|
|
if(!conf_room.has("m.room.name",""))
|
|
send(conf_room, me, "m.room.name", "",
|
|
{
|
|
{ "name", "Server Configuration" }
|
|
});
|
|
|
|
const m::room::id::buf tokens_room_id
|
|
{
|
|
"tokens", origin(homeserver)
|
|
};
|
|
|
|
const m::room tokens_room
|
|
{
|
|
tokens_room_id
|
|
};
|
|
|
|
if(!exists(tokens_room))
|
|
create(tokens_room, me);
|
|
|
|
if(!tokens_room.has("m.room.name",""))
|
|
send(tokens_room, me, "m.room.name", "",
|
|
{
|
|
{ "name", "User Tokens" }
|
|
});
|
|
|
|
const m::room::id::buf public_room_id
|
|
{
|
|
"public", origin(homeserver)
|
|
};
|
|
|
|
const m::room public_room
|
|
{
|
|
public_room_id
|
|
};
|
|
|
|
if(!exists(public_room))
|
|
create(public_room, me);
|
|
|
|
const m::room::id::buf alias_room_id
|
|
{
|
|
"alias", origin(homeserver)
|
|
};
|
|
|
|
const m::room alias_room
|
|
{
|
|
alias_room_id
|
|
};
|
|
|
|
if(!exists(alias_room))
|
|
create(alias_room, me);
|
|
|
|
const m::room::id::buf control_room_id
|
|
{
|
|
"control", origin(homeserver)
|
|
};
|
|
|
|
const m::room control_room
|
|
{
|
|
control_room_id
|
|
};
|
|
|
|
if(!exists(control_room))
|
|
create(control_room, me);
|
|
|
|
if(!control_room.has("m.room.name", ""))
|
|
send(control_room, me, "m.room.name", "",
|
|
{
|
|
{ "name", "Control Room" }
|
|
});
|
|
|
|
const m::room::id::buf bridge_room_id
|
|
{
|
|
"bridge", origin(homeserver)
|
|
};
|
|
|
|
const m::room bridge_room
|
|
{
|
|
bridge_room_id
|
|
};
|
|
|
|
if(!exists(bridge_room))
|
|
create(bridge_room, me);
|
|
|
|
log::info
|
|
{
|
|
log, "Bootstrap event generation completed nominally."
|
|
};
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
log::logf
|
|
{
|
|
log, log::level::CRITICAL,
|
|
"Failed to bootstrap server '%s' on network '%s' :%s",
|
|
server_name(homeserver),
|
|
origin(homeserver),
|
|
e.what()
|
|
};
|
|
|
|
throw ircd::panic
|
|
{
|
|
"bootstrap %s error :%s",
|
|
server_name(homeserver),
|
|
e.what()
|
|
};
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// m/self.h
|
|
//
|
|
//
|
|
// !!! DEPRECATED !!!
|
|
//
|
|
// These items are being replaced, but their widespread use throughout the
|
|
// codebase is keeping them here for now.
|
|
//
|
|
|
|
namespace ircd::m::self
|
|
{
|
|
static bool match(const net::hostport &a, const net::hostport &b) noexcept;
|
|
}
|
|
|
|
/// Get network name (origin) of the primary homeserver. Use of this function
|
|
/// is discouraged, though it's not marked as deprecated to reduce warnings
|
|
/// for now until an actual effort is made to eliminate all callsites. Instead
|
|
/// of using this function, try to obtain a more specific homeserver instance
|
|
/// being hosted from this server based on the context of the callsite.
|
|
ircd::string_view
|
|
ircd::m::self::my_host()
|
|
{
|
|
return origin(my());
|
|
}
|
|
|
|
bool
|
|
ircd::m::self::my_host(const string_view &name)
|
|
{
|
|
const auto it
|
|
{
|
|
homeserver::map.find(name)
|
|
};
|
|
|
|
return it != end(homeserver::map);
|
|
}
|
|
|
|
/// Determine if argument string is one of my homeserver's network names. This
|
|
/// is not a simple string comparison; strings postfixed with port :8448 are
|
|
/// compared equal to strings without a port.
|
|
bool
|
|
ircd::m::self::host(const string_view &other)
|
|
{
|
|
assert(m::canon_port == 8448);
|
|
const net::hostport other_host{other};
|
|
for(const auto &[my_network, hs_p] : homeserver::map)
|
|
if(match(my_network, other))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ircd::m::self::match(const net::hostport &a,
|
|
const net::hostport &b)
|
|
noexcept
|
|
{
|
|
// port() is 0 when the origin has no port (and implies 8448)
|
|
const auto my_port
|
|
{
|
|
port(a)?: m::canon_port
|
|
};
|
|
|
|
// If my_host has a non-canonical port number, then the argument must
|
|
// also have the same port number, or there is no possible match.
|
|
if(my_port != m::canon_port)
|
|
return my_port == port(b) && host(a) == host(b);
|
|
|
|
// Since my host is on the canonical port, if other host has some
|
|
// different port number, there is no possible match.
|
|
if(port(b) != m::canon_port)
|
|
return false;
|
|
|
|
// Both myself and input are using 8448; now the name has to match.
|
|
return host(a) == host(b);
|
|
}
|