[MOD] One-way Matrix->XMPP comms.

This is not sanitised. I need to make an XML writer.
This commit is contained in:
LDA 2024-06-17 18:26:37 +02:00
commit bdb4fd2f68
17 changed files with 421 additions and 61 deletions

2
.gitignore vendored
View file

@ -4,3 +4,5 @@ parsee*
parsee
*.swp
.*
data
data/*

View file

@ -74,18 +74,23 @@ ASRegisterUser(ParseeConfig *conf, char *user)
json = HashMapCreate();
HashMapSet(json,"type",JsonValueString("m.login.application_service"));
user = ParseeGetLocal(user);
HashMapSet(json,"username",JsonValueString(user));
ASAuthenticateRequest(conf, ctx);
status = ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
Free(user);
return status == HTTP_OK;
}
void
ASPing(ParseeConfig *conf)
ASPing(const ParseeConfig *conf)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
@ -116,26 +121,20 @@ void
ASJoin(ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL, *params_obj;
char *path, *params;
if (!conf || !id)
HashMap *json = NULL;
char *path;
if (!conf || !id || !masquerade)
{
Log(LOG_ERR, "Bad values %p %p", conf, id);
return;
}
params_obj = HashMapCreate();
if (masquerade)
{
HashMapSet(params_obj, "user_id", masquerade);
}
params = HttpParamEncode(params_obj);
HashMapFree(params_obj);
path = StrConcat(4,
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/join?",
params
"user_id=", masquerade
);
Free(params);
Log(LOG_INFO, "%s", path);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
@ -144,6 +143,7 @@ ASJoin(ParseeConfig *conf, char *id, char *masquerade)
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
HttpClientContextFree(ctx);
JsonFree(json);
}
@ -152,6 +152,7 @@ ASSend(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)

View file

@ -55,6 +55,7 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
HttpSendHeaders(ctx);
JsonEncode(response, stream, JSON_DEFAULT);
JsonFree(response);
return;
}
}

View file

@ -2,33 +2,43 @@
#include <Cytoplasm/Log.h>
#include <string.h>
#include <signal.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
static HttpServer *server = NULL;
static void
SignalHandler(int signal)
{
size_t i;
switch (signal)
{
case SIGPIPE:
return;
case SIGTERM:
case SIGINT:
if (!server)
{
return;
}
HttpServerStop(server);
break;
}
}
int
Main(void)
{
HttpServer *server = NULL;
HttpServerConfig conf;
ParseeData *data;
const ParseeConfig *parsee_conf;
Stream *yaml;
{
char *as = "TODO";
char *shared = "TODO";
Stream *jabber = XMPPInitialiseCompStream(as, 0);
XMPPAuthenticateCompStream(jabber, as, shared);
while (!StreamEof(jabber))
{
StreamPutc(StreamStderr(), StreamGetc(jabber));
StreamFlush(StreamStderr());
}
XMPPEndCompStream(jabber);
return 0;
}
XMPPComponent *jabber;
struct sigaction sigAction;
Log(LOG_INFO, "%s - v%s", NAME, VERSION);
ParseeConfigLoad("parsee.json");
@ -39,18 +49,48 @@ Main(void)
StreamClose(yaml);
parsee_conf = ParseeConfigGet();
{
jabber = XMPPInitialiseCompStream(
parsee_conf->component_host,
parsee_conf->component_port
);
XMPPAuthenticateCompStream(
jabber,
parsee_conf->shared_comp_secret
);
}
Log(LOG_INFO, "HS token: %s", parsee_conf->hs_token);
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
memset(&conf, 0, sizeof(conf));
conf.port = parsee_conf->port;
conf.threads = 4;
conf.maxConnections = 32;
conf.handlerArgs = ParseeInitData();
conf.handlerArgs = ParseeInitData(jabber);
conf.handler = ParseeRequest;
sigAction.sa_handler = SignalHandler;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags = SA_RESTART;
#define SIGACTION(sig, act, oact) \
if (sigaction(sig, act, oact) < 0) \
{ \
Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \
} \
else \
{ \
Log(LOG_DEBUG, "Installed signal handler: %s", #sig); \
}
SIGACTION(SIGINT, &sigAction, NULL);
SIGACTION(SIGTERM, &sigAction, NULL);
SIGACTION(SIGPIPE, &sigAction, NULL);
SIGACTION(SIGUSR1, &sigAction, NULL);
#undef SIGACTION
/* TODO: The rest of Parsee. */
server = HttpServerCreate(&conf);
HttpServerStart(server);
@ -60,5 +100,6 @@ Main(void)
HttpServerFree(server);
ParseeConfigFree();
ParseeFreeData(conf.handlerArgs);
return 0;
}

View file

@ -1,5 +1,6 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
@ -9,42 +10,77 @@
#define GrabString(obj, ...) JsonValueAsString(JsonGet(obj, __VA_ARGS__))
static void
ParseeMemberHandler(const ParseeConfig *conf, HashMap *event)
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");
const ParseeConfig *conf = data->config;
Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership);
if (StrEquals(membership, "invite") && ParseeIsPuppet(conf, state_key))
{
DbRef *ref;
HashMap *json;
char *jid;
Log(LOG_INFO, "Looks like %s was invited to %s",
state_key,
GrabString(event, 1, "room_id")
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);
HashMapSet(json, "is_direct", JsonValueBoolean(true));
HashMapSet(json, "xmpp_user", JsonValueString(jid));
/* Pretend everything is a dm, ignoring is_direct */
if (jid)
{
Free(jid);
}
DbUnlock(data->db, ref);
}
}
static void
ParseeMessageHandler(const ParseeConfig *conf, HashMap *event)
ParseeMessageHandler(ParseeData *data, HashMap *event)
{
XMPPComponent *jabber = data->jabber;
DbRef *ref;
HashMap *json;
char *msgtype = GrabString(event, 2, "content", "msgtype");
char *body = GrabString(event, 2, "content", "body");
char *id = GrabString(event, 1, "room_id");
if (StrEquals(body, "!help"))
char *sender = GrabString(event, 1, "sender");
ref = DbLock(data->db, 3, "rooms", id, "data");
json = DbJson(ref);
if (JsonValueAsBoolean(HashMapGet(json, "is_direct")))
{
Log(LOG_ERR, "Not implemented!");
ASSend(conf, id, NULL, "m.room.message",
MatrixCreateNotice("No help, pal.")
);
char *user = GrabString(json, 1, "xmpp_user");
char *local = ParseeEncodeMXID(sender);
Log(LOG_INFO, "Sending to %s on XMPP", user);
XMPPSendPlain(jabber, local, user, body, NULL);
Free(local);
}
DbUnlock(data->db, ref);
}
void
ParseeEventHandler(const ParseeConfig *conf, HashMap *event)
ParseeEventHandler(ParseeData *data, HashMap *event)
{
char *event_type;
if (!conf || !event)
if (!data || !event)
{
return;
}
@ -57,12 +93,12 @@ ParseeEventHandler(const ParseeConfig *conf, HashMap *event)
event_type = GrabString(event, 1, "type");
if (StrEquals(event_type, "m.room.member"))
{
ParseeMemberHandler(conf, event);
ParseeMemberHandler(data, event);
return;
}
if (StrEquals(event_type, "m.room.message"))
{
ParseeMessageHandler(conf, event);
ParseeMessageHandler(data, event);
return;
}
}

View file

@ -23,6 +23,10 @@ ParseeConfigInit(void)
config->listen_as = StrDuplicate("localhost");
config->port = 7642; /* proposed by Saint */
config->component_port = 0;
config->component_host = NULL;
config->shared_comp_secret = NULL;
stream = StreamOpen("parsee.json", "w");
json = HashMapCreate();
HashMapSet(json, "as_token", JsonValueString(config->as_token));
@ -67,6 +71,12 @@ ParseeConfigLoad(char *conf)
CopyToStr(homeserver_host, "hs_host");
CopyToInt(homeserver_port, "hs_port");
CopyToInt(component_port, "component_port");
CopyToStr(component_host, "component_host");
CopyToStr(shared_comp_secret, "shared_secret");
CopyToStr(db_path, "db");
JsonFree(json);
StreamClose(stream);
}
@ -92,10 +102,10 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, "namespaces: \n");
StreamPrintf(stream, " users:\n");
StreamPrintf(stream, " - exclusive: true\n");
StreamPrintf(stream, " regex: \"@%s_.*\n", config->namespace_base);
StreamPrintf(stream, " regex: \"@%s_.*\"\n", config->namespace_base);
StreamPrintf(stream, " aliases:\n");
StreamPrintf(stream, " - exclusive: true\n");
StreamPrintf(stream, " regex: \"#%s_.*\n", config->namespace_base);
StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base);
}
void
@ -105,6 +115,11 @@ ParseeConfigFree(void)
{
return;
}
Free(config->component_host);
Free(config->shared_comp_secret);
Free(config->db_path);
Free(config->homeserver_host);
Free(config->as_token);
Free(config->hs_token);
Free(config->sender_localpart);

View file

@ -6,7 +6,7 @@
#include <Routes.h>
ParseeData *
ParseeInitData(void)
ParseeInitData(XMPPComponent *comp)
{
ParseeData *data;
if (!ParseeConfigGet())
@ -17,6 +17,8 @@ ParseeInitData(void)
data = Malloc(sizeof(*data));
data->config = ParseeConfigGet();
data->router = HttpRouterCreate();
data->jabber = comp;
data->db = DbOpen(data->config->db_path, 0);
#define X_ROUTE(path, func) do {\
if (!HttpRouterAdd(data->router, path, func))\
@ -37,6 +39,8 @@ ParseeFreeData(ParseeData *data)
return;
}
XMPPEndCompStream(data->jabber);
DbClose(data->db);
HttpRouterFree(data->router);
Free(data);
}

View file

@ -1,5 +1,8 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h>
bool
@ -30,3 +33,125 @@ ParseeIsPuppet(const ParseeConfig *conf, char *user)
* room. */
return flag;
}
static char *
DecodeJID(char *str, char term)
{
char *out = NULL;
#define Okay(c) ((c) && ((c) != term))
while (Okay(*str))
{
char c = *str;
char buf[3] = { 0 };
char *tmp;
if (c == '=' && Okay(*(str + 1)) && Okay(*(str + 2)))
{
str++;
memcpy(buf, str, 2);
buf[0] = strtol(buf, NULL, 16);
buf[1] = '\0';
tmp = StrConcat(2, out, buf);
Free(out);
out = tmp;
str += 2;
continue;
}
memcpy(buf, str, 1);
tmp = StrConcat(2, out, buf);
Free(out);
out = tmp;
str++;
}
#undef Okay
return out;
}
char *
ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid)
{
char *localpart, *jid_flags, *data_start;
bool plain_jid = false;
if (!ParseeIsPuppet(c, mxid))
{
return NULL;
}
localpart = mxid + 1;
jid_flags = localpart + strlen(c->namespace_base) + 1;
data_start = jid_flags;
while (*data_start && *data_start != '_')
{
/* TODO: Make this a macro */
if (*data_start == 'l')
{
plain_jid = true;
}
data_start++;
}
if (!*data_start || !plain_jid)
{
return NULL;
}
data_start++;
/* Until the ':', data_start now is an encoded JID */
return DecodeJID(data_start, ':');
}
char *
ParseeGetLocal(char *mxid)
{
char *cpy;
size_t i;
if (!mxid)
{
return NULL;
}
if (*mxid != '@')
{
return StrDuplicate(mxid);
}
mxid++;
cpy = Malloc(strlen(mxid) + 1);
memset(cpy, '\0', strlen(mxid) + 1);
memcpy(cpy, mxid, strlen(mxid));
for (i = 0; i < strlen(mxid); i++)
{
if (cpy[i] == ':')
{
cpy[i] = '\0';
break;
}
}
return cpy;
}
char *
ParseeEncodeMXID(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 = '=';
}
ret[i] = src;
}
return ret;
}

View file

@ -6,8 +6,6 @@ RouteHead(RouteRoot, arr, argp)
{
ParseeHttpArg *args = argp;
ASPing(args->data->config);
HttpResponseHeader(args->ctx, "Content-Type", "text/html");
HttpSendHeaders(args->ctx);
StreamPrintf(args->stream, "<html lang=\"en\">");
@ -30,5 +28,6 @@ RouteHead(RouteRoot, arr, argp)
StreamPrintf(args->stream, " </p>");
StreamPrintf(args->stream, " </body>");
StreamPrintf(args->stream, "</html>");
return NULL;
}

View file

@ -38,7 +38,7 @@ RouteHead(RouteTxns, arr, argp)
for (i = 0; i < ArraySize(events); i++)
{
HashMap *event = JsonValueAsObject(ArrayGet(events, i));
ParseeEventHandler(args->data->config, event);
ParseeEventHandler(args->data, event);
}
/* TODO: Store TXN ID somewhere so that we can commit

41
src/Routes/UserAck.c Normal file
View file

@ -0,0 +1,41 @@
#include <Routes.h>
#include <Cytoplasm/Log.h>
#include <Matrix.h>
#include <AS.h>
RouteHead(RouteUserAck, arr, argp)
{
ParseeHttpArg *args = argp;
HashMap *request = NULL;
HashMap *response = NULL;
Array *events;
size_t i;
char *user = ArrayGet(arr, 0);
Log(LOG_INFO, "Alles Politischemacht");
response = ASVerifyRequest(args);
if (response)
{
goto end;
}
if (HttpRequestMethodGet(args->ctx) != HTTP_GET)
{
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError(
"M_UNRECOGNIZED",
"Path /users only accepts GET as a valid method."
);
goto end;
}
Log(LOG_INFO, "user=%s", user);
ASRegisterUser(args->data->config, user);
/* TODO: Verify the user, and create an XMPP mapping. */
response = HashMapCreate();
end:
JsonFree(request);
return response;
}

View file

@ -22,7 +22,7 @@
/* The default component port Prosody uses. */
#define DEFAULT_PROSODY_PORT 5347
Stream *
XMPPComponent *
XMPPInitialiseCompStream(char *host, int port)
{
/* TODO */
@ -31,6 +31,7 @@ XMPPInitialiseCompStream(char *host, int port)
int error;
char serv[8];
Stream *stream;
XMPPComponent *comp;
snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT);
@ -74,7 +75,11 @@ XMPPInitialiseCompStream(char *host, int port)
return NULL;
}
return stream;
comp = Malloc(sizeof(*comp));
comp->host = StrDuplicate(host);
comp->stream = stream;
return comp;
}
#include <Cytoplasm/Sha.h>
@ -95,17 +100,22 @@ ComputeHandshake(char *shared, char *stream)
return sha;
}
bool
XMPPAuthenticateCompStream(Stream *stream, char *as, char *shared)
XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
{
/* TODO */
XMLexer *sax;
XMLEvent *ev;
char *stream_id, *handshake;
bool ret = false;
if (!stream || !as || !shared)
Stream *stream;
char *as;
if (!comp || !shared)
{
return false;
}
stream = comp->stream;
as = comp->host;
StreamPrintf(stream,
"<stream:stream "
"xmlns='jabber:component:accept' "
@ -171,7 +181,13 @@ end:
}
void
XMPPEndCompStream(Stream *stream)
XMPPEndCompStream(XMPPComponent *comp)
{
StreamClose(stream);
if (!comp)
{
return;
}
StreamClose(comp->stream);
Free(comp->host);
Free(comp);
}

43
src/XMPP/Stanza.c Normal file
View file

@ -0,0 +1,43 @@
#include <XMPP.h>
void
XMPPSendPlain(XMPPComponent *comp, char *fr, char *to, char *msg, char *type)
{
if (!comp || !fr || !to || !msg)
{
return;
}
StreamPrintf(
comp->stream, "<message from='%s@%s' to='%s'",
fr, comp->host, to
);
if (type)
{
StreamPrintf(comp->stream, " type='%s'", type);
}
StreamPrintf(comp->stream, ">");
StreamPrintf(comp->stream,
"<body>"
"%s"
"</body>"
"</message>",
msg
);
StreamFlush(comp->stream);
}
void
XMPPSendPresence(XMPPComponent *comp, char *fr, char *to)
{
if (!comp || !fr || !to)
{
return;
}
StreamPrintf(comp->stream,
"<presence from='%s@%s' to='%s'>"
"<x xmlns='http://jabber.org/protocol/muc'/>"
"</presence>",
fr, comp->host,
to
);
StreamFlush(comp->stream);
}

View file

@ -20,7 +20,7 @@ extern void ASAuthenticateRequest(ParseeConfig *, HttpClientContext *);
extern bool ASRegisterUser(ParseeConfig *, char *);
/* Pings the homeserver to get attention. */
extern void ASPing(ParseeConfig *);
extern void ASPing(const ParseeConfig *);
/* Joins a room from an ID and a given user we want to masquerade
* as. */

View file

@ -5,8 +5,12 @@
#include <Cytoplasm/HttpRouter.h>
#include <Cytoplasm/HttpClient.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Db.h>
#include <XMPP.h>
typedef struct ParseeConfig {
/* -------- MATRIX -------- */
char *as_token, *hs_token;
/* id here is "Parsee XMPP". */
char *sender_localpart;
@ -18,11 +22,24 @@ typedef struct ParseeConfig {
/* Homeserver port info */
char *homeserver_host;
int homeserver_port;
/* ------- JABBER -------- */
char *component_host;
char *shared_comp_secret;
int component_port;
/* ------- DB -------- */
char *db_path;
} ParseeConfig;
typedef struct ParseeData {
const ParseeConfig *config;
HttpRouter *router;
XMPPComponent *jabber;
Db *db;
} ParseeData;
/* Initialises a Parsee config from scratch, and writes to it
@ -42,7 +59,7 @@ extern void ParseeExportConfigYAML(Stream *);
extern void ParseeConfigFree(void);
/* Creates and destroys a data structure, stored on the heap. */
extern ParseeData * ParseeInitData(void);
extern ParseeData * ParseeInitData(XMPPComponent *);
extern void ParseeFreeData(ParseeData *);
/* HTTP server handler for Parsee, takes in a config. */
@ -54,8 +71,17 @@ extern HttpClientContext * ParseeCreateRequest(ParseeConfig *, HttpRequestMethod
extern HttpStatus ParseeSetRequestJSON(HttpClientContext *, HashMap *);
/* This function is called on every event received, and routes/manages it. */
extern void ParseeEventHandler(const ParseeConfig *, HashMap *);
extern void ParseeEventHandler(ParseeData *, HashMap *);
/* Verifies if a user is a Parsee puppet user. */
extern bool ParseeIsPuppet(const ParseeConfig *, char *);
/* Decodes a local JID for a user into a string. */
extern char * ParseeDecodeLocalJID(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 *);
#endif

View file

@ -11,7 +11,8 @@ typedef struct ParseeHttpArg {
/* A list of all routes. */
#define ROUTES X_ROUTE("/", RouteRoot) \
X_ROUTE("/_matrix/app/v1/transactions/(.*)", RouteTxns) \
X_ROUTE("/_matrix/app/v1/ping", RoutePing)
X_ROUTE("/_matrix/app/v1/ping", RoutePing) \
X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck)
#define X_ROUTE(path, name) extern void * name(Array *, void *);
ROUTES

View file

@ -3,17 +3,26 @@
#include <Cytoplasm/Stream.h>
typedef struct XMPPComponent {
char *host;
Stream *stream;
} XMPPComponent;
/* Initialises a raw component stream to host, with an optional port.
* If said port is 0, then it is set to the default Prosody port */
extern Stream * XMPPInitialiseCompStream(char *host, int port);
extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port);
/* Authenticates a component stream with a given shared secret,
* with a stream ID from the server. This should be called right
* after XMPPInitialiseCompStream. */
extern bool XMPPAuthenticateCompStream(Stream *stream, char *as, char *shared);
extern bool XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared);
/* Sends a presence to a user */
extern void XMPPSendPresence(XMPPComponent *comp, char *fr, char *to);
/* TODO: XMPP stuff, I don't fucking know, I'm not a Jabbernerd. */
extern void XMPPSendPlain(XMPPComponent *c, char *f, char *t, char *m, char *type);
/* Closes a raw component stream. */
extern void XMPPEndCompStream(Stream *stream);
extern void XMPPEndCompStream(XMPPComponent *stream);
#endif