#include #include #include #include #include #include #include typedef struct XMPPSession { char *identifier; /* "Each session [...] SHOULD be valid only between one * requester/responder pair." */ /* The Parsee-managed entity managing the session. */ char *receiver; /* The command's issuer entity. */ char *issuer; /* The issued node */ char *node; /* Timestamp of the session's creation, from Parsee's * point-of-view */ uint64_t creation; } XMPPSession; struct XMPPCommandManager { pthread_mutex_t lock; /* A hashmap of XMPPCommands. */ HashMap *commands; HashMap *sessions; void *cookie; }; static void XMPPDestroySession(XMPPSession *session) { if (!session) { return; } Free(session->identifier); Free(session->receiver); Free(session->issuer); Free(session->node); Free(session); } static void InvalidateSession(XMPPCommandManager *m, char *session) { XMPPSession *s = HashMapDelete(m->sessions, session); XMPPDestroySession(s); } static char * NewSession(XMPPCommandManager *m, char *node) { char *session_id = NULL; do { 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); Free(pstr); } while (HashMapGet(m->sessions, session_id)); return session_id; } /* NOTE: The manager MUST be locked. */ static char * XMPPRegisterSession(XMPPCommandManager *m, char *p_jid, char *s_jid, char *node) { XMPPSession *session; char *session_ident; if (!m || !p_jid || !s_jid) { 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; } XMPPCommandManager * XMPPCreateManager(void *cookie) { XMPPCommandManager *ret = Malloc(sizeof(*ret)); pthread_mutex_init(&ret->lock, NULL); ret->commands = HashMapCreate(); ret->sessions = HashMapCreate(); ret->cookie = cookie; return ret; } void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd) { if (!m || !cmd) { return; } pthread_mutex_lock(&m->lock); HashMapSet(m->commands, XMPPGetCommandNode(cmd), cmd); pthread_mutex_unlock(&m->lock); } void XMPPFreeManager(XMPPCommandManager *manager) { char *node_name, *session_id; XMPPCommand *val; XMPPSession *session; if (!manager) { return; } while (HashMapIterate(manager->commands, &node_name, (void **) &val)) { XMPPFreeCommand(val); } while (HashMapIterate(manager->sessions, &session_id, (void **) &session)) { XMPPDestroySession(session); } pthread_mutex_destroy(&manager->lock); HashMapFree(manager->commands); HashMapFree(manager->sessions); Free(manager); } void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p) { char *node_name; XMPPCommand *val; XMLElement *item; if (!m || !p || !jid) { return; } pthread_mutex_lock(&m->lock); while (HashMapIterate(m->commands, &node_name, (void **) &val)) { item = XMLCreateTag("item"); XMLAddAttr(item, "jid", jid); XMLAddAttr(item, "node", node_name); XMLAddAttr(item, "name", XMPPGetCommandDesc(val)); XMLAddChild(p, item); } 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) { XMLElement *command; XMPPComponent *jabber; XMPPCommand *cmd; char *from, *to, *id; char *session_id = NULL; bool good = false; if (!m || !stanza || !data) { return false; } jabber = data->jabber; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); id = HashMapGet(stanza->attrs, "id"); pthread_mutex_lock(&m->lock); /* TODO */ #define CMD_NS "http://jabber.org/protocol/commands" if ((command = XMLookForTKV(stanza, "command", "xmlns", CMD_NS))) { char *action = HashMapGet(command->attrs, "action"); char *node = HashMapGet(command->attrs, "node"); char *session_given = HashMapGet(command->attrs, "sessionid"); if (!action || StrEquals(action, "execute")) { XMLElement *command_xml, *iq; /* This is an execution. */ cmd = HashMapGet(m->commands, node); if (!cmd) { /* TODO: Set an error note */ goto end; } if (XMPPCommandRequiresForm(cmd) && !session_given) { XMLElement *actions = XMLCreateTag("actions"); XMLElement *completed = XMLCreateTag("complete"); XMLElement *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); x = XMPPFormifyCommand(m, cmd, from); XMLAddChild(command_xml, x); XMPPSendStanza(jabber, iq); XMLFreeElement(iq); goto end; } /* Doesn't need to be freed, as it lives with m. */ /* TODO: I've noticed memory leakages around here with a * StrDuplicate'd session ID. I think StrDuplicate should * just "rat out" the original source in cases of leaks * like this, maybe with extra data beyond the NULL-terminator */ 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", "completed"); XMLAddAttr(command_xml, "sessionid", session_id); XMPPExecuteCommand(m, cmd, from, command_xml, NULL); XMLAddChild(iq, command_xml); XMPPSendStanza(jabber, iq); XMLFreeElement(iq); InvalidateSession(m, session_id); 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); XMPPSendStanza(jabber, iq); XMLFreeElement(iq); InvalidateSession(m, session_given); good = true; goto end; } } #undef CMD_NS end: pthread_mutex_unlock(&m->lock); return good; } void * XMPPGetManagerCookie(XMPPCommandManager *manager) { return manager ? manager->cookie : NULL; }