diff --git a/README.MD b/README.MD index 09172e9..58c6fe4 100644 --- a/README.MD +++ b/README.MD @@ -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 TODO diff --git a/src/AS.c b/src/AS.c index 9177389..85d657b 100644 --- a/src/AS.c +++ b/src/AS.c @@ -144,6 +144,30 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) JsonFree(json); } 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) { HttpClientContext *ctx = NULL; @@ -151,6 +175,7 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c) char *txn; if (!conf || !id || !type || !user || !c) { + JsonFree(c); return; } @@ -170,3 +195,59 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c) HttpClientContextFree(ctx); 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; +} diff --git a/src/Events.c b/src/Events.c index 1bd099a..464562a 100644 --- a/src/Events.c +++ b/src/Events.c @@ -31,3 +31,17 @@ MatrixCreateMessage(char *body) return map; } +HashMap * +MatrixCreateNameState(char *name) +{ + HashMap *map; + if (!name) + { + return NULL; + } + + map = HashMapCreate(); + HashMapSet(map, "name", JsonValueString(name)); + + return map; +} diff --git a/src/Main.c b/src/Main.c index 78da1f0..8affc87 100644 --- a/src/Main.c +++ b/src/Main.c @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include @@ -34,7 +36,9 @@ SignalHandler(int signal) return; } /* 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); break; } @@ -49,7 +53,12 @@ Main(void) Stream *yaml; 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"); ParseeConfigInit(); @@ -90,7 +99,7 @@ Main(void) if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs)) { Log(LOG_ERR, "Couldn't start XMPP listener thread."); - /* TODO: Die */ + goto end; } sigAction.sa_handler = SignalHandler; @@ -101,6 +110,7 @@ Main(void) if (sigaction(sig, act, oact) < 0) \ { \ Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \ + goto end; \ } \ else \ { \ @@ -113,12 +123,15 @@ Main(void) SIGACTION(SIGUSR1, &sigAction, NULL); #undef SIGACTION + Log(LOG_INFO, "%d", XMPPQueryMUC(jabber, "rootard@muc.ari.lt", NULL)); server = HttpServerCreate(&conf); HttpServerStart(server); HttpServerJoin(server); end: + LogConfigUnindent(LogConfigGlobal()); + Log(LOG_INFO, "Exiting..."); HttpServerFree(server); ParseeConfigFree(); ParseeFreeData(conf.handlerArgs); diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index bd28394..1389247 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -16,6 +16,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) char *membership = GrabString(event, 2, "content", "membership"); char *room_id = GrabString(event, 1, "room_id"); char *sender = GrabString(event, 1, "sender"); + char *chat_id; const ParseeConfig *conf = data->config; Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership); @@ -41,6 +42,24 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) 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 ParseeMessageHandler(ParseeData *data, HashMap *event) @@ -53,6 +72,9 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *sender = GrabString(event, 1, "sender"); + char *chat_id; + + bool direct = false; if (ParseeIsPuppet(data->config, sender)) { @@ -62,9 +84,10 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) ref = DbLock(data->db, 3, "rooms", id, "data"); json = DbJson(ref); + direct = JsonValueAsBoolean(HashMapGet(json, "is_direct")); + DbUnlock(data->db, ref); - - if (JsonValueAsBoolean(HashMapGet(json, "is_direct"))) + if (direct) { char *user = GrabString(json, 1, "xmpp_user"); char *local = ParseeEncodeMXID(sender); @@ -73,9 +96,17 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) XMPPSendPlain(jabber, local, user, body, NULL); 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 @@ -88,6 +119,7 @@ ParseeEventHandler(ParseeData *data, HashMap *event) } event_type = GrabString(event, 1, "type"); + Log(LOG_INFO, "E->%s", GrabString(event, 1, "sender")); if (StrEquals(event_type, "m.room.member")) { ParseeMemberHandler(data, event); diff --git a/src/ParseeUser.c b/src/ParseeUser.c index 2df2e3b..b966d90 100644 --- a/src/ParseeUser.c +++ b/src/ParseeUser.c @@ -38,10 +38,14 @@ ParseeIsPuppet(const ParseeConfig *conf, char *user) return flag; } -static char * -DecodeJID(char *str, char term) +char * +ParseeDecodeJID(char *str, char term) { char *out = NULL; + if (!str) + { + return NULL; + } #define Okay(c) ((c) && ((c) != term)) while (Okay(*str)) { @@ -101,7 +105,37 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid) } data_start++; /* 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 * @@ -325,3 +359,148 @@ ParseeTrimJID(char *jid) 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; +} diff --git a/src/Routes/Transactions.c b/src/Routes/Transactions.c index 7987f50..fd178f3 100644 --- a/src/Routes/Transactions.c +++ b/src/Routes/Transactions.c @@ -35,7 +35,9 @@ RouteHead(RouteTxns, arr, argp) for (i = 0; i < ArraySize(events); i++) { HashMap *event = JsonValueAsObject(ArrayGet(events, i)); + event = JsonDuplicate(event); ParseeEventHandler(args->data, event); + JsonFree(event); } /* TODO: Store TXN ID somewhere so that we can commit diff --git a/src/Routes/UserAck.c b/src/Routes/UserAck.c index 7d58046..8208fbf 100644 --- a/src/Routes/UserAck.c +++ b/src/Routes/UserAck.c @@ -1,8 +1,11 @@ #include +#include #include +#include #include +#include #include RouteHead(RouteUserAck, arr, argp) @@ -43,8 +46,13 @@ RouteHead(RouteRoomAck, arr, argp) HashMap *response = NULL; Array *events; 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); if (response) { @@ -60,14 +68,70 @@ RouteHead(RouteRoomAck, arr, argp) goto end; } - Log(LOG_INFO, "room=%s", room); - - HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED); - response = MatrixCreateError( - "M_UNRECOGNIZED", - "Path /users only accepts GET as a valid method." + 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); + response = MatrixCreateError( + "M_UNRECOGNIZED", + "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: + if (chatid) + { + Free(chatid); + } + if (muc) + { + Free(muc); + } + if (id) + { + Free(id); + } + if (creator) + { + Free(creator); + } + XMPPFreeMUCInfo(info); JsonFree(request); return response; } diff --git a/src/XML/Elements.c b/src/XML/Elements.c index fade5cb..bcb138a 100644 --- a/src/XML/Elements.c +++ b/src/XML/Elements.c @@ -110,3 +110,24 @@ XMLFreeElement(XMLElement *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; +} diff --git a/src/XML/SAX.c b/src/XML/SAX.c index 2a12fa1..a335d88 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -122,6 +123,7 @@ XMLCrank(XMLexer *lexer) { XMLEvent *event = NULL; char c; + int ch; char *attrname; HashMap *props; char *key, *val; @@ -136,6 +138,7 @@ XMLCrank(XMLexer *lexer) return NULL; } event = XMLCreateRelax(lexer); + switch (lexer->state) { case XML_STATE_NONE: @@ -235,6 +238,10 @@ XMLCrank(XMLexer *lexer) event = XMLCreateStart(lexer, props); break; } + else if (XMLookahead(lexer, "'", true)) + { + while (true); + } break; case XML_STATE_ATTRTAIL: attrname = XMLParseName(lexer); @@ -490,7 +497,7 @@ HashMap * XMLReadProps(XMLexer *lexer) { ssize_t point = XMLInitialiseBuffer(lexer); - HashMap *map = HashMapCreate(); + HashMap *map = NULL; bool error = false; while (true) { @@ -523,10 +530,14 @@ XMLReadProps(XMLexer *lexer) break; } } + if (!map) + { + map = HashMapCreate(); + } HashMapSet(map, name, value); Free(name); } - if (error) + if (error || !map) { XMLReset(lexer, point); } @@ -660,7 +671,20 @@ XMLParseAttQuote(XMLexer *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; } @@ -687,7 +711,21 @@ XMLParseAttDouble(XMLexer *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; } diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index f134d4d..082f47d 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -56,7 +56,6 @@ XMPPInitialiseCompStream(char *host, int port) sd = -1; continue; } - Log(LOG_INFO, "Connected to port %s", serv); break; } @@ -68,7 +67,6 @@ XMPPInitialiseCompStream(char *host, int port) freeaddrinfo(res0); stream = StreamFd(sd); - Log(LOG_INFO, "Got %d via %p", sd, stream); if (!stream) { close(sd); @@ -143,7 +141,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared) stream_id = StrDuplicate(HashMapGet(ev->attrs, "id")); handshake = ComputeHandshake(shared, stream_id); - /* y no stream id */ + Log(LOG_NOTICE, "- sID='%s'", stream_id); StreamPrintf(stream, "%s", handshake); StreamFlush(stream); diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c new file mode 100644 index 0000000..299a0fb --- /dev/null +++ b/src/XMPP/MUC.c @@ -0,0 +1,109 @@ +#include + +#include +#include +#include +#include + +#include +#include + +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; +} diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 878bc44..05ffa3e 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -2,6 +2,7 @@ #include #include +#include #include @@ -32,11 +33,11 @@ XMPPSendPlain(XMPPComponent *comp, char *fr, char *to, char *msg, char *type) Free(from); } void -XMPPSendPresence(XMPPComponent *comp, char *fr, char *to) +XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc) { XMLElement *presence, *x; char *from; - if (!comp || !fr || !to) + if (!comp || !fr || !muc) { return; } @@ -44,28 +45,33 @@ XMPPSendPresence(XMPPComponent *comp, char *fr, char *to) presence = XMLCreateTag("presence"); x = XMLCreateTag("x"); 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"); XMLAddChild(presence, x); + /* A*/ XMLEncode(comp->stream, presence); + Log(LOG_INFO, "Flushing..."); + /* B */ StreamFlush(comp->stream); + XMLFreeElement(presence); Free(from); + /* How is that leaking */ } void -XMPPKillThread(XMPPComponent *jabber) +XMPPKillThread(XMPPComponent *jabber, char *killer) { XMLElement *message, *body, *data; char *from; - if (!jabber) + if (!jabber || !killer) { return; } - from = StrConcat(2, "jabber_die@", jabber->host); + from = StrConcat(3, killer, "@", jabber->host); message = XMLCreateTag("message"); XMLAddAttr(message, "from", from); XMLAddAttr(message, "to", from); diff --git a/src/XMPPThread.c b/src/XMPPThread.c index 7e2a876..a3d6ddc 100644 --- a/src/XMPPThread.c +++ b/src/XMPPThread.c @@ -1,5 +1,8 @@ #include +#include +#include + #include #include #include @@ -9,6 +12,28 @@ #include #include + +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 * ParseeXMPPThread(void *argp) { @@ -21,26 +46,41 @@ ParseeXMPPThread(void *argp) XMLElement *body = NULL; XMLElement *data = NULL; 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); if (!stanza) { continue; } + if (StrEquals(stanza->name, "presence")) + { + XMLFreeElement(stanza); + continue; + } if (StrEquals(stanza->name, "message")) { size_t i; if (XMPPIsKiller(stanza)) { - /* There's not a lot of scenarios where we loopback */ - Log(LOG_INFO, "Dropping thread..."); - XMLFreeElement(stanza); - break; + const char *killer = "killer"; + const char *suspend="suspend"; + from = HashMapGet(stanza->attrs, "from"); + if (!strncmp(from, killer, strlen(killer))) + { + Log(LOG_INFO, "Dropping thread..."); + XMLFreeElement(stanza); + 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++) { @@ -56,6 +96,8 @@ ParseeXMPPThread(void *argp) XMLFreeElement(stanza); continue; } + + /* TODO: Check the type */ to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); from = HashMapGet(stanza->attrs, "from"); from_matrix = ParseeEncodeJID(args->config, from); @@ -63,10 +105,20 @@ ParseeXMPPThread(void *argp) data = ArrayGet(body->children, 0); /* TODO: Consider having rich messages */ - ASSend( - args->config, room, from_matrix, - "m.room.message", MatrixCreateMessage(data->data) - ); + chat_id = ParseeGetFromMUCID(args, from); + mroom_id = ParseeGetRoomID(args, chat_id); + 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(room); Free(to); diff --git a/src/include/AS.h b/src/include/AS.h index 6f82822..b85b997 100644 --- a/src/include/AS.h +++ b/src/include/AS.h @@ -30,4 +30,10 @@ extern void ASJoin(const ParseeConfig *, char *, char *); * Said body is freed during the function's execution. */ 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 diff --git a/src/include/Matrix.h b/src/include/Matrix.h index c37e67a..67f1701 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -11,4 +11,7 @@ extern HashMap * MatrixCreateNotice(char *body); /* Creates the content for a normal message. */ extern HashMap * MatrixCreateMessage(char *body); + +/* Creates the content for a m.room.name state event */ +extern HashMap * MatrixCreateNameState(char *name); #endif diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 36ca8dd..6a4bfd5 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -95,6 +95,8 @@ extern void ParseeRequest(HttpServerContext *, void *); /* A pthread callback used for listening to a component */ 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 * ID. */ @@ -108,4 +110,26 @@ extern void ParseePushDMRoom(ParseeData *, char *mxid, char *jid, char *r); /* Trims the component from a 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 diff --git a/src/include/XML.h b/src/include/XML.h index 72c4555..8114783 100644 --- a/src/include/XML.h +++ b/src/include/XML.h @@ -63,6 +63,7 @@ extern XMLElement * XMLCreateText(char *data); extern void XMLAddAttr(XMLElement *element, char *key, char *val); extern void XMLAddChild(XMLElement *element, XMLElement *child); +extern XMLElement * XMLookForUnique(XMLElement *parent, char *tag); /* Frees an XML element properly. */ extern void XMLFreeElement(XMLElement *element); diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 7272200..d772e78 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -19,8 +19,8 @@ extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port); * after XMPPInitialiseCompStream. */ extern bool XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared); -/* Sends a presence to a user */ -extern void XMPPSendPresence(XMPPComponent *comp, char *fr, char *to); +/* Makes a user join a MUC */ +extern void XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc); /* 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); @@ -30,8 +30,25 @@ extern void XMPPEndCompStream(XMPPComponent *stream); /* Sends a loopback stanza (a "killstanza"), used to kill an XMPP listener * thread. */ -extern void XMPPKillThread(XMPPComponent *jabber); +extern void XMPPKillThread(XMPPComponent *jabber, char *killer); /* Checks if a stanza is a "killstanza". */ 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 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