mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 16:45:10 +00:00
349 lines
10 KiB
C
349 lines
10 KiB
C
#include "XMPPThread/internal.h"
|
|
|
|
#include <Cytoplasm/Memory.h>
|
|
#include <Cytoplasm/Str.h>
|
|
#include <Cytoplasm/Log.h>
|
|
|
|
#include <Matrix.h>
|
|
#include <AS.h>
|
|
|
|
#include <string.h>
|
|
|
|
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
|
|
}
|
|
|