#include "XMPPThread/internal.h" #include #include #include #include #include #include #include #include #include static XMLElement * CreateTagWithText(const char *tn, char *text) { XMLElement *tag = XMLCreateTag((char *) tn); XMLElement *tex = XMLCreateText(text); XMLAddChild(tag, tex); return tag; } char * TrimBase64(char *b64) { char *ret, *tmp; ret = NULL; while (*b64) { char ch[2] = { *b64, 0 }; if (isspace(*b64)) { b64++; continue; } tmp = ret; ret = StrConcat(2, ret, ch); Free(tmp); b64++; } return ret; } #define DISCO "http://jabber.org/protocol/disco#info" static XMLElement * IQGenerateQuery(void) { XMLElement *query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", DISCO); { XMLElement *feature; #define IdentitySimple(c,t,n) do \ { \ feature = XMLCreateTag("identity"); \ XMLAddAttr(feature, "category", c); \ XMLAddAttr(feature, "type", t); \ XMLAddAttr(feature, "name", n); \ XMLAddChild(query, feature); \ } \ while (0); IQ_IDENTITY #undef IdentitySimple #define AdvertiseSimple(f) do \ { \ feature = XMLCreateTag("feature"); \ XMLAddAttr(feature, "var", f); \ XMLAddChild(query, feature); \ } \ while (0); IQ_ADVERT #undef AdvertiseSimple } return query; } void IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); id = HashMapGet(stanza->attrs, "id"); /* Generate an IQ reply with discovery information */ iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "from", to); XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "id", id); query = IQGenerateQuery(); { char *ver = XMPPGenerateVer(); char *node = StrConcat(3, REPOSITORY, "#", ver); XMLAddAttr(query, "node", node); Free(node); Free(ver); } XMLAddChild(iq_reply, query); pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, iq_reply); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(iq_reply); (void) args; } void IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp"); /* TODO: Move this to PEP */ XMLElement *event = XMLookForTKV(stanza, "pubsub", "xmlns", "http://jabber.org/protocol/pubsub" ); (void) thr; if (event) { size_t i; XMLElement *retrieve = XMLookForTKV(event, "items", "node", "urn:xmpp:avatar:data"); if (retrieve) { for (i = 0; i < ArraySize(retrieve->children); i++) { XMLElement *item = ArrayGet(retrieve->children, i); XMLElement *avatar_data = XMLookForTKV( item, "data", "xmlns", "urn:xmpp:avatar:data" ); XMLElement *data = ArrayGet(avatar_data->children, 0); char *id = HashMapGet(item->attrs, "id"); char *from = HashMapGet(stanza->attrs, "from"); char *base64; char *bdata; size_t length, b64len; Stream *datastream; char *mxc, *from_matrix, *jid; DbRef *avatars; HashMap *json; if (!data || !data->data) { return; } avatars = DbLock(args->db, 1, "avatars"); if (!avatars) { avatars = DbCreate(args->db, 1, "avatars"); } json = DbJson(avatars); if (StrEquals(GrabString(json, 1, from), id)) { DbUnlock(args->db, avatars); return; } JsonValueFree(JsonSet( json, JsonValueString(id), 1, from )); DbUnlock(args->db, avatars); base64 = TrimBase64(data->data); b64len = base64 ? strlen(base64) : 0; length = Base64DecodedSize(base64, b64len); bdata = (char *) Base64Decode(base64, b64len); datastream = StrStreamReaderN(bdata, length); /* XEP-0084 effectively assumes an image/png MIME. */ mxc = ASUpload(args->config, datastream, length, "image/png"); jid = ParseeLookupJID(from); from_matrix = ParseeGetBridgedUser(args, stanza); Log(LOG_DEBUG, "Setting the avatar of '%s' to %s", from_matrix, mxc ); ASSetAvatar(args->config, from_matrix, mxc); Free(mxc); Free(jid); Free(bdata); Free(from_matrix); Free(base64); StreamClose(datastream); } } } if (vcard) { XMLElement *photo = XMLookForUnique(vcard, "PHOTO"); if (photo) { XMLElement *binval = XMLookForUnique(photo, "BINVAL"); XMLElement *type = XMLookForUnique(photo, "TYPE"); XMLElement *data = binval ? ArrayGet(binval->children, 0) : NULL; XMLElement *tdata = type ? ArrayGet(type->children, 0) : NULL; char *base64; char *bdata; size_t length, b64len; Stream *datastream; char *mxc = NULL, *from_matrix = NULL, *jid = NULL; char *room = NULL, *resource; char *from = HashMapGet(stanza->attrs, "from"); if (!data || !data->data) { Log(LOG_ERR, "%s NOT FOUND", HashMapGet(stanza->attrs, "from")); return; } /* Get the base64, decode it, and shove it in a nicely put * MXC address */ base64 = TrimBase64(data->data); b64len = base64 ? strlen(base64) : 0; length = Base64DecodedSize(base64, b64len); bdata = Base64Decode(base64, b64len); datastream = StrStreamReaderN(bdata, length); Log(LOG_DEBUG, "(%dB @ %p) = %p (%d)", b64len, base64, bdata, length); mxc = ASUpload( args->config, datastream, length, tdata ? tdata->data : NULL ); Free(bdata); Free(base64); StreamClose(datastream); room = ParseeGetBridgedRoom(args, stanza); jid = ParseeLookupJID(HashMapGet(stanza->attrs, "from")); resource = ParseeTrimJID(from); /* TODO: More reliable system for telling the difference appart */ if (jid && !StrEquals(from, resource)) { char *oid = ParseeLookupOID(jid); char *hsh = ParseeSHA256(oid); bool scrambled = oid && StrEquals(jid, HashMapGet(stanza->attrs, "from")); from_matrix = scrambled ? ScrambleOID(args, oid) : ParseeGetBridgedUser(args, stanza); Log(LOG_DEBUG, "Setting the vCard of '%s'(%s) to %s (%d B)", from_matrix, jid, mxc, length ); Log(LOG_DEBUG, "OID=%s[%s]", oid, hsh ); ASSetAvatar(args->config, from_matrix, mxc); Free(oid); Free(hsh); } else if (room) { char *mask = ParseeMXID(args); HashMap *obj = HashMapCreate(); HashMapSet(obj, "url", JsonValueString(mxc)); ASSetState( args->config, room, "m.room.avatar", "", mask, obj ); Free(mask); } Free(from_matrix); Free(resource); Free(room); Free(jid); Free(mxc); } } } bool IQIsCommandList(ParseeData *args, XMLElement *stanza) { char *parsee = NULL; XMLElement *query = XMLookForTKV( stanza, "query", "xmlns", "http://jabber.org/protocol/disco#items" ); bool ret = false; if (!query || !StrEquals(HashMapGet(query->attrs, "node"), "http://jabber.org/protocol/commands")) { return false; } parsee = ParseeJID(args); ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee); Free(parsee); return ret; } void IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPComponent *jabber = args->jabber; XMLElement *pubsub; char *from = HashMapGet(stanza->attrs, "from"); char *to = HashMapGet(stanza->attrs, "to"); char *id = HashMapGet(stanza->attrs, "id"); (void) thr; if (IQIsCommandList(args, stanza)) { XMLElement *iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "from", to); XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "id", id); { XMLElement *q = XMLCreateTag("query"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); XMPPShoveCommandList(thr->info->m, to, q); XMLAddChild(iq_reply, q); } pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, iq_reply); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(iq_reply); } else if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp")) { char *to_matrix = ParseeGetBridgedUser(args, stanza); char *name = ASGetName(args->config, NULL, to_matrix); XMLElement *iqVCard; Log(LOG_DEBUG, "vCard information GET for %s", to); if (!strncmp(to, "parsee@", 7)) { iqVCard = XMLCreateTag("iq"); XMLAddAttr(iqVCard, "from", to); XMLAddAttr(iqVCard, "to", from); XMLAddAttr(iqVCard, "id", id); XMLAddAttr(iqVCard, "type", "result"); { XMLElement *vCard = XMLCreateTag("vCard"); XMLAddAttr(vCard, "xmlns", "vcard-temp"); { XMLElement *fn = CreateTagWithText( "FN", "Parsee Mizuhashi" ); XMLElement *nick = CreateTagWithText( "NICKNAME", "Parsee" ); XMLElement *url = CreateTagWithText( "URL", REPOSITORY ); /* TODO: Maybe abstract the vCard code. */ XMLAddChild(vCard, nick); XMLAddChild(vCard, url); XMLAddChild(vCard, fn); } XMLAddChild(iqVCard, vCard); } pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, iqVCard); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(iqVCard); Free(to_matrix); Free(name); return; } iqVCard = XMLCreateTag("iq"); XMLAddAttr(iqVCard, "from", to); XMLAddAttr(iqVCard, "to", from); XMLAddAttr(iqVCard, "id", id); XMLAddAttr(iqVCard, "type", "result"); { XMLElement *vCard = XMLCreateTag("vCard"); char *mto_link = ParseeGenerateMTO(to_matrix); XMLAddAttr(vCard, "xmlns", "vcard-temp"); { XMLElement *fn = CreateTagWithText( "FN", name ? name : to_matrix ); XMLElement *nick = CreateTagWithText( "NICKNAME", to_matrix ); XMLElement *url = CreateTagWithText( "URL", mto_link ); /* TODO: Maybe abstract the vCard code. */ /* TODO: Make a function to just get a user's avatar * automatically. */ XMLAddChild(vCard, nick); XMLAddChild(vCard, url); XMLAddChild(vCard, fn); Free(mto_link); } XMLAddChild(iqVCard, vCard); } pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, iqVCard); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); Free(to_matrix); Free(name); } #define PS "http://jabber.org/protocol/pubsub" else if ((pubsub = XMLookForTKV(stanza, "pubsub", "xmlns", PS))) { /* TODO: Pass this through the PEP manager */ XMLElement *a_items = XMLookForTKV(pubsub, "items", "node", "urn:xmpp:avatar:data" ); if (a_items) { /* Do, without regret, start shoving an avatar out the bus */ char *to_matrix = ParseeDecodeMXID(to); char *avatar = ASGetAvatar(args->config, NULL, to_matrix); char *buf, *mime; char *b64; size_t len; XMLElement *reply; ASGrab(args->config, avatar, &mime, &buf, &len); b64 = Base64Encode(buf, len); Free(buf); Log(LOG_DEBUG, "IQ-GET: PUBSUB AVATAR OF=%s", to_matrix); /* Strike back with a response */ reply = XMLCreateTag("iq"); XMLAddAttr(reply, "type", "result"); XMLAddAttr(reply, "to", from); XMLAddAttr(reply, "from", to); XMLAddAttr(reply, "id", HashMapGet(stanza->attrs, "id")); { XMLElement *ps = XMLCreateTag("pubsub"); XMLElement *items = XMLCreateTag("items"); XMLAddAttr(ps, "xmlns", PS); XMLAddAttr(items, "node", "urn:xmpp:avatar:data"); { XMLElement *item = XMLCreateTag("item"); XMLElement *data = XMLCreateTag("data"); XMLAddAttr(item, "id", "TODO"); XMLAddAttr(data, "xmlns", "urn:xmpp:avatar:data"); XMLAddChild(data, XMLCreateText(b64)); XMLAddChild(item, data); XMLAddChild(items, item); } XMLAddChild(ps, items); XMLAddChild(reply, ps); } pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, reply); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(reply); Free(to_matrix); Free(avatar); Free(mime); Free(b64); } } #undef PS else if (XMLookForTKV(stanza, "query", "xmlns", DISCO)) { IQDiscoGet(args, jabber, stanza); } else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) { XMLElement *iq_reply, *query; XMLElement *name, *version; iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "from", to); XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "id", id); query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", "jabber:iq:version"); { name = XMLCreateTag("name"); version = XMLCreateTag("version"); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); } XMLAddChild(query, name); XMLAddChild(query, version); XMLAddChild(iq_reply, query); pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, iq_reply); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(iq_reply); } else { Log(LOG_WARNING, "Unknown I/Q received:"); XMLEncode(StreamStdout(), stanza); StreamPrintf(StreamStdout(),"\n"); StreamFlush(StreamStdout()); } } void IQError(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { /* TODO */ (void) args; (void) stanza; (void) thr; } void IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPCommandManager *manager = thr->info->m; XMPPManageCommand(manager, stanza, args); } #undef DISCO void IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { char *type = HashMapGet(stanza->attrs, "type"); if (PEPManagerHandle(thr->info->pep_manager, stanza)) { return; } #define OnType(ctyp, callback) do \ { \ if (StrEquals(type, #ctyp)) \ { \ callback(args, stanza, thr); \ return; \ } \ } \ while (0) OnType(get, IQGet); OnType(set, IQSet); OnType(error, IQError); OnType(result, IQResult); #undef OnType }