2019-09-28 23:17:54 +02:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
namespace ircd::m
|
|
|
|
{
|
|
|
|
extern conf::item<seconds> cache_warmup_time;
|
|
|
|
extern conf::item<bool> x_matrix_verify_origin;
|
|
|
|
extern conf::item<bool> x_matrix_verify_destination;
|
|
|
|
|
|
|
|
static user::id::buf authenticate_user(const resource::method &, const client &, resource::request &);
|
|
|
|
static string_view authenticate_node(const resource::method &, const client &, resource::request &);
|
|
|
|
static void cache_warm_origin(const resource::request &);
|
|
|
|
}
|
|
|
|
|
|
|
|
decltype(ircd::m::cache_warmup_time)
|
|
|
|
ircd::m::cache_warmup_time
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.cache_warmup_time" },
|
|
|
|
{ "default", 3600L },
|
|
|
|
};
|
|
|
|
|
|
|
|
decltype(ircd::m::x_matrix_verify_origin)
|
|
|
|
ircd::m::x_matrix_verify_origin
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.x_matrix.verify_origin" },
|
|
|
|
{ "default", true },
|
|
|
|
};
|
|
|
|
|
|
|
|
decltype(ircd::m::x_matrix_verify_origin)
|
|
|
|
ircd::m::x_matrix_verify_destination
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.x_matrix.verify_destination" },
|
|
|
|
{ "default", true },
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// m::resource::method
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::m::resource::method::method(m::resource &resource,
|
|
|
|
const string_view &name,
|
|
|
|
handler function,
|
|
|
|
struct opts opts)
|
|
|
|
:ircd::resource::method
|
|
|
|
{
|
|
|
|
resource,
|
|
|
|
name,
|
|
|
|
std::bind(&method::handle, this, ph::_1, ph::_2),
|
|
|
|
std::move(opts),
|
|
|
|
}
|
|
|
|
,function
|
|
|
|
{
|
|
|
|
std::move(function)
|
|
|
|
}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::m::resource::method::~method()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::resource::response
|
|
|
|
ircd::m::resource::method::handle(client &client,
|
|
|
|
ircd::resource::request &request_)
|
|
|
|
try
|
|
|
|
{
|
|
|
|
m::resource::request request
|
|
|
|
{
|
|
|
|
*this, client, request_
|
|
|
|
};
|
|
|
|
|
|
|
|
if(request.origin)
|
|
|
|
{
|
|
|
|
// If we have an error cached from previously not being able to
|
|
|
|
// contact this origin we can clear that now that they're alive.
|
|
|
|
server::errclear(request.origin);
|
|
|
|
|
|
|
|
// The origin was verified so we can invoke the cache warming now.
|
|
|
|
cache_warm_origin(request);
|
|
|
|
}
|
|
|
|
|
|
|
|
return function(client, request);
|
|
|
|
}
|
|
|
|
catch(const json::print_error &e)
|
|
|
|
{
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::INTERNAL_SERVER_ERROR, "M_NOT_JSON",
|
|
|
|
"Generator Protection: %s",
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const json::not_found &e)
|
|
|
|
{
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::BAD_REQUEST, "M_BAD_JSON",
|
|
|
|
"Required JSON field: %s",
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const json::error &e)
|
|
|
|
{
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::BAD_REQUEST, "M_NOT_JSON",
|
|
|
|
"%s",
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const ctx::timeout &e)
|
|
|
|
{
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::BAD_GATEWAY, "M_REQUEST_TIMEOUT",
|
|
|
|
"%s",
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// resource::request
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::m::resource::request::request(const method &method,
|
|
|
|
const client &client,
|
|
|
|
ircd::resource::request &r)
|
|
|
|
:ircd::resource::request
|
|
|
|
{
|
|
|
|
r
|
|
|
|
}
|
|
|
|
,origin
|
|
|
|
{
|
|
|
|
//NOTE: may be assigned by authenticate_user()
|
|
|
|
}
|
|
|
|
,node_id
|
|
|
|
{
|
|
|
|
// Server X-Matrix header verified here. Similar to client auth, origin
|
|
|
|
// which has been authed is referenced in the client.request. If the method
|
|
|
|
// requires, and auth fails or not provided, this function throws.
|
|
|
|
// Otherwise it returns a string_view of the origin name in
|
|
|
|
// client.request.origin, or an empty string_view if an origin was not
|
|
|
|
// apropos for this request (i.e a client request rather than federation).
|
|
|
|
authenticate_node(method, client, *this)
|
|
|
|
}
|
|
|
|
,access_token
|
|
|
|
{
|
|
|
|
//NOTE: may be assigned by authenticate_user()
|
|
|
|
}
|
|
|
|
,user_id
|
|
|
|
{
|
|
|
|
// Client access token verified here. On success, user_id owning the token
|
|
|
|
// is copied into the client.request structure. On failure, the method is
|
|
|
|
// checked to see if it requires authentication and if so, this throws.
|
|
|
|
authenticate_user(method, client, *this)
|
|
|
|
}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Authenticate a client based on access_token either in the query string or
|
|
|
|
/// in the Authentication bearer header. If a token is found the user_id owning
|
|
|
|
/// the token is copied into the request. If it is not found or it is invalid
|
|
|
|
/// then the method being requested is checked to see if it is required. If so
|
|
|
|
/// the appropriate exception is thrown.
|
|
|
|
ircd::m::user::id::buf
|
|
|
|
ircd::m::authenticate_user(const resource::method &method,
|
|
|
|
const client &client,
|
|
|
|
resource::request &request)
|
|
|
|
{
|
|
|
|
request.access_token =
|
|
|
|
{
|
|
|
|
request.query["access_token"]
|
|
|
|
};
|
|
|
|
|
|
|
|
if(empty(request.access_token))
|
|
|
|
{
|
|
|
|
const auto authorization
|
|
|
|
{
|
|
|
|
split(request.head.authorization, ' ')
|
|
|
|
};
|
|
|
|
|
|
|
|
if(iequals(authorization.first, "bearer"_sv))
|
|
|
|
request.access_token = authorization.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(method.opts);
|
|
|
|
const auto requires_auth
|
|
|
|
{
|
|
|
|
method.opts->flags & resource::method::REQUIRES_AUTH
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!request.access_token && requires_auth)
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::UNAUTHORIZED, "M_MISSING_TOKEN",
|
|
|
|
"Credentials for this method are required but missing."
|
|
|
|
};
|
|
|
|
|
2019-10-05 00:16:07 +02:00
|
|
|
m::user::id::buf ret;
|
2019-09-28 23:17:54 +02:00
|
|
|
if(!request.access_token)
|
2019-10-05 00:16:07 +02:00
|
|
|
return ret;
|
2019-09-28 23:17:54 +02:00
|
|
|
|
|
|
|
static const m::event::fetch::opts fopts
|
|
|
|
{
|
|
|
|
m::event::keys::include {"sender"}
|
|
|
|
};
|
|
|
|
|
2019-10-01 05:50:58 +02:00
|
|
|
const m::room::id::buf tokens_room
|
|
|
|
{
|
|
|
|
"tokens", origin(my())
|
|
|
|
};
|
|
|
|
|
2019-09-28 23:17:54 +02:00
|
|
|
const m::room::state tokens
|
|
|
|
{
|
2019-10-01 05:50:58 +02:00
|
|
|
tokens_room, &fopts
|
2019-09-28 23:17:54 +02:00
|
|
|
};
|
|
|
|
|
2019-10-05 00:16:07 +02:00
|
|
|
tokens.get(std::nothrow, "ircd.access_token", request.access_token, [&ret]
|
2019-09-28 23:17:54 +02:00
|
|
|
(const m::event &event)
|
|
|
|
{
|
|
|
|
// The user sent this access token to the tokens room
|
2019-10-05 00:16:07 +02:00
|
|
|
ret = at<"sender"_>(event);
|
2019-09-28 23:17:54 +02:00
|
|
|
});
|
|
|
|
|
2019-10-05 00:16:07 +02:00
|
|
|
if(requires_auth && !ret)
|
2019-09-28 23:17:54 +02:00
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::UNAUTHORIZED, "M_UNKNOWN_TOKEN",
|
|
|
|
"Credentials for this method are required but invalid."
|
|
|
|
};
|
|
|
|
|
2019-10-05 00:16:07 +02:00
|
|
|
return ret;
|
2019-09-28 23:17:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ircd::string_view
|
|
|
|
ircd::m::authenticate_node(const resource::method &method,
|
|
|
|
const client &client,
|
|
|
|
resource::request &request)
|
|
|
|
try
|
|
|
|
{
|
|
|
|
assert(method.opts);
|
|
|
|
const auto required
|
|
|
|
{
|
|
|
|
method.opts->flags & resource::method::VERIFY_ORIGIN
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto authorization
|
|
|
|
{
|
|
|
|
split(request.head.authorization, ' ')
|
|
|
|
};
|
|
|
|
|
|
|
|
const bool supplied
|
|
|
|
{
|
|
|
|
iequals(authorization.first, "X-Matrix"_sv)
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!required && !supplied)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if(required && !supplied)
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::UNAUTHORIZED, "M_MISSING_AUTHORIZATION",
|
|
|
|
"Required X-Matrix Authorization was not supplied"
|
|
|
|
};
|
|
|
|
|
|
|
|
if(x_matrix_verify_destination && !m::my_host(request.head.host))
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::UNAUTHORIZED, "M_NOT_MY_HOST",
|
|
|
|
"The X-Matrix Authorization destination '%s' is not recognized here.",
|
|
|
|
request.head.host
|
|
|
|
};
|
|
|
|
|
|
|
|
const m::request::x_matrix x_matrix
|
|
|
|
{
|
|
|
|
request.head.authorization
|
|
|
|
};
|
|
|
|
|
|
|
|
const m::request object
|
|
|
|
{
|
|
|
|
x_matrix.origin, request.head.host, method.name, request.head.uri, request.content
|
|
|
|
};
|
|
|
|
|
|
|
|
if(x_matrix_verify_origin && !object.verify(x_matrix.key, x_matrix.sig))
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::FORBIDDEN, "M_INVALID_SIGNATURE",
|
|
|
|
"The X-Matrix Authorization is invalid."
|
|
|
|
};
|
|
|
|
|
|
|
|
request.origin = x_matrix.origin;
|
|
|
|
request.node_id = request.origin; //TODO: remove request.node_id.
|
|
|
|
return request.origin;
|
|
|
|
}
|
|
|
|
catch(const m::error &)
|
|
|
|
{
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
log::derror
|
|
|
|
{
|
|
|
|
resource::log, "X-Matrix Authorization from %s: %s",
|
|
|
|
string(remote(client)),
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
|
|
|
|
throw m::error
|
|
|
|
{
|
|
|
|
http::UNAUTHORIZED, "M_UNKNOWN_ERROR",
|
|
|
|
"An error has prevented authorization: %s",
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// We can smoothly warmup some memory caches after daemon startup as the
|
|
|
|
/// requests trickle in from remote servers. This function is invoked after
|
|
|
|
/// a remote contacts and reveals its identity with the X-Matrix verification.
|
|
|
|
///
|
|
|
|
/// This process helps us avoid cold caches for the first requests coming from
|
|
|
|
/// our server. Such requests are often parallel requests, for ex. to hundreds
|
|
|
|
/// of servers in a Matrix room at the same time.
|
|
|
|
///
|
|
|
|
/// This function does nothing after the cache warmup period has ended.
|
|
|
|
void
|
|
|
|
ircd::m::cache_warm_origin(const resource::request &request)
|
|
|
|
try
|
|
|
|
{
|
|
|
|
assert(request.origin);
|
|
|
|
if(ircd::uptime() > seconds(cache_warmup_time))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Make a query through SRV and A records.
|
|
|
|
//net::dns::resolve(origin, net::dns::prefetch_ipport);
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
log::derror
|
|
|
|
{
|
|
|
|
resource::log, "Cache warming for '%s' :%s",
|
|
|
|
request.origin,
|
|
|
|
e.what()
|
|
|
|
};
|
|
|
|
}
|