#include "XMPPThread/internal.h" #include #include #include #include #include #include #include static void LazyRegister(ParseeData *data, char *mxid, char *name) { DbRef *ref; char *hash = ParseeHMACS(data->id, mxid); char *dbname; if (!(ref = DbLock(data->db, 2, "users", hash))) { ASRegisterUser(data->config, mxid); ref = DbCreate(data->db, 2, "users", hash); if (ref) { HashMapSet(DbJson(ref), "mxid", JsonValueString(mxid)); HashMapSet(DbJson(ref), "ts", JsonValueInteger(UtilTsMillis()) ); } } dbname = GrabString(DbJson(ref), 1, "name"); if (name && !StrEquals(dbname, name)) { ASSetName(data->config, mxid, name); if (ref) { JsonValueFree(HashMapSet( DbJson(ref), "name", JsonValueString(name) )); } } DbUnlock(data->db, ref); Free(hash); } static char * LazySend(ParseeData *args, char *mxid, char *mroom_id, char *type, HashMap *ev) { HashMap *duplicate; char *event; if (!args || !mxid || !mroom_id || !ev) { return NULL; } if (!type) { type = "m.room.message"; } duplicate = JsonDuplicate(ev); event = ASSend( args->config, mroom_id, mxid, type, ev ); if (event) { JsonFree(duplicate); return event; } ASInvite(args->config, mroom_id, mxid); Free(ASJoin(args->config, mroom_id, mxid)); return ASSend( args->config, mroom_id, mxid, type, duplicate ); } static void ProcessChatstates(ParseeData *args, XMLElement *stanza) { char *from_matrix, *mroom_id; #define CHAT_STATES "http://jabber.org/protocol/chatstates" if (XMLookForTKV(stanza, "composing", "xmlns", CHAT_STATES)) { from_matrix = ParseeGetBridgedUser(args, stanza); mroom_id = ParseeGetBridgedRoom(args, stanza); ASType(args->config, from_matrix, mroom_id, true); Free(from_matrix); Free(mroom_id); mroom_id = NULL; from_matrix = NULL; } if (XMLookForTKV(stanza, "active", "xmlns", CHAT_STATES)) { char *latest = NULL; from_matrix = ParseeGetBridgedUser(args, stanza); mroom_id = ParseeGetBridgedRoom(args, stanza); latest = ParseeLookupHead(mroom_id); ASType(args->config, from_matrix, mroom_id, false); ASPresence(args->config, from_matrix, mroom_id, latest); Free(from_matrix); Free(latest); Free(mroom_id); mroom_id = NULL; from_matrix = NULL; } if (XMLookForTKV(stanza, "paused", "xmlns", CHAT_STATES) || XMLookForTKV(stanza, "inactive", "xmlns", CHAT_STATES)) { char *latest = NULL; from_matrix = ParseeGetBridgedUser(args, stanza); mroom_id = ParseeGetBridgedRoom(args, stanza); latest = ParseeLookupHead(mroom_id); ASType(args->config, from_matrix, mroom_id, false); ASPresence(args->config, from_matrix, mroom_id, latest); Free(from_matrix); Free(latest); Free(mroom_id); mroom_id = NULL; from_matrix = NULL; } if (XMLookForTKV(stanza, "received", "xmlns", "urn:xmpp:receipts") || XMLookForTKV(stanza, "displayed", "xmlns", "urn:xmpp:chat-markers:0")) { /* TODO: Use stanza ID if possible */ char *latest = NULL; from_matrix = ParseeGetBridgedUser(args, stanza); mroom_id = ParseeGetBridgedRoom(args, stanza); latest = ParseeLookupHead(mroom_id); ASPresence(args->config, from_matrix, mroom_id, latest); Free(from_matrix); Free(latest); Free(mroom_id); mroom_id = NULL; from_matrix = NULL; } #undef CHAT_STATES } static float TimeElapsed(uint64_t *rectime, uint64_t v) { uint64_t time = UtilTsMillis(); float t = ((time-v)/1000.f); *rectime = time; return t; } bool MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPComponent *jabber = args->jabber; XMLElement *reactions = NULL; XMLElement *body = NULL; XMLElement *data = NULL; char *to, *room, *from, *from_matrix, *decode_from; char *mroom_id = NULL; char *replaced = XMPPGetReplacedID(stanza); char *retracted = XMPPGetRetractedID(stanza); char *reply_to = XMPPGetReply(stanza); char *moderated = XMPPGetModeration(stanza); char *type = HashMapGet(stanza->attrs, "type"); bool chat = StrEquals(type, "chat"); size_t i; uint64_t time, rectime; #define Elapsed(v) (TimeElapsed(&rectime, v)) to = NULL; from = NULL; decode_from = NULL; from_matrix = NULL; Log(LOG_DEBUG, " usage=%d", MemoryAllocated()); time = UtilTsMillis(); rectime = time; from = HashMapGet(stanza->attrs, "from"); if (ParseeManageBan(args, from, NULL)) { XMLFreeElement(stanza); Log(LOG_DEBUG, " time=%f " "usage=%d (%s:%d)", Elapsed(time), MemoryAllocated(), __FILE__, __LINE__ ); return false; } if (ServerHasXEP421(args, from)) { XMLElement *occupant = XMLookForTKV( stanza, "occupant-id", "xmlns", "urn:xmpp:occupant-id:0" ); char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL; if (occ_id) { if (args->verbosity >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "'%s' has support for XEP-421, fetching OID=%s", from, occ_id ); } ParseePushOIDTable(from, occ_id); } } if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "XEP-421: %fs", Elapsed(rectime)); } if (StrEquals(type, "error")) { char *type, *text, *user, *parsee; char *message, *room; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); /* Tiny little trick. */ XMLAddAttr(stanza, "type", "groupchat"); parsee = ParseeMXID(args); type = XMPPGetErrtype(stanza); text = XMPPGetErrtext(stanza); user = ParseeDecodeMXID(to); /* Avoid fun hypothetical cases of looping. */ if (StrEquals(parsee, user)) { goto end_error; } message = StrConcat(3, type, ": ", text); room = ParseeGetBridgedRoom(args, stanza); Free(LazySend( args, parsee, room, NULL, MatrixCreateNotice(message) )); end_error: XMLFreeElement(stanza); Free(message); Free(parsee); Free(room); Free(user); Log(LOG_DEBUG, " time=%f " "usage=%d (%s:%d)", Elapsed(time), MemoryAllocated(), __FILE__, __LINE__ ); return false; } if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime)); } if (moderated) { /* TODO: Parsee MUST check if it is a valid MUC */ char *resource = ParseeGetResource(from); char *chat_id = ParseeGetFromMUCID(args, from); char *event_id = NULL; char *encoded = ParseeMXID(args); mroom_id = ParseeGetBridgedRoom(args, stanza); if (!resource && chat_id) { event_id = ParseeGetEventFromID(args, stanza, moderated); ASRedact(args->config, mroom_id, NULL, event_id); ParseePushAllStanza(args, stanza, event_id); pthread_mutex_unlock(&thr->info->chk_lock); Free(event_id); } Free(mroom_id); Free(resource); Free(encoded); Free(chat_id); } body = XMLookForUnique(stanza, "body"); PEPManagerHandle(thr->info->pep_manager, stanza); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "PEP management: %fs", Elapsed(rectime)); } to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); decode_from = ParseeLookupJID(from); from_matrix = ParseeEncodeJID(args->config, decode_from, true); room = ParseeFindDMRoom(args, to, from); data = body ? ArrayGet(body->children, 0) : NULL; if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Fetching user info: %fs", Elapsed(rectime)); } /* TODO: CLEAN THAT UP INTO A CREATEDM FUNCTION */ mroom_id = ParseeGetBridgedRoom(args, stanza); Log(LOG_DEBUG, "Bridging event to '%s'...", mroom_id); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime)); } if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && to && *to == '@') { DbRef *room_ref; HashMap *room_json; char *from = HashMapGet(stanza->attrs, "from"); LazyRegister(args, from_matrix, NULL); room = ASCreateDM(args->config, from_matrix, to); mroom_id = StrDuplicate(room); if (room) { room_ref = DbCreate(args->db, 3, "rooms", room, "data"); room_json = DbJson(room_ref); HashMapSet(room_json, "is_direct", JsonValueBoolean(true)); HashMapSet(room_json, "xmpp_user", JsonValueString(from)); DbUnlock(args->db, room_ref); ParseePushDMRoom(args, to, from, room); } } /* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER * SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED * ISSUE. * * I HATE THIS. I NEED TO FIND A BETTER WAY. */ if (!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7)) { goto end; } if (mroom_id && !XMPPIsParseeStanza(stanza)) { char *res = ParseeGetResource(from); char *encoded = ParseeGetBridgedUser(args, stanza); char *event_id = NULL; char *parsee = ParseeJID(args); char *trim = ParseeTrimJID(from); XMLElement *ps; /* Subscribe to the sender's metadata node. */ ps = CreatePubsubRequest( parsee, decode_from, "urn:xmpp:avatar:metadata" ); pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, ps); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(ps); ps = CreatePubsubRequest( parsee, trim, "urn:xmpp:avatar:metadata" ); pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, ps); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(ps); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Subscription to XEP-0084: %fs", Elapsed(rectime)); } Free(parsee); Free(trim); pthread_mutex_lock(&thr->info->chk_lock); if (ParseeVerifyAllStanza(args, stanza) && !replaced) { XMLElement *oob, *oob_data; pthread_mutex_unlock(&thr->info->chk_lock); LazyRegister(args, encoded, !chat ? res : NULL); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Matrix registration: %fs", Elapsed(rectime)); } reactions = XMLookForTKV(stanza, "reactions", "xmlns", "urn:xmpp:reactions:0" ); /* Check if it is a media link */ oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); if (oob && data) { char *mxc, *mime = NULL; HashMap *content = NULL; oob_data = XMLookForUnique(oob, "url"); oob_data = oob_data ? ArrayGet(oob_data->children, 0) : NULL; if (oob_data) { FileInfo *info = FileInfoFromXMPP(stanza); mxc = ASReupload(args->config, oob_data->data, &mime); if (mxc) { content = MatrixCreateMedia( mxc, data->data, mime, info ); HashMapSet(content, "at.kappach.at.parsee.external", JsonValueString(oob_data->data) ); ShoveStanza(content, stanza); event_id = LazySend( args, encoded, mroom_id, NULL, content ); Free(mxc); } FileInfoFree(info); Free(mime); } } else if (reactions) { Array *react_child = reactions->children; Array *to_redact; size_t reacts = ArraySize(react_child); event_id = ParseeGetReactedEvent(args, stanza); /* Redact the last few reactions. */ to_redact = ASGetRelations( args->config, 30, mroom_id, event_id, "m.reaction" ); for (i = 0; i < ArraySize(to_redact); i++) { HashMap *e = ArrayGet(to_redact, i); char *e_sender = GrabString(e, 1, "sender"); char *e_id = GrabString(e, 1, "event_id"); if (!StrEquals(e_sender, encoded)) { continue; } ASRedact(args->config, mroom_id, encoded, e_id); } ASFreeRelations(to_redact); /* ... and push out the new ones. */ for (i = 0; i < reacts; i++) { XMLElement *reaction, *react_data; reaction = ArrayGet(react_child, i); react_data = ArrayGet(reaction->children, 0); event_id = LazySend( args, encoded, mroom_id, "m.reaction", ShoveStanza( MatrixCreateReact(event_id, react_data->data), stanza ) ); } Free(event_id); event_id = NULL; } else if (retracted) { event_id = ParseeGetEventFromID(args, stanza, retracted); ASRedact(args->config, mroom_id, encoded, event_id); ParseePushAllStanza(args, stanza, event_id); pthread_mutex_unlock(&thr->info->chk_lock); Free(event_id); event_id = NULL; } else if (data) { /* TODO: Respect the fallback trims the stanza provides us * if possible. Element does not like raw bodies on replies * too. Go figure. */ size_t off = reply_to ? ParseeFindDatastart(data->data) : 0; HashMap *ev = MatrixCreateMessage(data->data + off); if (reply_to) { char *reply_id = ParseeGetEventFromID(args, stanza, reply_to); MatrixSetReply(ev, reply_id); Free(reply_id); } ShoveStanza(ev, stanza); event_id = LazySend( args, encoded, mroom_id, NULL, ev ); } pthread_mutex_lock(&thr->info->chk_lock); ParseePushAllStanza(args, stanza, event_id); Free(event_id); pthread_mutex_unlock(&thr->info->chk_lock); } else if (replaced && data) { /* TODO: Unify these. Maybe even make a function * that objectifies a stanza. */ size_t off = reply_to ? ParseeFindDatastart(data->data) : 0; event_id = ParseeGetEventFromID(args, stanza, replaced); HashMap *ev = ShoveStanza( MatrixCreateReplace(event_id, data->data + off), stanza ); Log(LOG_DEBUG, "Replacing events in %s(%s)", mroom_id, event_id); Free(LazySend( args, encoded, mroom_id, NULL, ev )); ParseePushAllStanza(args, stanza, event_id); pthread_mutex_unlock(&thr->info->chk_lock); Free(event_id); } else { pthread_mutex_unlock(&thr->info->chk_lock); } Free(res); Free(encoded); } else { XMLElement *parsee = XMLookForUnique(stanza, "x-parsee"); XMLElement *event = XMLookForUnique(parsee, "event-id"); XMLElement *e_d = ArrayGet(event ? event->children : NULL, 0); if (e_d) { ParseePushAllStanza(args, stanza, e_d->data); } } ProcessChatstates(args, stanza); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { Log(LOG_DEBUG, "Chatstate management: %fs", Elapsed(rectime)); } end: Free(mroom_id); mroom_id = NULL; Free(from_matrix); Free(decode_from); Free(room); Free(to); Log(LOG_DEBUG, " time=%f " "usage=%d (%s:%d)", (UtilTsMillis()-time)/1000.f, MemoryAllocated(), __FILE__, __LINE__ ); return true; }