mirror of https://github.com/pissnet/pissircd.git
254 lines
6.5 KiB
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;
|
|
}
|