From 7ad160f57af69773845537e2d9474ecda5a42e99 Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sat, 15 Apr 2023 09:22:44 +0200 Subject: [PATCH] JSON-RPC: WHOWAS fetching is now whowas.get, also expose not only logon_time/logoff_time but also connected_since. This also fixes the Makefile for the Windows build (i hope) --- Makefile.windows | 8 ++ doc/RELEASE-NOTES.md | 2 +- doc/conf/modules.default.conf | 1 + include/struct.h | 1 + src/modules/rpc/Makefile.in | 2 +- src/modules/rpc/user.c | 110 +------------------------ src/modules/rpc/whowas.c | 148 ++++++++++++++++++++++++++++++++++ src/modules/whowasdb.c | 24 ++++-- src/whowas.c | 5 +- 9 files changed, 181 insertions(+), 120 deletions(-) create mode 100644 src/modules/rpc/whowas.c diff --git a/Makefile.windows b/Makefile.windows index 93ead540a..5cc8be318 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -339,6 +339,7 @@ DLL_FILES=\ src/modules/restrict-commands.dll \ src/modules/rmtkl.dll \ src/modules/rpc/channel.dll \ + src/modules/rpc/log.dll \ src/modules/rpc/name_ban.dll \ src/modules/rpc/rpc.dll \ src/modules/rpc/stats.dll \ @@ -346,6 +347,7 @@ DLL_FILES=\ src/modules/rpc/server_ban.dll \ src/modules/rpc/server_ban_exception.dll \ src/modules/rpc/spamfilter.dll \ + src/modules/rpc/whowas.dll \ src/modules/rpc/user.dll \ src/modules/rules.dll \ src/modules/sajoin.dll \ @@ -1119,6 +1121,9 @@ src/modules/rmtkl.dll: src/modules/rmtkl.c $(INCLUDES) src/modules/rpc/channel.dll: src/modules/rpc/channel.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/rpc/channel.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/channel.pdb $(MODLFLAGS) +src/modules/rpc/log.dll: src/modules/rpc/log.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/rpc/log.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/log.pdb $(MODLFLAGS) + src/modules/rpc/name_ban.dll: src/modules/rpc/name_ban.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/rpc/name_ban.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/name_ban.pdb $(MODLFLAGS) @@ -1143,6 +1148,9 @@ src/modules/rpc/spamfilter.dll: src/modules/rpc/spamfilter.c $(INCLUDES) src/modules/rpc/user.dll: src/modules/rpc/user.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/rpc/user.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/user.pdb $(MODLFLAGS) +src/modules/rpc/whowas.dll: src/modules/rpc/whowas.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/rpc/whowas.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/whowas.pdb $(MODLFLAGS) + src/modules/rules.dll: src/modules/rules.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/rules.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/rules.pdb $(MODLFLAGS) diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md index 86fa1abe3..d604475b9 100644 --- a/doc/RELEASE-NOTES.md +++ b/doc/RELEASE-NOTES.md @@ -72,7 +72,7 @@ You can help us by testing this release and reporting any issues at https://bugs [`rpc.del_timer`](https://www.unrealircd.org/docs/JSON-RPC:Rpc#rpc.del_timer) so you can schedule JSON-RPC calls, like stats.get, to be executed every xyz msec. * New JSON-RPC method - [`user.get_whowas`](https://www.unrealircd.org/docs/JSON-RPC:User#user.get_whowas) + [`whowas.get`](https://www.unrealircd.org/docs/JSON-RPC:Whowas#whowas.get) to fetch WHOWAS history. * A new message tag `unrealircd.org/issued-by` which is IRCOp-only (and used intra-server) to communicate who actually issued a command. diff --git a/doc/conf/modules.default.conf b/doc/conf/modules.default.conf index 928063170..d972e9d17 100644 --- a/doc/conf/modules.default.conf +++ b/doc/conf/modules.default.conf @@ -250,6 +250,7 @@ loadmodule "rpc/server_ban_exception"; loadmodule "rpc/name_ban"; loadmodule "rpc/spamfilter"; loadmodule "rpc/log"; +loadmodule "rpc/whowas"; /*** Other ***/ // These are modules that don't fit in any of the previous sections diff --git a/include/struct.h b/include/struct.h index f4442d382..750059125 100644 --- a/include/struct.h +++ b/include/struct.h @@ -861,6 +861,7 @@ typedef struct Whowas { long umodes; time_t logon; time_t logoff; + time_t connected_since; WhoWasEvent event; struct Client *online; /* Pointer to new nickname for chasing or NULL */ struct Whowas *next; /* for hash table... */ diff --git a/src/modules/rpc/Makefile.in b/src/modules/rpc/Makefile.in index 49706c525..5a4b098ee 100644 --- a/src/modules/rpc/Makefile.in +++ b/src/modules/rpc/Makefile.in @@ -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 + log.so whowas.so MODULES=$(R_MODULES) MODULEFLAGS=@MODULEFLAGS@ diff --git a/src/modules/rpc/user.c b/src/modules/rpc/user.c index 44ecb0a36..df9abaf83 100644 --- a/src/modules/rpc/user.c +++ b/src/modules/rpc/user.c @@ -8,7 +8,7 @@ ModuleHeader MOD_HEADER = { "rpc/user", - "1.0.6", + "1.0.7", "user.* RPC calls", "UnrealIRCd Team", "unrealircd-6", @@ -28,7 +28,6 @@ RPC_CALL_FUNC(rpc_user_kill); RPC_CALL_FUNC(rpc_user_quit); RPC_CALL_FUNC(rpc_user_join); RPC_CALL_FUNC(rpc_user_part); -RPC_CALL_FUNC(rpc_user_get_whowas); MOD_INIT() { @@ -142,15 +141,6 @@ MOD_INIT() config_error("[rpc/user] Could not register RPC handler"); return MOD_FAILED; } - memset(&r, 0, sizeof(r)); - r.method = "user.get_whowas"; - r.loglevel = ULOG_DEBUG; - r.call = rpc_user_get_whowas; - if (!RPCHandlerAdd(modinfo->handle, &r)) - { - config_error("[rpc/user] Could not register RPC handler"); - return MOD_FAILED; - } return MOD_SUCCESS; } @@ -705,101 +695,3 @@ RPC_CALL_FUNC(rpc_user_part) rpc_response(client, request, result); json_decref(result); } - -extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH]; - -const char *whowas_event_to_string(WhoWasEvent event) -{ - if (event == WHOWAS_EVENT_QUIT) - return "quit"; - if (event == WHOWAS_EVENT_NICK_CHANGE) - return "nick-change"; - if (event == WHOWAS_EVENT_SERVER_TERMINATING) - return "server-terminating"; - return "unknown"; -} - -void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail) -{ - json_t *child; - json_t *user = NULL; - char buf[BUFSIZE+1]; - - if (key) - { - child = json_object(); - json_object_set_new(j, key, child); - } else { - child = j; - } - - json_object_set_new(child, "name", json_string_unreal(e->name)); - - if (detail == 0) - return; - - json_object_set_new(child, "hostname", json_string_unreal(e->hostname)); - json_object_set_new(child, "ip", json_string_unreal(e->ip)); - - snprintf(buf, sizeof(buf), "%s!%s@%s", e->name, e->username, e->hostname); - json_object_set_new(child, "details", json_string_unreal(buf)); - - if (detail < 2) - return; - - json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event))); - json_object_set_new(child, "logon_time", json_timestamp(e->logon)); - json_object_set_new(child, "logoff_time", json_timestamp(e->logoff)); - - /* client.user */ - user = json_object(); - json_object_set_new(child, "user", user); - - json_object_set_new(user, "username", json_string_unreal(e->username)); - if (!BadPtr(e->realname)) - json_object_set_new(user, "realname", json_string_unreal(e->realname)); - if (!BadPtr(e->virthost)) - json_object_set_new(user, "vhost", json_string_unreal(e->virthost)); - json_object_set_new(user, "servername", json_string_unreal(e->servername)); - if (!BadPtr(e->account)) - json_object_set_new(user, "account", json_string_unreal(e->account)); -} - -RPC_CALL_FUNC(rpc_user_get_whowas) -{ - json_t *result, *list, *item; - int details; - int i; - const char *name; - const char *ip; - - OPTIONAL_PARAM_STRING("name", name); - OPTIONAL_PARAM_STRING("ip", ip); - OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2); - if (details == 3) - { - rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4."); - return; - } - - result = json_object(); - list = json_array(); - json_object_set_new(result, "list", list); - - for (i=0; i < NICKNAMEHISTORYLENGTH; i++) - { - WhoWas *e = &WHOWAS[i]; - if (!e->name) - continue; - if (name && !match_simple(name, e->name)) - continue; - if (ip && !match_simple(ip, e->ip)) - continue; - item = json_object(); - json_expand_whowas(item, NULL, e, details); - json_array_append_new(list, item); - } - - rpc_response(client, request, result); - json_decref(result); -} diff --git a/src/modules/rpc/whowas.c b/src/modules/rpc/whowas.c new file mode 100644 index 000000000..800d744fe --- /dev/null +++ b/src/modules/rpc/whowas.c @@ -0,0 +1,148 @@ +/* whowas.* RPC calls + * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team + * License: GPLv2 or later + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER += { + "rpc/whowas", + "1.0.0", + "whowas.* RPC calls", + "UnrealIRCd Team", + "unrealircd-6", +}; + +/* Externals */ +extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH]; + +/* Forward declarations */ +RPC_CALL_FUNC(rpc_whowas_get); + +MOD_INIT() +{ + RPCHandlerInfo r; + + MARK_AS_OFFICIAL_MODULE(modinfo); + + memset(&r, 0, sizeof(r)); + r.method = "whowas.get"; + r.loglevel = ULOG_DEBUG; + r.call = rpc_whowas_get; + if (!RPCHandlerAdd(modinfo->handle, &r)) + { + config_error("[rpc/whowas] Could not register RPC handler"); + return MOD_FAILED; + } + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +const char *whowas_event_to_string(WhoWasEvent event) +{ + if (event == WHOWAS_EVENT_QUIT) + return "quit"; + if (event == WHOWAS_EVENT_NICK_CHANGE) + return "nick-change"; + if (event == WHOWAS_EVENT_SERVER_TERMINATING) + return "server-terminating"; + return "unknown"; +} + +void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail) +{ + json_t *child; + json_t *user = NULL; + char buf[BUFSIZE+1]; + + if (key) + { + child = json_object(); + json_object_set_new(j, key, child); + } else { + child = j; + } + + json_object_set_new(child, "name", json_string_unreal(e->name)); + json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event))); + json_object_set_new(child, "logon_time", json_timestamp(e->logon)); + json_object_set_new(child, "logoff_time", json_timestamp(e->logoff)); + + if (detail == 0) + return; + + json_object_set_new(child, "hostname", json_string_unreal(e->hostname)); + json_object_set_new(child, "ip", json_string_unreal(e->ip)); + + snprintf(buf, sizeof(buf), "%s!%s@%s", e->name, e->username, e->hostname); + json_object_set_new(child, "details", json_string_unreal(buf)); + + if (detail < 2) + return; + + if (e->connected_since) + json_object_set_new(child, "connected_since", json_timestamp(e->connected_since)); + + /* client.user */ + user = json_object(); + json_object_set_new(child, "user", user); + + json_object_set_new(user, "username", json_string_unreal(e->username)); + if (!BadPtr(e->realname)) + json_object_set_new(user, "realname", json_string_unreal(e->realname)); + if (!BadPtr(e->virthost)) + json_object_set_new(user, "vhost", json_string_unreal(e->virthost)); + json_object_set_new(user, "servername", json_string_unreal(e->servername)); + if (!BadPtr(e->account)) + json_object_set_new(user, "account", json_string_unreal(e->account)); +} + +RPC_CALL_FUNC(rpc_whowas_get) +{ + json_t *result, *list, *item; + int details; + int i; + const char *name; + const char *ip; + + OPTIONAL_PARAM_STRING("name", name); + OPTIONAL_PARAM_STRING("ip", ip); + OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2); + if (details == 3) + { + rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4."); + return; + } + + result = json_object(); + list = json_array(); + json_object_set_new(result, "list", list); + + for (i=0; i < NICKNAMEHISTORYLENGTH; i++) + { + WhoWas *e = &WHOWAS[i]; + if (!e->name) + continue; + if (name && !match_simple(name, e->name)) + continue; + if (ip && !match_simple(ip, e->ip)) + continue; + item = json_object(); + json_expand_whowas(item, NULL, e, details); + json_array_append_new(list, item); + } + + rpc_response(client, request, result); + json_decref(result); +} diff --git a/src/modules/whowasdb.c b/src/modules/whowasdb.c index 72b316818..0ea05e39b 100644 --- a/src/modules/whowasdb.c +++ b/src/modules/whowasdb.c @@ -364,19 +364,22 @@ int write_whowasdb(void) int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e) { + char connected_since[64]; char logontime[64]; char logofftime[64]; char event[16]; + snprintf(connected_since, sizeof(connected_since), "%lld", (long long)e->connected_since); snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon); snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff); snprintf(event, sizeof(event), "%d", e->event); W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START)); W_SAFE_PROPERTY(db, "nick", e->name); + W_SAFE_PROPERTY(db, "event", event); + W_SAFE_PROPERTY(db, "connected_since", connected_since); W_SAFE_PROPERTY(db, "logontime", logontime); W_SAFE_PROPERTY(db, "logofftime", logofftime); - W_SAFE_PROPERTY(db, "event", event); W_SAFE_PROPERTY(db, "username", e->username); W_SAFE_PROPERTY(db, "hostname", e->hostname); W_SAFE_PROPERTY(db, "ip", e->ip); @@ -399,7 +402,7 @@ int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e) safe_free(hostname); \ safe_free(ip); \ safe_free(realname); \ - logontime = logofftime = 0; \ + connected_since = logontime = logofftime = 0; \ event = 0; \ safe_free(server); \ safe_free(virthost); \ @@ -431,6 +434,7 @@ int read_whowasdb(void) char *hostname = NULL; char *ip = NULL; char *realname = NULL; + long long connected_since = 0; long long logontime = 0; long long logofftime = 0; int event = 0; @@ -491,7 +495,7 @@ int read_whowasdb(void) // Variables key = value = NULL; nick = username = hostname = ip = realname = virthost = account = server = NULL; - logontime = logofftime = 0; + connected_since = logontime = logofftime = 0; event = 0; R_SAFE(unrealdb_read_int32(db, &magic)); @@ -524,6 +528,11 @@ int read_whowasdb(void) { realname = value; } else + if (!strcmp(key, "connected_since")) + { + connected_since = atoll(value); + safe_free(value); + } else if (!strcmp(key, "logontime")) { logontime = atoll(value); @@ -581,13 +590,14 @@ int read_whowasdb(void) if (e->hashv != -1) free_whowas_fields(e); /* Set values */ - unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL, - "[whowasdb] Adding '$nick'...", - log_data_string("nick", nick)); + //unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL, + // "[whowasdb] Adding '$nick'...", + // log_data_string("nick", nick)); e->hashv = hash_whowas_name(nick); + e->event = event; + e->connected_since = connected_since; e->logon = logontime; e->logoff = logofftime; - e->event = event; safe_strdup(e->name, nick); safe_strdup(e->username, username); safe_strdup(e->hostname, hostname); diff --git a/src/whowas.c b/src/whowas.c index 7e5e2e21b..6890c1edd 100644 --- a/src/whowas.c +++ b/src/whowas.c @@ -46,6 +46,7 @@ void free_whowas_fields(WhoWas *e) e->event = 0; e->logon = 0; e->logoff = 0; + e->connected_since = 0; /* Remove from lists and reset hashv */ if (e->online) @@ -57,6 +58,8 @@ void free_whowas_fields(WhoWas *e) void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event) { e->hashv = hash_whowas_name(client->name); + e->event = event; + e->connected_since = get_creationtime(client); e->logon = client->lastnick; e->logoff = TStime(); e->umodes = client->umodes; @@ -78,8 +81,6 @@ void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event) */ /* strlcpy(e->servername, client->user->server,HOSTLEN); */ e->servername = client->user->server; - - e->event = event; } void add_history(Client *client, int online, WhoWasEvent event)