pissircd/src/modules/rpc/server.c

417 lines
9.7 KiB
C

/* server.* RPC calls
* (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"rpc/server",
"1.0.0",
"server.* RPC calls",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Forward declarations */
RPC_CALL_FUNC(rpc_server_list);
RPC_CALL_FUNC(rpc_server_get);
RPC_CALL_FUNC(rpc_server_rehash);
RPC_CALL_FUNC(rpc_server_connect);
RPC_CALL_FUNC(rpc_server_disconnect);
RPC_CALL_FUNC(rpc_server_module_list);
int rpc_server_rehash_log(int failure, json_t *rehash_log);
MOD_INIT()
{
RPCHandlerInfo r;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&r, 0, sizeof(r));
r.method = "server.list";
r.loglevel = ULOG_DEBUG;
r.call = rpc_server_list;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "server.get";
r.loglevel = ULOG_DEBUG;
r.call = rpc_server_get;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "server.rehash";
r.call = rpc_server_rehash;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "server.connect";
r.call = rpc_server_connect;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "server.disconnect";
r.call = rpc_server_disconnect;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "server.module_list";
r.loglevel = ULOG_DEBUG;
r.call = rpc_server_module_list;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/server] Could not register RPC handler");
return MOD_FAILED;
}
HookAdd(modinfo->handle, HOOKTYPE_REHASH_LOG, 0, rpc_server_rehash_log);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
RPC_CALL_FUNC(rpc_server_list)
{
json_t *result, *list, *item;
Client *acptr;
result = json_object();
list = json_array();
json_object_set_new(result, "list", list);
list_for_each_entry(acptr, &global_server_list, client_node)
{
if (!IsServer(acptr) && !IsMe(acptr))
continue;
item = json_object();
json_expand_client(item, NULL, acptr, 99);
json_array_append_new(list, item);
}
rpc_response(client, request, result);
json_decref(result);
}
RPC_CALL_FUNC(rpc_server_get)
{
json_t *result, *list, *item;
const char *server;
Client *acptr;
OPTIONAL_PARAM_STRING("server", server);
if (server)
{
if (!(acptr = find_server(server, NULL)))
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
return;
}
} else {
acptr = &me;
}
result = json_object();
json_expand_client(result, "server", acptr, 99);
rpc_response(client, request, result);
json_decref(result);
}
RPC_CALL_FUNC(rpc_server_rehash)
{
json_t *result, *list, *item;
const char *server;
Client *acptr;
OPTIONAL_PARAM_STRING("server", server);
if (server)
{
if (!(acptr = find_server(server, NULL)))
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
return;
}
} else {
acptr = &me;
}
if (acptr != &me)
{
/* Forward to remote */
if (rrpc_supported_simple(acptr, NULL))
{
/* Server supports RRPC and will handle the response */
rpc_send_request_to_remote(client, acptr, request);
} else {
/* Server does not support RRPC, so we can only do best effort: */
sendto_one(acptr, NULL, ":%s REHASH %s", me.id, acptr->name);
result = json_boolean(1);
rpc_response(client, request, result);
json_decref(result);
}
return;
}
if (client->rpc->rehash_request)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "A rehash initiated by you is already in progress");
return;
}
/* It's for me... */
SetMonitorRehash(client);
SetAsyncRPC(client);
client->rpc->rehash_request = json_copy(request); // or json_deep_copy ??
if (!loop.rehashing)
{
unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
request_rehash(client);
} /* else.. we simply joined the rehash request so we are notified as well ;) */
/* Do NOT send the JSON Response here, it is done by rpc_server_rehash_log()
* after the REHASH completed (which may take several seconds).
*/
}
int rpc_server_rehash_log(int failure, json_t *rehash_log)
{
Client *client, *next;
list_for_each_entry(client, &unknown_list, lclient_node)
{
if (IsRPC(client) && IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
{
rpc_response(client, client->rpc->rehash_request, rehash_log);
json_decref(client->rpc->rehash_request);
client->rpc->rehash_request = NULL;
}
}
list_for_each_entry_safe(client, next, &rpc_remote_list, client_node)
{
if (IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
{
rpc_response(client, client->rpc->rehash_request, rehash_log);
json_decref(client->rpc->rehash_request);
client->rpc->rehash_request = NULL;
free_client(client);
}
}
return 0;
}
RPC_CALL_FUNC(rpc_server_connect)
{
json_t *result, *list, *item;
const char *server, *link_name;
Client *acptr;
ConfigItem_link *link;
const char *err;
OPTIONAL_PARAM_STRING("server", server);
if (server)
{
if (!(acptr = find_server(server, NULL)))
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
return;
}
} else {
acptr = &me;
}
REQUIRE_PARAM_STRING("link", link_name);
if (acptr != &me)
{
/* Not supported atm */
result = json_boolean(0);
rpc_response(client, request, result);
json_decref(result);
return;
}
if (find_server_quick(link_name))
{
rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Server is already linked");
return;
}
link = find_link(link_name);
if (!link)
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name does not exist in any link block");
return;
}
if (!link->outgoing.hostname && !link->outgoing.file)
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name exists but is not configured as an OUTGOING server.");
return;
}
if ((err = check_deny_link(link, 0)))
{
rpc_error_fmt(client, request, JSON_RPC_ERROR_DENIED, "Server linking is denied via a deny link { } block: %s", err);
return;
}
unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
"CONNECT: Link to $link_block requested by $client",
log_data_link_block(link));
connect_server(link, client, NULL);
result = json_boolean(1);
rpc_response(client, request, result);
json_decref(result);
}
RPC_CALL_FUNC(rpc_server_disconnect)
{
json_t *result, *list, *item;
const char *server, *link_name, *reason;
Client *acptr, *target;
MessageTag *mtags = NULL;
OPTIONAL_PARAM_STRING("server", server);
if (server)
{
if (!(acptr = find_server(server, NULL)))
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
return;
}
} else {
acptr = &me;
}
REQUIRE_PARAM_STRING("link", link_name);
REQUIRE_PARAM_STRING("reason", reason);
if (acptr != &me)
{
/* Not supported atm */
result = json_boolean(0);
rpc_response(client, request, result);
json_decref(result);
return;
}
target = find_server_quick(link_name);
if (!target)
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server link not found");
return;
}
if (target == &me)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "We cannot disconnect ourselves");
return;
}
unreal_log(ULOG_INFO, "link", "SQUIT", client,
"SQUIT: Forced server disconnect of $target by $client ($reason)",
log_data_client("target", target),
log_data_string("reason", reason));
/* The actual SQUIT: */
new_message(client, NULL, &mtags);
exit_client_ex(target, NULL, mtags, reason);
free_message_tags(mtags);
/* Return true */
result = json_boolean(1);
rpc_response(client, request, result);
json_decref(result);
}
void json_expand_module(json_t *j, const char *key, Module *m, int detail)
{
char buf[BUFSIZE+1];
json_t *child;
if (key)
{
child = json_object();
json_object_set_new(j, key, child);
} else {
child = j;
}
json_object_set_new(child, "name", json_string_unreal(m->header->name));
json_object_set_new(child, "version", json_string_unreal(m->header->version));
json_object_set_new(child, "author", json_string_unreal(m->header->author));
json_object_set_new(child, "description", json_string_unreal(m->header->description));
json_object_set_new(child, "third_party", json_boolean(m->options & MOD_OPT_OFFICIAL ? 0 : 1));
json_object_set_new(child, "permanent", json_boolean(m->options & MOD_OPT_PERM ? 1 : 0));
json_object_set_new(child, "permanent_but_reloadable", json_boolean(m->options & MOD_OPT_PERM_RELOADABLE ? 1 : 0));
}
RPC_CALL_FUNC(rpc_server_module_list)
{
json_t *result, *list, *item;
const char *server;
Client *acptr;
Module *m;
OPTIONAL_PARAM_STRING("server", server);
if (server)
{
if (!(acptr = find_server(server, NULL)))
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
return;
}
} else {
acptr = &me;
}
if (acptr != &me)
{
/* Forward to remote */
rpc_send_request_to_remote(client, acptr, request);
return;
}
/* Return true */
result = json_object();
list = json_array();
json_object_set_new(result, "list", list);
for (m = Modules; m; m = m->next)
{
item = json_object();
json_expand_module(item, NULL, m, 1);
json_array_append_new(list, item);
}
rpc_response(client, request, result);
json_decref(result);
}