#include #include #include #include #include #include #include #include #include #include #include static void JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name) { char *sender = GrabString(event, 1, "sender"); char *rev = StrConcat(3, muc, "/", name); int nonce = 0; while (!XMPPJoinMUC(data->jabber, jid, rev, true) && nonce < 20) { char *nonce_str = StrInt(nonce); char *input = StrConcat(4, sender, name, data->id, nonce_str); unsigned char *digest = Sha256(input); char *hex = ShaToHex(digest); if (strlen(hex) >= 8) { hex[8] = '\0'; } Free(rev); rev = StrConcat(6, muc, "/", name, "[", hex, "]"); nonce++; Free(nonce_str); Free(digest); Free(input); Free(hex); } Free(rev); } static void ParseeMemberHandler(ParseeData *data, HashMap *event) { char *state_key = GrabString(event, 1, "state_key"); char *membership = GrabString(event, 2, "content", "membership"); char *room_id = GrabString(event, 1, "room_id"); char *sender = GrabString(event, 1, "sender"); char *chat_id; char *local = data->config->sender_localpart; const ParseeConfig *conf = data->config; if (StrEquals(membership, "invite") && ParseeIsPuppet(conf, state_key)) { DbRef *ref = NULL; HashMap *json; char *jid; bool direct = GrabBoolean(event, 2, "content", "is_direct"); bool bot = !strncmp(sender + 1, local, strlen(local)); Free(ASJoin(conf, room_id, state_key)); jid = ParseeDecodeLocalJID(conf, state_key); if (direct && !bot) { ref = DbCreate(data->db, 3, "rooms", room_id, "data"); json = DbJson(ref); HashMapSet(json, "is_direct", JsonValueBoolean(direct && !bot)); HashMapSet(json, "xmpp_user", JsonValueString(jid)); DbUnlock(data->db, ref); ParseePushDMRoom(data, sender, jid, room_id); } if (jid) { Free(jid); } } else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key)) { char *jid = ParseeEncodeMXID(state_key); chat_id = ParseeGetFromRoomID(data, room_id); if (chat_id) { char *muc = ParseeGetMUCID(data, chat_id); char *name = ASGetName(data->config, room_id, state_key); JoinMUC(data, event, jid, muc, name); Free(name); Free(muc); /* TODO: XEP-0084 magic to advertise a new avatar if possible. */ } Free(jid); Free(chat_id); } else if ((StrEquals(membership, "leave") || StrEquals(membership, "ban")) && !ParseeIsPuppet(conf, state_key)) { /* TODO: Manage bans on their own, rather than a spicy leave. * Then again, this could cause desync issues. */ XMPPComponent *jabber = data->jabber; char *jid = ParseeEncodeMXID(state_key); char *name = NULL, *rev = NULL, *muc_id = NULL; char *reason = GrabString(event, 2, "content", "reason"); /* Try to find the chat ID */ chat_id = ParseeGetFromRoomID(data, room_id); muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { goto end; } /* TODO: Check the name's validity */ name = ASGetName(data->config, room_id, state_key); rev = StrConcat(4, muc_id, "/", name, "[p]"); XMPPLeaveMUC(jabber, jid, rev, reason); end: Free(chat_id); Free(muc_id); Free(name); Free(rev); Free(jid); } } static void ParseeBotHandler(ParseeData *data, HashMap *event) { char *msgtype = GrabString(event, 2, "content", "msgtype"); char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *sender = GrabString(event, 1, "sender"); char *profile = ParseeMXID(data); Command *cmd = NULL; ParseeCmdArg arg = { .data = data, .event = event }; if (StrEquals(msgtype, "m.notice")) { Free(profile); return; } if (*body != '!') { /* All commands are to be marked with a ! */ Free(ASSend( data->config, id, profile, "m.room.message", MatrixCreateNotice("Please enter a valid command") )); Free(profile); return; } if (!ParseeIsAdmin(data, sender)) { Free(ASSend( data->config, id, profile, "m.room.message", MatrixCreateNotice("You are not authorised to do this.") )); Free(profile); return; } body++; cmd = CommandParse(body); RouteCommand(data->handler, cmd, &arg); Free(profile); CommandFree(cmd); } static const char * GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) { const char *type = NULL; char *room_id = GrabString(event, 1, "room_id"); char *matrix_sender = GrabString(event, 1, "sender"); char *chat_id = NULL, *muc_id = NULL; char *user; DbRef *room_data; HashMap *data_json; bool direct = false; if (!data || !event || !from || !to) { return NULL; } *from = NULL; *to = NULL; chat_id = ParseeGetFromRoomID(data, room_id); room_data = DbLockIntent(data->db, DB_HINT_READONLY, 3, "rooms", room_id, "data" ); data_json = DbJson(room_data); direct = GrabBoolean(data_json, 1, "is_direct"); type = direct ? "chat" : "groupchat"; user = GrabString(data_json, 1, "xmpp_user"); *from = ParseeEncodeMXID(matrix_sender); if (direct) { *to = StrDuplicate(user); Free(chat_id); } else { char *matrix_name; muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { /* muc_id is already implied to be freed by this point */ Free(*from); *from = NULL; DbUnlock(data->db, room_data); return NULL; } matrix_name = ASGetName(data->config, room_id, matrix_sender); /* TODO: Manage name conflicts. That would have been an easy * task(try the original one, and use a counter if it fails), * but that'd involve modifying the rest of the code, which * I'm not doing at 01:39 ... */ JoinMUC(data, event, *from, muc_id, matrix_name); *to = muc_id; Free(matrix_name); } Free(chat_id); DbUnlock(data->db, room_data); return type; } static void ParseeMessageHandler(ParseeData *data, HashMap *event) { XMPPComponent *jabber = data->jabber; StanzaBuilder *builder; DbRef *ref = NULL; HashMap *json; char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); char *m_sender = GrabString(event, 1, "sender"); char *chat_id, *muc_id; char *reply_id = MatrixGetReply(event); char *xepd = ParseeXMPPify(event); char *type, *user, *xmppified_user = NULL, *to = NULL; char *unauth = NULL; char *origin_id = NULL, *stanza = NULL; char *sender = NULL; char *unedited_id = MatrixGetEdit(event); char *url = GrabString(event, 2, "content", "url"); char *encoded_from = NULL; bool direct = false; if (ParseeIsPuppet(data->config, m_sender) || ParseeManageBan(data, m_sender, id)) { Free(reply_id); Free(xepd); Free(unedited_id); return; } chat_id = ParseeGetFromRoomID(data, id); ref = DbLockIntent(data->db, DB_HINT_READONLY, 3, "rooms", id, "data"); json = DbJson(ref); direct = JsonValueAsBoolean(HashMapGet(json, "is_direct")); if (!direct && !chat_id) { ParseeBotHandler(data, event); DbUnlock(data->db, ref); ref = NULL; Free(chat_id); Free(reply_id); Free(xepd); Free(unedited_id); return; } type = direct ? "chat" : "groupchat"; user = GrabString(json, 1, "xmpp_user"); unauth = ParseeToUnauth(data, url); encoded_from = ParseeEncodeMXID(m_sender); xmppified_user = StrConcat(3, encoded_from, "@", jabber->host ); if (direct) { to = StrDuplicate(user); Free(chat_id); } else { char *name; /* Try to find the chat ID */ muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { goto end; } /* TODO: Check the name's validity. * Is there a good way to check for that that isn't * just "await on join and try again?" */ name = ASGetName(data->config, id, m_sender); JoinMUC(data, event, encoded_from, muc_id, name); to = muc_id; Free(name); } if (reply_id) { /* TODO: Monocles chat DM users HATE this trick! * Replies don't work there. Go figure why. */ if (!ParseeGetStanzaInfo(data, chat_id, reply_id, &stanza, &sender)) { ParseeGetDMStanzaInfo(data, id, reply_id, &stanza, &sender); } } else if (unedited_id) { if (!ParseeGetOrigin(data, chat_id, unedited_id, &origin_id)) { ParseeGetDMOrigin(data, id, unedited_id, &origin_id); } } if (direct && sender) { char *sndr_tmp = sender; sender = ParseeTrimJID(sender); Free(sndr_tmp); } { char *xmpp_ident = StrRandom(32); builder = CreateStanzaBuilder(xmppified_user, to, xmpp_ident); SetStanzaType(builder, type); SetStanzaBody(builder, unauth ? unauth : (xepd ? xepd : body)); SetStanzaReply(builder, stanza, sender); SetStanzaLink(builder, unauth); SetStanzaEdit(builder, origin_id); SetStanzaXParsee(builder, event); WriteoutStanza(builder, jabber); DestroyStanzaBuilder(builder); if (direct) { ParseePushDMStanza( data, id, NULL, xmpp_ident, ev_id, xmppified_user ); } Free(xmpp_ident); } end: Free(origin_id); Free(xmppified_user); Free(chat_id); Free(to); Free(reply_id); Free(xepd); Free(stanza); Free(sender); Free(unauth); Free(unedited_id); Free(encoded_from); DbUnlock(data->db, ref); ref = NULL; } void ParseeEventHandler(ParseeData *data, HashMap *event) { char *event_type, *event_id, *room_id, *sender; char *parsee = ParseeMXID(data); event_id = GrabString(event, 1, "event_id"); event_type = GrabString(event, 1, "type"); room_id = GrabString(event, 1, "room_id"); sender = GrabString(event, 1, "sender"); if (!data || !event || ParseeIsPuppet(data->config, sender)) { Free(parsee); return; } ParseePushHeadTable(room_id, event_id); if (StrEquals(event_type, "m.room.member")) { ParseeMemberHandler(data, event); Free(parsee); return; } else if (StrEquals(event_type, "m.room.message") || StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker * support here... */ { ParseeMessageHandler(data, event); Free(parsee); return; } else if (StrEquals(event_type, "m.room.redaction") && !StrEquals(sender, parsee)) { char *from, *to; char *redacted = GrabString(event, 1, "redacts"); char *redacted_stanza = NULL; char *chat_id; XMPPComponent *jabber = data->jabber; const char *type = GetXMPPInformation(data, event, &from, &to); chat_id = ParseeGetFromRoomID(data, room_id); if (!ParseeGetOrigin(data, chat_id, redacted, &redacted_stanza)) { ParseeGetDMOrigin(data, room_id, redacted, &redacted_stanza); } /* Some clients don't support retractions *at all*, which smell. * This therefore serves as a fallback, just in case that fails. * * TODO: BAD IDEA. XMPP CLIENTS, FUCKING IMPLEMENT IT YOU LAZY * FUCKTARDS. */ XMPPRetract(jabber, from, to, (char *) type, redacted_stanza); Free(redacted_stanza); Free(chat_id); Free(from); Free(to); } Free(parsee); }