#include "XMPPThread/internal.h" #include #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((int) *b64)) { b64++; continue; } tmp = ret; ret = StrConcat(2, ret, ch); Free(tmp); b64++; } return ret; } static bool AvatarGrab(ParseeData *data, char *mxc, char **mime, char **b64, size_t *len) { char *mimei = NULL, *outi = NULL, *b64i = NULL; size_t sizei; if (!data || !mxc || !mime || !b64 || !len) { return false; } if (!ASGrab(data->config, mxc, &mimei, &outi, &sizei)) { Free(mimei); Free(outi); return false; } b64i = Base64Encode(outi, sizei); Free(outi); if (!b64i) { Free(mimei); b64i = StrDuplicate(media_unknown); /* TODO: Different assets! */ mimei = StrDuplicate("image/png"); } *mime = mimei; *b64 = b64i; *len = sizei; return true; } XMLElement * GenerateAvatarData(ParseeData *data, char *mxid) { char *mxc = NULL, *mime = NULL, *b64 = NULL; XMLElement *elem = NULL, *type, *binval; size_t len = 0; if (!data || !mxid) { return NULL; } /* TODO: Use the right room for the avatar! */ mxc = ASGetAvatar(data->config, NULL, mxid); if (!mxc || !AvatarGrab(data, mxc, &mime, &b64, &len)) { goto end; } elem = XMLCreateTag("PHOTO"); type = XMLCreateTag("TYPE"); binval = XMLCreateTag("BINVAL"); XMLAddChild(type, XMLCreateText(mime)); XMLAddChild(binval, XMLCreateText(b64)); XMLAddChild(elem, type); XMLAddChild(elem, binval); end: Free(mime); Free(mxc); Free(b64); return elem; } #define DISCO "http://jabber.org/protocol/disco#info" static XMLElement * IQGenerateQuery(IQFeatures *features) { XMLElement *query; if (!features) { return NULL; } query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", DISCO); { XMLElement *feature; size_t i; for (i = 0; i < ArraySize(features->identity); i++) { XMPPIdentity *identity = ArrayGet(features->identity, i); feature = XMLCreateTag("identity"); XMLAddAttr(feature, "category", identity->category); XMLAddAttr(feature, "type", identity->type); XMLAddAttr(feature, "name", identity->name); XMLAddChild(query, feature); } for (i = 0; i < ArraySize(features->adverts); i++) { char *var = ArrayGet(features->adverts, i); feature = XMLCreateTag("feature"); XMLAddAttr(feature, "var", var); XMLAddChild(query, feature); } } return query; } void IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; IQFeatures *features; 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); features = LookupJIDFeatures(to); query = IQGenerateQuery(features); { char *ver = XMPPGenerateVer(features); char *node = StrConcat(3, REPOSITORY, "#", ver); XMLAddAttr(query, "node", node); Free(node); Free(ver); } XMLAddChild(iq_reply, query); FreeIQFeatures(features); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); 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, *to; 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); to = HashMapGet(stanza->attrs, "to"); ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host); Free(parsee); return ret; } static bool IsInMUC(ParseeData *data, char *jid) { char *trimmed = ParseeTrimJID(jid); char *chat_id = ParseeGetFromMUCID(data, trimmed); Free(trimmed); Free(chat_id); return !!chat_id; } 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"); char *trimmed = ParseeTrimJID(from); XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "from", to); XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "id", id); { XMLElement *q = XMLCreateTag("query"); char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); XMPPShoveCommandList(thr->info->m, IsInMUC(args, from) ? parsee_muc_jid : to, q, stanza ); XMLAddChild(iq_reply, q); Free(parsee_muc_jid); } XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMLFreeElement(iq_reply); Free(trimmed); } 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 (%s)", to, to_matrix); 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); } XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size); XMLFreeElement(iqVCard); Free(to_matrix); Free(name); return; } Free(to_matrix); to_matrix = ParseeDecodeMXID(to); 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"); { XMLAddChild(vCard, GenerateAvatarData(args, to_matrix)); Free(mto_link); } XMLAddChild(iqVCard, vCard); } XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size); XMLFreeElement(iqVCard); 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. * NOTE: I explicitely choose to not do any manipulation * because messing with random user images is inherently a * risk I do *not* want to take. */ char *to_matrix = ParseeDecodeMXID(to); char *avatar = ASGetAvatar(args->config, NULL, to_matrix); char *mime = NULL; char *b64 = NULL; size_t len = 0; XMLElement *reply; AvatarGrab(args, avatar, &mime, &b64, &len); 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); } XMPPSendStanza(jabber, reply, args->config->max_stanza_size); 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, *os; 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"); { struct utsname info; name = XMLCreateTag("name"); version = XMLCreateTag("version"); os = XMLCreateTag("os"); uname(&info); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); XMLAddChild(os, XMLCreateText(info.sysname)); } XMLAddChild(query, name); XMLAddChild(query, version); XMLAddChild(query, os); XMLAddChild(iq_reply, query); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMLFreeElement(iq_reply); } else { char *buf = NULL; Stream *s = StrStreamWriter(&buf); Log(LOG_WARNING, "Unknown I/Q received:"); XMLEncode(s, stanza); StreamFlush(s); StreamClose(s); Log(LOG_WARNING, "%s", buf); Free(buf); } } 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 }