diff --git a/Makefile.windows b/Makefile.windows index 845b92cc6..bc04ee037 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -184,7 +184,7 @@ EXP_OBJ_FILES=src/ircd_vars.obj src/channel.obj src/send.obj src/socket.obj \ src/version.obj src/ircsprintf.obj \ src/scache.obj src/dns.obj src/modules.obj \ src/aliases.obj src/api-event.obj src/api-usermode.obj src/auth.obj src/tls.obj \ - src/random.obj src/api-channelmode.obj src/api-moddata.obj src/mempool.obj \ + src/random.obj src/api-channelmode.obj src/api-moddata.obj src/api-rpc.obj src/mempool.obj \ src/dispatch.obj src/api-isupport.obj src/api-command.obj \ src/api-clicap.obj src/api-messagetag.obj src/api-history-backend.obj \ src/api-extban.obj src/api-efunctions.obj src/crypt_blowfish.obj \ @@ -583,6 +583,9 @@ src/api-channelmode.obj: src/api-channelmode.c $(INCLUDES) src/api-moddata.obj: src/api-moddata.c $(INCLUDES) $(CC) $(CFLAGS) src/api-moddata.c +src/api-rpc.obj: src/api-rpc.c $(INCLUDES) + $(CC) $(CFLAGS) src/api-rpc.c + src/mempool.obj: src/mempool.c $(INCLUDES) $(CC) $(CFLAGS) src/mempool.c diff --git a/include/modules.h b/include/modules.h index bc6971f39..5e334fb1a 100644 --- a/include/modules.h +++ b/include/modules.h @@ -107,6 +107,7 @@ typedef enum ModuleObjectType { MOBJ_CLICAP = 16, MOBJ_MTAG = 17, MOBJ_HISTORY_BACKEND = 18, + MOBJ_RPC = 18, } ModuleObjectType; typedef struct Umode Umode; @@ -604,6 +605,35 @@ typedef struct { int (*history_destroy)(const char *object); } HistoryBackendInfo; +/** @defgroup RPCAPI RPC API + * @{ + */ + +/** No special flags set */ +#define RPC_HANDLER_FLAGS_NONE 0x0 + +/** Message Tag Handler */ +typedef struct RPCHandler RPCHandler; +struct RPCHandler { + RPCHandler *prev, *next; + char *method; /**< Name of the method handler, eg "client.get" */ + int flags; /**< A flag of RPC_HANDLER_FLAG_* */ + int (*call)(Client *, json_t *request); /**< RPC call */ + Module *owner; /**< Module introducing this. */ + char unloaded; /**< Internal flag to indicate module is being unloaded */ +}; + +/** The struct used to register a RPC handler. + * For documentation, see the RPCHandler struct. + */ +typedef struct { + char *method; + int flags; + int (*call)(Client *, json_t *request); +} RPCHandlerInfo; + +/** @} */ + struct Hook { Hook *prev, *next; int priority; @@ -688,6 +718,7 @@ typedef struct ModuleObject { ClientCapability *clicap; MessageTagHandler *mtag; HistoryBackend *history_backend; + RPCHandler *rpc; } object; } ModuleObject; @@ -825,6 +856,10 @@ extern HistoryBackend *HistoryBackendFind(const char *name); extern HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mreq); extern void HistoryBackendDel(HistoryBackend *m); +extern RPCHandler *RPCHandlerFind(const char *method); +extern RPCHandler *RPCHandlerAdd(Module *module, RPCHandlerInfo *mreq); +extern void RPCHandlerDel(RPCHandler *m); + #ifndef GCC_TYPECHECKING #define HookAdd(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, func, NULL, NULL, NULL) #define HookAddVoid(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, func, NULL, NULL) diff --git a/src/Makefile.in b/src/Makefile.in index b33b44b4e..6d5d9d2b9 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -30,7 +30,7 @@ OBJS=ircd_vars.o dns.o auth.o channel.o crule.o dbuf.o \ version.o whowas.o random.o api-usermode.o api-channelmode.o \ api-moddata.o api-extban.o api-isupport.o api-command.o \ api-clicap.o api-messagetag.o api-history-backend.o api-efunctions.o \ - api-event.o \ + api-event.o api-rpc.o \ crypt_blowfish.o unrealdb.o crashreport.o modulemanager.o \ utf8.o log.o \ openssl_hostname_validation.o $(URL) diff --git a/src/api-rpc.c b/src/api-rpc.c new file mode 100644 index 000000000..a0b0855da --- /dev/null +++ b/src/api-rpc.c @@ -0,0 +1,147 @@ +/************************************************************************ + * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-rpc.c + * (c) 2022- Bram Matthys and The UnrealIRCd Team + * License: GPLv2 or later + */ + +/** @file + * @brief RPC API + */ +#include "unrealircd.h" + +/** This is the RPC API used for web requests. + * For an overview of available RPC's (not the API) + * see https://www.unrealircd.org/docs/RPC + * @defgroup RPCAPI RPC API + * @{ + */ + +/** List of RPC handlers */ +MODVAR RPCHandler *rpchandlers = NULL; + +/* Forward declarations */ +static void unload_rpc_handler_commit(RPCHandler *m); + +/** Adds a new RPC handler. + * @param module The module which owns this RPC handler. + * @param mreq The details of the request such as the method name, callback, etc. + * @return Returns the handle to the RPC handler if successful, otherwise NULL. + * The module's error code contains specific information about the + * error. + */ +RPCHandler *RPCHandlerAdd(Module *module, RPCHandlerInfo *mreq) +{ + RPCHandler *m; + ModuleObject *mobj; + + /* Some consistency checks to avoid a headache for module devs later on: */ + if (!mreq->method || !mreq->call) + { + unreal_log(ULOG_ERROR, "module", "RPCHANDLERADD_API_ERROR", NULL, + "RPCHandlerAdd() from module $module_name: " + "Missing required fields.", + log_data_string("module_name", module->header->name)); + abort(); + } + + m = RPCHandlerFind(mreq->method); + if (m) + { + if (m->unloaded) + { + m->unloaded = 0; + } else { + if (module) + module->errorcode = MODERR_EXISTS; + return NULL; + } + } else { + /* New RPC handler */ + m = safe_alloc(sizeof(RPCHandler)); + safe_strdup(m->method, mreq->method); + AddListItem(m, rpchandlers); + } + /* Add or update the following fields: */ + m->owner = module; + m->flags = mreq->flags; + m->call = mreq->call; + + /* Add module object */ + mobj = safe_alloc(sizeof(ModuleObject)); + mobj->type = MOBJ_RPC; + mobj->object.rpc = m; + AddListItem(mobj, module->objects); + module->errorcode = MODERR_NOERROR; + + return m; +} + +/** Returns the RPC handler for the given method name. + * @param method The method to search for. + * @return Returns the handle to the RPC handler, + * or NULL if not found. + */ +RPCHandler *RPCHandlerFind(const char *method) +{ + RPCHandler *m; + + for (m = rpchandlers; m; m = m->next) + { + if (!strcasecmp(method, m->method)) + return m; + } + return NULL; +} + +/** Remove the specified RPC handler - modules should not call this. + * This is done automatically for modules on unload, so is only called internally. + * @param m The PRC handler to remove. + */ +void RPCHandlerDel(RPCHandler *m) +{ + if (m->owner) + { + ModuleObject *mobj; + for (mobj = m->owner->objects; mobj; mobj = mobj->next) { + if (mobj->type == MOBJ_RPC && mobj->object.rpc == m) + { + DelListItem(mobj, m->owner->objects); + safe_free(mobj); + break; + } + } + m->owner = NULL; + } + + if (loop.rehashing) + m->unloaded = 1; + else + unload_rpc_handler_commit(m); +} + +/** @} */ + +static void unload_rpc_handler_commit(RPCHandler *m) +{ + /* This is an unusual operation, I think we should log it. */ + unreal_log(ULOG_INFO, "module", "UNLOAD_RPC_HANDLER", NULL, + "Unloading RPC handler for '$method'", + log_data_string("method", m->method)); + + /* Destroy the object */ + DelListItem(m, rpchandlers); + safe_free(m->method); + safe_free(m); +} + +void unload_all_unused_rpc_handlers(void) +{ + RPCHandler *m, *m_next; + + for (m = rpchandlers; m; m = m_next) + { + m_next = m->next; + if (m->unloaded) + unload_rpc_handler_commit(m); + } +} diff --git a/src/modules.c b/src/modules.c index 208c6b556..f93e4da4a 100644 --- a/src/modules.c +++ b/src/modules.c @@ -560,6 +560,9 @@ void FreeModObj(ModuleObject *obj, Module *m) else if (obj->type == MOBJ_HISTORY_BACKEND) { HistoryBackendDel(obj->object.history_backend); } + else if (obj->type == MOBJ_RPC) { + RPCHandlerDel(obj->object.rpc); + } else { unreal_log(ULOG_FATAL, "module", "FREEMODOBJ_UNKNOWN_TYPE", NULL,