[ADD/WIP] Matrix<->XMPP DMs.

There is still this fun issue with the blocking XML parser leaking
memory on pthread_cancel, I'll try to deal with that later, maybe with a
test stanza.

I also apologise for swearing in the last commit. I promise it won't
happen again, Prosody.
This commit is contained in:
LDA 2024-06-18 20:59:35 +02:00
commit 2cc4b0ed17
11 changed files with 325 additions and 63 deletions

View file

@ -24,7 +24,6 @@ ASVerifyRequest(ParseeHttpArg *arg)
if (!authorisation || strncmp(authorisation, "Bearer ", 7))
{
Log(LOG_INFO, "Bad auth %s", authorisation);
HttpResponseStatus(arg->ctx, HTTP_FORBIDDEN);
ret = MatrixCreateError("M_MISSING_TOKEN", "No 'hs_token' given in.");
goto end;
@ -42,7 +41,7 @@ end:
}
void
ASAuthenticateRequest(ParseeConfig *data, HttpClientContext *ctx)
ASAuthenticateRequest(const ParseeConfig *data, HttpClientContext *ctx)
{
char *bearer;
if (!data || !ctx)
@ -55,7 +54,7 @@ ASAuthenticateRequest(ParseeConfig *data, HttpClientContext *ctx)
Free(bearer);
}
bool
ASRegisterUser(ParseeConfig *conf, char *user)
ASRegisterUser(const ParseeConfig *conf, char *user)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
@ -100,7 +99,6 @@ ASPing(const ParseeConfig *conf)
return;
}
Log(LOG_NOTICE, "Pinging...");
path = StrConcat(3,
"/_matrix/client/v1/appservice/",
"Parsee%20XMPP",
@ -118,14 +116,13 @@ ASPing(const ParseeConfig *conf)
JsonFree(json);
}
void
ASJoin(ParseeConfig *conf, char *id, char *masquerade)
ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path;
if (!conf || !id || !masquerade)
{
Log(LOG_ERR, "Bad values %p %p", conf, id);
return;
}
@ -134,7 +131,6 @@ ASJoin(ParseeConfig *conf, char *id, char *masquerade)
"user_id=", masquerade
);
Log(LOG_INFO, "%s", path);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
@ -148,40 +144,29 @@ ASJoin(ParseeConfig *conf, char *id, char *masquerade)
JsonFree(json);
}
void
ASSend(ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL, *params_obj;
Stream *s;
char *path, *params;
char *txn;
if (!conf || !id || !type || !c)
if (!conf || !id || !type || !user || !c)
{
return;
}
txn = StrRandom(16);
params_obj = HashMapCreate();
if (user)
{
HashMapSet(params_obj, "user_id", user);
}
params = HttpParamEncode(params_obj);
HashMapFree(params_obj);
path = StrConcat(8,
path = StrConcat(9,
"/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?",
params
"user_id=", user
);
Log(LOG_INFO, "Sending %s", path);
Free(params);
Free(txn);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
Log(LOG_INFO, "%d", ParseeSetRequestJSON(ctx, c));
ParseeSetRequestJSON(ctx, c);
HttpClientContextFree(ctx);
JsonFree(c);
}

View file

@ -61,7 +61,7 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
}
HttpClientContext *
ParseeCreateRequest(ParseeConfig *conf, HttpRequestMethod meth, char *path)
ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path)
{
HttpClientContext *ctx;
if (!conf || !path)

View file

@ -1,6 +1,7 @@
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Log.h>
#include <pthread.h>
#include <string.h>
#include <signal.h>
@ -10,6 +11,7 @@
#include <AS.h>
static HttpServer *server = NULL;
static pthread_t xmpp_thr;
static void
SignalHandler(int signal)
{
@ -26,6 +28,7 @@ SignalHandler(int signal)
return;
}
HttpServerStop(server);
pthread_cancel(xmpp_thr);
break;
}
}
@ -59,7 +62,6 @@ Main(void)
parsee_conf->shared_comp_secret
);
}
Log(LOG_INFO, "HS token: %s", parsee_conf->hs_token);
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
@ -70,6 +72,12 @@ Main(void)
conf.handlerArgs = ParseeInitData(jabber);
conf.handler = ParseeRequest;
if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs))
{
Log(LOG_ERR, "Couldn't start XMPP listener thread.");
/* TODO: Die */
}
sigAction.sa_handler = SignalHandler;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags = SA_RESTART;

View file

@ -15,6 +15,7 @@ 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");
const ParseeConfig *conf = data->config;
Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership);
@ -23,28 +24,22 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
DbRef *ref;
HashMap *json;
char *jid;
Log(LOG_INFO, "Looks like %s was invited to %s",
state_key,
room_id
);
ASJoin(conf, room_id, state_key);
ref = DbCreate(data->db, 3, "rooms", room_id, "data");
json = DbJson(ref);
Log(LOG_INFO, "Grabbing JID");
jid = ParseeDecodeLocalJID(conf, state_key);
Log(LOG_INFO, "JID: %s", jid);
ref = DbCreate(data->db, 3, "rooms", room_id, "data");
json = DbJson(ref);
HashMapSet(json, "is_direct", JsonValueBoolean(true));
HashMapSet(json, "xmpp_user", JsonValueString(jid));
DbUnlock(data->db, ref);
ParseePushDMRoom(data, sender, jid, room_id);
/* Pretend everything is a dm, ignoring is_direct */
if (jid)
{
Free(jid);
}
DbUnlock(data->db, ref);
}
}
static void
@ -59,9 +54,16 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender");
if (ParseeIsPuppet(data->config, sender))
{
Log(LOG_INFO, "Do not bridge the puppet");
return;
}
ref = DbLock(data->db, 3, "rooms", id, "data");
json = DbJson(ref);
if (JsonValueAsBoolean(HashMapGet(json, "is_direct")))
{
char *user = GrabString(json, 1, "xmpp_user");
@ -85,11 +87,6 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
return;
}
Log(LOG_INFO, "Event by '%s', with type '%s'",
JsonValueAsString(HashMapGet(event, "sender")),
JsonValueAsString(HashMapGet(event, "type"))
);
event_type = GrabString(event, 1, "type");
if (StrEquals(event_type, "m.room.member"))
{

View file

@ -1,9 +1,13 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Sha.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
bool
ParseeIsPuppet(const ParseeConfig *conf, char *user)
@ -99,6 +103,54 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid)
/* Until the ':', data_start now is an encoded JID */
return DecodeJID(data_start, ':');
}
char *
ParseeEncodeJID(const ParseeConfig *c, char *jid)
{
char *ret, *tmp;
size_t i;
if (!c || !jid)
{
return NULL;
}
ret = StrConcat(2, c->namespace_base, "_l_");
for (i = 0; i < strlen(jid); i++)
{
const char *HEX = "0123456789abcdef";
char cpy = jid[i];
char cs[4] = { 0 };
cs[0] = cpy;
if (*cs == '/')
{
/* RID: Break everything and die. */
break;
}
if (islower(*cs) || isalpha(*cs) || *cs == '_' ||
*cs == '=' || *cs == '-' || *cs == '/' ||
*cs == '+' || *cs == '.')
{
tmp = ret;
ret = StrConcat(2, ret, cs);
Free(tmp);
continue;
}
/* Hex encode everything */
cs[0] = '=';
cs[1] = HEX[(cpy >> 4) & 0xF];
cs[2] = HEX[(cpy >> 0) & 0xF];
tmp = ret;
ret = StrConcat(2, ret, cs);
Free(tmp);
}
tmp = ret;
ret = StrConcat(4, "@", ret, ":", c->homeserver_host);
Free(tmp);
return ret;
}
char *
ParseeGetLocal(char *mxid)
{
@ -129,6 +181,8 @@ ParseeGetLocal(char *mxid)
return cpy;
}
/* TODO: More robust system */
char *
ParseeEncodeMXID(char *mxid)
{
@ -155,3 +209,119 @@ ParseeEncodeMXID(char *mxid)
return ret;
}
char *
ParseeDecodeMXID(char *mxid)
{
char *ret;
size_t i;
if (!mxid)
{
return NULL;
}
ret = Malloc(strlen(mxid) + 1);
for (i = 0; i < strlen(mxid) + 1; i++)
{
char src = mxid[i];
if (src == '%')
{
src = '@';
}
else if (src == '=')
{
src = ':';
}
else if (src == '@')
{
ret[i] = '\0';
break;
}
ret[i] = src;
}
return ret;
}
char *
ParseeGetDMID(char *mxid, char *jid)
{
char *concat, *sha;
unsigned char *raw;
if (!mxid || !jid)
{
return NULL;
}
jid = ParseeTrimJID(jid);
concat = StrConcat(2, mxid, jid);
raw = Sha1(concat);
sha = ShaToHex(raw);
Free(concat);
Free(raw);
Free(jid);
return sha;
}
char *
ParseeFindDMRoom(ParseeData *data, char *mxid, char *jid)
{
DbRef *ref;
HashMap *j;
char *room, *dmid;
if (!data || !mxid || !jid)
{
return NULL;
}
dmid = ParseeGetDMID(mxid, jid);
ref = DbLock(data->db, 2, "users", dmid);
j = DbJson(ref);
room = StrDuplicate(JsonValueAsString(HashMapGet(j, "room")));
DbUnlock(data->db, ref);
Free(dmid);
return room;
}
void
ParseePushDMRoom(ParseeData *d, char *mxid, char *jid, char *r)
{
DbRef *ref;
HashMap *j;
char *dmid;
if (!d || !mxid || !jid || !r)
{
return;
}
dmid = ParseeGetDMID(mxid, jid);
ref = DbCreate(d->db, 2, "users", dmid);
j = DbJson(ref);
HashMapSet(j, "room", JsonValueString(r));
DbUnlock(d->db, ref);
Free(dmid);
return;
}
char *
ParseeTrimJID(char *jid)
{
char *ret;
size_t i;
if (!jid)
{
return NULL;
}
ret = StrDuplicate(jid);
for (i = 0; i < strlen(ret); i++)
{
if (ret[i] == '/')
{
ret[i] = '\0';
break;
}
}
return ret;
}

View file

@ -13,11 +13,9 @@ RouteHead(RouteTxns, arr, argp)
Array *events;
size_t i;
Log(LOG_INFO, "Caught one!");
response = ASVerifyRequest(args);
if (response)
{
Log(LOG_INFO, ":(");
goto end;
}
if (HttpRequestMethodGet(args->ctx) != HTTP_PUT)
@ -30,7 +28,6 @@ RouteHead(RouteTxns, arr, argp)
goto end;
}
Log(LOG_INFO, "Caught a decent one!");
RequestJSON();
/* TODO: Do a thing with these. */
@ -44,7 +41,6 @@ RouteHead(RouteTxns, arr, argp)
/* TODO: Store TXN ID somewhere so that we can commit
* "Epic Matrix Idempotency" */
Log(LOG_INFO, "OK!");
response = HashMapCreate();
end:
JsonFree(request);

View file

@ -1,9 +1,5 @@
#include <XML.h>
#include <Cytoplasm/Log.h>
/* TODO: The rest of all that. */
XMLElement *
XMLDecode(Stream *stream, bool autofree)
{
@ -27,12 +23,12 @@ XMLDecode(Stream *stream, bool autofree)
stack = ArrayCreate();
while ((event = XMLCrank(lexer)) && (!ret || ArraySize(stack)))
{
bool flag = false;
switch (event->type)
{
case XML_LEXER_STARTELEM:
/* Create a new element that will populated. */
top = XMLCreateTag(event->element);
Log(LOG_INFO, "<%s>", top->name);
XMLAddChild(peek(), top);
while (HashMapIterate(event->attrs, &key, (void **) &val))
{
@ -50,7 +46,6 @@ XMLDecode(Stream *stream, bool autofree)
/* Create a new element that will populated. */
top = XMLCreateTag(event->element);
XMLAddChild(peek(), top);
Log(LOG_INFO, "<%s />", top->name);
while (HashMapIterate(event->attrs, &key, (void **) &val))
{
XMLAddAttr(top, key, val);
@ -65,11 +60,25 @@ XMLDecode(Stream *stream, bool autofree)
/* Fallthrough */
case XML_LEXER_ENDELEM:
/* Pop out an element out of the DOM. */
Log(LOG_INFO, "</>");
pop();
if (!ArraySize(stack))
{
XMLFreeEvent(event);
event = NULL;
flag = true;
break;
}
break;
case XML_LEXER_DATA:
top = XMLCreateText(event->data);
XMLAddChild(peek(), top);
break;
default: break;
}
if (flag)
{
break;
}
XMLFreeEvent(event);
event = NULL;
}

View file

@ -658,8 +658,6 @@ XMLParseAttQuote(XMLexer *lexer)
point = XMLInitialiseBuffer(lexer);
/* TODO: My Prosody is actually trolling me.
* GIVE ME A FUCKING STREAM ID YOU RETARD. */
while ((c = XMLGetc(lexer)))
{
if (!IsNormalQ(c))

75
src/XMPPThread.c Normal file
View file

@ -0,0 +1,75 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <Matrix.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void *
ParseeXMPPThread(void *argp)
{
ParseeData *args = argp;
XMPPComponent *jabber = args->jabber;
XMLElement *stanza = NULL;
while (true)
{
/* TODO: pthread cancelation points */
XMLElement *body = NULL;
XMLElement *data = NULL;
char *to, *room, *from, *from_matrix;
/* Decoding XML is blocking, which will cause memory issues when we
* want to murder this thread.
* We could ping the server, and die on a "response" stanza, however.
*/
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{
ASSend(
args->config, "!jdHnoKgHCm51ujbw:ari.lt", "_parsee_bridge",
"m.room.message", MatrixCreateNotice("Hi.")
);
continue;
}
if (StrEquals(stanza->name, "message"))
{
size_t i;
for (i = 0; i < ArraySize(stanza->children); i++)
{
XMLElement *child = ArrayGet(stanza->children, i);
if (StrEquals(child->name, "body"))
{
body = child;
break;
}
}
if (!body)
{
XMLFreeElement(stanza);
continue;
}
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
from = HashMapGet(stanza->attrs, "from");
from_matrix = ParseeEncodeJID(args->config, from);
room = ParseeFindDMRoom(args, to, from);
data = ArrayGet(body->children, 0);
/* TODO: Manage that out. $[to] corresponds to a Matrix
* user, which we need to find the */
ASSend(
args->config, room, from_matrix,
"m.room.message", MatrixCreateNotice(data->data)
);
Free(from_matrix);
Free(room);
Free(to);
}
XMLFreeElement(stanza);
}
return NULL;
}

View file

@ -13,21 +13,21 @@ extern HashMap * ASVerifyRequest(ParseeHttpArg *);
/* Authenticates a request with the correct as_token.
* It does not send the request, however. */
extern void ASAuthenticateRequest(ParseeConfig *, HttpClientContext *);
extern void ASAuthenticateRequest(const ParseeConfig *, HttpClientContext *);
/* Registers an user through the Application Service API. Returns
* true if the user was created. */
extern bool ASRegisterUser(ParseeConfig *, char *);
extern bool ASRegisterUser(const ParseeConfig *, char *);
/* Pings the homeserver to get attention. */
extern void ASPing(const ParseeConfig *);
/* Joins a room from an ID and a given user we want to masquerade
* as. */
extern void ASJoin(ParseeConfig *, char *, char *);
extern void ASJoin(const ParseeConfig *, char *, char *);
/* Sends a message event with a specific type and body.
* Said body is freed during the function's execution. */
extern void ASSend(ParseeConfig *, char *, char *, char *, HashMap *);
extern void ASSend(const ParseeConfig *, char *, char *, char *, HashMap *);
#endif

View file

@ -62,10 +62,7 @@ extern void ParseeConfigFree(void);
extern ParseeData * ParseeInitData(XMPPComponent *);
extern void ParseeFreeData(ParseeData *);
/* HTTP server handler for Parsee, takes in a config. */
extern void ParseeRequest(HttpServerContext *, void *);
extern HttpClientContext * ParseeCreateRequest(ParseeConfig *, HttpRequestMethod, char *);
extern HttpClientContext * ParseeCreateRequest(const ParseeConfig *, HttpRequestMethod, char *);
/* Sends headers, and writes the JSON object. */
extern HttpStatus ParseeSetRequestJSON(HttpClientContext *, HashMap *);
@ -79,9 +76,36 @@ extern bool ParseeIsPuppet(const ParseeConfig *, char *);
/* Decodes a local JID for a user into a string. */
extern char * ParseeDecodeLocalJID(const ParseeConfig *, char *);
/* Encodes a JID into a Parsee localpart */
extern char * ParseeEncodeJID(const ParseeConfig *, char *);
/* Gets the localpart of a MXID */
extern char * ParseeGetLocal(char *);
/* Encodes an MXID to a valid Jabber ID head */
extern char * ParseeEncodeMXID(char *);
/* Decodes a Jabber ID head into a valid MXID */
extern char * ParseeDecodeMXID(char *);
/* HTTP server handler for Parsee, takes in a config. */
extern void ParseeRequest(HttpServerContext *, void *);
/* A pthread callback used for listening to a component */
extern void * ParseeXMPPThread(void *data);
/* Finds the room a DM is associated to, from a Matrix user and a Jabber
* ID. */
extern char * ParseeFindDMRoom(ParseeData *data, char *mxid, char *jid);
/* Gets a DM ID from a Matrix and Jabber user */
extern char * ParseeGetDMID(char *mxid, char *jid);
/* Creates a DM mapping in the database */
extern void ParseePushDMRoom(ParseeData *, char *mxid, char *jid, char *r);
/* Trims the component from a JID */
extern char * ParseeTrimJID(char *jid);
#endif