From a84ce05b9d69800e455b48e4bde1297d00d2ae48 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 21 Jun 2024 18:31:43 +0200 Subject: [PATCH] [ADD/WIP] Start having MUCwork half-decent Still lots of things required(like using the users JIDs whenever possible, otherwise dropping to occupant ID), images, replies, rich data with HTML and whatever XMPP has, etc... --- src/AS.c | 29 +++++++++ src/Events.c | 16 +++++ src/Main.c | 11 +++- src/MatrixEventHandler.c | 18 +++--- src/ParseeUser.c | 132 ++++++++++++++++++++++++++++++++++++++- src/XML/SAX.c | 44 ++++++++++++- src/XMPP/Stanza.c | 38 +++++++++-- src/XMPPThread.c | 57 ++++++++++------- src/include/AS.h | 3 + src/include/Matrix.h | 3 + src/include/Parsee.h | 17 ++++- src/include/XMPP.h | 1 + 12 files changed, 329 insertions(+), 40 deletions(-) diff --git a/src/AS.c b/src/AS.c index 85d657b..1e05dad 100644 --- a/src/AS.c +++ b/src/AS.c @@ -251,3 +251,32 @@ ASCreateRoom(const ParseeConfig *conf, char *by, char *alias) return id; } +void +ASSetName(const ParseeConfig *conf, char *user, char *name) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path; + if (!conf || !user || !name) + { + return; + } + + user = HttpUrlEncode(user); + path = StrConcat(6, + "/_matrix/client/v3/profile/", + user, "/displayname", "?", + "user_id=", user + ); + + json = HashMapCreate(); + HashMapSet(json, "displayname", JsonValueString(name)); + ctx = ParseeCreateRequest(conf, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); + Free(user); +} diff --git a/src/Events.c b/src/Events.c index 464562a..b746b93 100644 --- a/src/Events.c +++ b/src/Events.c @@ -45,3 +45,19 @@ MatrixCreateNameState(char *name) return map; } + +HashMap * +MatrixCreateNickChange(char *nick) +{ + HashMap *map; + if (!nick) + { + return NULL; + } + + map = HashMapCreate(); + HashMapSet(map, "displayname", JsonValueString(nick)); + HashMapSet(map, "membership", JsonValueString("join")); + + return map; +} diff --git a/src/Main.c b/src/Main.c index 8affc87..2b9da00 100644 --- a/src/Main.c +++ b/src/Main.c @@ -57,6 +57,7 @@ Main(void) "%s - v%s (Cytoplasm %s)", NAME, VERSION, CytoplasmGetVersionStr() ); + Log(LOG_INFO, "======================="); LogConfigIndent(LogConfigGlobal()); ParseeConfigLoad("parsee.json"); @@ -102,6 +103,11 @@ Main(void) goto end; } + Log(LOG_NOTICE, "Listening to MUCs..."); + LogConfigIndent(LogConfigGlobal()); + ParseeSendPresence(conf.handlerArgs); + LogConfigUnindent(LogConfigGlobal()); + sigAction.sa_handler = SignalHandler; sigfillset(&sigAction.sa_mask); sigAction.sa_flags = SA_RESTART; @@ -123,14 +129,13 @@ Main(void) SIGACTION(SIGUSR1, &sigAction, NULL); #undef SIGACTION - Log(LOG_INFO, "%d", XMPPQueryMUC(jabber, "rootard@muc.ari.lt", NULL)); - server = HttpServerCreate(&conf); HttpServerStart(server); + LogConfigUnindent(LogConfigGlobal()); + Log(LOG_INFO, "======================="); HttpServerJoin(server); end: - LogConfigUnindent(LogConfigGlobal()); Log(LOG_INFO, "Exiting..."); HttpServerFree(server); ParseeConfigFree(); diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 1389247..bc634ed 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -46,12 +46,10 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) { char *jid = ParseeEncodeMXID(state_key); chat_id = ParseeGetFromRoomID(data, room_id); - Log(LOG_INFO, "JID=%s", jid); if (chat_id) { char *muc = ParseeGetMUCID(data, chat_id); char *rev = StrConcat(2, muc, "/parsee"); - Log(LOG_INFO, "chat_id=%s muc=%s", chat_id, muc); /* Make the user join the MUC */ XMPPJoinMUC(data->jabber, jid, rev); Free(rev); @@ -72,13 +70,12 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *sender = GrabString(event, 1, "sender"); - char *chat_id; + char *chat_id, *muc_id, *jid; bool direct = false; if (ParseeIsPuppet(data->config, sender)) { - Log(LOG_INFO, "Do not bridge the puppet"); return; } @@ -92,7 +89,6 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *user = GrabString(json, 1, "xmpp_user"); char *local = ParseeEncodeMXID(sender); - Log(LOG_INFO, "Sending to %s on XMPP", user); XMPPSendPlain(jabber, local, user, body, NULL); Free(local); @@ -101,12 +97,21 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) /* Try to find the chat ID */ chat_id = ParseeGetFromRoomID(data, id); + muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { return; } - Log(LOG_INFO, "Chat ID=%s", chat_id); + jid = ParseeEncodeMXID(sender); + { + char *rev = StrConcat(3, muc_id, "/", jid); + XMPPJoinMUC(jabber, jid, rev); + XMPPSendPlain(jabber, jid, muc_id, body, "groupchat"); + Free(rev); + } Free(chat_id); + Free(muc_id); + Free(jid); } void @@ -119,7 +124,6 @@ ParseeEventHandler(ParseeData *data, HashMap *event) } event_type = GrabString(event, 1, "type"); - Log(LOG_INFO, "E->%s", GrabString(event, 1, "sender")); if (StrEquals(event_type, "m.room.member")) { ParseeMemberHandler(data, event); diff --git a/src/ParseeUser.c b/src/ParseeUser.c index b966d90..6430f37 100644 --- a/src/ParseeUser.c +++ b/src/ParseeUser.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -37,7 +38,22 @@ ParseeIsPuppet(const ParseeConfig *conf, char *user) * room. */ return flag; } +bool +ParseeIsJabberPuppet(const ParseeConfig *config, char *jid) +{ + char *resource; + bool ret; + if (!config || !jid) + { + return false; + } + resource = ParseeGetResource(jid); + ret = *resource == '%'; + Free(resource); + + return ret; +} char * ParseeDecodeJID(char *str, char term) { @@ -139,7 +155,7 @@ ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias) } char * -ParseeEncodeJID(const ParseeConfig *c, char *jid) +ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim) { char *ret, *tmp; size_t i; @@ -155,7 +171,7 @@ ParseeEncodeJID(const ParseeConfig *c, char *jid) char cpy = jid[i]; char cs[4] = { 0 }; cs[0] = cpy; - if (*cs == '/') + if (*cs == '/' && trim) { /* RID: Break everything and die. */ break; @@ -360,6 +376,24 @@ ParseeTrimJID(char *jid) return ret; } char * +ParseeGetResource(char *jid) +{ + if (!jid) + { + return NULL; + } + while (*jid && *jid != '/') + { + jid++; + } + if (!*jid) + { + return NULL; + } + + return StrDuplicate(jid + 1); +} +char * ParseePushMUC(ParseeData *data, char *room_id, char *jid) { DbRef *ref; @@ -504,3 +538,97 @@ ParseeGetMUCID(ParseeData *data, char *chat_id) DbUnlock(data->db, ref); return ret; } + +void +ParseePushStanza(ParseeData *data, char *chat_id, char *stanza_id) +{ + DbRef *ref; + HashMap *j; + HashMap *stanzas; + bool new_stanzas = false; + if (!data || !chat_id || !stanza_id) + { + return; + } + + ref = DbLock(data->db, 2, "chats", chat_id); + j = DbJson(ref); + if (!ref) + { + return; + } + + stanzas = JsonValueAsObject(HashMapGet(j, "stanzas")); + if (!stanzas) + { + stanzas = HashMapCreate(); + new_stanzas = true; + } + + JsonValueFree(HashMapSet(stanzas, stanza_id, JsonValueNull())); + + if (new_stanzas) + { + HashMapSet(j, "stanzas", JsonValueObject(stanzas)); + } + + DbUnlock(data->db, ref); +} + +bool +ParseeVerifyStanza(ParseeData *data, char *chat_id, char *stanza_id) +{ + DbRef *ref = NULL; + HashMap *j = NULL; + HashMap *stanzas = NULL; + bool ret = true; + if (!data || !chat_id || !stanza_id) + { + return true; + } + + ref = DbLock(data->db, 2, "chats", chat_id); + j = DbJson(ref); + if (!ref) + { + goto end; + } + + stanzas = JsonValueAsObject(HashMapGet(j, "stanzas")); + if (!stanzas) + { + goto end; + } + + ret = !HashMapGet(stanzas, stanza_id); +end: + DbUnlock(data->db, ref); + return ret; +} + +void +ParseeSendPresence(ParseeData *data) +{ + DbRef *ref; + HashMap *j, *mucs; + JsonValue *val; + char *chatid = NULL, *muc; + if (!data) + { + return; + } + ref = DbLock(data->db, 1, "chats"); + j = DbJson(ref); + mucs = JsonValueAsObject(HashMapGet(j, "mucs")); + + while (HashMapIterate(mucs, &muc, (void **) &val)) + { + char *rev = StrConcat(2, muc, "/parsee"); + /* Make a fake user join the MUC */ + Log(LOG_NOTICE, "Sending presence to %s", rev); + XMPPJoinMUC(data->jabber, "parsee", rev); + + Free(rev); + } + DbUnlock(data->db, ref); +} diff --git a/src/XML/SAX.c b/src/XML/SAX.c index a335d88..7fe73f6 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -598,6 +598,47 @@ XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs) return event; } + +static char * +XMLDecodeString(char *s) +{ + char *ret = NULL, *tmp; + char c, cs[2] = { 0 }; + + while (*s) + { + cs[0] = *s; + if (!strncmp(s, "'", 6)) + { + cs[0] = '\''; + s += 6; + } + else if (!strncmp(s, "<", 4)) + { + cs[0] = '<'; + s += 4; + } + else if (!strncmp(s, ">", 4)) + { + cs[0] = '>'; + s += 4; + } + else if (!strncmp(s, "&", 5)) + { + cs[0] = '&'; + s += 5; + } + else + { + s++; + } + + tmp = ret; + ret = StrConcat(2, ret, cs); + Free(tmp); + } + return ret; +} XMLEvent * XMLCreateData(XMLexer *lexer) { @@ -609,13 +650,14 @@ XMLCreateData(XMLexer *lexer) StrDuplicate(ArrayGet(lexer->data.elements, elements - 1)) : NULL; event->attrs = NULL; - event->data = lexer->data.str; + event->data = XMLDecodeString(lexer->data.str); /* TODO */ event->line = 0; event->col = 0; event->offset = 0; + Free(lexer->data.str); lexer->data.str = NULL; return event; diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 05ffa3e..80edee8 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -33,10 +33,40 @@ XMPPSendPlain(XMPPComponent *comp, char *fr, char *to, char *msg, char *type) Free(from); } void +XMPPSendMUC(XMPPComponent *comp, char *fr, char *as, char *to, char *msg, char *type) +{ + XMLElement *message, *body, *data; + char *from, *id; + if (!comp || !fr || !to || !as || !msg) + { + return; + } + + message = XMLCreateTag("message"); + XMLAddAttr(message, "from", + (from = StrConcat(5, fr, "@", comp->host, "/", as))); + XMLAddAttr(message, "to", to); + XMLAddAttr(message, "type", type); + XMLAddAttr(message, "id", (id = StrRandom(8))); + + body = XMLCreateTag("body"); + data = XMLCreateText(msg); + + XMLAddChild(message, body); + XMLAddChild(body, data); + + XMLEncode(comp->stream, message); + StreamFlush(comp->stream); + + XMLFreeElement(message); + Free(from); + Free(id); +} +void XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc) { XMLElement *presence, *x; - char *from; + char *from, *id; if (!comp || !fr || !muc) { return; @@ -46,19 +76,17 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc) x = XMLCreateTag("x"); XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host))); XMLAddAttr(presence, "to", muc); + XMLAddAttr(presence, "id", (id = StrRandom(8))); XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc"); XMLAddChild(presence, x); - /* A*/ XMLEncode(comp->stream, presence); - Log(LOG_INFO, "Flushing..."); - /* B */ StreamFlush(comp->stream); XMLFreeElement(presence); Free(from); - /* How is that leaking */ + Free(id); } void XMPPKillThread(XMPPComponent *jabber, char *killer) diff --git a/src/XMPPThread.c b/src/XMPPThread.c index a3d6ddc..7432f7a 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -13,7 +13,7 @@ #include -static pthread_mutex_t cond_var_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t cond_var_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER; void @@ -27,7 +27,6 @@ ParseeWakeupThread(void) static void ParseeDMHandler(char *room, char *from, XMLElement *data, const ParseeConfig *c) { - Log(LOG_INFO, "Trying to send %s %s", room, from); ASSend( c, room, from, "m.room.message", MatrixCreateMessage(data->data) @@ -42,9 +41,9 @@ ParseeXMPPThread(void *argp) XMLElement *stanza = NULL; while (true) { - /* TODO: pthread cancelation points */ XMLElement *body = NULL; XMLElement *data = NULL; + XMLElement *stanza_id = NULL; char *to, *room, *from, *from_matrix; char *chat_id, *mroom_id; @@ -59,7 +58,7 @@ ParseeXMPPThread(void *argp) XMLFreeElement(stanza); continue; } - if (StrEquals(stanza->name, "message")) + else if (StrEquals(stanza->name, "message")) { size_t i; if (XMPPIsKiller(stanza)) @@ -69,7 +68,6 @@ ParseeXMPPThread(void *argp) from = HashMapGet(stanza->attrs, "from"); if (!strncmp(from, killer, strlen(killer))) { - Log(LOG_INFO, "Dropping thread..."); XMLFreeElement(stanza); break; } @@ -82,40 +80,50 @@ ParseeXMPPThread(void *argp) continue; } } - for (i = 0; i < ArraySize(stanza->children); i++) - { - XMLElement *child = ArrayGet(stanza->children, i); - if (StrEquals(child->name, "body")) - { - body = child; - break; - } - } + body = XMLookForUnique(stanza, "body"); if (!body) { XMLFreeElement(stanza); continue; } + stanza_id = XMLookForUnique(stanza, "stanza-id"); - /* TODO: Check the type */ to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); from = HashMapGet(stanza->attrs, "from"); - from_matrix = ParseeEncodeJID(args->config, from); + from_matrix = ParseeEncodeJID(args->config, from, true); room = ParseeFindDMRoom(args, to, from); data = ArrayGet(body->children, 0); - /* TODO: Consider having rich messages */ + /* TODO: Consider having rich messages, and move that out + * to separate functions */ chat_id = ParseeGetFromMUCID(args, from); mroom_id = ParseeGetRoomID(args, chat_id); if (room) { ParseeDMHandler(room, from_matrix, data, args->config); } - if (mroom_id) + if (mroom_id && !ParseeIsJabberPuppet(args->config, from)) { - Log(LOG_INFO, "Send to %s", mroom_id); - /* TODO: Grab username, create a puppet, and go from - * there */ + char *res = ParseeGetResource(from); + char *encoded = ParseeEncodeJID(args->config, from, false); + char *s_id_str = HashMapGet(stanza_id->attrs, "id"); + + /* TODO: Create smarter puppet names, and send presence + * in every room at Parsee's bootup. */ + if (ParseeVerifyStanza(args, chat_id, s_id_str)) + { + ASRegisterUser(args->config, encoded); + ASJoin(args->config, mroom_id, encoded); + ASSetName(args->config, encoded, res); + ASSend( + args->config, mroom_id, encoded, + "m.room.message", MatrixCreateMessage(data->data) + ); + ParseePushStanza(args, chat_id, s_id_str); + } + + Free(res); + Free(encoded); } Free(chat_id); Free(mroom_id); @@ -123,6 +131,13 @@ ParseeXMPPThread(void *argp) Free(room); Free(to); } + else + { + Log(LOG_WARNING, "Unknown stanza '%s':", stanza->name); + XMLEncode(StreamStdout(), stanza); + StreamPrintf(StreamStdout(), "\n"); + StreamFlush(StreamStdout()); + } XMLFreeElement(stanza); } diff --git a/src/include/AS.h b/src/include/AS.h index b85b997..a9aef2f 100644 --- a/src/include/AS.h +++ b/src/include/AS.h @@ -36,4 +36,7 @@ extern void ASSetState(const ParseeConfig *conf, char *id, char *type, char *key /* Creates a room, with a masquerade user as its creator. This function * returns it's ID if it exists. */ extern char * ASCreateRoom(const ParseeConfig *c, char *by, char *alias); + +/* Sets a user's displayname */ +extern void ASSetName(const ParseeConfig *c, char *user, char *name); #endif diff --git a/src/include/Matrix.h b/src/include/Matrix.h index 67f1701..0c0acce 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -14,4 +14,7 @@ extern HashMap * MatrixCreateMessage(char *body); /* Creates the content for a m.room.name state event */ extern HashMap * MatrixCreateNameState(char *name); + +/* Creates a join membership with a specific nickname */ +extern HashMap * MatrixCreateNickChange(char *nick); #endif diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 6a4bfd5..b3f3e11 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -73,11 +73,14 @@ extern void ParseeEventHandler(ParseeData *, HashMap *); /* Verifies if a user is a Parsee puppet user. */ extern bool ParseeIsPuppet(const ParseeConfig *, char *); +/* Verifies if a user is a Parsee puppet user on XMPP. */ +extern bool ParseeIsJabberPuppet(const ParseeConfig *, char *); + /* Decodes a local JID for a user into a string. */ extern char * ParseeDecodeLocalJID(const ParseeConfig *, char *); /* Encodes a JID into a Parsee localpart */ -extern char * ParseeEncodeJID(const ParseeConfig *, char *); +extern char * ParseeEncodeJID(const ParseeConfig *, char *, bool); /* Gets the localpart of a MXID */ extern char * ParseeGetLocal(char *); @@ -111,6 +114,9 @@ extern void ParseePushDMRoom(ParseeData *, char *mxid, char *jid, char *r); /* Trims the component from a JID */ extern char * ParseeTrimJID(char *jid); +/* Gets the component from a JID */ +extern char * ParseeGetResource(char *jid); + /* Decodes a room alias into a MUC JID */ extern char * ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias); @@ -132,4 +138,13 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id); /* Finds the MUC JID from a chat ID */ extern char * ParseeGetMUCID(ParseeData *, char *chat_id); +/* Pushes a stanza ID to a chat ID */ +extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id); + +/* Checks if a stanza is not duplicated in a chat ID */ +extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id); + +/* Sends presence requests for every MUC around as a fake JID */ +extern void ParseeSendPresence(ParseeData *); + #endif diff --git a/src/include/XMPP.h b/src/include/XMPP.h index d772e78..f55223f 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -24,6 +24,7 @@ extern void XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc); /* TODO: XMPP stuff, I don't fucking know, I'm not a Jabbernerd. */ extern void XMPPSendPlain(XMPPComponent *c, char *f, char *t, char *m, char *type); +extern void XMPPSendMUC(XMPPComponent *comp, char *fr, char *as, char *to, char *msg, char *type); /* Closes a raw component stream. */ extern void XMPPEndCompStream(XMPPComponent *stream);