mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-12 17:14:46 +02:00
Add RPC methods for security_group and connthrottle (#328)
New RPC methods: - security_group.list: List all security groups - security_group.get: Get details of a specific security group - connthrottle.status: Get full connection throttle status, counters, and config - connthrottle.set: Enable/disable connection throttling - connthrottle.reset: Reset connection throttling counts This also adds json_expand_mask_list(), json_expand_name_list(), and json_expand_nvplist() to src/json.c for reuse by RPC modules.
This commit is contained in:
@@ -274,6 +274,7 @@ loadmodule "rpc/spamfilter";
|
||||
loadmodule "rpc/log";
|
||||
loadmodule "rpc/whowas";
|
||||
loadmodule "rpc/message";
|
||||
loadmodule "rpc/security_group";
|
||||
|
||||
/*** Other ***/
|
||||
// These are modules that don't fit in any of the previous sections
|
||||
|
||||
@@ -1308,6 +1308,9 @@ extern void json_expand_client_security_groups(json_t *parent, Client *client);
|
||||
extern void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail);
|
||||
extern void json_expand_tkl(json_t *j, const char *key, TKL *tkl, int detail);
|
||||
extern void json_expand_textanalysis(json_t *root, const char *key, TextAnalysis *ta, int detail);
|
||||
extern void json_expand_mask_list(json_t *parent, const char *key, ConfigItem_mask *mask);
|
||||
extern void json_expand_name_list(json_t *parent, const char *key, NameList *list);
|
||||
extern void json_expand_nvplist(json_t *parent, const char *key, NameValuePrioList *list);
|
||||
/* end of json.c */
|
||||
/* securitygroup.c start */
|
||||
extern MODVAR SecurityGroup *securitygroups;
|
||||
|
||||
+1
-1
@@ -2710,7 +2710,7 @@ typedef enum JsonRpcError {
|
||||
} while(0)
|
||||
|
||||
#define REQUIRE_PARAM_BOOLEAN(name, varname) do { \
|
||||
json_t *vvv = json_object_get(params, name); \
|
||||
json_t *v = json_object_get(params, name); \
|
||||
if (!v || !json_is_boolean(v)) \
|
||||
{ \
|
||||
rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", name); \
|
||||
|
||||
+60
@@ -648,3 +648,63 @@ void json_expand_textanalysis(json_t *root, const char *key, TextAnalysis *ta, i
|
||||
json_object_set_new(blk, utf8_get_block_name(i), json_integer(ta->unicode_blockmap[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/** Expand a ConfigItem_mask list to a JSON array.
|
||||
* @param parent The parent JSON object
|
||||
* @param key The key name for the array
|
||||
* @param mask The mask list to expand
|
||||
*/
|
||||
void json_expand_mask_list(json_t *parent, const char *key, ConfigItem_mask *mask)
|
||||
{
|
||||
json_t *arr;
|
||||
ConfigItem_mask *m;
|
||||
|
||||
if (!mask)
|
||||
return;
|
||||
|
||||
arr = json_array();
|
||||
json_object_set_new(parent, key, arr);
|
||||
|
||||
for (m = mask; m; m = m->next)
|
||||
json_array_append_new(arr, json_string_unreal(m->mask));
|
||||
}
|
||||
|
||||
/** Expand a NameList to a JSON array.
|
||||
* @param parent The parent JSON object
|
||||
* @param key The key name for the array
|
||||
* @param list The name list to expand
|
||||
*/
|
||||
void json_expand_name_list(json_t *parent, const char *key, NameList *list)
|
||||
{
|
||||
json_t *arr;
|
||||
NameList *n;
|
||||
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
arr = json_array();
|
||||
json_object_set_new(parent, key, arr);
|
||||
|
||||
for (n = list; n; n = n->next)
|
||||
json_array_append_new(arr, json_string_unreal(n->name));
|
||||
}
|
||||
|
||||
/** Expand a NameValuePrioList to a JSON object.
|
||||
* @param parent The parent JSON object
|
||||
* @param key The key name for the object
|
||||
* @param list The name-value list to expand
|
||||
*/
|
||||
void json_expand_nvplist(json_t *parent, const char *key, NameValuePrioList *list)
|
||||
{
|
||||
json_t *obj;
|
||||
NameValuePrioList *n;
|
||||
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
obj = json_object();
|
||||
json_object_set_new(parent, key, obj);
|
||||
|
||||
for (n = list; n; n = n->next)
|
||||
json_object_set_new(obj, n->name, json_string_unreal(n->value));
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ int ct_rconnect(Client *);
|
||||
CMD_FUNC(ct_throttle);
|
||||
EVENT(connthrottle_evt);
|
||||
void ucounter_free(ModData *m);
|
||||
RPC_CALL_FUNC(rpc_connthrottle_status);
|
||||
RPC_CALL_FUNC(rpc_connthrottle_set);
|
||||
RPC_CALL_FUNC(rpc_connthrottle_reset);
|
||||
|
||||
MOD_TEST()
|
||||
{
|
||||
@@ -90,11 +93,14 @@ MOD_TEST()
|
||||
|
||||
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, ct_config_test);
|
||||
HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, ct_config_posttest);
|
||||
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
MOD_INIT()
|
||||
{
|
||||
RPCHandlerInfo r;
|
||||
|
||||
MARK_AS_OFFICIAL_MODULE(modinfo);
|
||||
LoadPersistentPointer(modinfo, ucounter, ucounter_free);
|
||||
if (!ucounter)
|
||||
@@ -104,6 +110,36 @@ MOD_INIT()
|
||||
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, ct_lconnect);
|
||||
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, ct_rconnect);
|
||||
CommandAdd(modinfo->handle, MSG_THROTTLE, ct_throttle, MAXPARA, CMD_USER|CMD_SERVER);
|
||||
|
||||
/* RPC handlers */
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.method = "connthrottle.status";
|
||||
r.loglevel = ULOG_DEBUG;
|
||||
r.call = rpc_connthrottle_status;
|
||||
if (!RPCHandlerAdd(modinfo->handle, &r))
|
||||
{
|
||||
config_error("[connthrottle] Could not register RPC handler 'connthrottle.status'");
|
||||
return MOD_FAILED;
|
||||
}
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.method = "connthrottle.set";
|
||||
r.call = rpc_connthrottle_set;
|
||||
if (!RPCHandlerAdd(modinfo->handle, &r))
|
||||
{
|
||||
config_error("[connthrottle] Could not register RPC handler 'connthrottle.set'");
|
||||
return MOD_FAILED;
|
||||
}
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.method = "connthrottle.reset";
|
||||
r.call = rpc_connthrottle_reset;
|
||||
if (!RPCHandlerAdd(modinfo->handle, &r))
|
||||
{
|
||||
config_error("[connthrottle] Could not register RPC handler 'connthrottle.reset'");
|
||||
return MOD_FAILED;
|
||||
}
|
||||
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -598,3 +634,115 @@ void ucounter_free(ModData *m)
|
||||
{
|
||||
safe_free(ucounter);
|
||||
}
|
||||
|
||||
/* ==================== RPC HANDLERS ==================== */
|
||||
|
||||
RPC_CALL_FUNC(rpc_connthrottle_status)
|
||||
{
|
||||
json_t *result, *counters, *config, *stats;
|
||||
time_t start_delay_end;
|
||||
int start_delay_remaining;
|
||||
int reputation_gathering;
|
||||
|
||||
result = json_object();
|
||||
|
||||
/* Basic state */
|
||||
json_object_set_new(result, "enabled", json_boolean(!ucounter->disabled));
|
||||
json_object_set_new(result, "throttling_this_minute", json_boolean(ucounter->throttling_this_minute));
|
||||
json_object_set_new(result, "throttling_previous_minute", json_boolean(ucounter->throttling_previous_minute));
|
||||
|
||||
/* Calculate state info */
|
||||
start_delay_end = me.local->creationtime + cfg.start_delay;
|
||||
if (start_delay_end > TStime())
|
||||
start_delay_remaining = (int)(start_delay_end - TStime());
|
||||
else
|
||||
start_delay_remaining = 0;
|
||||
reputation_gathering = still_reputation_gathering();
|
||||
|
||||
/* Determine overall state */
|
||||
if (ucounter->disabled)
|
||||
json_object_set_new(result, "state", json_string_unreal("disabled_by_oper"));
|
||||
else if (start_delay_remaining > 0)
|
||||
json_object_set_new(result, "state", json_string_unreal("start_delay"));
|
||||
else if (reputation_gathering)
|
||||
json_object_set_new(result, "state", json_string_unreal("reputation_gathering"));
|
||||
else if (ucounter->throttling_this_minute)
|
||||
json_object_set_new(result, "state", json_string_unreal("throttling"));
|
||||
else
|
||||
json_object_set_new(result, "state", json_string_unreal("active"));
|
||||
|
||||
json_object_set_new(result, "start_delay_remaining", json_integer(start_delay_remaining));
|
||||
json_object_set_new(result, "reputation_gathering", json_boolean(reputation_gathering));
|
||||
|
||||
/* Current counters */
|
||||
counters = json_object();
|
||||
json_object_set_new(counters, "local_count", json_integer(ucounter->local.count));
|
||||
json_object_set_new(counters, "global_count", json_integer(ucounter->global.count));
|
||||
if (ucounter->local.t > 0)
|
||||
json_object_set_new(counters, "local_period_start", json_timestamp(ucounter->local.t));
|
||||
if (ucounter->global.t > 0)
|
||||
json_object_set_new(counters, "global_period_start", json_timestamp(ucounter->global.t));
|
||||
json_object_set_new(result, "counters", counters);
|
||||
|
||||
/* Statistics for last minute */
|
||||
stats = json_object();
|
||||
json_object_set_new(stats, "rejected_clients", json_integer(ucounter->rejected_clients));
|
||||
json_object_set_new(stats, "allowed_except", json_integer(ucounter->allowed_except));
|
||||
json_object_set_new(stats, "allowed_unknown_users", json_integer(ucounter->allowed_unknown_users));
|
||||
json_object_set_new(result, "stats_last_minute", stats);
|
||||
|
||||
/* Configuration */
|
||||
config = json_object();
|
||||
json_object_set_new(config, "local_throttle_count", json_integer(cfg.local.count));
|
||||
json_object_set_new(config, "local_throttle_period", json_integer(cfg.local.period));
|
||||
json_object_set_new(config, "global_throttle_count", json_integer(cfg.global.count));
|
||||
json_object_set_new(config, "global_throttle_period", json_integer(cfg.global.period));
|
||||
json_object_set_new(config, "start_delay", json_integer(cfg.start_delay));
|
||||
json_object_set_new(config, "except_reputation_score", json_integer(cfg.except ? cfg.except->reputation_score : 0));
|
||||
json_object_set_new(config, "except_sasl_bypass", json_boolean(cfg.except ? cfg.except->identified : 0));
|
||||
json_object_set_new(config, "except_webirc_bypass", json_boolean(cfg.except ? cfg.except->webirc : 0));
|
||||
json_object_set_new(result, "config", config);
|
||||
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
}
|
||||
|
||||
RPC_CALL_FUNC(rpc_connthrottle_set)
|
||||
{
|
||||
json_t *result;
|
||||
int enabled;
|
||||
const char *parv[3];
|
||||
|
||||
REQUIRE_PARAM_BOOLEAN("enabled", enabled);
|
||||
|
||||
/* Execute the THROTTLE command as the server */
|
||||
parv[0] = NULL;
|
||||
parv[1] = enabled ? "on" : "off";
|
||||
parv[2] = NULL;
|
||||
do_cmd(&me, NULL, "THROTTLE", 2, parv);
|
||||
|
||||
result = json_object();
|
||||
json_object_set_new(result, "success", json_boolean(1));
|
||||
json_object_set_new(result, "enabled", json_boolean(enabled));
|
||||
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
}
|
||||
|
||||
RPC_CALL_FUNC(rpc_connthrottle_reset)
|
||||
{
|
||||
json_t *result;
|
||||
const char *parv[3];
|
||||
|
||||
/* Execute the THROTTLE reset command as the server */
|
||||
parv[0] = NULL;
|
||||
parv[1] = "reset";
|
||||
parv[2] = NULL;
|
||||
do_cmd(&me, NULL, "THROTTLE", 2, parv);
|
||||
|
||||
result = json_object();
|
||||
json_object_set_new(result, "success", json_boolean(1));
|
||||
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ INCLUDES = ../../include/channel.h \
|
||||
R_MODULES= \
|
||||
rpc.so stats.so user.so server.so channel.so server_ban.so \
|
||||
server_ban_exception.so name_ban.so spamfilter.so \
|
||||
log.so whowas.so message.so
|
||||
log.so whowas.so message.so security_group.so
|
||||
|
||||
MODULES=$(R_MODULES)
|
||||
MODULEFLAGS=@MODULEFLAGS@
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/* security_group.* RPC calls
|
||||
* (C) Copyright 2025-.. Valware and the UnrealIRCd team
|
||||
* License: GPLv2 or later
|
||||
*/
|
||||
|
||||
#include "unrealircd.h"
|
||||
|
||||
ModuleHeader MOD_HEADER
|
||||
= {
|
||||
"rpc/security_group",
|
||||
"1.0.0",
|
||||
"security_group.* RPC calls",
|
||||
"UnrealIRCd Team",
|
||||
"unrealircd-6",
|
||||
};
|
||||
|
||||
/* Forward declarations */
|
||||
RPC_CALL_FUNC(rpc_security_group_list);
|
||||
RPC_CALL_FUNC(rpc_security_group_get);
|
||||
|
||||
MOD_INIT()
|
||||
{
|
||||
RPCHandlerInfo r;
|
||||
|
||||
MARK_AS_OFFICIAL_MODULE(modinfo);
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.method = "security_group.list";
|
||||
r.loglevel = ULOG_DEBUG;
|
||||
r.call = rpc_security_group_list;
|
||||
if (!RPCHandlerAdd(modinfo->handle, &r))
|
||||
{
|
||||
config_error("[rpc/security_group] Could not register RPC handler");
|
||||
return MOD_FAILED;
|
||||
}
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.method = "security_group.get";
|
||||
r.loglevel = ULOG_DEBUG;
|
||||
r.call = rpc_security_group_get;
|
||||
if (!RPCHandlerAdd(modinfo->handle, &r))
|
||||
{
|
||||
config_error("[rpc/security_group] Could not register RPC handler");
|
||||
return MOD_FAILED;
|
||||
}
|
||||
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
MOD_LOAD()
|
||||
{
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
MOD_UNLOAD()
|
||||
{
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
/** Helper: Expand security group details to JSON */
|
||||
static void json_expand_security_group(json_t *j, const char *key, SecurityGroup *s, int detail)
|
||||
{
|
||||
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(s->name));
|
||||
json_object_set_new(child, "priority", json_integer(s->priority));
|
||||
|
||||
if (detail == 0)
|
||||
return;
|
||||
|
||||
/* Inclusion criteria */
|
||||
if (s->identified)
|
||||
json_object_set_new(child, "identified", json_boolean(1));
|
||||
if (s->webirc)
|
||||
json_object_set_new(child, "webirc", json_boolean(1));
|
||||
if (s->websocket)
|
||||
json_object_set_new(child, "websocket", json_boolean(1));
|
||||
if (s->tls)
|
||||
json_object_set_new(child, "tls", json_boolean(1));
|
||||
if (s->reputation_score != 0)
|
||||
json_object_set_new(child, "reputation_score", json_integer(s->reputation_score));
|
||||
if (s->connect_time != 0)
|
||||
json_object_set_new(child, "connect_time", json_integer(s->connect_time));
|
||||
|
||||
/* Mask lists */
|
||||
json_expand_mask_list(child, "mask", s->mask);
|
||||
json_expand_mask_list(child, "exclude_mask", s->exclude_mask);
|
||||
|
||||
/* Name lists */
|
||||
json_expand_name_list(child, "ip", s->ip);
|
||||
json_expand_name_list(child, "exclude_ip", s->exclude_ip);
|
||||
json_expand_name_list(child, "security_group", s->security_group);
|
||||
json_expand_name_list(child, "exclude_security_group", s->exclude_security_group);
|
||||
json_expand_name_list(child, "server_port", s->server_port);
|
||||
json_expand_name_list(child, "exclude_server_port", s->exclude_server_port);
|
||||
|
||||
/* Extended criteria (account, realname, etc) */
|
||||
json_expand_nvplist(child, "extended", s->extended);
|
||||
json_expand_nvplist(child, "exclude_extended", s->exclude_extended);
|
||||
|
||||
/* Rules (as strings) */
|
||||
if (s->prettyrule)
|
||||
json_object_set_new(child, "rule", json_string_unreal(s->prettyrule));
|
||||
if (s->exclude_prettyrule)
|
||||
json_object_set_new(child, "exclude_rule", json_string_unreal(s->exclude_prettyrule));
|
||||
}
|
||||
|
||||
RPC_CALL_FUNC(rpc_security_group_list)
|
||||
{
|
||||
json_t *result, *list;
|
||||
SecurityGroup *s;
|
||||
|
||||
result = json_object();
|
||||
list = json_array();
|
||||
json_object_set_new(result, "list", list);
|
||||
|
||||
/* Add the magic 'unknown-users' and 'known-users' groups first */
|
||||
{
|
||||
json_t *item = json_object();
|
||||
json_object_set_new(item, "name", json_string_unreal("unknown-users"));
|
||||
json_object_set_new(item, "priority", json_integer(0));
|
||||
json_object_set_new(item, "builtin", json_boolean(1));
|
||||
json_array_append_new(list, item);
|
||||
}
|
||||
|
||||
for (s = securitygroups; s; s = s->next)
|
||||
{
|
||||
json_t *item = json_object();
|
||||
json_expand_security_group(item, NULL, s, 0);
|
||||
if (!strcmp(s->name, "known-users"))
|
||||
json_object_set_new(item, "builtin", json_boolean(1));
|
||||
json_array_append_new(list, item);
|
||||
}
|
||||
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
}
|
||||
|
||||
RPC_CALL_FUNC(rpc_security_group_get)
|
||||
{
|
||||
json_t *result;
|
||||
SecurityGroup *s;
|
||||
const char *name;
|
||||
|
||||
REQUIRE_PARAM_STRING("name", name);
|
||||
|
||||
/* Handle the magic 'unknown-users' case */
|
||||
if (!strcmp(name, "unknown-users"))
|
||||
{
|
||||
result = json_object();
|
||||
json_object_set_new(result, "name", json_string_unreal("unknown-users"));
|
||||
json_object_set_new(result, "priority", json_integer(0));
|
||||
json_object_set_new(result, "builtin", json_boolean(1));
|
||||
json_object_set_new(result, "description", json_string_unreal("Users not matching the 'known-users' security group"));
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
return;
|
||||
}
|
||||
|
||||
s = find_security_group(name);
|
||||
if (!s)
|
||||
{
|
||||
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Security group not found");
|
||||
return;
|
||||
}
|
||||
|
||||
result = json_object();
|
||||
json_expand_security_group(result, NULL, s, 1);
|
||||
if (!strcmp(s->name, "known-users"))
|
||||
json_object_set_new(result, "builtin", json_boolean(1));
|
||||
|
||||
rpc_response(client, request, result);
|
||||
json_decref(result);
|
||||
}
|
||||
Reference in New Issue
Block a user