[ADD/WIP] Continue MUCwerk

oh man this is gonna be painful to do... xmpp is fine iff youre doing
DMs isnt it?
This commit is contained in:
LDA 2024-06-21 00:48:27 +02:00
commit d3b7f2fee0
19 changed files with 707 additions and 44 deletions

View file

@ -24,6 +24,9 @@ $ make # This generates a 'parsee' executable.
$ $
``` ```
### DEPENDENCIES
Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm).
## RUNNING ## RUNNING
TODO TODO

View file

@ -144,6 +144,30 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
JsonFree(json); JsonFree(json);
} }
void void
ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state)
{
HttpClientContext *ctx = NULL;
char *path, *params;
if (!conf || !id || !type || !mask || !state)
{
JsonFree(state);
return;
}
path = StrConcat(9,
"/_matrix/client/v3/rooms/", id, "/state/",
type, "/", key, "?", "user_id=", mask
);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, state);
HttpClientContextFree(ctx);
JsonFree(state);
}
void
ASSend(const 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; HttpClientContext *ctx = NULL;
@ -151,6 +175,7 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
char *txn; char *txn;
if (!conf || !id || !type || !user || !c) if (!conf || !id || !type || !user || !c)
{ {
JsonFree(c);
return; return;
} }
@ -170,3 +195,59 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
HttpClientContextFree(ctx); HttpClientContextFree(ctx);
JsonFree(c); JsonFree(c);
} }
char *
ASCreateRoom(const ParseeConfig *conf, char *by, char *alias)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *id;
if (!conf || !by)
{
return NULL;
}
path = StrConcat(3,
"/_matrix/client/v3/createRoom",
"?user_id=", by
);
ctx = ParseeCreateRequest(
conf,
HTTP_POST, path
);
Free(path);
json = HashMapCreate();
if (alias)
{
char *trimmed = StrDuplicate(alias);
if (*alias == '#')
{
char *tmp, cb[2] = { 0 };
alias++;
Free(trimmed);
trimmed = NULL;
while (*alias && *alias != ':')
{
cb[0] = *alias;
tmp = trimmed;
trimmed = StrConcat(2, trimmed, cb);
Free(tmp);
alias ++;
}
}
HashMapSet(json, "room_alias_name", JsonValueString(trimmed));
Free(trimmed);
}
HashMapSet(json, "visibility", JsonValueString("public"));
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id")));
HttpClientContextFree(ctx);
JsonFree(json);
return id;
}

View file

@ -31,3 +31,17 @@ MatrixCreateMessage(char *body)
return map; return map;
} }
HashMap *
MatrixCreateNameState(char *name)
{
HashMap *map;
if (!name)
{
return NULL;
}
map = HashMapCreate();
HashMapSet(map, "name", JsonValueString(name));
return map;
}

View file

@ -1,5 +1,7 @@
#include <Cytoplasm/HttpServer.h> #include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
@ -34,7 +36,9 @@ SignalHandler(int signal)
return; return;
} }
/* Create a loopback stanza, forcing the thread to die */ /* Create a loopback stanza, forcing the thread to die */
XMPPKillThread(jabber); Log(LOG_INFO, "Killing thread...");
XMPPKillThread(jabber, "killer");
pthread_join(xmpp_thr, NULL);
HttpServerStop(server); HttpServerStop(server);
break; break;
} }
@ -49,7 +53,12 @@ Main(void)
Stream *yaml; Stream *yaml;
struct sigaction sigAction; struct sigaction sigAction;
Log(LOG_INFO, "%s - v%s", NAME, VERSION); Log(LOG_INFO,
"%s - v%s (Cytoplasm %s)",
NAME, VERSION, CytoplasmGetVersionStr()
);
LogConfigIndent(LogConfigGlobal());
ParseeConfigLoad("parsee.json"); ParseeConfigLoad("parsee.json");
ParseeConfigInit(); ParseeConfigInit();
@ -90,7 +99,7 @@ Main(void)
if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs)) if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs))
{ {
Log(LOG_ERR, "Couldn't start XMPP listener thread."); Log(LOG_ERR, "Couldn't start XMPP listener thread.");
/* TODO: Die */ goto end;
} }
sigAction.sa_handler = SignalHandler; sigAction.sa_handler = SignalHandler;
@ -101,6 +110,7 @@ Main(void)
if (sigaction(sig, act, oact) < 0) \ if (sigaction(sig, act, oact) < 0) \
{ \ { \
Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \ Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \
goto end; \
} \ } \
else \ else \
{ \ { \
@ -113,12 +123,15 @@ Main(void)
SIGACTION(SIGUSR1, &sigAction, NULL); SIGACTION(SIGUSR1, &sigAction, NULL);
#undef SIGACTION #undef SIGACTION
Log(LOG_INFO, "%d", XMPPQueryMUC(jabber, "rootard@muc.ari.lt", NULL));
server = HttpServerCreate(&conf); server = HttpServerCreate(&conf);
HttpServerStart(server); HttpServerStart(server);
HttpServerJoin(server); HttpServerJoin(server);
end: end:
LogConfigUnindent(LogConfigGlobal());
Log(LOG_INFO, "Exiting...");
HttpServerFree(server); HttpServerFree(server);
ParseeConfigFree(); ParseeConfigFree();
ParseeFreeData(conf.handlerArgs); ParseeFreeData(conf.handlerArgs);

View file

@ -16,6 +16,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
char *membership = GrabString(event, 2, "content", "membership"); char *membership = GrabString(event, 2, "content", "membership");
char *room_id = GrabString(event, 1, "room_id"); char *room_id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
char *chat_id;
const ParseeConfig *conf = data->config; const ParseeConfig *conf = data->config;
Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership); Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership);
@ -41,6 +42,24 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
Free(jid); Free(jid);
} }
} }
else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key))
{
char *jid = ParseeEncodeMXID(state_key);
chat_id = ParseeGetFromRoomID(data, room_id);
Log(LOG_INFO, "JID=%s", jid);
if (chat_id)
{
char *muc = ParseeGetMUCID(data, chat_id);
char *rev = StrConcat(2, muc, "/parsee");
Log(LOG_INFO, "chat_id=%s muc=%s", chat_id, muc);
/* Make the user join the MUC */
XMPPJoinMUC(data->jabber, jid, rev);
Free(rev);
Free(muc);
}
Free(jid);
Free(chat_id);
}
} }
static void static void
ParseeMessageHandler(ParseeData *data, HashMap *event) ParseeMessageHandler(ParseeData *data, HashMap *event)
@ -53,6 +72,9 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *body = GrabString(event, 2, "content", "body"); char *body = GrabString(event, 2, "content", "body");
char *id = GrabString(event, 1, "room_id"); char *id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
char *chat_id;
bool direct = false;
if (ParseeIsPuppet(data->config, sender)) if (ParseeIsPuppet(data->config, sender))
{ {
@ -62,9 +84,10 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
ref = DbLock(data->db, 3, "rooms", id, "data"); ref = DbLock(data->db, 3, "rooms", id, "data");
json = DbJson(ref); json = DbJson(ref);
direct = JsonValueAsBoolean(HashMapGet(json, "is_direct"));
DbUnlock(data->db, ref);
if (direct)
if (JsonValueAsBoolean(HashMapGet(json, "is_direct")))
{ {
char *user = GrabString(json, 1, "xmpp_user"); char *user = GrabString(json, 1, "xmpp_user");
char *local = ParseeEncodeMXID(sender); char *local = ParseeEncodeMXID(sender);
@ -73,9 +96,17 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
XMPPSendPlain(jabber, local, user, body, NULL); XMPPSendPlain(jabber, local, user, body, NULL);
Free(local); Free(local);
return;
} }
DbUnlock(data->db, ref); /* Try to find the chat ID */
chat_id = ParseeGetFromRoomID(data, id);
if (!chat_id)
{
return;
}
Log(LOG_INFO, "Chat ID=%s", chat_id);
Free(chat_id);
} }
void void
@ -88,6 +119,7 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
} }
event_type = GrabString(event, 1, "type"); event_type = GrabString(event, 1, "type");
Log(LOG_INFO, "E->%s", GrabString(event, 1, "sender"));
if (StrEquals(event_type, "m.room.member")) if (StrEquals(event_type, "m.room.member"))
{ {
ParseeMemberHandler(data, event); ParseeMemberHandler(data, event);

View file

@ -38,10 +38,14 @@ ParseeIsPuppet(const ParseeConfig *conf, char *user)
return flag; return flag;
} }
static char * char *
DecodeJID(char *str, char term) ParseeDecodeJID(char *str, char term)
{ {
char *out = NULL; char *out = NULL;
if (!str)
{
return NULL;
}
#define Okay(c) ((c) && ((c) != term)) #define Okay(c) ((c) && ((c) != term))
while (Okay(*str)) while (Okay(*str))
{ {
@ -101,7 +105,37 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid)
} }
data_start++; data_start++;
/* Until the ':', data_start now is an encoded JID */ /* Until the ':', data_start now is an encoded JID */
return DecodeJID(data_start, ':'); return ParseeDecodeJID(data_start, ':');
}
char *
ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias)
{
char *localpart, *jid_flags, *data_start;
bool plain_jid = false;
if (!c || !alias || *alias != '#')
{
return NULL;
}
localpart = alias + 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 == 'm')
{
plain_jid = true;
}
data_start++;
}
if (!*data_start || !plain_jid)
{
return NULL;
}
data_start++;
/* Until the ':', data_start now is an encoded JID */
return ParseeDecodeJID(data_start, ':');
} }
char * char *
@ -325,3 +359,148 @@ ParseeTrimJID(char *jid)
return ret; return ret;
} }
char *
ParseePushMUC(ParseeData *data, char *room_id, char *jid)
{
DbRef *ref;
HashMap *j;
char *chatid = NULL;
if (!data || !room_id || !jid)
{
return NULL;
}
chatid = ParseeGetDMID(room_id, jid);
ref = DbCreate(data->db, 2, "chats", chatid);
j = DbJson(ref);
HashMapSet(j, "room_id", JsonValueString(room_id));
HashMapSet(j, "jabber_id", JsonValueString(jid));
DbUnlock(data->db, ref);
/* Create a double-mapping */
ref = DbLock(data->db, 1, "chats");
if (!ref)
{
ref = DbCreate(data->db, 1, "chats");
if (!ref)
{
Free(chatid);
return NULL;
}
}
j = DbJson(ref);
{
HashMap *table;
table = JsonValueAsObject(HashMapGet(j, "mucs"));
if (!table)
{
table = HashMapCreate();
}
HashMapSet(table, jid, JsonValueString(chatid));
HashMapSet(j, "mucs", JsonValueObject(table));
table = JsonValueAsObject(HashMapGet(j, "rooms"));
if (!table)
{
table = HashMapCreate();
}
HashMapSet(table, room_id, JsonValueString(chatid));
HashMapSet(j, "rooms", JsonValueObject(table));
DbJsonSet(ref, (j = JsonDuplicate(j)));
JsonFree(j);
}
DbUnlock(data->db, ref);
return chatid;
}
char *
ParseeGetFromMUCID(ParseeData *data, char *jid)
{
DbRef *ref;
HashMap *j;
char *cid;
if (!data || !jid)
{
return NULL;
}
jid = ParseeTrimJID(jid);
ref = DbLock(data->db, 1, "chats");
j = DbJson(ref);
cid = JsonValueAsString(JsonGet(j, 2, "mucs", jid));
cid = StrDuplicate(cid);
DbUnlock(data->db, ref);
Free(jid);
return cid;
}
char *
ParseeGetFromRoomID(ParseeData *data, char *room_id)
{
DbRef *ref;
HashMap *j;
char *cid;
if (!data || !room_id)
{
return NULL;
}
ref = DbLock(data->db, 1, "chats");
j = DbJson(ref);
cid = JsonValueAsString(JsonGet(j, 2, "rooms", room_id));
cid = StrDuplicate(cid);
DbUnlock(data->db, ref);
return cid;
}
char *
ParseeGetRoomID(ParseeData *data, char *chat_id)
{
DbRef *ref;
HashMap *j;
char *ret;
if (!data || !chat_id)
{
return NULL;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
ret = StrDuplicate(JsonValueAsString(HashMapGet(j, "room_id")));
DbUnlock(data->db, ref);
return ret;
}
char *
ParseeGetMUCID(ParseeData *data, char *chat_id)
{
DbRef *ref;
HashMap *j;
char *ret;
if (!data || !chat_id)
{
return NULL;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
ret = StrDuplicate(JsonValueAsString(HashMapGet(j, "jabber_id")));
DbUnlock(data->db, ref);
return ret;
}

View file

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

View file

@ -1,8 +1,11 @@
#include <Routes.h> #include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <Matrix.h> #include <Matrix.h>
#include <XMPP.h>
#include <AS.h> #include <AS.h>
RouteHead(RouteUserAck, arr, argp) RouteHead(RouteUserAck, arr, argp)
@ -43,8 +46,13 @@ RouteHead(RouteRoomAck, arr, argp)
HashMap *response = NULL; HashMap *response = NULL;
Array *events; Array *events;
size_t i; size_t i;
MUCInfo info = { .exists = false };
char *room = ArrayGet(arr, 0), *muc = NULL, *id = NULL;
char *creator = NULL, *muc_name = NULL, *chatid = NULL;
Log(LOG_INFO, "Ack");
char *room = ArrayGet(arr, 0);
response = ASVerifyRequest(args); response = ASVerifyRequest(args);
if (response) if (response)
{ {
@ -60,14 +68,70 @@ RouteHead(RouteRoomAck, arr, argp)
goto end; goto end;
} }
Log(LOG_INFO, "room=%s", room); muc = ParseeDecodeLocalMUC(args->data->config, room);
Log(LOG_INFO, "room=%s", muc);
if (!XMPPQueryMUC(args->data->jabber, muc, &info))
{
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED); HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError( response = MatrixCreateError(
"M_UNRECOGNIZED", "M_UNRECOGNIZED",
"Path /users only accepts GET as a valid method." "Room does not map to a real XMPP MUC"
); );
Log(LOG_INFO, "No MUC");
goto end;
}
creator = StrConcat(
4,
"@", args->data->config->sender_localpart, ":",
args->data->config->homeserver_host
);
id = ASCreateRoom(
args->data->config,
creator, room
);
if (!id)
{
Log(LOG_INFO, "No ID");
HttpResponseStatus(args->ctx, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixCreateError(
"M_UNKNOWN",
"Could not create the room."
);
goto end;
}
muc_name = XMPPGetMUCName(info);
if (muc_name)
{
ASSetState(
args->data->config, id, "m.room.name", "", creator,
MatrixCreateNameState(muc_name)
);
Free(muc_name);
}
/* Creates a mapping */
chatid = ParseePushMUC(args->data, id, muc);
Log(LOG_INFO, "Chat ID=%s", chatid);
response = HashMapCreate();
end: end:
if (chatid)
{
Free(chatid);
}
if (muc)
{
Free(muc);
}
if (id)
{
Free(id);
}
if (creator)
{
Free(creator);
}
XMPPFreeMUCInfo(info);
JsonFree(request); JsonFree(request);
return response; return response;
} }

View file

@ -110,3 +110,24 @@ XMLFreeElement(XMLElement *element)
Free(element); Free(element);
} }
XMLElement *
XMLookForUnique(XMLElement *parent, char *tag)
{
size_t i;
if (!parent || !tag)
{
return NULL;
}
for (i = 0; i < ArraySize(parent->children); i++)
{
XMLElement *child = ArrayGet(parent->children, i);
if (StrEquals(child->name, tag))
{
return child;
}
}
return NULL;
}

View file

@ -4,6 +4,7 @@
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -122,6 +123,7 @@ XMLCrank(XMLexer *lexer)
{ {
XMLEvent *event = NULL; XMLEvent *event = NULL;
char c; char c;
int ch;
char *attrname; char *attrname;
HashMap *props; HashMap *props;
char *key, *val; char *key, *val;
@ -136,6 +138,7 @@ XMLCrank(XMLexer *lexer)
return NULL; return NULL;
} }
event = XMLCreateRelax(lexer); event = XMLCreateRelax(lexer);
switch (lexer->state) switch (lexer->state)
{ {
case XML_STATE_NONE: case XML_STATE_NONE:
@ -235,6 +238,10 @@ XMLCrank(XMLexer *lexer)
event = XMLCreateStart(lexer, props); event = XMLCreateStart(lexer, props);
break; break;
} }
else if (XMLookahead(lexer, "'", true))
{
while (true);
}
break; break;
case XML_STATE_ATTRTAIL: case XML_STATE_ATTRTAIL:
attrname = XMLParseName(lexer); attrname = XMLParseName(lexer);
@ -490,7 +497,7 @@ HashMap *
XMLReadProps(XMLexer *lexer) XMLReadProps(XMLexer *lexer)
{ {
ssize_t point = XMLInitialiseBuffer(lexer); ssize_t point = XMLInitialiseBuffer(lexer);
HashMap *map = HashMapCreate(); HashMap *map = NULL;
bool error = false; bool error = false;
while (true) while (true)
{ {
@ -523,10 +530,14 @@ XMLReadProps(XMLexer *lexer)
break; break;
} }
} }
if (!map)
{
map = HashMapCreate();
}
HashMapSet(map, name, value); HashMapSet(map, name, value);
Free(name); Free(name);
} }
if (error) if (error || !map)
{ {
XMLReset(lexer, point); XMLReset(lexer, point);
} }
@ -660,7 +671,20 @@ XMLParseAttQuote(XMLexer *lexer)
while ((c = XMLGetc(lexer))) while ((c = XMLGetc(lexer)))
{ {
if (!IsNormalQ(c)) if (c == '&')
{
char *code = NULL;
int c2;
int p2 = XMLInitialiseBuffer(lexer);
while ((c2 = XMLGetc(lexer)))
{
if (c2 == ';')
{
break;
}
}
}
else if (!IsNormalQ(c))
{ {
break; break;
} }
@ -687,7 +711,21 @@ XMLParseAttDouble(XMLexer *lexer)
while ((c = XMLGetc(lexer))) while ((c = XMLGetc(lexer)))
{ {
if (!IsNormalD(c)) if (c == '&')
{
char *code = NULL;
int c2;
int p2 = XMLInitialiseBuffer(lexer);
while ((c2 = XMLGetc(lexer)))
{
if (c2 == ';')
{
break;
}
}
continue;
}
else if (!IsNormalD(c))
{ {
break; break;
} }

View file

@ -56,7 +56,6 @@ XMPPInitialiseCompStream(char *host, int port)
sd = -1; sd = -1;
continue; continue;
} }
Log(LOG_INFO, "Connected to port %s", serv);
break; break;
} }
@ -68,7 +67,6 @@ XMPPInitialiseCompStream(char *host, int port)
freeaddrinfo(res0); freeaddrinfo(res0);
stream = StreamFd(sd); stream = StreamFd(sd);
Log(LOG_INFO, "Got %d via %p", sd, stream);
if (!stream) if (!stream)
{ {
close(sd); close(sd);
@ -143,7 +141,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
stream_id = StrDuplicate(HashMapGet(ev->attrs, "id")); stream_id = StrDuplicate(HashMapGet(ev->attrs, "id"));
handshake = ComputeHandshake(shared, stream_id); handshake = ComputeHandshake(shared, stream_id);
/* y no stream id */
Log(LOG_NOTICE, "- sID='%s'", stream_id); Log(LOG_NOTICE, "- sID='%s'", stream_id);
StreamPrintf(stream, "<handshake>%s</handshake>", handshake); StreamPrintf(stream, "<handshake>%s</handshake>", handshake);
StreamFlush(stream); StreamFlush(stream);

109
src/XMPP/MUC.c Normal file
View file

@ -0,0 +1,109 @@
#include <XMPP.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Parsee.h>
#include <XML.h>
bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
XMLElement *iq_query, *query;
char *uuid, *from;
if (!jabber || !muc)
{
return false;
}
iq_query = XMLCreateTag("iq");
query = XMLCreateTag("query");
XMLAddAttr(iq_query, "from",(from = StrConcat(2,"parsee@",jabber->host)));
XMLAddAttr(iq_query, "to", muc);
XMLAddAttr(iq_query, "id", (uuid = StrRandom(8)));
XMLAddAttr(iq_query, "type", "get");
XMLAddAttr(query, "xmlns", "http://jabber.org/protocol/disco#info");
XMLAddChild(iq_query, query);
Free(from);
Free(uuid);
/* Pause the XMPP thread */
XMPPKillThread(jabber, "suspend");
UtilSleepMillis(500);
{
XMLElement *identity;
/* -- WE ARE ON OUR OWN HERE WITH STANZAS -- */
XMLEncode(jabber->stream, iq_query);
StreamFlush(jabber->stream);
XMLFreeElement(iq_query);
/* Except an IQ reply */
iq_query = XMLDecode(jabber->stream, false);
if (!iq_query || !StrEquals(iq_query->name, "iq"))
{
XMLFreeElement(iq_query);
ParseeWakeupThread();
return false;
}
query = XMLookForUnique(iq_query, "query");
identity = XMLookForUnique(query, "identity");
if (!identity ||
!StrEquals(HashMapGet(identity->attrs, "category"),
"conference"))
{
XMLFreeElement(iq_query);
ParseeWakeupThread();
return false;
}
/* We found a MUC! */
if (out)
{
out->exists = true;
out->jabber = jabber;
out->iq_reply = iq_query;
}
else
{
XMLFreeElement(iq_query);
}
}
/* Wake it up once we're done */
ParseeWakeupThread();
return true;
}
void
XMPPFreeMUCInfo(MUCInfo info)
{
if (!info.exists)
{
return;
}
XMLFreeElement(info.iq_reply);
info.exists = false;
}
char *
XMPPGetMUCName(MUCInfo info)
{
XMLElement *query, *identity;
char *name;
if (!info.exists)
{
return NULL;
}
query = XMLookForUnique(info.iq_reply, "query");
identity = XMLookForUnique(query, "identity");
name = StrDuplicate(HashMapGet(identity->attrs, "name"));
return name;
}

View file

@ -2,6 +2,7 @@
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XML.h> #include <XML.h>
@ -32,11 +33,11 @@ XMPPSendPlain(XMPPComponent *comp, char *fr, char *to, char *msg, char *type)
Free(from); Free(from);
} }
void void
XMPPSendPresence(XMPPComponent *comp, char *fr, char *to) XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc)
{ {
XMLElement *presence, *x; XMLElement *presence, *x;
char *from; char *from;
if (!comp || !fr || !to) if (!comp || !fr || !muc)
{ {
return; return;
} }
@ -44,28 +45,33 @@ XMPPSendPresence(XMPPComponent *comp, char *fr, char *to)
presence = XMLCreateTag("presence"); presence = XMLCreateTag("presence");
x = XMLCreateTag("x"); x = XMLCreateTag("x");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host))); XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", to); XMLAddAttr(presence, "to", muc);
XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc"); XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc");
XMLAddChild(presence, x); XMLAddChild(presence, x);
/* A*/
XMLEncode(comp->stream, presence); XMLEncode(comp->stream, presence);
Log(LOG_INFO, "Flushing...");
/* B */
StreamFlush(comp->stream); StreamFlush(comp->stream);
XMLFreeElement(presence); XMLFreeElement(presence);
Free(from); Free(from);
/* How is that leaking */
} }
void void
XMPPKillThread(XMPPComponent *jabber) XMPPKillThread(XMPPComponent *jabber, char *killer)
{ {
XMLElement *message, *body, *data; XMLElement *message, *body, *data;
char *from; char *from;
if (!jabber) if (!jabber || !killer)
{ {
return; return;
} }
from = StrConcat(2, "jabber_die@", jabber->host); from = StrConcat(3, killer, "@", jabber->host);
message = XMLCreateTag("message"); message = XMLCreateTag("message");
XMLAddAttr(message, "from", from); XMLAddAttr(message, "from", from);
XMLAddAttr(message, "to", from); XMLAddAttr(message, "to", from);

View file

@ -1,5 +1,8 @@
#include <Parsee.h> #include <Parsee.h>
#include <pthread.h>
#include <string.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
@ -9,6 +12,28 @@
#include <XML.h> #include <XML.h>
#include <AS.h> #include <AS.h>
static pthread_mutex_t cond_var_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
void
ParseeWakeupThread(void)
{
pthread_mutex_lock(&cond_var_lock);
pthread_cond_signal(&cond_var);
pthread_mutex_unlock(&cond_var_lock);
}
static void
ParseeDMHandler(char *room, char *from, XMLElement *data, const ParseeConfig *c)
{
Log(LOG_INFO, "Trying to send %s %s", room, from);
ASSend(
c, room, from,
"m.room.message", MatrixCreateMessage(data->data)
);
}
void * void *
ParseeXMPPThread(void *argp) ParseeXMPPThread(void *argp)
{ {
@ -21,27 +46,42 @@ ParseeXMPPThread(void *argp)
XMLElement *body = NULL; XMLElement *body = NULL;
XMLElement *data = NULL; XMLElement *data = NULL;
char *to, *room, *from, *from_matrix; char *to, *room, *from, *from_matrix;
char *chat_id, *mroom_id;
/* 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); stanza = XMLDecode(jabber->stream, false);
if (!stanza) if (!stanza)
{ {
continue; continue;
} }
if (StrEquals(stanza->name, "presence"))
{
XMLFreeElement(stanza);
continue;
}
if (StrEquals(stanza->name, "message")) if (StrEquals(stanza->name, "message"))
{ {
size_t i; size_t i;
if (XMPPIsKiller(stanza)) if (XMPPIsKiller(stanza))
{ {
/* There's not a lot of scenarios where we loopback */ const char *killer = "killer";
const char *suspend="suspend";
from = HashMapGet(stanza->attrs, "from");
if (!strncmp(from, killer, strlen(killer)))
{
Log(LOG_INFO, "Dropping thread..."); Log(LOG_INFO, "Dropping thread...");
XMLFreeElement(stanza); XMLFreeElement(stanza);
break; break;
} }
else if (!strncmp(from, suspend, strlen(suspend)))
{
XMLFreeElement(stanza);
pthread_mutex_lock(&cond_var_lock);
pthread_cond_wait(&cond_var, &cond_var_lock);
pthread_mutex_unlock(&cond_var_lock);
continue;
}
}
for (i = 0; i < ArraySize(stanza->children); i++) for (i = 0; i < ArraySize(stanza->children); i++)
{ {
XMLElement *child = ArrayGet(stanza->children, i); XMLElement *child = ArrayGet(stanza->children, i);
@ -56,6 +96,8 @@ ParseeXMPPThread(void *argp)
XMLFreeElement(stanza); XMLFreeElement(stanza);
continue; continue;
} }
/* TODO: Check the type */
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
from_matrix = ParseeEncodeJID(args->config, from); from_matrix = ParseeEncodeJID(args->config, from);
@ -63,10 +105,20 @@ ParseeXMPPThread(void *argp)
data = ArrayGet(body->children, 0); data = ArrayGet(body->children, 0);
/* TODO: Consider having rich messages */ /* TODO: Consider having rich messages */
ASSend( chat_id = ParseeGetFromMUCID(args, from);
args->config, room, from_matrix, mroom_id = ParseeGetRoomID(args, chat_id);
"m.room.message", MatrixCreateMessage(data->data) if (room)
); {
ParseeDMHandler(room, from_matrix, data, args->config);
}
if (mroom_id)
{
Log(LOG_INFO, "Send to %s", mroom_id);
/* TODO: Grab username, create a puppet, and go from
* there */
}
Free(chat_id);
Free(mroom_id);
Free(from_matrix); Free(from_matrix);
Free(room); Free(room);
Free(to); Free(to);

View file

@ -30,4 +30,10 @@ extern void ASJoin(const ParseeConfig *, char *, char *);
* Said body is freed during the function's execution. */ * Said body is freed during the function's execution. */
extern void ASSend(const ParseeConfig *, char *, char *, char *, HashMap *); extern void ASSend(const ParseeConfig *, char *, char *, char *, HashMap *);
/* Sets a state event with a specific type and body */
extern void ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *event);
/* Creates a room, with a masquerade user as its creator. This function
* returns it's ID if it exists. */
extern char * ASCreateRoom(const ParseeConfig *c, char *by, char *alias);
#endif #endif

View file

@ -11,4 +11,7 @@ extern HashMap * MatrixCreateNotice(char *body);
/* Creates the content for a normal message. */ /* Creates the content for a normal message. */
extern HashMap * MatrixCreateMessage(char *body); extern HashMap * MatrixCreateMessage(char *body);
/* Creates the content for a m.room.name state event */
extern HashMap * MatrixCreateNameState(char *name);
#endif #endif

View file

@ -95,6 +95,8 @@ extern void ParseeRequest(HttpServerContext *, void *);
/* A pthread callback used for listening to a component */ /* A pthread callback used for listening to a component */
extern void * ParseeXMPPThread(void *data); extern void * ParseeXMPPThread(void *data);
/* Wakes up the XMPP thread from a "suspend" killer stanza */
extern void ParseeWakeupThread(void);
/* Finds the room a DM is associated to, from a Matrix user and a Jabber /* Finds the room a DM is associated to, from a Matrix user and a Jabber
* ID. */ * ID. */
@ -108,4 +110,26 @@ extern void ParseePushDMRoom(ParseeData *, char *mxid, char *jid, char *r);
/* Trims the component from a JID */ /* Trims the component from a JID */
extern char * ParseeTrimJID(char *jid); extern char * ParseeTrimJID(char *jid);
/* Decodes a room alias into a MUC JID */
extern char * ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias);
/* Decodes an encoded string(from an MXID/Room ID) into a JID */
extern char * ParseeDecodeJID(char *str, char term);
/* Creates a Room/MUC mapping, and returns it's chat ID. */
extern char * ParseePushMUC(ParseeData *, char *room_id, char *jid);
/* Finds a chat ID from a MUC JID */
extern char * ParseeGetFromMUCID(ParseeData *, char *jid);
/* Finds a chat ID from a room ID */
extern char * ParseeGetFromRoomID(ParseeData *, char *room_id);
/* Finds the room ID from a chat ID */
extern char * ParseeGetRoomID(ParseeData *, char *chat_id);
/* Finds the MUC JID from a chat ID */
extern char * ParseeGetMUCID(ParseeData *, char *chat_id);
#endif #endif

View file

@ -63,6 +63,7 @@ extern XMLElement * XMLCreateText(char *data);
extern void XMLAddAttr(XMLElement *element, char *key, char *val); extern void XMLAddAttr(XMLElement *element, char *key, char *val);
extern void XMLAddChild(XMLElement *element, XMLElement *child); extern void XMLAddChild(XMLElement *element, XMLElement *child);
extern XMLElement * XMLookForUnique(XMLElement *parent, char *tag);
/* Frees an XML element properly. */ /* Frees an XML element properly. */
extern void XMLFreeElement(XMLElement *element); extern void XMLFreeElement(XMLElement *element);

View file

@ -19,8 +19,8 @@ extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port);
* after XMPPInitialiseCompStream. */ * after XMPPInitialiseCompStream. */
extern bool XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared); extern bool XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared);
/* Sends a presence to a user */ /* Makes a user join a MUC */
extern void XMPPSendPresence(XMPPComponent *comp, char *fr, char *to); extern void XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc);
/* TODO: XMPP stuff, I don't fucking know, I'm not a Jabbernerd. */ /* 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); extern void XMPPSendPlain(XMPPComponent *c, char *f, char *t, char *m, char *type);
@ -30,8 +30,25 @@ extern void XMPPEndCompStream(XMPPComponent *stream);
/* Sends a loopback stanza (a "killstanza"), used to kill an XMPP listener /* Sends a loopback stanza (a "killstanza"), used to kill an XMPP listener
* thread. */ * thread. */
extern void XMPPKillThread(XMPPComponent *jabber); extern void XMPPKillThread(XMPPComponent *jabber, char *killer);
/* Checks if a stanza is a "killstanza". */ /* Checks if a stanza is a "killstanza". */
extern bool XMPPIsKiller(XMLElement *); extern bool XMPPIsKiller(XMLElement *);
typedef struct MUCInfo {
bool exists;
XMPPComponent *jabber;
XMLElement *iq_reply;
} MUCInfo;
/* Queries a MUC's existence, and if $[out] is set, stores information
* pertaining the MUC itself from an <iq> query, to be freed by
* XMPPFreeMUCInfo */
extern bool XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out);
/* Retrieves the MUC's name from an IQ reply */
extern char * XMPPGetMUCName(MUCInfo info);
extern void XMPPFreeMUCInfo(MUCInfo info);
#endif #endif