1
0
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:
Valerie Liu
2025-12-06 13:58:57 +00:00
committed by GitHub
parent d2586a4b9c
commit 7964345c0b
7 changed files with 398 additions and 2 deletions
+1
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
+148
View File
@@ -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);
}
+1 -1
View File
@@ -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@
+184
View File
@@ -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);
}