From df394172ec7b6a4a8603b998873b35399796daa5 Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 14 Jul 2024 11:42:30 +0200 Subject: [PATCH] [ADD] No-fly, forms --- src/XMPPCommand/Commands.c | 54 ++++++++++++++-- src/XMPPCommand/Manager.c | 119 +++++++++++++++++++++++++++++++++--- src/XMPPCommand/Options.c | 15 +++++ src/XMPPCommands/AddAdmin.c | 49 +++++++++++++++ src/XMPPCommands/AddNofly.c | 46 ++++++++++++++ src/XMPPCommands/Admins.c | 17 ++---- src/XMPPCommands/Nofly.c | 16 ++--- src/XMPPCommands/Status.c | 10 +-- src/XMPPThread.c | 5 +- src/include/XMPPCommand.h | 27 ++++++-- src/include/XMPPFormTool.h | 32 ++++++++++ 11 files changed, 338 insertions(+), 52 deletions(-) create mode 100644 src/XMPPCommands/AddAdmin.c create mode 100644 src/XMPPCommands/AddNofly.c diff --git a/src/XMPPCommand/Commands.c b/src/XMPPCommand/Commands.c index 4fb77d0..d16863c 100644 --- a/src/XMPPCommand/Commands.c +++ b/src/XMPPCommand/Commands.c @@ -108,14 +108,58 @@ XMPPCommandRequiresForm(XMPPCommand *cmd) return cmd ? !!cmd->options : false; } void -XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, HashMap *arg_table) +XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form) { if (!m || !cmd || !from || !to) { return; } - cmd->callback(m, from, arg_table, to); - - /* TODO Free arg_table's strings */ - HashMapFree(arg_table); + cmd->callback(m, from, form, to); +} + +void +XMPPShoveOptions(XMPPCommand *cmd, XMLElement *form) +{ + size_t i; + if (!cmd || !form) + { + return; + } + + for (i = 0; i < ArraySize(cmd->options); i++) + { + XMPPOption *opt = ArrayGet(cmd->options, i); + XMLElement *xml = XMPPOptionToXML(opt); + + XMLAddChild(form, xml); + } +} +bool +XMPPVerifyForm(XMPPCommand *cmd, XMLElement *form) +{ + size_t i; + if (!cmd || !form) + { + return false; + } + + for (i = 0; i < ArraySize(cmd->options); i++) + { + XMPPOption *opt = ArrayGet(cmd->options, i); + if (XMPPIsOptionRequired(opt)) + { + /* Check in the form for a field with the right var */ + char *var = XMPPOptionVar(opt); + XMLElement *field = XMLookForTKV(form, "field", "var", var); + + if (!field) + { + /* Required field not found; die. */ + return false; + } + /* TODO */ + } + } + + return true; } diff --git a/src/XMPPCommand/Manager.c b/src/XMPPCommand/Manager.c index bd03a7d..9052072 100644 --- a/src/XMPPCommand/Manager.c +++ b/src/XMPPCommand/Manager.c @@ -66,6 +66,8 @@ NewSession(XMPPCommandManager *m, char *node) int period = (UtilTsMillis() / (10 SECONDS)); char *rand = StrRandom(16); char *pstr = StrInt(period); + + Free(session_id); session_id = StrConcat(5, node, "_", pstr, "_", rand); Free(rand); @@ -85,17 +87,24 @@ XMPPRegisterSession(XMPPCommandManager *m, char *p_jid, char *s_jid, char *node) { return NULL; } + s_jid = ParseeTrimJID(s_jid); + p_jid = ParseeTrimJID(p_jid); + session = Malloc(sizeof(*session)); session->node = StrDuplicate(node); session->issuer = StrDuplicate(s_jid); session->receiver = StrDuplicate(p_jid); session->creation = UtilTsMillis(); + session_ident = NewSession(m, node); session->identifier = session_ident; HashMapSet(m->sessions, session_ident, session); + Free(s_jid); + Free(p_jid); + return session_ident; } @@ -173,6 +182,30 @@ XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p) } pthread_mutex_unlock(&m->lock); } + +static bool +XMPPVerifySession(XMPPCommandManager *mgr, char *s_id, char *from, char *to) +{ + XMPPSession *session = mgr ? HashMapGet(mgr->sessions, s_id) : NULL; + bool ret = false; + if (!session) + { + return false; + } + + from = ParseeTrimJID(from); + to = ParseeTrimJID(to); + + ret = StrEquals(from, session->issuer) && + StrEquals(to, session->receiver); + + /* TODO: Expirations */ + + Free(from); + Free(to); + + return ret; +} bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) { @@ -201,6 +234,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) { char *action = HashMapGet(command->attrs, "action"); char *node = HashMapGet(command->attrs, "node"); + char *session_given = HashMapGet(command->attrs, "sessionid"); if (!action || StrEquals(action, "execute")) { @@ -210,19 +244,44 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) if (!cmd) { - Log(LOG_WARNING, - "User %s asked to execute '%s', but it doesn't exist.", - from, node - ); + /* TODO: Set an error note */ goto end; } - if (XMPPCommandRequiresForm(cmd)) + if (XMPPCommandRequiresForm(cmd) && !session_given) { - Log(LOG_WARNING, - "User %s asked to execute '%s', but it requires a form, " - "which is currently UNIMPLEMENTED", from, node - ); + XMLElement *actions = XMLCreateTag("actions"); + XMLElement *completed = XMLCreateTag("complete"); + XMLElement *x = XMLCreateTag("x"); + XMLAddChild(actions, completed); + + session_id = XMPPRegisterSession(m, to, from, node); + + /* No forms, we good. */ + iq = XMLCreateTag("iq"); + XMLAddAttr(iq, "type", "result"); + XMLAddAttr(iq, "from", to); + XMLAddAttr(iq, "to", from); + XMLAddAttr(iq, "id", id); + command_xml = XMLCreateTag("command"); + XMLAddAttr(command_xml, "xmlns", CMD_NS); + XMLAddAttr(command_xml, "node", node); + XMLAddAttr(command_xml, "status", "executing"); + XMLAddAttr(command_xml, "sessionid", session_id); + XMLAddChild(command_xml, actions); + XMLAddChild(iq, command_xml); + + XMLAddAttr(x, "xmlns", "jabber:x:data"); + XMLAddAttr(x, "type", "form"); + XMPPShoveOptions(cmd, x); + XMLAddChild(command_xml, x); + + pthread_mutex_lock(&jabber->write_lock); + XMLEncode(jabber->stream, iq); + StreamFlush(jabber->stream); + pthread_mutex_unlock(&jabber->write_lock); + XMLFreeElement(iq); + goto end; } @@ -254,6 +313,48 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) good = true; goto end; } + else if (StrEquals(action, "complete") && + XMPPVerifySession(m, session_given, from, to)) + { + XMLElement *x_form = XMLookForTKV(command, + "x", "xmlns", "jabber:x:data" + ); + XMLElement *command_xml, *iq; + if (!x_form || + !StrEquals(HashMapGet(x_form->attrs, "type"), "submit")) + { + goto end; + } + + cmd = HashMapGet(m->commands, node); + if (!XMPPVerifyForm(cmd, x_form)) + { + goto end; + } + + iq = XMLCreateTag("iq"); + XMLAddAttr(iq, "type", "result"); + XMLAddAttr(iq, "from", to); + XMLAddAttr(iq, "to", from); + XMLAddAttr(iq, "id", id); + command_xml = XMLCreateTag("command"); + XMLAddAttr(command_xml, "xmlns", CMD_NS); + XMLAddAttr(command_xml, "node", node); + XMLAddAttr(command_xml, "status", "completed"); + XMLAddAttr(command_xml, "sessionid", session_given); + XMPPExecuteCommand(m, cmd, from, command_xml, x_form); + XMLAddChild(iq, command_xml); + + pthread_mutex_lock(&jabber->write_lock); + XMLEncode(jabber->stream, iq); + StreamFlush(jabber->stream); + pthread_mutex_unlock(&jabber->write_lock); + XMLFreeElement(iq); + + InvalidateSession(m, session_given); + good = true; + goto end; + } } #undef CMD_NS diff --git a/src/XMPPCommand/Options.c b/src/XMPPCommand/Options.c index f177294..15cdc5f 100644 --- a/src/XMPPCommand/Options.c +++ b/src/XMPPCommand/Options.c @@ -249,3 +249,18 @@ XMPPSetDescription(XMPPOption *opt, char *desc) Free(opt->desc); opt->desc = StrDuplicate(desc); } +bool +XMPPIsOptionRequired(XMPPOption *opt) +{ + return opt ? opt->required : false; +} +char * +XMPPOptionVar(XMPPOption *opt) +{ + if (!opt) + { + return NULL; + } + + return opt->id; +} diff --git a/src/XMPPCommands/AddAdmin.c b/src/XMPPCommands/AddAdmin.c new file mode 100644 index 0000000..a9bd02b --- /dev/null +++ b/src/XMPPCommands/AddAdmin.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +void +AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +{ + ParseeData *data = XMPPGetManagerCookie(m); + char *trimmed = ParseeTrimJID(from); + char *glob = NULL; + + DbRef *ref; + Array *admins; + + GetFieldValue(glob, "glob", form); + + if (!ParseeIsAdmin(data, trimmed)) + { + SetNote("error", "User is not authorised to execute command."); + + Free(trimmed); + return; + } + if (!glob) + { + SetNote("error", "No glob found."); + Free(trimmed); + return; + } + + Free(trimmed); + + SetNote("info", "Glob sucessfully added."); + + ref = DbLock(data->db, 1, "admins"); + admins = GrabArray(DbJson(ref), 1, "admins"); + + ArrayAdd(admins, JsonValueString(glob)); + DbUnlock(data->db, ref); +} diff --git a/src/XMPPCommands/AddNofly.c b/src/XMPPCommands/AddNofly.c new file mode 100644 index 0000000..80f81e5 --- /dev/null +++ b/src/XMPPCommands/AddNofly.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +void +AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +{ + ParseeData *data = XMPPGetManagerCookie(m); + char *trimmed = ParseeTrimJID(from); + char *entity = NULL, *reason = NULL; + + GetFieldValue(entity, "entity", form); + GetFieldValue(reason, "reason", form); + + if (!ParseeIsAdmin(data, trimmed)) + { + SetNote("error", "User is not authorised to execute command."); + + Free(trimmed); + return; + } + if (!reason) + { + reason = "[no reason specified]"; + } + if (!entity) + { + SetNote("error", "No entity found."); + Free(trimmed); + return; + } + + Free(trimmed); + + SetNote("info", "User sucessfully put into no-fly."); + ParseeGlobalBan(data, entity, reason); +} diff --git a/src/XMPPCommands/Admins.c b/src/XMPPCommands/Admins.c index a56d870..7f0b172 100644 --- a/src/XMPPCommands/Admins.c +++ b/src/XMPPCommands/Admins.c @@ -9,9 +9,10 @@ #include #include #include +#include void -AdminsCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out) +AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -22,13 +23,7 @@ AdminsCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out if (!ParseeIsAdmin(data, trimmed)) { - XMLElement *note = XMLCreateTag("note"); - XMLAddAttr(note, "type", "error"); - - txt = XMLCreateText("User is not authorised to execute command."); - XMLAddChild(note, txt); - XMLAddChild(out, note); - + SetNote("error", "User is not authorised to execute command."); Free(trimmed); return; } @@ -36,10 +31,8 @@ AdminsCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out x = XMLCreateTag("x"); title = XMLCreateTag("title"); - { - XMLElement *title_text = XMLCreateText("Parsee administrators"); - XMLAddChild(title, title_text); - } + SetTitle(x, "Parsee administrators"); + XMLAddChild(x, title); XMLAddAttr(x, "xmlns", "jabber:x:data"); diff --git a/src/XMPPCommands/Nofly.c b/src/XMPPCommands/Nofly.c index 358044d..bdbe6e4 100644 --- a/src/XMPPCommands/Nofly.c +++ b/src/XMPPCommands/Nofly.c @@ -9,11 +9,12 @@ #include #include #include +#include #define TITLE "Parsee global bans" void -NoflyCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out) +NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -23,12 +24,7 @@ NoflyCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out) if (!ParseeIsAdmin(data, trimmed)) { - XMLElement *note = XMLCreateTag("note"); - XMLAddAttr(note, "type", "error"); - - txt = XMLCreateText("User is not authorised to execute command."); - XMLAddChild(note, txt); - XMLAddChild(out, note); + SetNote("error", "User is not authorised to execute command."); Free(trimmed); return; @@ -41,10 +37,8 @@ NoflyCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out) XMLAddChild(out, x); Free(trimmed); - { - XMLElement *title_text = XMLCreateText(TITLE); - XMLAddChild(title, title_text); - } + + SetTitle(x, TITLE); XMLAddAttr(x, "type", "result"); { diff --git a/src/XMPPCommands/Status.c b/src/XMPPCommands/Status.c index 6af7162..f09d896 100644 --- a/src/XMPPCommands/Status.c +++ b/src/XMPPCommands/Status.c @@ -9,9 +9,10 @@ #include #include #include +#include void -StatusCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out) +StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -26,12 +27,7 @@ StatusCallback(XMPPCommandManager *m, char *from, HashMap *form, XMLElement *out if (!ParseeIsAdmin(data, trimmed)) { - XMLElement *note = XMLCreateTag("note"); - XMLAddAttr(note, "type", "error"); - - txt = XMLCreateText("User is not authorised to execute command."); - XMLAddChild(note, txt); - XMLAddChild(out, note); + SetNote("error", "User is not authorised to execute command."); Free(trimmed); return; diff --git a/src/XMPPThread.c b/src/XMPPThread.c index db5b2da..24e858d 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -1186,10 +1186,7 @@ static void IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPCommandManager *manager = thr->info->m; - if (!XMPPManageCommand(manager, stanza, args)) - { - Log(LOG_WARNING, "NOT A COMMAND"); - } + XMPPManageCommand(manager, stanza, args); } #undef DISCO static void diff --git a/src/include/XMPPCommand.h b/src/include/XMPPCommand.h index 095634f..cec5d08 100644 --- a/src/include/XMPPCommand.h +++ b/src/include/XMPPCommand.h @@ -10,7 +10,7 @@ typedef struct XMPPCommandManager XMPPCommandManager; typedef struct XMPPCommand XMPPCommand; typedef struct XMPPOption XMPPOption; -typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, HashMap *, XMLElement *); +typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *); /** Creates a simple XMPP command manager, which routes commands * with a single-stage form system, with a {cookie} @@ -32,7 +32,7 @@ extern XMLElement * XMPPFormifyCommand(XMPPCommand *cmd); extern char * XMPPGetCommandNode(XMPPCommand *cmd); extern char * XMPPGetCommandDesc(XMPPCommand *cmd); extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); -extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, HashMap *arg_table); +extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form); /** Create a basic option. * ----------------------------------------------------- @@ -47,11 +47,17 @@ extern XMPPOption * XMPPCreateFixed(char *id, char *text); extern void XMPPAddListOption(XMPPOption *list, char *option); extern void XMPPSetDescription(XMPPOption *opt, char *descri); +extern bool XMPPIsOptionRequired(XMPPOption *opt); +extern char * XMPPOptionVar(XMPPOption *opt); + extern XMLElement * XMPPOptionToXML(XMPPOption *opt); +extern void XMPPShoveOptions(XMPPCommand *cmd, XMLElement *form); extern void XMPPFreeOption(XMPPOption *opt); extern void XMPPFreeCommand(XMPPCommand *cmd); +extern bool XMPPVerifyForm(XMPPCommand *cmd, XMLElement *form); + /** Registers a {cmd} to the {m}anager, with no extra metadata or callback. * It also makes {cmd} belong to {m}anager, and therefore does not belong to * its creator anymore. @@ -87,9 +93,22 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD #define XMPPCOMMANDS \ XMPP_COMMAND(StatusCallback, "stats", "Get Parsee statistics", {}) \ XMPP_COMMAND(AdminsCallback, "admin", "Get Parsee admin list", {}) \ - XMPP_COMMAND(NoflyCallback, "nofly", "Get Parsee nofly list", {}) + XMPP_COMMAND(AddAdminCallback, "add-admin", "Adds glob as admin", { \ + XMPPOption *glob = XMPPCreateText(true, "glob", ""); \ + XMPPSetDescription(glob, "Glob pattern to set as admin"); \ + XMPPAddOption(cmd, glob); \ + }) \ + XMPP_COMMAND(AddNoflyCallback, "add-nofly", "Adds user to nofly", { \ + XMPPOption *entity = XMPPCreateText(true, "entity", ""); \ + XMPPOption *reason = XMPPCreateText(false, "reason", "Not behaving"); \ + XMPPSetDescription(entity, "Entity(glob) to no-fly"); \ + XMPPAddOption(cmd, entity); \ + XMPPSetDescription(reason, "Reason for the no-fly"); \ + XMPPAddOption(cmd, reason); \ + }) \ + XMPP_COMMAND(NoflyCallback, "nofly", "Get Parsee nofly list", {}) -#define XMPP_COMMAND(f,n,t,s) extern void f(XMPPCommandManager *, char *, HashMap *, XMLElement *); +#define XMPP_COMMAND(f,n,t,s) extern void f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); XMPPCOMMANDS #undef XMPP_COMMAND #endif diff --git a/src/include/XMPPFormTool.h b/src/include/XMPPFormTool.h index 2e44f94..c46e9d9 100644 --- a/src/include/XMPPFormTool.h +++ b/src/include/XMPPFormTool.h @@ -35,4 +35,36 @@ } \ while(0) +#define GetFieldValue(to, id, form) do \ +{ \ + XMLElement *field_content = XMLookForTKV(form, "field", "var", id); \ + XMLElement *value = XMLookForUnique(field_content, "value"); \ + XMLElement *val_data = value ? ArrayGet(value->children, 0) : NULL; \ + if (val_data && val_data->data) \ + { \ + to = val_data->data; \ + } \ +} \ +while(0) + +#define SetNote(type, text) do \ +{ \ + XMLElement *note_xml = XMLCreateTag("note"); \ + XMLElement *note_msg = XMLCreateText(text); \ + XMLAddAttr(note_xml, "type", type); \ + XMLAddChild(note_xml, note_msg); \ + XMLAddChild(out, note_xml); \ +} \ +while(0) + +#define SetTitle(form, text) do \ +{ \ + XMLElement *title_xml, *title_txt; \ + title_xml = XMLCreateTag("title"); \ + title_txt = XMLCreateText(text); \ + XMLAddChild(title_xml, title_txt); \ + XMLAddChild(form, title_xml); \ +} \ +while(0) + #endif