#include "XMPPThread/internal.h" #include #include #include #include #include #include static XMLElement * CreateVCardRequest(char *from, char *to) { XMLElement *vcard_request, *vcard; char *id; vcard_request = XMLCreateTag("iq"); XMLAddAttr(vcard_request, "from", from); XMLAddAttr(vcard_request, "to", to); XMLAddAttr(vcard_request, "id", (id = StrRandom(16))); XMLAddAttr(vcard_request, "type", "get"); vcard = XMLCreateTag("vCard"); XMLAddAttr(vcard, "xmlns", "vcard-temp"); XMLAddChild(vcard_request, vcard); Free(id); return vcard_request; } static UserStatus GuessStatus(XMLElement *stanza) { /* C.F RFC3921: XMPP IM */ XMLElement *show = XMLookForUnique(stanza, "show"); XMLElement *data = show ? ArrayGet(show->children, 0) : NULL; if (!show || !data) { return USER_STATUS_ONLINE; } if (StrEquals(data->data, "away") || StrEquals(data->data, "xa")) { return USER_STATUS_OFFLINE; } if (StrEquals(data->data, "chat")) { return USER_STATUS_ONLINE; } if (StrEquals(data->data, "dnd")) { return USER_STATUS_UNAVAILABLE; } return USER_STATUS_ONLINE; } void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { #define MUC_USER_NS "http://jabber.org/protocol/muc#user" XMLElement *user_info; XMLElement *vc = XMLookForTKV(stanza, "x", "xmlns", "vcard-temp:x:update"); XMLElement *status = XMLookForUnique(stanza, "status"); char *oid = HashMapGet(stanza->attrs, "from"); char *dst = HashMapGet(stanza->attrs, "to"); char *type = HashMapGet(stanza->attrs, "type"); if (StrEquals(type, "subscribe")) { Log(LOG_WARNING, "!PRESENCE SUBSCRIPTION REQUEST! (%s:%s)", oid, dst); AddPresenceSubscriber(args, oid, dst); /* TODO: Send presence updates * whenever possible. */ } if (PEPManagerHandle(thr->info->pep_manager, stanza)) { return; } if (ServerHasXEP421(args, oid)) { XMLElement *occupant = XMLookForTKV( stanza, "occupant-id", "xmlns", "urn:xmpp:occupant-id:0" ); char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL; if (occ_id) { ParseePushOIDTable(oid, occ_id); } } if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS))) { XMLElement *item = XMLookForUnique(user_info, "item"); XMLElement *status = XMLookForUnique(user_info, "status"); #define IsStatus(code) (status && \ StrEquals(HashMapGet(status->attrs, "code"), #code)) char *jid = item ? HashMapGet(item->attrs, "jid") : NULL; char *trim = ParseeTrimJID(jid); char *from = NULL; char *room = ParseeGetBridgedRoom(args, stanza); char *decode_from, *real_matrix; char *matrix_user_pl = ParseeEncodeJID(args->config, trim, false); char *affiliation = HashMapGet(item->attrs, "affiliation"); char *role = HashMapGet(item->attrs, "role"); int power_level = 0; char *parsee = ParseeMXID(args); char *parsee_j = ParseeJID(args); Free(trim); if (jid) { ParseePushJIDTable(oid, jid); } decode_from = ParseeLookupJID(oid); real_matrix = ParseeDecodeMXID(decode_from); if (!real_matrix || *real_matrix != '@') { Free(real_matrix); real_matrix = ParseeGetBridgedUser(args, stanza); } if (StrEquals(affiliation, "owner")) { power_level = 98; } else if (StrEquals(affiliation, "admin")) { power_level = 50; } else if (StrEquals(affiliation, "member")) { power_level = 0; } else if (StrEquals(affiliation, "outcast")) { power_level = -1; } if (StrEquals(role, "visitor")) { if (!StrEquals(HashMapGet(stanza->attrs, "to"), parsee_j) && IsStatus(110)) { char *muc = ParseeTrimJID(HashMapGet(stanza->attrs, "from")); char *usr = HashMapGet(stanza->attrs, "to"); /* Ask for voice in a visitor self-presence. We do not notify * the user, as an error MUST occur, which is handled and * logged out. */ XMPPRequestVoice(args->jabber, usr, muc); Free(muc); } } /* Set the user's PL * NOTE: Do NOT change the PL of *real* people nilly-willy. * In some scenarios, this is really bad behaviour. */ if (room) { HashMap *powers = ASGetPL(args->config, room); HashMap *users = GrabObject(powers, 1, "users"); int64_t level = GrabInteger(powers, 2, "users", matrix_user_pl); /* I may or may not have fucked up the state hard with this * lacking any checking. (--gen) */ if (StrEquals(parsee, matrix_user_pl)) { Log(LOG_ERR, "BAD PL DOWNGRADE (%d->%d)", level, power_level); } if (users && level != power_level && !StrEquals(parsee, matrix_user_pl) && matrix_user_pl) { JsonValue *val = JsonValueInteger(power_level); JsonValueFree(JsonSet(users, val, 1, matrix_user_pl)); ASSetPL(args->config, room, powers); } else { JsonFree(powers); } } if (StrEquals(type, "unavailable") && !StrEquals(parsee, real_matrix)) { /* If not an MXID, use the Parsee user */ if (IsStatus(301)) { ASBan(args->config, room, real_matrix); } else if (IsStatus(307)) { ASKick(args->config, room, real_matrix); } else if (IsStatus(303)) { char *nick = HashMapGet(item->attrs, "nick"); ASSetName(args->config, real_matrix, nick); } else { ASLeave(args->config, room, real_matrix); } } if (StrEquals(type, "unavailable") && StrEquals(dst, parsee_j) && (IsStatus(301) || IsStatus(307))) { char *chat_id = ParseeGetFromRoomID(args, room); char *muc = ParseeTrimJID(oid); DbRef *ref; ref = DbLock(args->db, 1, "chats"); JsonValueFree(HashMapDelete( GrabObject(DbJson(ref), 1, "rooms"), room )); JsonValueFree(HashMapDelete( GrabObject(DbJson(ref), 1, "mucs"), muc )); DbUnlock(args->db, ref); DbDelete(args->db, 2, "chats", chat_id); Free(ASSend( args->config, room, parsee, "m.room.message", MatrixCreateNotice("This room has been unlinked.") )); ASLeave(args->config, room, parsee); Free(chat_id); Free(muc); } Free(from); Free(decode_from); Free(real_matrix); Free(matrix_user_pl); Free(parsee_j); Free(parsee); Free(room); } if (status) { XMLElement *status_data = ArrayGet(status->children, 0); char *decode_from = ParseeLookupJID(oid); char *from_matrix = ParseeGetBridgedUser(args, stanza); char *status_str = NULL; if (status_data) { status_str = status_data->data; } /* TODO: "The server will automatically set a user's presence to * unavailable if their last active time was over a threshold value * (e.g. 5 minutes)." * We _will_ need to manage those cases properly(cronjob?) if we want * XMPP presences to sync properly */ ASSetStatus( args->config, from_matrix, GuessStatus(stanza), status_str ); Free(decode_from); Free(from_matrix); } if (vc) { XMLElement *photo = XMLookForUnique(vc, "photo"); XMLElement *p_dat = photo ? ArrayGet(photo->children, 0) : NULL; XMLElement *vcard_request; XMPPComponent *jabber = args->jabber; char *from; DbRef *avatars; HashMap *json; char *avatar_id; if (!p_dat) { return; } if (args->verbosity >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Looking at VCard in presence from %s", oid ); } avatars = DbLock(args->db, 1, "avatars"); if (!avatars) { avatars = DbCreate(args->db, 1, "avatars"); } json = DbJson(avatars); if (args->verbosity >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "VCard: Locked the database!"); } avatar_id = GrabString(json, 1, oid); if (avatar_id && StrEquals(avatar_id, p_dat->data)) { if (args->verbosity >= PARSEE_VERBOSE_COMICAL) { Log(LOG_WARNING, "VCard: %s is already cached for %s", avatar_id, oid ); } DbUnlock(args->db, avatars); return; } JsonValueFree(JsonSet( json, JsonValueString(p_dat->data), 1, oid) ); DbUnlock(args->db, avatars); from = ParseeJID(args); Log(LOG_DEBUG, "Sending a vCard avatar request for %s(=%s)", oid, p_dat->data ); vcard_request = CreateVCardRequest( from, HashMapGet(stanza->attrs, "from") ); pthread_mutex_lock(&jabber->write_lock); XMLEncode(jabber->stream, vcard_request); StreamFlush(jabber->stream); pthread_mutex_unlock(&jabber->write_lock); XMLFreeElement(vcard_request); Free(from); } #undef MUC_USER_NS }