pissircd/src/modules/central-api.c

254 lines
6.5 KiB
C

/* Central API - API access to unrealircd.org
* (C) Copyright 2023 Bram Matthys and The UnrealIRCd Team
* License: GPLv2
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"central-api",
"1.0.2",
"Acquire and set API key for unrealircd.org services",
"UnrealIRCd Team",
"unrealircd-6",
};
struct cfgstruct {
char *request_key_challenge;
char *request_key_response;
char *api_key;
};
static struct cfgstruct cfg;
struct reqstruct {
char api_key;
char request_key;
};
static struct reqstruct req;
/* Forward declarations */
int capi_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int capi_config_posttest(int *errs);
int capi_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
CMD_FUNC(cmd_centralapisrv);
const char *_get_central_api_key(void);
static void free_config(void)
{
safe_free(cfg.request_key_challenge);
safe_free(cfg.request_key_response);
safe_free(cfg.api_key);
memset(&cfg, 0, sizeof(cfg)); /* needed! */
}
MOD_TEST()
{
memset(&req, 0, sizeof(req));
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, capi_config_test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, capi_config_posttest);
EfunctionAddConstString(modinfo->handle, EFUNC_GET_CENTRAL_API_KEY, _get_central_api_key);
return MOD_SUCCESS;
}
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, capi_config_run);
CommandAdd(modinfo->handle, "CENTRALAPISRV", cmd_centralapisrv, MAXPARA, CMD_UNREGISTERED);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
free_config();
return MOD_SUCCESS;
}
/** Test the set::central-api configuration */
int capi_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
ConfigEntry *cep, *cepp;
if (type != CONFIG_SET)
return 0;
/* We are only interrested in set::central-api.. */
if (!ce || !ce->name || strcmp(ce->name, "central-api"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
if (!cep->value)
{
config_error("%s:%i: set::central-api::%s with no value",
cep->file->filename, cep->line_number, cep->name);
errors++;
} else
if (!strcmp(cep->name, "request-key"))
{
char *p = strchr(cep->value, '-');
if (!p)
{
config_error("%s:%i: set::central-api::request-key: Invalid format. "
"Please check if you copy-pasted the key correctly.",
cep->file->filename, cep->line_number);
errors++;
}
req.request_key = 1;
} else
if (!strcmp(cep->name, "api-key"))
{
if (!strchr(cep->value, ':'))
{
config_error("%s:%i: set::central-api::api-key: Invalid format. "
"Please check if you copy-pasted the api-key correctly. "
"You can log in at your account to view API keys at "
"https://www.unrealircd.org/central-api/account/",
cep->file->filename, cep->line_number);
errors++;
}
req.api_key = 1;
} else
{
config_error("%s:%i: unknown directive set::central-api::%s",
cep->file->filename, cep->line_number, cep->name);
errors++;
continue;
}
}
*errs = errors;
return errors ? -1 : 1;
}
int capi_config_posttest(int *errs)
{
int errors = 0;
if (!req.api_key && !req.request_key)
{
config_error("You need to set either set::central-api::request-key or set::central-api::api-key (not both or none).");
config_error("See https://www.unrealircd.org/docs/Central_API for the documentation");
errors++;
}
*errs = errors;
return errors ? -1 : 1;
}
/* These are prefix and suffix strings. We use sha256(prefix + text + suffix).
* These are public. The only purpose they serve is that we don't do
* simple sha256(hash) which a little bit too simple and there may be
* precomputed rainbowtables like... ah well.. what am i saying, this is
* totally over the top at the moment for our purpose...
* One could even argue why we hash at all in this particular use-case.
*/
#define CAPI_HASH_STRING_PREFIX "7Wre2KPYLumXyi04I5T3QLlzbKVpYxlYGk8rI1M2ypWIoKZKINWnUiMrQ8fPWByw"
#define CAPI_HASH_STRING_SUFFIX "Q3KLNPyEla2F88TOcs11ZARfHpWJaZajNvzWYoadJA6MAKXMtOaR16EugTUi3Kja"
char *capi_hash(const char *in)
{
char buf[512];
static char hashbuf[128];
snprintf(buf, sizeof(buf), "%s:%s:%s",
CAPI_HASH_STRING_PREFIX,
in,
CAPI_HASH_STRING_SUFFIX);
return sha256hash(hashbuf, buf, strlen(buf));
}
/* Configure ourselves based on the set::central-api settings */
int capi_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep, *cepp;
if (type != CONFIG_SET)
return 0;
/* We are only interrested in set::central-api.. */
if (!ce || !ce->name || strcmp(ce->name, "central-api"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "request-key"))
{
char buf[512];
char hashbuf[128];
char *p;
strlcpy(buf, cep->value, sizeof(buf));
p = strchr(buf, '-');
*p++ = '\0'; /* no null pointer, already validated by config test */
safe_strdup(cfg.request_key_challenge, capi_hash(buf));
safe_strdup(cfg.request_key_response, capi_hash(p));
} else
if (!strcmp(cep->name, "api-key"))
{
safe_strdup(cfg.api_key, cep->value);
}
}
return 1;
}
CMD_FUNC(cmd_centralapisrv)
{
if (!MyConnect(client) || !IsSecure(client) || (parc < 2))
return;
if (!strcmp(parv[1], "REQUEST_CHALLENGE") && (parc > 2))
{
if (cfg.request_key_challenge && !strcmp(parv[2], cfg.request_key_challenge))
{
json_t *j;
char *json_serialized;
unreal_log(ULOG_INFO, "central-api", "CENTRALAPI_HANDSHAKE", client,
"Received central-api key request handshake from $client.details");
j = json_object();
json_object_set_new(j, "response", json_string_unreal(cfg.request_key_response));
json_object_set_new(j, "network", json_string_unreal(iConf.network_name_005));
json_object_set_new(j, "lusers", json_integer(irccounts.me_clients));
json_object_set_new(j, "gusers", json_integer(irccounts.clients));
json_object_set_new(j, "servers", json_integer(irccounts.servers));
json_serialized = json_dumps(j, JSON_COMPACT);
if (!json_serialized)
{
unreal_log(ULOG_ERROR, "central-api", "CENTRALAPI_JSON_OUTPUT_ERROR", client,
"Error writing JSON response!?");
json_decref(j);
return;
}
sendto_one(client, NULL, ":%s CENTRALAPISRV REQUEST_RESPONSE :%s",
me.name, json_serialized);
safe_free(json_serialized);
json_decref(j);
return;
}
}
}
const char *_get_central_api_key(void)
{
if (cfg.api_key)
return cfg.api_key;
return NULL;
}