From 2e566c73fcca4b9ba940eb37c9bddea3c6d1f519 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 29 Nov 2024 13:58:49 +0100 Subject: [PATCH 01/44] [MOD] Start not hardcoding discovery features The eventual goal here is to have different disco replies for specific kinds of JID, effectively allowing us to have MUCs. Yeah. --- src/XMPP/MUC.c | 25 +++++++- src/XMPP/Stanza.c | 21 ------ src/XMPPThread/Caps.c | 125 +++++++++++++++++++++++++++--------- src/XMPPThread/Stanzas/IQ.c | 10 ++- src/XMPPThread/internal.h | 25 +++++++- src/include/XMPP.h | 10 --- 6 files changed, 149 insertions(+), 67 deletions(-) diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 7064698..5813cd2 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -8,6 +8,8 @@ #include #include +#include "XMPPThread/internal.h" + bool XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) { @@ -162,6 +164,7 @@ bool XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret) { XMLElement *presence, *x, *reply, *history, *photo; + IQFeatures *features; char *from, *id, *stime = "3600"; if (!comp || !fr || !muc) { @@ -189,7 +192,15 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool XMLAddChild(x, history); XMLAddChild(presence, x); - XMPPAnnotatePresence(presence); + features = CreateIQFeatures(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + XMPPAnnotatePresence(presence, features); + FreeIQFeatures(features); if (hash) { @@ -225,6 +236,7 @@ void XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) { XMLElement *presence; + IQFeatures *features; char *from, *id; if (!comp || !fr || !muc) { @@ -246,7 +258,16 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMLAddChild(presence, status); } - XMPPAnnotatePresence(presence); + features = CreateIQFeatures(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + XMPPAnnotatePresence(presence, features); + FreeIQFeatures(features); + XMPPSendStanza(comp, presence, 10000); diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 451c16f..27a658e 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -159,27 +159,6 @@ XMPPGetReply(XMLElement *elem) return HashMapGet(rep->attrs, "id"); } -void -XMPPAnnotatePresence(XMLElement *presence) -{ - XMLElement *c; - char *ver; - if (!presence) - { - return; - } - - ver = XMPPGenerateVer(); - c = XMLCreateTag("c"); - XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps"); - XMLAddAttr(c, "hash", "sha-1"); - XMLAddAttr(c, "node", REPOSITORY); - XMLAddAttr(c, "ver", ver); - - Free(ver); - XMLAddChild(presence, c); -} - char * XMPPGetModeration(XMLElement *stanza) { diff --git a/src/XMPPThread/Caps.c b/src/XMPPThread/Caps.c index 867adbd..5e53ee1 100644 --- a/src/XMPPThread/Caps.c +++ b/src/XMPPThread/Caps.c @@ -12,33 +12,86 @@ #include "XMPPThread/internal.h" +IQFeatures * +CreateIQFeatures(void) +{ + IQFeatures *ret = Malloc(sizeof(*ret)); + + ret->identity = ArrayCreate(); + ret->adverts = ArrayCreate(); + + return ret; +} +void +FreeIQFeatures(IQFeatures *features) +{ + size_t i; + if (!features) + { + return; + } + + for (i = 0; i < ArraySize(features->adverts); i++) + { + Free(ArrayGet(features->adverts, i)); + } + ArrayFree(features->adverts); + + for (i = 0; i < ArraySize(features->identity); i++) + { + XMPPIdentity *identity = ArrayGet(features->identity, i); + + Free(identity->category); + Free(identity->type); + Free(identity->lang); + Free(identity->name); + + Free(identity); + } + ArrayFree(features->identity); + + Free(features); +} + +void +AdvertiseIQFeature(IQFeatures *f, char *feature) +{ + if (!f || !feature) + { + return; + } + + ArrayAdd(f->adverts, StrDuplicate(feature)); +} +void +AddIQIdentity(IQFeatures *f, char *cat, char *lang, char *type, char *name) +{ + XMPPIdentity *identity; + if (!f) + { + return; + } + + identity = Malloc(sizeof(*identity)); + identity->category = StrDuplicate(cat); + identity->type = StrDuplicate(type); + identity->lang = StrDuplicate(lang); + identity->name = StrDuplicate(name); + ArrayAdd(f->identity, identity); +} /* Generates a SHA-256 hash of the ver field. */ char * -XMPPGenerateVer(void) +XMPPGenerateVer(IQFeatures *features) { char *S = NULL; unsigned char *Sha = NULL; - Array *identities = ArrayCreate(); - Array *features = ArrayCreate(); size_t i; /* Initialise identity table, to be sorted */ -#define IdentitySimple(cat, Type, Name) { \ - XMPPIdentity *id = Malloc(sizeof(*id)); \ - id->category = cat; \ - id->lang = NULL; \ - id->type = Type; \ - id->name = Name; \ - ArrayAdd(identities, id); } - IQ_IDENTITY -#undef IdentitySimple -#define AdvertiseSimple(feature) ArrayAdd(features, feature); - IQ_ADVERT -#undef AdvertiseSimple - ArraySort(identities, IdentitySort); - for (i = 0; i < ArraySize(identities); i++) + ArraySort(features->identity, IdentitySort); + for (i = 0; i < ArraySize(features->identity); i++) { - XMPPIdentity *identity = ArrayGet(identities, i); + XMPPIdentity *identity = ArrayGet(features->identity, i); char *id_chunk = StrConcat(7, identity->category, "/", identity->type, "/", @@ -50,10 +103,10 @@ XMPPGenerateVer(void) Free(id_chunk); } - ArraySort(features, ((int (*) (void *, void *)) ICollate)); - for (i = 0; i < ArraySize(features); i++) + ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate)); + for (i = 0; i < ArraySize(features->adverts); i++) { - char *feature = ArrayGet(features, i); + char *feature = ArrayGet(features->adverts, i); char *tmp = S; S = StrConcat(3, S, feature, "<"); Free(tmp); @@ -64,16 +117,28 @@ XMPPGenerateVer(void) S = Base64Encode((const char *) Sha, 20); Free(Sha); - ArrayFree(features); - for (i = 0; i < ArraySize(identities); i++) - { - XMPPIdentity *identity = ArrayGet(identities, i); - /* We don't have to do anything here. */ - Free(identity); - } - ArrayFree(identities); - return S; } +void +XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features) +{ + XMLElement *c; + char *ver; + if (!presence || !features) + { + return; + } + + ver = XMPPGenerateVer(features); + c = XMLCreateTag("c"); + XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps"); + XMLAddAttr(c, "hash", "sha-1"); + XMLAddAttr(c, "node", REPOSITORY); + XMLAddAttr(c, "ver", ver); + + Free(ver); + XMLAddChild(presence, c); +} + diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 4ced5dc..396fec4 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -151,6 +151,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; + IQFeatures *features = CreateIQFeatures(); from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); @@ -164,8 +165,14 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) XMLAddAttr(iq_reply, "id", id); query = IQGenerateQuery(); +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple { - char *ver = XMPPGenerateVer(); + char *ver = XMPPGenerateVer(features); char *node = StrConcat(3, REPOSITORY, "#", ver); XMLAddAttr(query, "node", node); @@ -173,6 +180,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) Free(ver); } XMLAddChild(iq_reply, query); + FreeIQFeatures(features); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); XMLFreeElement(iq_reply); diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 3a158b5..8602621 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -32,13 +32,17 @@ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("component", "generic", "Parsee's component") -typedef struct PEPManager PEPManager; -typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); - +typedef struct IQFeatures { + Array *identity; + Array *adverts; +} IQFeatures; typedef struct XMPPIdentity { char *category, *type, *lang, *name; } XMPPIdentity; +typedef struct PEPManager PEPManager; +typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); + typedef struct XMPPThread XMPPThread; typedef struct XMPPThreadInfo { /* A FIFO of stanzas inbound, to be read by dispatcher @@ -65,6 +69,21 @@ struct XMPPThread { int ICollate(unsigned char *cata, unsigned char *catb); int IdentitySort(void *idap, void *idbp); +IQFeatures * CreateIQFeatures(void); +void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *name); +void AdvertiseIQFeature(IQFeatures *, char *feature); +void FreeIQFeatures(IQFeatures *); + +/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. + * -------- + * Returns: A base64 encoded ver hash[LA:HEAP] + * Modifies: NOTHING + * See-Also: https://xmpp.org/extensions/xep-0115.html */ +char * XMPPGenerateVer(IQFeatures *features); + +/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */ +void XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features); + char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza); char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id); char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza); diff --git a/src/include/XMPP.h b/src/include/XMPP.h index b2c5293..92a5d80 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -98,16 +98,6 @@ extern char * XMPPGetReply(XMLElement *elem); * See-Also: https://xmpp.org/extensions/xep-0425.html */ extern char * XMPPGetModeration(XMLElement *stanza); -/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. - * -------- - * Returns: A base64 encoded ver hash[LA:HEAP] - * Modifies: NOTHING - * See-Also: https://xmpp.org/extensions/xep-0115.html */ -extern char * XMPPGenerateVer(void); - -/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */ -extern void XMPPAnnotatePresence(XMLElement *presence); - extern bool XMPPHasError(XMLElement *stanza, char *type); #include From 5ddc5d3e5cb4aec90ba260f7114fa18c4b902162 Mon Sep 17 00:00:00 2001 From: LDA Date: Thu, 19 Dec 2024 20:59:12 +0100 Subject: [PATCH 02/44] [WIP/ADD] Try to separate discovery, make links nicer --- configure.c | 71 +++------------- src/Main.c | 3 +- src/MatrixID.c | 34 ++++++++ src/Parsee/Data.c | 2 + src/Parsee/Utils/Formatting.c | 48 ++++++++--- src/Routes/Media.c | 43 +++++++--- src/XMPP/MUC.c | 4 +- src/XMPP/MUCServ.c | 153 ++++++++++++++++++++++++++++++++++ src/XMPPThread/Caps.c | 37 ++++++++ src/XMPPThread/ReListener.c | 6 ++ src/XMPPThread/Stanzas/IQ.c | 69 +++++++-------- src/XMPPThread/internal.h | 8 ++ src/include/MUCServ.h | 39 +++++++++ src/include/Matrix.h | 7 ++ src/include/Parsee.h | 3 + 15 files changed, 409 insertions(+), 118 deletions(-) create mode 100644 src/XMPP/MUCServ.c create mode 100644 src/include/MUCServ.h diff --git a/configure.c b/configure.c index a72a68a..d3a01d0 100644 --- a/configure.c +++ b/configure.c @@ -90,22 +90,6 @@ string_cat(char *in1, char *in2) return out; } -static char * -trim_nl(char *in) -{ - char *tc; - if (!in) - { - return NULL; - } - - while ((tc = strrchr(in, '\n'))) - { - *tc = '\0'; - } - - return in; -} typedef struct str_array { char **values; @@ -115,6 +99,10 @@ static str_array_t * str_array_create(void) { str_array_t *ret = malloc(sizeof(*ret)); + if (!ret) + { + return NULL; + } ret->values = NULL; ret->quantity = 0; @@ -216,36 +204,6 @@ failure: free(line); return NULL; } -static int -exec_code(char *program, char *argv[]) -{ - pid_t forkRet; - if (!program || !argv) - { - return -1; - } - - forkRet = fork(); - if (forkRet == 0) - { - /* Child */ - execvp(program, argv); - exit(0); - } - else - { - /* Parent */ - int status; - if (waitpid(forkRet, &status, 0) == -1) - { - return -1; - } - return status; - } - - /* We're not meant to ever be there, but TCC is stupid. */ - return -1; -} static char * strchrn(char *s, char c) @@ -253,17 +211,6 @@ strchrn(char *s, char c) s = strchr(s, c); return s ? s+1 : NULL; } -static char * -strchrl(char *s, char c) -{ - char *s1 = NULL; - while ((s = strchr(s, c))) - { - s1 = s; - s++; - } - return s1; -} static str_array_t * split(char *dir) { @@ -512,8 +459,7 @@ main_build(int argc, char *argv[]) { FILE *makefile; char *repo = cmd_stdout("git remote get-url origin"); - size_t size, i; - ssize_t nread; + size_t i; bool with_static = false, with_lmdb = false; int opt; str_array_t *sources, *images, *utils, *aya; @@ -546,6 +492,13 @@ main_build(int argc, char *argv[]) } makefile = fopen("Makefile", "w"); + if (!makefile) + { + fprintf(stderr, "Couldn't create Makefile.\n"); + fprintf(stderr, "This isn't good, actually.\n"); + + return EXIT_FAILURE; + } fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n"); fprintf(makefile, "# Ideally do not touch, unless you have a very "); fprintf(makefile, "good reason to do it. \n\n"); diff --git a/src/Main.c b/src/Main.c index a9b0a50..633e117 100644 --- a/src/Main.c +++ b/src/Main.c @@ -72,12 +72,13 @@ ParseeCheckMatrix(void *datp) Log(LOG_ERR, "Please check if your homeserver is active."); Log(LOG_ERR, "%s shall now exit...", NAME); + /* TODO: SEGV! */ pthread_mutex_lock(&data->halt_lock); data->halted = true; pthread_mutex_unlock(&data->halt_lock); XMPPFinishCompStream(data->jabber); - pthread_join(xmpp_thr, NULL); + //pthread_join(xmpp_thr, NULL); Log(LOG_INFO, "Stopping server..."); HttpServerStop(data->server); } diff --git a/src/MatrixID.c b/src/MatrixID.c index 4488fd4..bb7c584 100644 --- a/src/MatrixID.c +++ b/src/MatrixID.c @@ -1,6 +1,9 @@ #include #include +#include +#include +#include #include @@ -32,3 +35,34 @@ MatrixParseID(char *user) return ret; } +UserID * +MatrixParseIDFromMTO(Uri *uri) +{ + UserID *id = NULL; + char *path, *params, *decoded; + if (!uri) + { + return NULL; + } + + if (!StrEquals(uri->proto, "https") || !StrEquals(uri->host, "matrix.to")) + { + return NULL; + } + if (strncmp(uri->path, "/#/", 3)) + { + return NULL; + } + path = StrDuplicate(uri->path + 3); + params = path ? strchr(path, '?') : NULL; + if (params) + { + *params = '\0'; + } + decoded = HttpUrlDecode(path); + id = MatrixParseID(decoded); + Free(decoded); + Free(path); + + return id; +} diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index cb1b64b..68ddf3c 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -27,6 +27,7 @@ ParseeInitData(XMPPComponent *comp) data->config = ParseeConfigGet(); data->router = HttpRouterCreate(); data->jabber = comp; + data->muc = CreateMUCServer(data); data->handler = CommandCreateRouter(); data->oid_servers = HashMapCreate(); @@ -116,6 +117,7 @@ ParseeFreeData(ParseeData *data) pthread_mutex_destroy(&data->halt_lock); Free(data->id); XMPPEndCompStream(data->jabber); + FreeMUCServer(data->muc); DbClose(data->db); HttpRouterFree(data->router); CommandFreeRouter(data->handler); diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index da6194f..5a2c6b8 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -151,20 +153,42 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) else if (StrEquals(elem->name, "a")) { char *href = HashMapGet(elem->attrs, "href"); - /* TODO: Check if the element here is a Matrix.TO - * pointing to a Parsee user. */ - Concat("("); - for (i = 0; i < ArraySize(elem->children); i++) + Uri *pref = UriParse(href); + if (StrEquals(pref->host, "matrix.to")) { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); + /* TODO: Check if the element here is a Matrix.TO + * pointing to a Parsee user. */ + UserID *id = MatrixParseIDFromMTO(pref); + if (id) + { + /* TODO: Detect if it already is a Parsee user */ + Concat("@"); + Concat(id->localpart); + Concat(":"); + Concat(id->server); + } + else + { + Concat(href); + } + + Free(id); } - Concat(" points to "); - Concat(href); - Concat(" )"); + else + { + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(event, child, flags); + + Concat(subxep); + Free(subxep); + } + Concat(" < "); + Concat(href); + Concat(" >"); + } + UriFree(pref); } else { diff --git a/src/Routes/Media.c b/src/Routes/Media.c index 7d6bc68..004a854 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -10,6 +10,37 @@ #include +static HttpClientContext * +TryDownload(ParseeData *data, char *server, char *identi) +{ + HttpClientContext *cctx; + char *path; + server = HttpUrlEncode(server); + identi = HttpUrlEncode(identi); + + path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi); + cctx = ParseeCreateRequest(data->config, HTTP_GET, path); + ASAuthenticateRequest(data->config, cctx); + Free(path); + + HttpRequestSendHeaders(cctx); + if (HttpRequestSend(cctx) != HTTP_OK) + { + Log(LOG_WARNING, "Failing back."); + HttpClientContextFree(cctx); + path = StrConcat(4, "/_matrix/client/v1/media/download/", server, "/", identi); + cctx = ParseeCreateRequest(data->config, HTTP_GET, path); + ASAuthenticateRequest(data->config, cctx); + Free(path); + HttpRequestSendHeaders(cctx); + HttpRequestSend(cctx); + } + + Free(server); + Free(identi); + return cctx; +} + RouteHead(RouteMedia, arr, argp) { ParseeHttpArg *args = argp; @@ -44,15 +75,7 @@ RouteHead(RouteMedia, arr, argp) /* Proxy the media through an authenticated endpoint if the HMAC * is valid. */ - server = HttpUrlEncode(server); - identi = HttpUrlEncode(identi); - path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi); - cctx = ParseeCreateRequest(args->data->config, HTTP_GET, path); - ASAuthenticateRequest(args->data->config, cctx); - Free(path); - - HttpRequestSendHeaders(cctx); - HttpRequestSend(cctx); + cctx = TryDownload(args->data, server, identi); reqh = HttpResponseHeaders(cctx); while (HashMapIterate(reqh, &key, (void **) &val)) { @@ -65,8 +88,6 @@ RouteHead(RouteMedia, arr, argp) } HttpClientContextFree(cctx); - Free(server); - Free(identi); return NULL; } diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 5813cd2..67bc9d1 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -192,7 +192,7 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool XMLAddChild(x, history); XMLAddChild(presence, x); - features = CreateIQFeatures(); + features = LookupJIDFeatures(from); #define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); IQ_IDENTITY #undef IdentitySimple @@ -258,7 +258,7 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMLAddChild(presence, status); } - features = CreateIQFeatures(); + features = LookupJIDFeatures(from); #define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); IQ_IDENTITY #undef IdentitySimple diff --git a/src/XMPP/MUCServ.c b/src/XMPP/MUCServ.c new file mode 100644 index 0000000..66f4840 --- /dev/null +++ b/src/XMPP/MUCServ.c @@ -0,0 +1,153 @@ +#include + +#include +#include +#include + +#include +#include + +#define MUCNS "http://jabber.org/protocol/muc" + +struct MUCServer { + pthread_mutex_t mut; + + ParseeData *data; +}; + +static char * +GetMUCName(char *jid) +{ + char *name, *at; + size_t len; + if (!jid || *jid != '#') + { + return NULL; + } + + jid++; + + at = strchr(jid, '@'); + if (!at) + { + return StrDuplicate(jid); + } + len = at - jid; + name = Malloc(len + 1); + memset(name, '\0', len + 1); + memcpy(name, jid, len); + + return name; +} + +MUCServer * +CreateMUCServer(ParseeData *data) +{ + MUCServer *server; + if (!data) + { + return NULL; + } + + server = Malloc(sizeof(*server)); + pthread_mutex_init(&server->mut, NULL); + server->data = data; + return server; +} + +static bool +MUCManagePresence(MUCServer *serv, XMLElement *stanza, char *from, char *to) +{ + char *name; + XMLElement *x = XMLookForTKV(stanza, "x", "xmlns", MUCNS); + char *type = HashMapGet(stanza->attrs, "type"); + if (x && !type) + { + /* The user is trying to join the MUC */ + name = GetMUCName(to); + Log(LOG_WARNING, "%s is trying to join MUC '%s'", from, name); + + /* TODO: Check if the user should be joining. If so, make them. + * Implementing MUCs is gonna be fun. */ + + /* TODO: Presence broadcast hell. */ + Free(name); + } + return false; +} + +static bool +MUCManageMessage(MUCServer *serv, XMLElement *stanza, char *from, char *to) +{ + Log(LOG_WARNING, "MUCSERV: got a message %s->%s", from, to); + return false; +} + +bool +ManageMUCStanza(MUCServer *serv, XMLElement *stanza) +{ + char *stype, *from, *to; + bool ret; + if (!serv || !stanza) + { + return false; + } + + from = HashMapGet(stanza->attrs, "from"); + to = HashMapGet(stanza->attrs, "to"); + stype = stanza->name; + + if (to && *to != '#') + { + /* We aren't interacting with a MUC at all. Don't do anything. */ + return false; + } + if (StrEquals(stype, "iq")) + { + /* TODO: Worry about IQ later */ + return false; + } + + ret = false; + pthread_mutex_lock(&serv->mut); + if (StrEquals(stype, "presence")) + { + ret = MUCManagePresence(serv, stanza, from, to); + } + else if (StrEquals(stype, "message")) + { + ret = MUCManageMessage(serv, stanza, from, to); + } + /* TODO: Do stuff while locked */ + pthread_mutex_unlock(&serv->mut); + + /* TODO: Verify the destination, and make sure we aren't doing + * anything stupid(especially with discovery) */ + return false; +} + +bool +MUCServerExists(MUCServer *serv, char *muc) +{ + if (!serv || !muc) + { + return false; + } + + return false; +} + +void +FreeMUCServer(MUCServer *serv) +{ + if (!serv) + { + return; + } + + pthread_mutex_lock(&serv->mut); + /* TODO */ + pthread_mutex_unlock(&serv->mut); + pthread_mutex_destroy(&serv->mut); + Free(serv); +} diff --git a/src/XMPPThread/Caps.c b/src/XMPPThread/Caps.c index 5e53ee1..d79a30c 100644 --- a/src/XMPPThread/Caps.c +++ b/src/XMPPThread/Caps.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -142,3 +143,39 @@ XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features) XMLAddChild(presence, c); } + +IQFeatures * +LookupJIDFeatures(char *jid) +{ + IQFeatures *features; + if (!jid) + { + return NULL; + } + + features = CreateIQFeatures(); + + if (*jid == '#') + { + /* This is a MUC. As such, we need to advertise MUCs */ +#define ID(...) AddIQIdentity(features, __VA_ARGS__) +#define AD(var) AdvertiseIQFeature(features, var) + ID("gateway", NULL, "matrix", "Parsee MUC gateway"); + ID("conference", NULL, "text", "Parsee MUC gateway"); + ID("component", NULL, "generic", "Parsee component"); + + AD("http://jabber.org/protocol/muc"); +#undef AD +#undef ID + } + else + { +#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); + IQ_IDENTITY +#undef IdentitySimple +#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); + IQ_ADVERT +#undef AdvertiseSimple + } + return features; +} diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 559ffcb..f68b887 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -65,6 +65,12 @@ XMPPDispatcher(void *argp) Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); } + if (ManageMUCStanza(args->muc, stanza)) + { + XMLFreeElement(stanza); + continue; + } + if (StrEquals(stanza->name, "presence")) { PresenceStanza(args, stanza, thread); diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 396fec4..17092b0 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -115,33 +115,37 @@ end: #define DISCO "http://jabber.org/protocol/disco#info" static XMLElement * -IQGenerateQuery(void) +IQGenerateQuery(IQFeatures *features) { - XMLElement *query = XMLCreateTag("query"); + XMLElement *query; + if (!features) + { + return NULL; + } + query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", DISCO); { XMLElement *feature; -#define IdentitySimple(c,t,n) do \ - { \ - feature = XMLCreateTag("identity"); \ - XMLAddAttr(feature, "category", c); \ - XMLAddAttr(feature, "type", t); \ - XMLAddAttr(feature, "name", n); \ - XMLAddChild(query, feature); \ - } \ - while (0); - IQ_IDENTITY -#undef IdentitySimple -#define AdvertiseSimple(f) do \ - { \ - feature = XMLCreateTag("feature"); \ - XMLAddAttr(feature, "var", f); \ - XMLAddChild(query, feature); \ - } \ - while (0); + size_t i; + for (i = 0; i < ArraySize(features->identity); i++) + { + XMPPIdentity *identity = ArrayGet(features->identity, i); - IQ_ADVERT -#undef AdvertiseSimple + feature = XMLCreateTag("identity"); + XMLAddAttr(feature, "category", identity->category); + XMLAddAttr(feature, "type", identity->type); + XMLAddAttr(feature, "name", identity->name); + + XMLAddChild(query, feature); + } + for (i = 0; i < ArraySize(features->adverts); i++) + { + char *var = ArrayGet(features->adverts, i); + + feature = XMLCreateTag("feature"); + XMLAddAttr(feature, "var", var); + XMLAddChild(query, feature); + } } return query; @@ -151,7 +155,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; - IQFeatures *features = CreateIQFeatures(); + IQFeatures *features; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); @@ -164,13 +168,8 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "id", id); - query = IQGenerateQuery(); -#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name); - IQ_IDENTITY -#undef IdentitySimple -#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature); - IQ_ADVERT -#undef AdvertiseSimple + features = LookupJIDFeatures(to); + query = IQGenerateQuery(features); { char *ver = XMPPGenerateVer(features); char *node = StrConcat(3, REPOSITORY, "#", ver); @@ -599,10 +598,14 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } else { + char *buf = NULL; + Stream *s = StrStreamWriter(&buf); Log(LOG_WARNING, "Unknown I/Q received:"); - XMLEncode(StreamStdout(), stanza); - StreamPrintf(StreamStdout(),"\n"); - StreamFlush(StreamStdout()); + XMLEncode(s, stanza); + StreamFlush(s); + StreamClose(s); + Log(LOG_WARNING, "%s", buf); + Free(buf); } } diff --git a/src/XMPPThread/internal.h b/src/XMPPThread/internal.h index 8602621..efdd300 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -74,6 +74,14 @@ void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *n void AdvertiseIQFeature(IQFeatures *, char *feature); void FreeIQFeatures(IQFeatures *); +/** + * Looks up the features supported by a JID + * ---------------------- + * Returns: an IQ featureset[LA:HEAP] | NULL + * Thrasher: FreeIQFeatures + * Modifies: NOTHING */ +IQFeatures * LookupJIDFeatures(char *jid); + /** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps. * -------- * Returns: A base64 encoded ver hash[LA:HEAP] diff --git a/src/include/MUCServ.h b/src/include/MUCServ.h new file mode 100644 index 0000000..f9a2e4b --- /dev/null +++ b/src/include/MUCServ.h @@ -0,0 +1,39 @@ +#ifndef PARSEE_MUCSERV_H +#define PARSEE_MUCSERV_H + +/*-* An easy interface for handling MUCs. + */ + +typedef struct MUCServer MUCServer; + +#include + +#include + +/** Creates a MUC server handle given a ParseeData handle. + * This handle shall be used for all MUC-related operations. + * ---------------------------------------------------------- + * Returns: a MUC server handle[HEAP] | NULL + * Thrasher: FreeMUCServer + * See-Also: https://xmpp.org/extensions/xep-0045.html */ +extern MUCServer * CreateMUCServer(ParseeData *data); + +/** Manages a stanza from a MUC, and returns true if the stanza + * was actually processed by the MUC server. + * ------------------------------------------------------------- + * See-Also: CreateMUCServer */ +extern bool ManageMUCStanza(MUCServer *serv, XMLElement *stanza); + +/** Checks if a MUC(from its localpart, so + * '#foobar@bridge.blow.hole' -> 'foobar') exists. + * -------------- + * Returns: whenever the MUC exists */ +extern bool MUCServerExists(MUCServer *serv, char *muc); + +/** Destroys all memory and references from a MUC server handle. + * -------------- + * Thrashes: CreateMUCServer + * See-Also: https://xmpp.org/extensions/xep-0045.html */ +extern void FreeMUCServer(MUCServer *serv); + +#endif diff --git a/src/include/Matrix.h b/src/include/Matrix.h index a408389..fb0b5f7 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -2,6 +2,7 @@ #define PARSEE_MATRIX_H #include +#include #include "FileInfo.h" @@ -20,6 +21,12 @@ typedef struct UserID { * Thrasher: Free */ extern UserID * MatrixParseID(char *user); +/** Attempts to parse a Matrix ID from a matrix.to URL. + * ------------------------------------------------- + * Returns: a valid user ID[HEAP] | NULL + * Thrasher: Free */ +extern UserID * MatrixParseIDFromMTO(Uri *uri); + /* Creates an error message JSON, with the specified code and message. */ extern HashMap * MatrixCreateError(char *err, char *msg); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 30c8b61..228bf27 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -18,6 +18,8 @@ typedef struct ParseeData ParseeData; #include #include +#include + #define PARSEE_VERBOSE_NONE 0 #define PARSEE_VERBOSE_LOG 1 #define PARSEE_VERBOSE_TIMINGS 2 @@ -62,6 +64,7 @@ typedef struct ParseeData { HttpServer *server; XMPPComponent *jabber; + MUCServer *muc; Db *db; char *id; From 86deab29afefeac7f16e7227323de831a2e1e09c Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 28 Dec 2024 20:17:03 +0100 Subject: [PATCH 03/44] [FIX] Properly handle new contents with Matrix No more ugly asterisks. It annoyed me enough. --- src/MatrixEventHandler.c | 8 +++++++- src/Parsee/Utils/Formatting.c | 31 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 3d6a69e..1202484 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -370,6 +370,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) DbRef *ref = NULL; HashMap *json; + char *unedited_id = MatrixGetEdit(event); char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); @@ -381,12 +382,17 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *unauth = NULL; char *origin_id = NULL, *stanza = NULL; char *sender = NULL; - char *unedited_id = MatrixGetEdit(event); char *url = GrabString(event, 2, "content", "url"); char *encoded_from = NULL; bool direct = false; + if (unedited_id) + { + char *new_content = GrabString(event, 3, "content", "m.new_content", "body"); + if (new_content) body = new_content; + } + if (ParseeIsPuppet(data->config, m_sender) || ParseeManageBan(data, m_sender, id)) { diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 5a2c6b8..2abf3b5 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -207,6 +207,32 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) } return xepd; } +static char * +GetRawBody(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + char *new = GrabString(event, 3, "content", "m.new_content", "body"); + if (new) + { + return new; + } + } + return GrabString(event, 2, "content", "body"); +} +static char * +GetHTMLBody(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + char *new = GrabString(event, 3, "content", "m.new_content", "formatted_body"); + if (new) + { + return new; + } + } + return GrabString(event, 2, "content", "formatted_body"); +} char * ParseeXMPPify(HashMap *event) { @@ -231,11 +257,12 @@ ParseeXMPPify(HashMap *event) if (!StrEquals(format, "org.matrix.custom.html")) { /* Settle for the raw body instead. */ - char *body = JsonValueAsString(JsonGet(event, 2, "content", "body")); + char *body = GetRawBody(event); return StrDuplicate(body); } - html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body")); + html = GetHTMLBody(event); + html = StrConcat(3, "", html, ""); elem = XMLCDecode(StrStreamReader(html), true, true); From c365681fcbd5af5a411ea8004a63a5ce82592836 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 3 Jan 2025 15:01:35 +0100 Subject: [PATCH 04/44] [MOD] Notify MUCs about errors --- src/Main.c | 43 +++++++++++++++++++++++++++------------- src/Parsee/Data.c | 3 --- src/Parsee/User.c | 3 +-- src/Routes/Media.c | 2 +- src/Routes/Ping.c | 2 -- src/XMPP/MUCServ.c | 1 + src/XMPPCommands/MUCKV.c | 6 +++--- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Main.c b/src/Main.c index 633e117..47a9576 100644 --- a/src/Main.c +++ b/src/Main.c @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -66,21 +66,36 @@ ParseeCheckMatrix(void *datp) if (!ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); - if (++streak >= 5) + if (++streak == 10) { - Log(LOG_ERR, "This has been at least the fifth time in a row."); - Log(LOG_ERR, "Please check if your homeserver is active."); - Log(LOG_ERR, "%s shall now exit...", NAME); + DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); + HashMap *json = DbJson(ref); + HashMap *mucs = GrabObject(json, 1, "mucs"); + char *muc; + void *ignored; - /* TODO: SEGV! */ - pthread_mutex_lock(&data->halt_lock); - data->halted = true; - pthread_mutex_unlock(&data->halt_lock); - XMPPFinishCompStream(data->jabber); - //pthread_join(xmpp_thr, NULL); - Log(LOG_INFO, "Stopping server..."); - HttpServerStop(data->server); + /* Notify any potential MUCs about this */ + while (HashMapIterate(mucs, &muc, &ignored)) + { + char *id = StrRandom(32); + char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee"); + StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id); + SetStanzaType(b, "groupchat"); + SetStanzaBody(b, + "This bridge hasn't been able to reach the Matrix host, and " + "as such, some messages may not have been sent over." + ); + + WriteoutStanza(b, data->jabber, 0); + DestroyStanzaBuilder(b); + + Free(sender); + Free(id); + } + (void) ignored; + + DbUnlock(data->db, ref); } return; } @@ -109,7 +124,7 @@ Main(Array *args, HashMap *env) ); ParseePrintASCII(); Log(LOG_INFO, "======================="); - Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors"); + Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); Log(LOG_INFO, "(This program is free software, see LICENSE.)"); #ifdef PLATFORM_IPHONE diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 68ddf3c..46d1433 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -132,7 +132,6 @@ ParseeCleanup(void *datp) char *chat; size_t i; uint64_t ts = UtilTsMillis(); - size_t entries = 0; chats = DbList(data->db, 1, "chats"); @@ -176,7 +175,6 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ DbDelete(data->db, 4, "chats", chat, #field"s", field); \ - entries++; \ } \ Free(field); \ } \ @@ -233,7 +231,6 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ JsonValueFree(HashMapDelete(field##s, field)); \ - entries++; \ } \ Free(field); \ } \ diff --git a/src/Parsee/User.c b/src/Parsee/User.c index e77f20a..31ce6ff 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -538,7 +538,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id) return ret; } - void ParseeSendPresence(ParseeData *data) { @@ -562,7 +561,7 @@ ParseeSendPresence(ParseeData *data) uint64_t ts = GrabInteger(DbJson(chat), 1, "ts"); int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1; /* Make a fake user join the MUC */ - Log(LOG_NOTICE, "Sending presence to %s last=%ds", rev, diff); + Log(LOG_NOTICE, "Sending presence to %s", rev); XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false); DbUnlock(data->db, chat); diff --git a/src/Routes/Media.c b/src/Routes/Media.c index 004a854..498eba2 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -48,7 +48,7 @@ RouteHead(RouteMedia, arr, argp) HashMap *reqh, *params; char *server = ArrayGet(arr, 0); char *identi = ArrayGet(arr, 1); - char *path, *key, *val; + char *key, *val; char *hmac, *chkmak = NULL; params = HttpRequestParams(args->ctx); diff --git a/src/Routes/Ping.c b/src/Routes/Ping.c index a508336..1d4191e 100644 --- a/src/Routes/Ping.c +++ b/src/Routes/Ping.c @@ -10,8 +10,6 @@ RouteHead(RoutePing, arr, argp) ParseeHttpArg *args = argp; HashMap *request = NULL; HashMap *response = NULL; - Array *events; - size_t i; response = ASVerifyRequest(args); if (response) diff --git a/src/XMPP/MUCServ.c b/src/XMPP/MUCServ.c index 66f4840..557c4a2 100644 --- a/src/XMPP/MUCServ.c +++ b/src/XMPP/MUCServ.c @@ -123,6 +123,7 @@ ManageMUCStanza(MUCServer *serv, XMLElement *stanza) /* TODO: Verify the destination, and make sure we aren't doing * anything stupid(especially with discovery) */ + (void) ret; return false; } diff --git a/src/XMPPCommands/MUCKV.c b/src/XMPPCommands/MUCKV.c index 6dba478..80ac9c4 100644 --- a/src/XMPPCommands/MUCKV.c +++ b/src/XMPPCommands/MUCKV.c @@ -18,9 +18,9 @@ void MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) { ParseeData *data = XMPPGetManagerCookie(m); - char *affiliation, *role; - char *chat_id; - char *muc; + char *affiliation = NULL, *role = NULL; + char *chat_id = NULL; + char *muc = NULL; char *key = NULL, *val = NULL; From c433e314610e22d1b145b83a8979ae58e194397d Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 3 Jan 2025 16:00:24 +0100 Subject: [PATCH 05/44] [DEL] Please enter a valid command Please enter a valid command --- src/MatrixEventHandler.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 1202484..aab9f25 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -260,11 +260,6 @@ ParseeBotHandler(ParseeData *data, HashMap *event) if (*body != '!') { /* All commands are to be marked with a ! */ - Free(ASSend( - data->config, id, profile, - "m.room.message", - MatrixCreateNotice("Please enter a valid command"), 0 - )); Free(profile); return; } From 5e1931a19f8954628287802e9f15a26145001d7b Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 6 Jan 2025 17:09:36 +0000 Subject: [PATCH 06/44] [MOD] Change media path, connect to component through another address --- README.MD | 3 +-- src/HttParsee.c | 2 +- src/Main.c | 3 ++- src/Parsee/Config.c | 10 ++++++++++ src/Parsee/User.c | 2 +- src/XMPP/Component.c | 13 +++++++++++-- src/include/Parsee.h | 2 ++ src/include/Routes.h | 2 +- src/include/XMPP.h | 2 +- 9 files changed, 30 insertions(+), 9 deletions(-) diff --git a/README.MD b/README.MD index 9a7fc6f..ce51662 100644 --- a/README.MD +++ b/README.MD @@ -18,8 +18,7 @@ A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solutio and maybe as a testing ground for Cytoplasm features I sometimes add. (Well, I'm *trying* to do that, at least. -Please scream at me if that fails(or just doesn't run on a overclocked Raspberry -Pi 4B, which, by the way, is literally where Parsee+XMPP is running for now.)) +Please scream at me if that fails(or just doesn't run on a overclocked Raspberry Pi 4B)) ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" diff --git a/src/HttParsee.c b/src/HttParsee.c index 724f9ff..572487c 100644 --- a/src/HttParsee.c +++ b/src/HttParsee.c @@ -73,7 +73,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path ctx = HttpRequest( meth, - HTTP_FLAG_TLS, + conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, conf->homeserver_port, conf->homeserver_host, path ); diff --git a/src/Main.c b/src/Main.c index 47a9576..3e740ed 100644 --- a/src/Main.c +++ b/src/Main.c @@ -223,9 +223,11 @@ Main(Array *args, HashMap *env) Log(LOG_NOTICE, "Connecting to XMPP..."); jabber = XMPPInitialiseCompStream( + parsee_conf->component_addr, parsee_conf->component_host, parsee_conf->component_port ); + Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber); if (!XMPPAuthenticateCompStream( jabber, parsee_conf->shared_comp_secret @@ -290,7 +292,6 @@ Main(Array *args, HashMap *env) { char *parsee = ParseeMXID(conf.handlerArgs); - /* TODO: An hardcoded avatar like this sucks. */ ASSetAvatar(parsee_conf, parsee, "mxc://tedomum.net/" diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 0898063..3b4ce45 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -44,6 +44,9 @@ ParseeConfigLoad(char *conf) #define CopyToInt(to, str) config->to = (int) ( \ JsonValueAsInteger(HashMapGet(json, str)) \ ) +#define CopyToBool(to, str) config->to = (int) ( \ + JsonValueAsBoolean(HashMapGet(json, str)) \ + ) config->http_threads = 8; config->xmpp_threads = 8; @@ -58,8 +61,14 @@ ParseeConfigLoad(char *conf) CopyToStr(server_base, "hs_base"); CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); + CopyToBool(homeserver_tls, "hs_tls"); + if (!HashMapGet(json, "hs_tls")) + { + config->homeserver_tls = true; + } CopyToInt(component_port, "component_port"); + CopyToStr(component_addr, "component_addr"); CopyToStr(component_host, "component_host"); CopyToStr(shared_comp_secret, "shared_secret"); CopyToInt(max_stanza_size, "max_stanza_size"); @@ -129,6 +138,7 @@ ParseeConfigFree(void) return; } Free(config->component_host); + Free(config->component_addr); Free(config->shared_comp_secret); Free(config->db_path); Free(config->homeserver_host); diff --git a/src/Parsee/User.c b/src/Parsee/User.c index 31ce6ff..5a945ea 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -692,7 +692,7 @@ ParseeToUnauth(ParseeData *data, char *mxc) Uri *url = NULL; char *ret; char *key, *hmac; -#define PAT "%s/_matrix/client/v1/media/download/%s%s?hmac=%s" +#define PAT "%s/media/%s%s?hmac=%s" size_t l; if (!data || !mxc) { diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 42e2992..840bec2 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -19,7 +19,7 @@ #define DEFAULT_PROSODY_PORT 5347 XMPPComponent * -XMPPInitialiseCompStream(char *host, int port) +XMPPInitialiseCompStream(char *addr, char *host, int port) { int sd = -1; struct addrinfo hints, *res, *res0; @@ -28,12 +28,17 @@ XMPPInitialiseCompStream(char *host, int port) Stream *stream; XMPPComponent *comp; + if (!addr) + { + addr = host; + } + snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - error = getaddrinfo(host, serv, &hints, &res0); + error = getaddrinfo(addr, serv, &hints, &res0); if (error) { const char *error_str = gai_strerror(error); @@ -66,6 +71,10 @@ XMPPInitialiseCompStream(char *host, int port) if (sd < 0) { + Log(LOG_ERR, + "%s: cannot connect to '%s': no socket available", __func__, + host + ); return NULL; } freeaddrinfo(res0); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 228bf27..a2c73d2 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -41,9 +41,11 @@ typedef struct ParseeConfig { /* Homeserver port info */ char *homeserver_host; int homeserver_port; + int homeserver_tls; /* ------- JABBER -------- */ + char *component_addr; char *component_host; char *shared_comp_secret; int component_port; diff --git a/src/include/Routes.h b/src/include/Routes.h index 8d233e6..cfeea4f 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -75,7 +75,7 @@ typedef struct ParseeCmdArg { X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \ X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \ X_ROUTE("/_matrix/app/v1/ping", RoutePing) \ - X_ROUTE("/_matrix/client/v1/media/download/(.*)/(.*)", RouteMedia) + X_ROUTE("/media/(.*)/(.*)", RouteMedia) #define X_ROUTE(path, name) extern void * name(Array *, void *); ROUTES diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 92a5d80..22e98f6 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -22,7 +22,7 @@ typedef struct 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 XMPPComponent * XMPPInitialiseCompStream(char *host, int port); +extern XMPPComponent * XMPPInitialiseCompStream(char *addr, 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 From 0facbaa5e5e4c0d90fd9742a4b4f86f48b9ef7d6 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 7 Jan 2025 15:14:47 +0000 Subject: [PATCH 07/44] [ADD/FIX/WIP] Allow reverting noflys, ignore MUC DMs (for now) --- src/Commands/BanUser.c | 19 +++++++++++++++++++ src/MatrixEventHandler.c | 14 +++++++------- src/Parsee/Utils/Nofly.c | 22 ++++++++++++++++++++++ src/XMPPThread/Stanzas/Message.c | 17 ++++++++++++++--- src/include/Parsee.h | 9 ++++++++- src/include/Routes.h | 4 ++++ 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/Commands/BanUser.c b/src/Commands/BanUser.c index adc505e..dd380ed 100644 --- a/src/Commands/BanUser.c +++ b/src/Commands/BanUser.c @@ -28,6 +28,25 @@ CommandHead(CmdBanUser, cmd, argp) BotDestroy(); } +CommandHead(CmdNoFlyListDel, cmd, argp) +{ + ParseeCmdArg *args = argp; + ParseeData *data = args->data; + HashMap *event = args->event; + char *user = HashMapGet(cmd->arguments, "user"); + BotInitialise(); + + if (!user) + { + BotDestroy(); + return; + } + + ReplySprintf("Unbanning %s", user); + ParseeGlobalUnban(data, user); + + BotDestroy(); +} CommandHead(CmdNoFlyList, cmd, argp) { ParseeCmdArg *args = argp; diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index aab9f25..89e342d 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -291,10 +291,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) char *room_id = GrabString(event, 1, "room_id"); char *matrix_sender = GrabString(event, 1, "sender"); char *chat_id = NULL, *muc_id = NULL; - char *user; + char *user = NULL; - DbRef *room_data; - HashMap *data_json; + DbRef *room_data = NULL; + HashMap *data_json = NULL; bool direct = false; if (!data || !event || !from || !to) @@ -326,8 +326,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) } else { - char *matrix_name, *matrix_avatar; - char *mime, *sha; + char *matrix_name = NULL, *matrix_avatar = NULL; + char *mime = NULL, *sha = NULL; muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) @@ -361,9 +361,9 @@ static void ParseeMessageHandler(ParseeData *data, HashMap *event) { XMPPComponent *jabber = data->jabber; - StanzaBuilder *builder; + StanzaBuilder *builder = NULL; DbRef *ref = NULL; - HashMap *json; + HashMap *json = NULL; char *unedited_id = MatrixGetEdit(event); char *body = GrabString(event, 2, "content", "body"); diff --git a/src/Parsee/Utils/Nofly.c b/src/Parsee/Utils/Nofly.c index 8a82c0f..439c7d0 100644 --- a/src/Parsee/Utils/Nofly.c +++ b/src/Parsee/Utils/Nofly.c @@ -6,6 +6,28 @@ #include #include +void +ParseeGlobalUnban(ParseeData *data, char *glob) +{ + DbRef *ref; + HashMap *j; + if (!data || !glob) + { + return; + } + + ref = DbLock(data->db, 1, "global_bans"); + if (!ref) + { + ref = DbCreate(data->db, 1, "global_bans"); + } + + j = DbJson(ref); + + JsonValueFree(HashMapDelete(j, glob)); + + DbUnlock(data->db, ref); +} void ParseeGlobalBan(ParseeData *data, char *glob, char *reason) { diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 3c57e69..5497f19 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -394,11 +394,17 @@ end_error: bool media_enabled = chat_id ? ParseeIsMediaEnabled(args, chat_id) : true ; - + bool is_parsee; + /* Note that chat_id still has meaningful info as a boolean + * despite being freed */ Free(chat_id); - pthread_mutex_unlock(&thr->info->chk_lock); + parsee = ParseeJID(args); + is_parsee = StrEquals(to, parsee); + Free(parsee); + parsee = NULL; + LazyRegister(args, encoded, !chat ? res : NULL); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) { @@ -411,7 +417,12 @@ end_error: /* Check if it is a media link */ oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); - if (oob && data && media_enabled) + if (chat_id && StrEquals(type, "chat") && !is_parsee) + { + /* Very clearly a MUC DM. */ + event_id = NULL; + } + else if (oob && data && media_enabled) { char *mxc, *mime = NULL; HashMap *content = NULL; diff --git a/src/include/Parsee.h b/src/include/Parsee.h index a2c73d2..8bc4429 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -397,10 +397,17 @@ extern void ParseeDestroyNickTable(void); * to ban them from rooms where Parsee has the ability to do so ("noflying"). * --------------- * Returns: NOTHING - * See-Also: ParseeManageBan + * See-Also: ParseeManageBan, ParseeGlobalUnban * Modifies: the database */ extern void ParseeGlobalBan(ParseeData *, char *user, char *reason); +/** Revokes a user/room/MUC's nofly status + * --------------- + * Returns: NOTHING + * See-Also: ParseeManageBan, ParseeGlobalBan + * Modifies: the database */ +extern void ParseeGlobalUnban(ParseeData *, char *user); + /** Verifies if a user was banned globally. If so (and if {room} is set), * tries to ban the user from it. * --------------- diff --git a/src/include/Routes.h b/src/include/Routes.h index cfeea4f..b927eff 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -27,6 +27,10 @@ typedef struct ParseeCmdArg { "ban-list", CmdNoFlyList, \ "Globally bans a user from using Parsee" \ ) \ + X_COMMAND( \ + "unban-list", CmdNoFlyListDel, \ + "Globally unbans a user from using Parsee" \ + ) \ X_COMMAND( \ "nuke-muc", CmdUnlinkMUC, \ "Removes a MUC. Users should then run " \ From 389870c5d361597023b324e0c71a49541e7f9509 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 25 Jan 2025 12:20:47 +0000 Subject: [PATCH 08/44] [FIX] Ignore unavailable statuses They aren't that useful, especially in MUCs. --- etc/man/man7/.parsee-bridge-guidebook.7.swp | Bin 0 -> 12288 bytes etc/man/man7/.parsee-cmd-syntax.7.swp | Bin 0 -> 12288 bytes etc/man/man7/parsee-bridge-guidebook.7 | 70 ++++++++++++++++++++ src/MatrixEventHandler.c | 18 +++-- src/Parsee/Utils/Formatting.c | 4 +- src/Unistr.c | 10 +++ src/XMPPThread/Stanzas/IQ.c | 5 +- src/XMPPThread/Stanzas/Presence.c | 4 +- src/include/Unistring.h | 6 ++ 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 etc/man/man7/.parsee-bridge-guidebook.7.swp create mode 100644 etc/man/man7/.parsee-cmd-syntax.7.swp create mode 100644 etc/man/man7/parsee-bridge-guidebook.7 diff --git a/etc/man/man7/.parsee-bridge-guidebook.7.swp b/etc/man/man7/.parsee-bridge-guidebook.7.swp new file mode 100644 index 0000000000000000000000000000000000000000..50976eabdb0b742fe8fbb5be8a1dc7c97357d1b7 GIT binary patch literal 12288 zcmeHN&5ImG6fYIOjf$T1ATKMJ1a^88KZwS!Y<9?+_o|nz z(ZNT0YjjU^P~dgF5O?17+T>Mnv-oCsgUAxeS1Acif2}jBbTUz$Nv#t#GUiNl_@7t}V*&$#feSKFyK(34J)3vje#n>ltqXhU#v3*- zI4uka3NJ|NZ~@bwa!aJOMloYy#H; zdw}1r72;RmLm&li0nY6a;wRue;0@pv;6>mC;A!AE@Br}hH9~v^dfc_{f7^TXyXKJbi1^Ca${YHVX7@9Ca!Z; zIKub3NK{2-J<4>98#n@2ERv~cW#u60F%`y<%rY}o35{lynOJ6&$!X;6p6so3`x{*$ zw4;eCRE424(#4j^wv?sZZ0TY`E|rePI#c8f%}iZUYI0>oW@G}hx;XE$s&eAYzp(a`S7XCk5jtrr;PQq^9Kp0b$k0|9Z3@nn%B@)l|f6Jp!jsb*&t z0)!{9J||rzk?48qWrmQBWYDcYsNYph!@Gyc9De%lWcUCJ@Jq(SbyFatw~quvp(5~(e$OM=sAa@=Kj7?UALWa)+iR5^x+!j#JiBS~Y1OLY8fUP7H^Gd7%5Jsg6@$*cQXN zesYztgRLM?32gx!X#Hv^K+t3zmdRPnT=rO16>&8yol5q$l1=p_MHHwplcR%h`TFW^ zJ>f#~o-&8fZ(f>$`;2cJoEe|ZUh_MJAdein{f;}gOtJA;IE8xnXvsut&Z=M{uotO0}h z+=;jrEJDoCSA1c&{<2uK+h@tvqjnSr%MNer9r~<%363FPnDs8R+OP)qkW_9Vns$*L zpS*NICHFD(V9gE9PuR=4h|vxvjVogqg`MjBz7f$NQ)mF37fzPJ*BI`OYhNnR2c~g_ zkVxWRI_ZHbjMR#VIdm959V!dKS&k97OP-gWUDN!y3VeGSiL zmgKt7C{u|o&PD_>nFZeBd;Q_*_0`Q4PKFTWUmrxL4lT3S=sG+0Q6yE})Qg;_qdCFu z4fmJVu)#;e6Z6N5Kb~%YVfTY@=jDCbM$I7 M2BeIpau$i-0os$Jhx%iV+&`&e|jG4|iwX z)UC>o8$EC-f*XSWg1>_^;*7+F0~|PT;5+MesMzP5W0g2|HfKd! z&5whcT_m}>3E5CdXB z42Xf}+(15Cqt7sh7s`1w%KO@5_wrG0Vn7Ut0Wly3#DEwO17bi7hygJm2E@Q~Xg~!- zS6?A|=^T>B|NmEi|NrzV(RV)0QbQs-~@aCZi4He1ulS> z0fE2I6a5AL1b=|v!EfLf@HKb{z5-u@&%sUb9=HbH20tUu@4>g=A$S1pfx939Z-Fb| zP2lGAB9KcAhygJm2E>3E5CdXB42Xfp40wCIJcheoze?Wl1`pe9ZXOH|iH@ema*+D- zSSLB-_dE%7#>8TEQyx=JHRm71x^@_L$omRUy zY!hYL&a|JK+d5j{)gF)5Gg~2*1gnQC^s-~o4=y?*n0 zd&ut}4!GYO6gzX!=HuRe8x6;M*iyWI*ll)tm`{b>T8fm-IW#&7iVaXK#6Tx1)^NEf KR_3X~w)+SCjabber, jid, rev, hash, -1, true) && nonce < 32) { char *nonce_str = StrInt(nonce); char *input = StrConcat(3, sender, name, nonce_str); char *hex = ParseeHMACS(data->id, input); + Unistr *filterASCII = UnistrFilter(uninick, UnistrIsASCII); if (strlen(hex) >= 8) { @@ -45,6 +48,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char Free(nick); Free(rev); + Free(revscii); nick = StrConcat(4, name, "[", hex, "]"); rev = StrConcat(3, muc, "/", nick); @@ -364,13 +368,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) StanzaBuilder *builder = NULL; DbRef *ref = NULL; HashMap *json = NULL; - - char *unedited_id = MatrixGetEdit(event); + + char *m_sender = GrabString(event, 1, "sender"); + char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); - char *m_sender = GrabString(event, 1, "sender"); - char *chat_id, *muc_id; + char *chat_id = NULL, *muc_id = NULL; char *reply_id = MatrixGetReply(event); char *xepd = ParseeXMPPify(event); char *type, *user, *xmppified_user = NULL, *to = NULL; @@ -381,6 +385,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *encoded_from = NULL; bool direct = false; + unedited_id = MatrixGetEdit(event); if (unedited_id) { @@ -455,6 +460,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Free(name); Free(avatar); } + if (reply_id) { /* TODO: Monocles chat DM users HATE this trick! @@ -511,8 +517,8 @@ end: Free(stanza); Free(sender); Free(unauth); - Free(unedited_id); Free(encoded_from); + Free(unedited_id); DbUnlock(data->db, ref); ref = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 2abf3b5..8f41a56 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -210,9 +210,11 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) static char * GetRawBody(HashMap *event) { - if (MatrixGetEdit(event)) + void *id; + if ((id = MatrixGetEdit(event))) { char *new = GrabString(event, 3, "content", "m.new_content", "body"); + Free(id); if (new) { return new; diff --git a/src/Unistr.c b/src/Unistr.c index 5eaae43..a1eb1e4 100644 --- a/src/Unistr.c +++ b/src/Unistr.c @@ -192,6 +192,16 @@ UnistrGetch(Unistr *unistr, size_t i) return i < unistr->length ? unistr->codepoints[i] : 0; } bool +UnistrIsASCII(uint32_t u) +{ + if (u == 0) + { + return NULL; + } + + return u < 0x7F; +} +bool UnistrIsBMP(uint32_t u) { if (u == 0) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 17092b0..af71cca 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -572,7 +572,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) { XMLElement *iq_reply, *query; - XMLElement *name, *version; + XMLElement *name, *version, *os; iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "to", from); @@ -585,12 +585,15 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { name = XMLCreateTag("name"); version = XMLCreateTag("version"); + os = XMLCreateTag("os"); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); + XMLAddChild(os, XMLCreateText(VERSION "POSIX-like")); } XMLAddChild(query, name); XMLAddChild(query, version); + XMLAddChild(query, os); XMLAddChild(iq_reply, query); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); diff --git a/src/XMPPThread/Stanzas/Presence.c b/src/XMPPThread/Stanzas/Presence.c index 65c2082..947fc4b 100644 --- a/src/XMPPThread/Stanzas/Presence.c +++ b/src/XMPPThread/Stanzas/Presence.c @@ -298,7 +298,7 @@ end_item: Free(room); FreeStatuses(statuses); } - if (status) + if (status && !StrEquals(type, "unavailable")) { XMLElement *status_data = ArrayGet(status->children, 0); char *decode_from = ParseeLookupJID(oid); @@ -309,6 +309,8 @@ end_item: status_str = status_data->data; } + + /* TODO: "The server will automatically set a user's presence to * unavailable if their last active time was over a threshold value * (e.g. 5 minutes)." diff --git a/src/include/Unistring.h b/src/include/Unistring.h index 6fbc296..d90d61c 100644 --- a/src/include/Unistring.h +++ b/src/include/Unistring.h @@ -64,6 +64,12 @@ extern void UnistrFree(Unistr *unistr); * Returns: whenever the character is within the BMP */ extern bool UnistrIsBMP(uint32_t u); +/** Returns true IFF the character is within the 7-bit ASCII range + * not 0x0000 + * ------------------------------------------------------------ + * Returns: whenever the character is within ASCII */ +extern bool UnistrIsASCII(uint32_t u); + typedef bool (*UnistrFilterFunc)(uint32_t u); /** "Filters" characters in a Unistring by codepoint, removing * those with callbacks which return false into a new unistring. From f9de7f17504c01759d59e4b581d36a3832cd8c70 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 28 Jan 2025 15:01:57 +0000 Subject: [PATCH 09/44] [MOD/FIX] Be a bit more specific with component errors --- src/MatrixEventHandler.c | 10 ++-------- src/Signal.c | 7 +------ src/XMPP/Component.c | 8 ++++++++ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 60a41c7..bf42124 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -26,20 +26,15 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char char *rev = StrConcat(3, muc, "/", nick); int nonce = 0; - Log(LOG_DEBUG, "MUCJOINER: filtered '%s' to '%s'", name, nick); - UnistrFree(uninick); UnistrFree(filtered); - UnistrFree(filterASCII); - /* TODO: Make sure that we fall back to plain ASCII if it fails too many - * times. */ + /* TODO: vCards! */ while (!XMPPJoinMUC(data->jabber, jid, rev, hash, -1, true) && nonce < 32) { char *nonce_str = StrInt(nonce); char *input = StrConcat(3, sender, name, nonce_str); char *hex = ParseeHMACS(data->id, input); - Unistr *filterASCII = UnistrFilter(uninick, UnistrIsASCII); if (strlen(hex) >= 8) { @@ -48,7 +43,6 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char Free(nick); Free(rev); - Free(revscii); nick = StrConcat(4, name, "[", hex, "]"); rev = StrConcat(3, muc, "/", nick); @@ -368,7 +362,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) StanzaBuilder *builder = NULL; DbRef *ref = NULL; HashMap *json = NULL; - + char *m_sender = GrabString(event, 1, "sender"); char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); diff --git a/src/Signal.c b/src/Signal.c index e9d00e8..3f01dcb 100644 --- a/src/Signal.c +++ b/src/Signal.c @@ -26,11 +26,6 @@ SignalHandler(int signal) HttpServerStop(data->server); return; } - if (signal == SIGPIPE) - { - Log(LOG_DEBUG, "Caught a SIGPIPE..."); - return; - } } bool @@ -46,7 +41,7 @@ ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp) sa.sa_flags = SA_RESTART; #define Register(act) (sigaction(act, &sa, NULL) >= 0) - if (!Register(SIGTERM) || !Register(SIGINT) || !Register(SIGPIPE)) + if (!Register(SIGTERM) || !Register(SIGINT)) { Log(LOG_ERR, "Couldn't register signals..."); return false; diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 840bec2..1bdff53 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -61,6 +61,10 @@ XMPPInitialiseCompStream(char *addr, char *host, int port) if (connect(sd, res->ai_addr, res->ai_addrlen) < 0) { + Log(LOG_ERR, + "%s: cannot connect to '%s': %s", __func__, + host, strerror(errno) + ); close(sd); sd = -1; continue; @@ -82,6 +86,10 @@ XMPPInitialiseCompStream(char *addr, char *host, int port) stream = StreamFd(sd); if (!stream) { + Log(LOG_ERR, + "%s: cannot connect to '%s': %s", __func__, + host, "couldn't create a Cytoplasm stream" + ); close(sd); return NULL; } From b485298fbc6893582ef4623ae6cea7643119fa49 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 29 Jan 2025 19:15:23 +0100 Subject: [PATCH 10/44] [FIX] Log error if the config was not parsed --- src/Parsee/Config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 3b4ce45..46a391f 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -36,6 +36,12 @@ ParseeConfigLoad(char *conf) return; } json = JsonDecode(stream); + if (!json) + { + Log(LOG_ERR, "Could not parse config JSON"); + StreamClose(stream); + return; + } config = Malloc(sizeof(*config)); #define CopyToStr(to, str) config->to = StrDuplicate( \ From 1e7d71f9f61890675a987257592217e32de0dbd2 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 5 Feb 2025 08:54:45 +0100 Subject: [PATCH 11/44] [ADD] Allow people to see component errors --- src/XMPP/Component.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 1bdff53..12f9f9e 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -182,6 +182,49 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared) { Log(LOG_ERR, "Excepted empty handshake reply, got nonsense."); Log(LOG_ERR, "Another service (possibly Parsee) may have taken over."); + while ((ev = XMLCrank(sax))) + { + char *key, *val; + switch (ev->type) + { + case XML_LEXER_STARTELEM: + Log(LOG_DEBUG, "<%s>", ev->element); + + LogConfigIndent(LogConfigGlobal()); + LogConfigIndent(LogConfigGlobal()); + /* TODO: Log out attributes a little better */ + while (HashMapIterate(ev->attrs, &key, (void **) &val)) + { + Log(LOG_DEBUG, "(%s=%s)", key, val); + } + LogConfigUnindent(LogConfigGlobal()); + LogConfigUnindent(LogConfigGlobal()); + + LogConfigIndent(LogConfigGlobal()); + break; + case XML_LEXER_ELEM: + Log(LOG_DEBUG, "<%s/>", ev->element); + LogConfigIndent(LogConfigGlobal()); + LogConfigIndent(LogConfigGlobal()); + /* TODO: Log out attributes a little better */ + while (HashMapIterate(ev->attrs, &key, (void **) &val)) + { + Log(LOG_DEBUG, "(%s=%s)", key, val); + } + LogConfigUnindent(LogConfigGlobal()); + LogConfigUnindent(LogConfigGlobal()); + break; + case XML_LEXER_ENDELEM: + Log(LOG_DEBUG, "", ev->element); + LogConfigIndent(LogConfigGlobal()); + break; + case XML_LEXER_DATA: + Log(LOG_DEBUG, "%s", ev->data); + break; + } + XMLFreeEvent(ev); + } + LogConfigIndentSet(LogConfigGlobal(), 0); Log(LOG_ERR, ""); Log(LOG_ERR, "Simply jealous of that other service..."); Free(stream_id); From 110a1b695f2689b982944971bfb654d3ba43bc1c Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 5 Feb 2025 08:58:25 +0100 Subject: [PATCH 12/44] [FIX] Fix the indenting --- src/XMPP/Component.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPP/Component.c b/src/XMPP/Component.c index 12f9f9e..e3ff4e4 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -215,8 +215,8 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared) LogConfigUnindent(LogConfigGlobal()); break; case XML_LEXER_ENDELEM: + LogConfigUnindent(LogConfigGlobal()); Log(LOG_DEBUG, "", ev->element); - LogConfigIndent(LogConfigGlobal()); break; case XML_LEXER_DATA: Log(LOG_DEBUG, "%s", ev->data); From 4f694129fc9ff297cae318fb53fe238948a5bab1 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 7 Feb 2025 18:34:45 +0000 Subject: [PATCH 13/44] [FIX] Fix silly SEGV Thanks, EP --- src/MatrixEventHandler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index bf42124..fa44d2d 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -255,7 +255,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event) return; } - if (*body != '!') + if (!body || *body != '!') { /* All commands are to be marked with a ! */ Free(profile); From c2536c2e846bb764bcb59ebd42866af81e94a745 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 7 Feb 2025 18:52:58 +0000 Subject: [PATCH 14/44] [FIX] Log out some MUC information --- src/XMPP/MUC.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 67bc9d1..9ecfcf2 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -44,6 +44,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) Free(uuid); if (!iq_query || !StrEquals(iq_query->name, "iq")) { + Log(LOG_ERR, "Didn't receive an stanza"); XMLFreeElement(iq_query); return false; } @@ -55,6 +56,11 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) "conference")) { XMLFreeElement(iq_query); + Log(LOG_DEBUG, "MUC INFO ERROR"); + Log(LOG_DEBUG, + "identityp=%p category=%s", identity, + identity ? HashMapGet(identity->attrs, "category") : NULL + ); return false; } From 1936be0078dcd7a53b35577220bca8228792356a Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 8 Feb 2025 08:45:21 +0000 Subject: [PATCH 15/44] [FIX] Fix minor DB fuckup with tools Used to open a flatfile database whenever it couldn't find an LMDB one, which obviously caused *problems* --- tools/adminify.c | 39 +++++++++++++++++++-------------------- tools/common.h | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/tools/adminify.c b/tools/adminify.c index e87d97e..1fdd6cf 100644 --- a/tools/adminify.c +++ b/tools/adminify.c @@ -89,31 +89,30 @@ Main(Array *args, HashMap *env) glob = ArrayGet(args, 2); parsee = GetDB(db_path); - if (parsee) + if (!parsee) { + Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); + (void) env; + return EXIT_FAILURE; + } - if (glob) - { - Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path); - AddAdmin(parsee, glob); - Log(LOG_INFO, "Successfully added glob %s.", glob); - Log(LOG_INFO, "*I'm jealous of all these admins!*"); - - DbClose(parsee); - return EXIT_SUCCESS; - } - - /* List admins */ - Log(LOG_INFO, "Admin list:"); - LogConfigIndent(LogConfigGlobal()); - ListAdmins(parsee); - LogConfigUnindent(LogConfigGlobal()); + if (glob) + { + Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path); + AddAdmin(parsee, glob); + Log(LOG_INFO, "Successfully added glob %s.", glob); + Log(LOG_INFO, "*I'm jealous of all these admins!*"); DbClose(parsee); return EXIT_SUCCESS; } - Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); - (void) env; - return EXIT_FAILURE; + /* List admins */ + Log(LOG_INFO, "Admin list:"); + LogConfigIndent(LogConfigGlobal()); + ListAdmins(parsee); + LogConfigUnindent(LogConfigGlobal()); + + DbClose(parsee); + return EXIT_SUCCESS; } diff --git a/tools/common.h b/tools/common.h index eee4793..9687ea3 100644 --- a/tools/common.h +++ b/tools/common.h @@ -108,7 +108,7 @@ GetDB(char *config) { ret = DbOpenLMDB(db_path, lmdb_size); } - if (!ret) + else { ret = DbOpen(db_path, 0); } From f94db460ac6459f8ebe94d63f7140c2e40325dcc Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 9 Feb 2025 21:51:38 +0000 Subject: [PATCH 16/44] [FIX/WIP] Fix annoying formatting bug Fixes #16 --- src/Parsee/Utils/Formatting.c | 9 ++ src/XML/Parser.c | 6 ++ src/XML/SAX.c | 37 ++++++++- src/include/XML.h | 2 + tools/plumb.c | 152 ++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 tools/plumb.c diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 8f41a56..17f513f 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -267,6 +268,14 @@ ParseeXMPPify(HashMap *event) html = StrConcat(3, "", html, ""); elem = XMLCDecode(StrStreamReader(html), true, true); + if (!elem) + { + /* Settle for the raw body instead. + * TODO: Have the parser be more leinent on errors in HTML mode. */ + char *body = GetRawBody(event); + Free(html); + return StrDuplicate(body); + } flags.quote = false; xepd = XMPPifyElement(event, elem, flags); diff --git a/src/XML/Parser.c b/src/XML/Parser.c index 506e49d..350f0e4 100644 --- a/src/XML/Parser.c +++ b/src/XML/Parser.c @@ -31,6 +31,12 @@ XMLCDecode(Stream *stream, bool autofree, bool html) bool flag = false; switch (event->type) { + case XML_ERROR: + XMLFreeEvent(event); + XMLFreeElement(ret); + ArrayFree(stack); + XMLFreeLexer(lexer); + return NULL; case XML_LEXER_STARTELEM: /* Create a new element that will populated. */ top = XMLCreateTag(event->element); diff --git a/src/XML/SAX.c b/src/XML/SAX.c index 21430bb..defea6b 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -58,6 +58,7 @@ static char * XMLPopElement(XMLexer *lexer); static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateRelax(XMLexer *lexer); +static XMLEvent * XMLCreateError(XMLexer *lexer); static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end); static XMLEvent * XMLCreateData(XMLexer *lexer); @@ -198,7 +199,9 @@ XMLCrank(XMLexer *lexer) else if (XMLookahead(lexer, "--", false)) { /* Throw error */ - return NULL; + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } break; case XML_STATE_PI: @@ -215,6 +218,9 @@ XMLCrank(XMLexer *lexer) if (!attrname) { /* TODO: Throw error */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } XMLPushElement(lexer, attrname); @@ -241,7 +247,10 @@ XMLCrank(XMLexer *lexer) } else if (XMLookahead(lexer, "'", true)) { - while (true); + //while (true); uh oh + XMLFreeEvent(event); + event = XMLCreateError(lexer); + break; } break; case XML_STATE_ATTRTAIL: @@ -250,6 +259,8 @@ XMLCrank(XMLexer *lexer) if (!XMLookahead(lexer, ">", true)) { /* TODO: Throw error. */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); break; } lexer->state = XML_STATE_NONE; @@ -258,6 +269,8 @@ XMLCrank(XMLexer *lexer) break; default: /* TODO */ + XMLFreeEvent(event); + event = XMLCreateError(lexer); break; } /* TODO: Crank our XML parser. */ @@ -693,6 +706,26 @@ XMLCreateData(XMLexer *lexer) return event; } static XMLEvent * +XMLCreateError(XMLexer *lexer) +{ + XMLEvent *event = Malloc(sizeof(*event)); + size_t elements = ArraySize(lexer->data.elements); + + event->type = XML_ERROR; + event->element = elements ? + StrDuplicate(ArrayGet(lexer->data.elements, elements - 1)) : + NULL; + event->attrs = NULL; + event->data = NULL; + + /* TODO */ + event->line = 0; + event->col = 0; + event->offset = 0; + + return event; +} +static XMLEvent * XMLCreateRelax(XMLexer *lexer) { XMLEvent *event = Malloc(sizeof(*event)); diff --git a/src/include/XML.h b/src/include/XML.h index 8fd108d..c7b6610 100644 --- a/src/include/XML.h +++ b/src/include/XML.h @@ -13,6 +13,8 @@ typedef struct XMLEvent { XML_LEXER_DATA, XML_LEXER_ELEM, /* Empty attr */ XML_LEXER_ENDELEM, + + XML_ERROR, XML_RELAX } type; diff --git a/tools/plumb.c b/tools/plumb.c new file mode 100644 index 0000000..1bc9e14 --- /dev/null +++ b/tools/plumb.c @@ -0,0 +1,152 @@ +/* plumb.c - Small utility to manage plumbings from a shutoff instance. + * ============================================================ + * TODO: write other commands, and move some code to common.h + * + * Under CC0, as its a rather useful example of a Parsee tool. + * See LICENSE for more information about Parsee's licensing. */ + +#include "common.h" + +#include + +static void +DeletePlumbID(Db *parsee, char *chat_id) +{ + DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id); + DbRef *chats = DbLock(parsee, 1, "chats"); + char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id"); + char *jabber_id = GrabString(DbJson(chat_id_ref), 1, "jabber_id"); + + HashMap *rooms = GrabObject(DbJson(chats), 1, "rooms"); + HashMap *mucs = GrabObject(DbJson(chats), 1, "mucs"); + + JsonValueFree(HashMapDelete(rooms, matrix_id)); + JsonValueFree(HashMapDelete(mucs, jabber_id)); + + DbUnlock(parsee, chat_id_ref); + DbUnlock(parsee, chats); + DbDelete(parsee, 2, "chats", chat_id); +} +static void +DeletePlumb(Db *parsee, char *potential_id) +{ + if (!parsee || !potential_id) + { + return; + } + + if (!strncmp(potential_id, "xmpp:", 5)) + { + DbRef *ref; + HashMap *mucs; + char *chat_id; + /* Try to parse it as an XMPP address */ + potential_id += 5; + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + mucs = GrabObject(DbJson(ref), 1, "mucs"); + chat_id = StrDuplicate(GrabString(mucs, 1, potential_id)); + DbUnlock(parsee, ref); + + DeletePlumbID(parsee, chat_id); + Free(chat_id); + + return; + } + if (*potential_id == '!') + { + /* Try to parse it as a Matrix room ID */ + DbRef *ref; + HashMap *rooms; + char *chat_id; + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + rooms = GrabObject(DbJson(ref), 1, "rooms"); + chat_id = StrDuplicate(GrabString(rooms, 1, potential_id)); + DbUnlock(parsee, ref); + + DeletePlumbID(parsee, chat_id); + Free(chat_id); + + return; + } + + /* Try to parse it as a chat ID */ + DeletePlumbID(parsee, potential_id); +} +static void +ListPlumbs(Db *parsee) +{ + DbRef *ref; + HashMap *mucs; + + char *muc; + JsonValue *value; + if (!parsee) + { + return; + } + + ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats"); + mucs = GrabObject(DbJson(ref), 1, "mucs"); + while (HashMapIterate(mucs, &muc, (void **) &value)) + { + char *chat_id = JsonValueAsString(value); + DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id); + char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id"); + + /* TODO */ + Log(LOG_INFO, "- Plumb xmpp:%s <=> %s", muc, matrix_id); + Log(LOG_INFO, " - ID=%s", chat_id); + + DbUnlock(parsee, chat_id_ref); + } + + DbUnlock(parsee, ref); +} + +int +Main(Array *args, HashMap *env) +{ + char *db_path, *action, *exec; + int ret = EXIT_SUCCESS; + Db *parsee; + + exec = ArrayGet(args, 0); + + if (ArraySize(args) < 3) + { + Log(LOG_ERR, "Usage: %s [config] [action] ", exec); + return EXIT_FAILURE; + } + + db_path = ArrayGet(args, 1); + action = ArrayGet(args, 2); + + parsee = GetDB(db_path); + if (!parsee) + { + Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); + return EXIT_FAILURE; + } + + if (StrEquals(action, "list") || StrEquals(action, "ls")) + { + ListPlumbs(parsee); + } + else if (StrEquals(action, "del")) + { + if (ArraySize(args) != 4) + { + Log(LOG_ERR, "%s: please show a !roomid:matrix.org, xmpp:mucid@jabber.org, or local hash", exec); + ret = EXIT_FAILURE; + goto end; + } + DeletePlumb(parsee, ArrayGet(args, 3)); + } + + +end: + DbClose(parsee); + return ret; +} From 176f390c4bfd7f624cbce992c411d6ed70cc4716 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 11 Feb 2025 17:18:01 +0000 Subject: [PATCH 17/44] [FIX] Use the puppet's name when pinged if possible --- src/MatrixEventHandler.c | 2 +- src/Parsee/Utils/Formatting.c | 36 +++++++++++++++++++++-------------- src/XMPPThread/ReListener.c | 2 ++ src/include/Parsee.h | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index fa44d2d..2c5c5bd 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -370,7 +370,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *ev_id = GrabString(event, 1, "event_id"); char *chat_id = NULL, *muc_id = NULL; char *reply_id = MatrixGetReply(event); - char *xepd = ParseeXMPPify(event); + char *xepd = ParseeXMPPify(data, event); char *type, *user, *xmppified_user = NULL, *to = NULL; char *unauth = NULL; char *origin_id = NULL, *stanza = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 17f513f..44e255a 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -20,7 +20,7 @@ typedef struct XMPPFlags { bool quote; } XMPPFlags; static char * -XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) +XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags) { char *xepd = NULL, *tmp = NULL; @@ -70,7 +70,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -83,7 +83,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -96,7 +96,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -129,7 +129,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -144,7 +144,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -162,11 +162,19 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) UserID *id = MatrixParseIDFromMTO(pref); if (id) { + char *real_id = StrConcat(4, "@", id->localpart, ":", id->server); /* TODO: Detect if it already is a Parsee user */ - Concat("@"); - Concat(id->localpart); - Concat(":"); - Concat(id->server); + if (ParseeIsPuppet(conf, real_id)) + { + char *name = ASGetName(conf, NULL, real_id); + Concat((name ? name : real_id)); + Free(name); + } + else + { + Concat(real_id); + } + Free(real_id); } else { @@ -180,7 +188,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -196,7 +204,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); + subxep = XMPPifyElement(conf, event, child, flags); Concat(subxep); Free(subxep); @@ -237,7 +245,7 @@ GetHTMLBody(HashMap *event) return GrabString(event, 2, "content", "formatted_body"); } char * -ParseeXMPPify(HashMap *event) +ParseeXMPPify(ParseeData *data, HashMap *event) { char *type, *format, *html; char *xepd = NULL; @@ -278,7 +286,7 @@ ParseeXMPPify(HashMap *event) } flags.quote = false; - xepd = XMPPifyElement(event, elem, flags); + xepd = XMPPifyElement(data ? data->config : NULL, event, elem, flags); XMLFreeElement(elem); Free(html); diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index f68b887..28d117b 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -56,6 +56,8 @@ XMPPDispatcher(void *argp) if (!stanza) { + /* TODO: We shouldn't be busywaiting. Even with a sleep call. + */ UtilSleepMillis(10); continue; } diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 8bc4429..92aacec 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -356,7 +356,7 @@ extern int ParseeFindDatastartU(char *data); /* XMPP-ifies a message event to XEP-0393 if possible. */ -extern char * ParseeXMPPify(HashMap *event); +extern char * ParseeXMPPify(ParseeData *data, HashMap *event); /* Finds an event ID from an ID in the stanza's attributes */ extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id); From 1c51d573558ca1c591f660325e79c6495fa390ae Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 08:24:18 +0000 Subject: [PATCH 18/44] [ADD] Add accept_pings parameters This is off by default, but highly encouraged for Synapse deployments --- .gitignore | 6 ++++-- CHANGELOG.md | 13 +++++++++++++ build.conf | 4 ++-- src/Main.c | 2 +- src/Parsee/Config.c | 1 + src/include/Parsee.h | 2 ++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 05919ab..5f7d56c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,11 @@ parsee* parsee *.swp .* -data -data/* +data* +data*/* Makefile configure +gmon.out tools/out tools/out/* @@ -24,3 +25,4 @@ tags !.forgejo !.forgejo/* !.forgejo/** + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5999d7f..04358d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,19 @@ commit done between releases. *There is currently no beta releases of Parsee* ## Alpha + +### v0.3.0[lunar-rainbow] +This is the first release of 2025! +TBD +#### New things +- Allow admins to remove users from the nofly list. +- Parsee can now access the homeserver/component locally (rather than over the network) +- The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' +- Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. +#### Bugfixes +- Fix potential infinite loops when processing some messages. +- Parsee now handles pinging puppets from Matrix more sanely. + ### v0.2.0[star-of-hope] <8/11/2024> Fixes some media metadata things, replaces the build system, tries out avatar support some more, MUC contexts, and speeds diff --git a/build.conf b/build.conf index 9ebf253..a2e17c1 100644 --- a/build.conf +++ b/build.conf @@ -1,6 +1,6 @@ -CODE=star-of-hope +CODE=lunar-rainbow NAME=Parsee -VERSION=0.2.0 +VERSION=0.3.0 BINARY=parsee SOURCE=src INCLUDES=src/include diff --git a/src/Main.c b/src/Main.c index 3e740ed..f596126 100644 --- a/src/Main.c +++ b/src/Main.c @@ -63,7 +63,7 @@ ParseeCheckMatrix(void *datp) { static volatile uint64_t streak = 0; ParseeData *data = datp; - if (!ASPing(data->config)) + if (data->config->accept_pings && !ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); if (++streak == 10) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 46a391f..fa4b96e 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -68,6 +68,7 @@ ParseeConfigLoad(char *conf) CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); CopyToBool(homeserver_tls, "hs_tls"); + CopyToBool(homeserver_tls, "accept_pings"); if (!HashMapGet(json, "hs_tls")) { config->homeserver_tls = true; diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 92aacec..5726286 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -43,6 +43,8 @@ typedef struct ParseeConfig { int homeserver_port; int homeserver_tls; + bool accept_pings; + /* ------- JABBER -------- */ char *component_addr; From 7f396a037927330d0063a9bfffcebd76b73457f3 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 08:33:05 +0000 Subject: [PATCH 19/44] [FIX] Write config to the proper field! --- src/Parsee/Config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index fa4b96e..45e6fe5 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -68,11 +68,11 @@ ParseeConfigLoad(char *conf) CopyToStr(homeserver_host, "hs_host"); CopyToInt(homeserver_port, "hs_port"); CopyToBool(homeserver_tls, "hs_tls"); - CopyToBool(homeserver_tls, "accept_pings"); if (!HashMapGet(json, "hs_tls")) { config->homeserver_tls = true; } + CopyToBool(accept_pings, "accept_pings"); CopyToInt(component_port, "component_port"); CopyToStr(component_addr, "component_addr"); From 9a2d4188e2d3886d600d9a6dfe77182bfe6bca9b Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 16:43:43 +0000 Subject: [PATCH 20/44] [FIX] Do not put version in the OS field --- src/XMPPThread/Stanzas/IQ.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index af71cca..1b55bf4 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -589,7 +589,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); - XMLAddChild(os, XMLCreateText(VERSION "POSIX-like")); + XMLAddChild(os, XMLCreateText("POSIX-like")); } XMLAddChild(query, name); XMLAddChild(query, version); From 71f3836ee1dc0c7056156b9e1ec3386df53f5132 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 12 Feb 2025 16:47:48 +0000 Subject: [PATCH 21/44] [ADD] Use actual OS as information --- src/XMPPThread/Stanzas/IQ.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 1b55bf4..5dc9bc3 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -583,13 +584,15 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", "jabber:iq:version"); { + struct utsname info; name = XMLCreateTag("name"); version = XMLCreateTag("version"); os = XMLCreateTag("os"); + uname(&info); XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); - XMLAddChild(os, XMLCreateText("POSIX-like")); + XMLAddChild(os, XMLCreateText(info.sysname)); } XMLAddChild(query, name); XMLAddChild(query, version); From 3bef6afa5dae72d34996c891c225fb3edd8985a7 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 19:10:26 +0000 Subject: [PATCH 22/44] [FIX/WIP] Try escaping room IDs First attempt at dealing with #20 --- src/AS/Send.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AS/Send.c b/src/AS/Send.c index 766f393..a787c53 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -47,11 +47,13 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u ts_str = TSToStr(ts); txn = StrRandom(16); + id = HttpUrlEncode(id); path = StrConcat(11, "/_matrix/client/v3/rooms/", id, "/send/", type, "/", txn, "?", "user_id=", user, "&ts=", ts_str ); + Free(id); Free(txn); Free(ts_str); From 43175e32e52fc14bfa64203ad0123658146001e6 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 20:28:49 +0000 Subject: [PATCH 23/44] [FIX/WIP] Log out error info on ASSend --- src/AS/Send.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/AS/Send.c b/src/AS/Send.c index a787c53..9b81a71 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -33,6 +33,11 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u char *path; char *txn, *ret; char *ts_str; + HttpStatus status; + if (!ret) + { + Log(LOG_ERR, "%", ret); + } HashMap *reply; if (!conf || !id || !type || !user || !c) { @@ -60,10 +65,17 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u ctx = ParseeCreateRequest(conf, HTTP_PUT, path); Free(path); ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, c); + status = ParseeSetRequestJSON(ctx, c); reply = JsonDecode(HttpClientStream(ctx)); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); + if (!ret) + { + Log(LOG_ERR, "Got %s from HTTP", HttpStatusToString(status)); + JsonEncode(reply, StreamStdout(), JSON_PRETTY); + StreamPrintf(StreamStdout(), "\n"); + StreamFlush(StreamStdout()); + } JsonFree(reply); HttpClientContextFree(ctx); From d12255b2265b9cc79891d53e1740ab6071a89484 Mon Sep 17 00:00:00 2001 From: lda Date: Fri, 14 Feb 2025 21:58:42 +0000 Subject: [PATCH 24/44] [FIX/WIP] Do not use room ID for plumbing --- src/Commands/Plumb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Plumb.c b/src/Commands/Plumb.c index f51e4e6..bee94f2 100644 --- a/src/Commands/Plumb.c +++ b/src/Commands/Plumb.c @@ -59,7 +59,7 @@ CommandHead(CmdPlumb, cmd, argp) goto end; } - chat_id = ParseePushMUC(args->data, room_id, muc); + chat_id = ParseePushMUC(args->data, room, muc); // ew. if (chat_id) { char *rev = StrConcat(2, muc, "/parsee"); From e2b014d00056d8e39a5c277e8d286249a16d44e9 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 15 Feb 2025 08:44:25 +0000 Subject: [PATCH 25/44] [FIX/WIP] Fix replies and try fixing a bug with MUCs --- src/AS/Room.c | 18 +++++++++-- src/Main.c | 1 + src/MatrixEventHandler.c | 4 +-- src/Parsee/User.c | 51 ++++++++++++++++++++++++-------- src/Unistr.c | 35 ++++++++++++++++++++++ src/XMPP/Stanza.c | 25 ++++++++++++++++ src/XMPPThread/Stanzas/Message.c | 14 ++++++++- src/include/Parsee.h | 2 +- src/include/Routes.h | 3 +- src/include/Unistring.h | 6 ++++ src/include/XMPP.h | 3 ++ 11 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/AS/Room.c b/src/AS/Room.c index 9e10579..e9691e1 100644 --- a/src/AS/Room.c +++ b/src/AS/Room.c @@ -25,11 +25,13 @@ ASInvite(const ParseeConfig *conf, char *id, char *invited) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/invite", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -60,11 +62,13 @@ ASBan(const ParseeConfig *conf, char *id, char *banned) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/ban", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -95,11 +99,13 @@ ASKick(const ParseeConfig *conf, char *id, char *banned) "@", conf->sender_localpart, ":", conf->server_base ); + id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/rooms/", id, "/kick", "?user_id=", bridge ); Free(bridge); + Free(id); ctx = ParseeCreateRequest( conf, @@ -120,7 +126,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) { HttpClientContext *ctx = NULL; HashMap *json = NULL; - char *path, *ret; + char *path, *ret, *serv; + int status; if (!conf || !id) { return NULL; @@ -139,6 +146,11 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) { masquerade = HttpUrlEncode(masquerade); } + serv = strchr(id, ':'); + if (serv) + { + serv = serv + 1; + } id = HttpUrlEncode(id); path = StrConcat(5, "/_matrix/client/v3/join/", id, "?", @@ -152,7 +164,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(path); json = HashMapCreate(); ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); + status = ParseeSetRequestJSON(ctx, json); JsonFree(json); json = JsonDecode(HttpClientStream(ctx)); @@ -163,6 +175,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(masquerade); Free(id); + (void) serv; // TODO + return ret; } void diff --git a/src/Main.c b/src/Main.c index f596126..10c809f 100644 --- a/src/Main.c +++ b/src/Main.c @@ -298,6 +298,7 @@ Main(Array *args, HashMap *env) "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" ); ASSetName(parsee_conf, parsee, "Parsee bridge"); + Free(parsee); } diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 2c5c5bd..32050dd 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -100,7 +100,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) char *jid = ParseeEncodeMXID(state_key); char *sha = NULL, *mime = NULL; char *avatar = ASGetAvatar(data->config, NULL, state_key); - char *url = ParseeToUnauth(data, avatar); + char *url = ParseeToUnauth(data, avatar, NULL); chat_id = ParseeGetFromRoomID(data, room_id); ASGetMIMESHA(data->config, avatar, &mime, &sha); @@ -417,7 +417,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) type = direct ? "chat" : "groupchat"; user = GrabString(json, 1, "xmpp_user"); - unauth = ParseeToUnauth(data, url); + unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename")); encoded_from = ParseeEncodeMXID(m_sender); xmppified_user = StrConcat(3, diff --git a/src/Parsee/User.c b/src/Parsee/User.c index 5a945ea..a9320d8 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -687,12 +687,13 @@ end: #include char * -ParseeToUnauth(ParseeData *data, char *mxc) +ParseeToUnauth(ParseeData *data, char *mxc, char *filename) { Uri *url = NULL; char *ret; char *key, *hmac; #define PAT "%s/media/%s%s?hmac=%s" +#define PATF "%s/media/%s%s/%s?hmac=%s" size_t l; if (!data || !mxc) { @@ -713,19 +714,43 @@ ParseeToUnauth(ParseeData *data, char *mxc) hmac = ParseeHMACS(data->id, key); Free(key); - l = snprintf(NULL, 0, - PAT, - data->config->media_base, - url->host, url->path, - hmac - ); + if (!filename) + { + l = snprintf(NULL, 0, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); + } + else + { + l = snprintf(NULL, 0, + PATF, + data->config->media_base, + url->host, url->path, filename, + hmac + ); + } ret = Malloc(l + 3); - snprintf(ret, l + 1, - PAT, - data->config->media_base, - url->host, url->path, - hmac - ); + if (!filename) + { + snprintf(ret, l + 1, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); + } + else + { + snprintf(ret, l + 1, + PATF, + data->config->media_base, + url->host, url->path, filename, + hmac + ); + } UriFree(url); Free(hmac); return ret; diff --git a/src/Unistr.c b/src/Unistr.c index a1eb1e4..7eab2e0 100644 --- a/src/Unistr.c +++ b/src/Unistr.c @@ -277,3 +277,38 @@ UnistrGetOffset(Unistr *str, uint32_t sep) } return 0; } +size_t +UnistrGetUTFOffset(char *cstr, size_t unicode) +{ + Unistr *tmp; + size_t ret = 0; + if (!cstr) + { + return 0; + } + + tmp = UnistrCreate(cstr); + for (size_t i = 0; i < unicode && i < tmp->length; i++) + { + uint32_t codepoint = tmp->codepoints[i]; + if (codepoint >= 0x0000 && codepoint <= 0x007F) + { + ret += 1; + } + else if (codepoint >= 0x0080 && codepoint <= 0x07FF) + { + ret += 2; + } + else if (codepoint >= 0x0800 && codepoint <= 0xFFFF) + { + ret += 3; + } + else if (codepoint >= 0x010000 && codepoint <= 0x10FFFF) + { + ret += 4; + } + } +end: + Free(tmp); + return ret; +} diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 27a658e..58c3876 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -159,6 +159,31 @@ XMPPGetReply(XMLElement *elem) return HashMapGet(rep->attrs, "id"); } +ssize_t +XMPPGetReplyOffset(XMLElement *elem) +{ + if (!elem) + { + return -1; + } + for (size_t i = 0; i < ArraySize(elem->children); i++) + { + XMLElement *child = ArrayGet(elem->children, i); + char *xmlns = HashMapGet(child->attrs, "xmlns"); + char *xfor = HashMapGet(child->attrs, "for"); + if (StrEquals(child->name, "fallback") && + StrEquals(xmlns, "urn:xmpp:feature-fallback:0") && + StrEquals(xfor, "urn:xmpp:reply:0")) + { + XMLElement *body = XMLookForUnique(child, "body"); + if (body && HashMapGet(body->attrs, "end")) + { + return strtol(HashMapGet(body->attrs, "end"), NULL, 10); + } + } + } + return -1; +} char * XMPPGetModeration(XMLElement *stanza) { diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 5497f19..c4066d9 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -324,8 +325,9 @@ end_error: { Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime)); } + char *trim = ParseeTrimJID(from); if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && - to && *to == '@') + to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL)) { DbRef *room_ref; HashMap *room_json; @@ -344,6 +346,7 @@ end_error: ParseePushDMRoom(args, to, from, room); } } + Free(trim); /* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER * SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED @@ -518,6 +521,15 @@ end_error: * too. Go figure. */ size_t off = reply_to ? ParseeFindDatastart(data->data) : 0; + size_t stanzaoff = XMPPGetReplyOffset(stanza); + if (reply_to && stanzaoff != -1) + { + off = UnistrGetUTFOffset(data->data, stanzaoff); + } + if (data->data && off >= strlen(data->data)) + { + off = 0; + } HashMap *ev = MatrixCreateMessage(data->data + off); if (reply_to) { diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 5726286..ea5d18b 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -368,7 +368,7 @@ extern char * ParseeDMEventFromID(ParseeData *d, char *r_id, char *ori_id); extern char * ParseeDMEventFromSID(ParseeData *d, char *r_id, char *ori_id); /* Gets a Parsee "shim" link to an MXC, usable as unauth for a limited time */ -extern char * ParseeToUnauth(ParseeData *data, char *mxc); +extern char * ParseeToUnauth(ParseeData *data, char *mxc, char *filename); extern void ParseeInitialiseOIDTable(void); extern void ParseePushOIDTable(char *muc, char *occupant); diff --git a/src/include/Routes.h b/src/include/Routes.h index b927eff..664032b 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -79,7 +79,8 @@ typedef struct ParseeCmdArg { X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \ X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \ X_ROUTE("/_matrix/app/v1/ping", RoutePing) \ - X_ROUTE("/media/(.*)/(.*)", RouteMedia) + X_ROUTE("/media/(.*)/(.*)", RouteMedia) \ + X_ROUTE("/media/(.*)/(.*)/(.*)", RouteMedia) #define X_ROUTE(path, name) extern void * name(Array *, void *); ROUTES diff --git a/src/include/Unistring.h b/src/include/Unistring.h index d90d61c..7eff7a1 100644 --- a/src/include/Unistring.h +++ b/src/include/Unistring.h @@ -82,4 +82,10 @@ extern Unistr * UnistrFilter(Unistr *str, UnistrFilterFunc filter); * -------- * Returns: an offset of the first line to not start by {c} | 0 */ extern size_t UnistrGetOffset(Unistr *str, uint32_t sep); + +/** Locates the new index within a regular UTF-8 sequence from an index + * within all codepoints. + * ----------------------------------- + * Returns: an offset */ +extern size_t UnistrGetUTFOffset(char *cstr, size_t unicode); #endif diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 22e98f6..3b53f04 100644 --- a/src/include/XMPP.h +++ b/src/include/XMPP.h @@ -90,6 +90,9 @@ extern char * XMPPGetRetractedID(XMLElement *); /* Get the replied-to stanza ID, if existent. */ extern char * XMPPGetReply(XMLElement *elem); +/* Get the replied-to stanza offset, if existent. */ +extern ssize_t XMPPGetReplyOffset(XMLElement *elem); + /** Get the moderated message ID(as a stanza ID/plain ID) from a moderation * stanza, that lives *alongside* the stanza itself. * ---------------------------------------------------------------------- From c96f0486ff48a2a0ea99cde3bff86fe17127470e Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 08:30:18 +0000 Subject: [PATCH 26/44] [FIX/WIP] Chrck URI before doing anything funny --- src/MatrixEventHandler.c | 4 ++++ src/Parsee/Utils/Formatting.c | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 32050dd..17a3bb9 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -358,6 +358,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) static void ParseeMessageHandler(ParseeData *data, HashMap *event) { + if (!data || !event) + { + return; + } XMPPComponent *jabber = data->jabber; StanzaBuilder *builder = NULL; DbRef *ref = NULL; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 44e255a..dc7b1a6 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -58,7 +58,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF } \ } \ while (0) - switch (elem->type) + switch (elem ? elem->type : -1) { case XML_ELEMENT_DATA: Concat(elem->data); @@ -155,7 +155,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF { char *href = HashMapGet(elem->attrs, "href"); Uri *pref = UriParse(href); - if (StrEquals(pref->host, "matrix.to")) + if (pref && StrEquals(pref->host, "matrix.to")) { /* TODO: Check if the element here is a Matrix.TO * pointing to a Parsee user. */ From cdbdc9345ad963fcbd9babe6a9d185efebc79441 Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 17:44:09 +0000 Subject: [PATCH 27/44] [MOD] Don't always add two newlines --- src/Parsee/Utils/Formatting.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index dc7b1a6..07cc191 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -149,7 +149,10 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF Concat(subxep); Free(subxep); } - Concat("\n"); + if (i != 0) + { + Concat("\n"); + } } else if (StrEquals(elem->name, "a")) { From 0a4aa45de5f76625ca9f97ff4303f08a3cbd126b Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 17 Feb 2025 20:24:06 +0000 Subject: [PATCH 28/44] [FIX] Add proper includes --- src/XMPP/Stanza.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XMPP/Stanza.c b/src/XMPP/Stanza.c index 58c3876..a2daf5a 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -7,6 +7,8 @@ #include #include +#include + void XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact) From fb44ad4bf63816c34678ee088b84829f6b8aeb05 Mon Sep 17 00:00:00 2001 From: lda Date: Tue, 18 Feb 2025 22:52:44 +0000 Subject: [PATCH 29/44] [FIX] Go back to using room IDs I already gave up on urandom anyways. --- src/Commands/Plumb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Plumb.c b/src/Commands/Plumb.c index bee94f2..f51e4e6 100644 --- a/src/Commands/Plumb.c +++ b/src/Commands/Plumb.c @@ -59,7 +59,7 @@ CommandHead(CmdPlumb, cmd, argp) goto end; } - chat_id = ParseePushMUC(args->data, room, muc); // ew. + chat_id = ParseePushMUC(args->data, room_id, muc); if (chat_id) { char *rev = StrConcat(2, muc, "/parsee"); From fd1b3499b684cb4be69bc0ce4474d74c1ef1ef17 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 15:32:52 +0000 Subject: [PATCH 30/44] [ADD] Allow the main component to be used to issue commands --- src/AS/Send.c | 7 ------- src/XMPPThread/Stanzas/IQ.c | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/AS/Send.c b/src/AS/Send.c index 9b81a71..6811548 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -69,13 +69,6 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u reply = JsonDecode(HttpClientStream(ctx)); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); - if (!ret) - { - Log(LOG_ERR, "Got %s from HTTP", HttpStatusToString(status)); - JsonEncode(reply, StreamStdout(), JSON_PRETTY); - StreamPrintf(StreamStdout(), "\n"); - StreamFlush(StreamStdout()); - } JsonFree(reply); HttpClientContextFree(ctx); diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 5dc9bc3..1b5087d 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -374,7 +374,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) bool IQIsCommandList(ParseeData *args, XMLElement *stanza) { - char *parsee = NULL; + char *parsee = NULL, *to; XMLElement *query = XMLookForTKV( stanza, "query", "xmlns", "http://jabber.org/protocol/disco#items" @@ -389,7 +389,8 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza) } parsee = ParseeJID(args); - ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee); + to = HashMapGet(stanza->attrs, "to"); + ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host); Free(parsee); return ret; From b78f7b6ab32f90a719d546c10f4ae2182bd21af2 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 15:38:22 +0000 Subject: [PATCH 31/44] [ADD/TOOL] Add -j flag for parsee-config --- etc/man/man1/parsee-config.1 | 10 +++++++++- tools/config.c | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/etc/man/man1/parsee-config.1 b/etc/man/man1/parsee-config.1 index a297075..5dc258b 100644 --- a/etc/man/man1/parsee-config.1 +++ b/etc/man/man1/parsee-config.1 @@ -1,6 +1,6 @@ ." The last field is the codename, by the way. ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee-config 1 "Parsee Utility" "star-of-hope" +.TH parsee-config 1 "Parsee Utility" "lunar-rainbow" .SH NAME parsee-config - generate a basic configuration file @@ -11,6 +11,7 @@ parsee-config .B [-s SHARED_SECRET] .B [-m MEDIA_URL] .B [-J JABBER_HOST] +.B [-j JABBER_ADDR] .B [-p JABBER_PORT] .B [-d DATABASE] .B [-M MAX_STANZA] @@ -34,6 +35,7 @@ $ parsee-config \\ -H 'blow.hole' \\ -s 'The Dark Shared Secret' \\ -J 'xmpp.blow.hole' \\ + -j 'localhost' \\ -S 128 .fi .if n \{\ @@ -68,6 +70,12 @@ media. It must be publicly accessible (behind a reverse proxy to HTTP:7642) .I JABBER_HOST is used as the component host for Parsee. .TP +.BR -j JABBER_ADDR +.I JABBER_ADDR +can optionally be used to change the hostname Parsee will try to contact +for XMPP. Users should ideally use localhost (or a hostname pointing to +the server itself), as XMPP component streams are not encrypted. +.TP .BR -p JABBER_PORT .I JABBER_PORT is used as the component post for Parsee. Parsee uses it alongside diff --git a/tools/config.c b/tools/config.c index b949c14..d8e0aea 100644 --- a/tools/config.c +++ b/tools/config.c @@ -20,6 +20,7 @@ Main(Array *args, HashMap *env) Uri *api_base; char *homeserver = NULL, *jcp = NULL, *jabber = NULL; char *data = NULL, *media = NULL, *listen = NULL; + char *component_as = NULL; int flag, code = EXIT_FAILURE; int port = 5347; size_t lmdb_size = 0; @@ -28,7 +29,7 @@ Main(Array *args, HashMap *env) listen = "localhost"; ArgParseStateInit(&state); - while ((flag = ArgParse(&state, args, "H:J:s:d:p:m:l:S:M:")) != -1) + while ((flag = ArgParse(&state, args, "H:J:j:s:d:p:m:l:S:M:")) != -1) { switch (flag) { @@ -45,6 +46,9 @@ Main(Array *args, HashMap *env) case 'J': jabber = state.optArg; break; + case 'j': + component_as = state.optArg; + break; case 'd': data = state.optArg; break; @@ -123,6 +127,7 @@ Main(Array *args, HashMap *env) JsonSet(json, JsonValueString(media), 1, "media_base"); JsonSet(json, JsonValueString(listen), 1, "listen_as"); + JsonSet(json, JsonValueString(component_as), 1, "component_addr"); JsonEncode(json, file, JSON_PRETTY); StreamFlush(file); From 40e324246510585ea6fab202fcaac8e2aa6f6650 Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 19 Feb 2025 16:14:30 +0000 Subject: [PATCH 32/44] [ADD] Try to renegociate an XMPP stream on failure --- src/XMPPThread/ReListener.c | 126 ++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 28d117b..44f22f8 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -244,66 +244,96 @@ ParseeXMPPThread(void *argp) } } - while (true) + while (!args->halted) { - char *id; - - stanza = XMLDecode(jabber->stream, false); - if (!stanza) + while (true) { - /* Try to check if an error is abound */ - if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + char *id; + + stanza = XMLDecode(jabber->stream, false); + if (!stanza) { - Log(LOG_DEBUG, "RECEIVED EOF."); + /* Try to check if an error is abound */ + if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "RECEIVED EOF."); + } + break; } - break; - } - if (args->verbosity >= PARSEE_VERBOSE_STANZA) - { - Stream *output = StreamStderr(); - StreamPrintf(output, "-------STANZA BEGIN-------" "\n"); - XMLEncode(output, stanza); - StreamPrintf(output, "\n--------STANZA END--------" "\n"); - StreamFlush(output); - } - - id = HashMapGet(stanza->attrs, "id"); - if (id) - { - XMPPAwait *await; - /* Lock out the table to see if we're awaiting. */ - pthread_mutex_lock(&await_lock); - if ((await = HashMapGet(await_table, id))) + if (args->verbosity >= PARSEE_VERBOSE_STANZA) { - pthread_mutex_lock(&await->cond_lock); - await->stanza = stanza; - pthread_cond_signal(&await->condition); - pthread_mutex_unlock(&await->cond_lock); + Stream *output = StreamStderr(); + StreamPrintf(output, "-------STANZA BEGIN-------" "\n"); + XMLEncode(output, stanza); + StreamPrintf(output, "\n--------STANZA END--------" "\n"); + StreamFlush(output); + } - HashMapDelete(await_table, id); + id = HashMapGet(stanza->attrs, "id"); + if (id) + { + XMPPAwait *await; + /* Lock out the table to see if we're awaiting. */ + pthread_mutex_lock(&await_lock); + if ((await = HashMapGet(await_table, id))) + { + pthread_mutex_lock(&await->cond_lock); + await->stanza = stanza; + pthread_cond_signal(&await->condition); + pthread_mutex_unlock(&await->cond_lock); + HashMapDelete(await_table, id); + + pthread_mutex_unlock(&await_lock); + continue; + } pthread_mutex_unlock(&await_lock); - continue; } - pthread_mutex_unlock(&await_lock); - } - /* Push it into the stanza FIFO. A dispatcher thread should then - * be able to freely grab a value(locked by a mutex). We can't make - * dispatchers read stanzas on their own, since that's _not_ how - * streams work, but this should mitigate some issues, and allow a - * few threads to be busy, while the rest of Parsee works. */ - PushStanza(&info, stanza); + /* Push it into the stanza FIFO. A dispatcher thread should then + * be able to freely grab a value(locked by a mutex). We can't make + * dispatchers read stanzas on their own, since that's _not_ how + * streams work, but this should mitigate some issues, and allow a + * few threads to be busy, while the rest of Parsee works. */ + PushStanza(&info, stanza); + } + pthread_mutex_lock(&args->halt_lock); + if (!args->halted) + { + Log(LOG_WARNING, "XMPP server is closing stream..."); + for (size_t i = 0; i < 50; i++) + { + UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */ + + Log(LOG_WARNING, "Restarting XMPP stream."); + /* This is the part where a new connection is being considered */ + XMPPFinishCompStream(jabber); + XMPPEndCompStream(jabber); + + args->jabber = XMPPInitialiseCompStream( + args->config->component_addr, + args->config->component_host, + args->config->component_port + ); + jabber = args->jabber; + if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret)) + { + /* Oops, there is something wrong! */ + Log(LOG_ERR, "Couldn't authenticate to XMPP server"); + XMPPEndCompStream(jabber); + args->jabber = NULL; + jabber = NULL; + if (i == 4) + { + pthread_mutex_unlock(&args->halt_lock); + break; + } + } + } + } + pthread_mutex_unlock(&args->halt_lock); } - pthread_mutex_lock(&args->halt_lock); - if (!args->halted) - { - Log(LOG_WARNING, "XMPP server is closing stream..."); - Log(LOG_WARNING, "Stopping %s...", NAME); - error = true; - } - pthread_mutex_unlock(&args->halt_lock); info.running = false; for (i = 0; i < info.available_dispatchers; i++) From 6762d1c1ce777ee8529afbc6e4d5aff2d994a8db Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 18:15:49 +0100 Subject: [PATCH 33/44] [FIX/WIP] remove .guix from gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5f7d56c..09b6fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ tags !.forgejo/* !.forgejo/** +!.guix +!.guix/* +!.guix/** + From a2c1de52dceb9ef39e1afacd0afafddd86141b1b Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 18:16:24 +0100 Subject: [PATCH 34/44] [ADD] new guix.scm with cytoplasm --- .guix/modules/parsee.scm | 43 ++++++++++++++++++++++++++++++++++++++++ guix.scm | 1 + 2 files changed, 44 insertions(+) create mode 100644 .guix/modules/parsee.scm create mode 120000 guix.scm diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm new file mode 100644 index 0000000..f2d6f48 --- /dev/null +++ b/.guix/modules/parsee.scm @@ -0,0 +1,43 @@ +(define-module (parsee) + #:use-module (guix packages) + #:use-module (guix git-download) + #:use-module (guix build-system gnu) + #:use-module (guix gexp) + #:use-module (guix utils) + #:use-module (gnu packages tls) + #:use-module ((guix licenses) #:prefix license:)) + +(define-public cytoplasm + (package + (name "cytoplasm") + (version "0.4.1") + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://git.telodendria.io/Telodendria/Cytoplasm") + (commit (string-append "v" version)))) + (sha256 + (base32 "13iwh37xfmz98vmb6dzl4v6vlxcis5q85c8rzmv3fs28khsfk45n")))) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:phases #~(modify-phases %standard-phases + (add-before 'configure 'add-ld-flags + (lambda _ + (substitute* "./configure" + (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) + (string-append flags " -Wl,-rpath=" #$output "/lib\""))) + (mkdir-p (string-append #$output "/lib")))) + (replace 'configure + (lambda* (#:key configure-flags #:allow-other-keys) + (apply invoke `("./configure" + ,(string-append "--prefix=" #$output) + ,@configure-flags))))))) + (inputs (list openssl)) + (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") + (synopsis "General-purpose high-level networked C library") + (description "Cytoplasm is a general-purpose C library for creating +high-level (particularly networked and multi-threaded) C applications.") + (license license:expat))) diff --git a/guix.scm b/guix.scm new file mode 120000 index 0000000..021acc5 --- /dev/null +++ b/guix.scm @@ -0,0 +1 @@ +.guix/modules/parsee.scm \ No newline at end of file From b50b9bd6155ffc03baa7c683e35702c4b84127e3 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:23:03 +0100 Subject: [PATCH 35/44] [FIX] pin cytoplasm to the latest commit --- .guix/modules/parsee.scm | 68 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm index f2d6f48..3c5ce83 100644 --- a/.guix/modules/parsee.scm +++ b/.guix/modules/parsee.scm @@ -8,36 +8,40 @@ #:use-module ((guix licenses) #:prefix license:)) (define-public cytoplasm - (package - (name "cytoplasm") - (version "0.4.1") - (source - (origin - (method git-fetch) - (uri (git-reference - (url "https://git.telodendria.io/Telodendria/Cytoplasm") - (commit (string-append "v" version)))) - (sha256 - (base32 "13iwh37xfmz98vmb6dzl4v6vlxcis5q85c8rzmv3fs28khsfk45n")))) - (build-system gnu-build-system) - (arguments - (list - #:tests? #f - #:phases #~(modify-phases %standard-phases - (add-before 'configure 'add-ld-flags - (lambda _ - (substitute* "./configure" - (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) - (string-append flags " -Wl,-rpath=" #$output "/lib\""))) - (mkdir-p (string-append #$output "/lib")))) - (replace 'configure - (lambda* (#:key configure-flags #:allow-other-keys) - (apply invoke `("./configure" - ,(string-append "--prefix=" #$output) - ,@configure-flags))))))) - (inputs (list openssl)) - (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") - (synopsis "General-purpose high-level networked C library") - (description "Cytoplasm is a general-purpose C library for creating + (let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f") + (revision "0")) + (package + (name "cytoplasm") + (version (git-version "0.4.1" revision commit)) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://git.telodendria.io/Telodendria/Cytoplasm") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 "09x5xfswryf3wjs1synh972yr2fmpjmffi7pjyjdzb4asqh4whrv")))) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:configure-flags #~'("--with-lmdb") + #:phases #~(modify-phases %standard-phases + (add-before 'configure 'add-ld-flags + (lambda _ + (substitute* "./configure" + (("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags) + (string-append flags " -Wl,-rpath=" #$output "/lib\""))) + (mkdir-p (string-append #$output "/lib")))) + (replace 'configure + (lambda* (#:key configure-flags #:allow-other-keys) + (apply invoke `("./configure" + ,(string-append "--prefix=" #$output) + ,@configure-flags))))))) + (inputs (list openssl lmdb)) + (home-page "https://git.telodendria.io/Telodendria/Cytoplasm") + (synopsis "General-purpose high-level networked C library") + (description "Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications.") - (license license:expat))) + (license license:expat)))) From 44473878d09b806e40d0ad146b867610712dd8a9 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:23:39 +0100 Subject: [PATCH 36/44] [ADD] add parsee guix package --- .guix/modules/parsee.scm | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm index 3c5ce83..75b8b64 100644 --- a/.guix/modules/parsee.scm +++ b/.guix/modules/parsee.scm @@ -5,8 +5,14 @@ #:use-module (guix gexp) #:use-module (guix utils) #:use-module (gnu packages tls) + #:use-module (gnu packages databases) #:use-module ((guix licenses) #:prefix license:)) +(define vcs-file? + (or (git-predicate + (dirname (dirname (current-source-directory)))) + (const #t))) + (define-public cytoplasm (let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f") (revision "0")) @@ -45,3 +51,32 @@ (description "Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications.") (license license:expat)))) + +(define-public parsee + (package + (name "parsee") + (version "0.0.1-git") + (source + (local-file "../.." "parsee-checkout" + #:recursive? #t + #:select? vcs-file?)) + (build-system gnu-build-system) + (arguments + (list + #:tests? #f + #:make-flags #~(list "CC=gcc" + (string-append "CYTO_INC=" #$cytoplasm "/include") + (string-append "CYTO_LIB=" #$cytoplasm "/lib") + (string-append "PREFIX=" #$output)) + #:phases #~(modify-phases %standard-phases + (replace 'configure + (lambda* (#:key inputs #:allow-other-keys) + (let ((gcc (string-append (assoc-ref inputs "gcc") "/bin/gcc"))) + (invoke gcc "configure.c" "-o" "configure") + (invoke "./configure"))))))) + (home-page "https://git.kappach.at/lda/Parsee") + (synopsis "Jealous Matrix to XMPP bridge") + (description "Parsee is a Matrix-XMPP bridge written in C99 with Cytoplasm.") + (license license:agpl3+))) + +parsee From 680b4261c25f74ed1551ec75d9efc07346e6bf56 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 10 Feb 2025 22:54:45 +0100 Subject: [PATCH 37/44] [ADD] small documentation paragraph --- README.MD | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.MD b/README.MD index ce51662..ee223de 100644 --- a/README.MD +++ b/README.MD @@ -43,6 +43,13 @@ Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendri Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but you can get away without those if you're adventurous). +### BUILDING WITH GUIX +If you have [Guix](https://guix.gnu.org/) installed, you can build Parsee using `guix package -f guix.scm`, or test it +using `guix shell -f guix.scm`. You can also generate a Docker/OCI image. +```sh +guix pack -f docker -S /bin=bin -L.guix/modules parsee +``` + ## RUNNING First off, you may want to configure Parsee by running the `config` tool(generally named `parsee-config` in most cases), with the correct flags, like here. From e21ebed134bd553dd7dd8e79f431467494b66fd6 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 23 Feb 2025 22:38:38 +0000 Subject: [PATCH 38/44] [META/ADD] Add Guix to the CHANGELOG quick und dirty --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04358d7..9b12530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ TBD - Parsee can now access the homeserver/component locally (rather than over the network) - The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' - Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. +- Add packaging for Guix (see #18) #### Bugfixes - Fix potential infinite loops when processing some messages. - Parsee now handles pinging puppets from Matrix more sanely. From 7454c8c597e93c4c19d98ee0946a555bb445f71e Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 9 Mar 2025 11:05:55 +0000 Subject: [PATCH 39/44] [FIX] Fix a few bugs about edits and XEP-393 formatting --- src/Parsee/Utils/Formatting.c | 12 ++++++++++-- src/XEP-0393.c | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 07cc191..ec95aef 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -247,6 +247,15 @@ GetHTMLBody(HashMap *event) } return GrabString(event, 2, "content", "formatted_body"); } +static char * +GetBodyFormat(HashMap *event) +{ + if (MatrixGetEdit(event)) + { + return GrabString(event, 3, "content", "m.new_content", "format"); + } + return GrabString(event, 2, "content", "format"); +} char * ParseeXMPPify(ParseeData *data, HashMap *event) { @@ -267,8 +276,7 @@ ParseeXMPPify(ParseeData *data, HashMap *event) return NULL; } - format = JsonValueAsString(JsonGet(event, 2, "content", "format")); - if (!StrEquals(format, "org.matrix.custom.html")) + if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html")) { /* Settle for the raw body instead. */ char *body = GetRawBody(event); diff --git a/src/XEP-0393.c b/src/XEP-0393.c index a0f6be9..784bacf 100644 --- a/src/XEP-0393.c +++ b/src/XEP-0393.c @@ -329,8 +329,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent) break; case XEP393_MONO: head = XMLCreateTag("code"); + XMLAddChild(xmlparent, XMLCreateText("`")); XMLAddChild(xmlparent, head); - XMLAddChild(head, XMLCreateText("`")); break; case XEP393_SRKE: head = XMLCreateTag("s"); @@ -372,7 +372,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent) XMLAddChild(head, XMLCreateText("_")); break; case XEP393_MONO: - XMLAddChild(head, XMLCreateText("`")); + XMLAddChild(xmlparent, XMLCreateText("`")); break; case XEP393_SRKE: XMLAddChild(head, XMLCreateText("~")); From 45250096ad60ecc731bc70ada02106dd2e704749 Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:23:51 +0000 Subject: [PATCH 40/44] [FIX] argh --- src/AS/Media.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AS/Media.c b/src/AS/Media.c index 65f049c..5fbc659 100644 --- a/src/AS/Media.c +++ b/src/AS/Media.c @@ -150,7 +150,7 @@ ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha) return false; } - path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); cctx = ParseeCreateRequest(c, HTTP_GET, path); ASAuthenticateRequest(c, cctx); HttpRequestSendHeaders(cctx); @@ -195,7 +195,7 @@ ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len) return false; } - path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); cctx = ParseeCreateRequest(c, HTTP_GET, path); ASAuthenticateRequest(c, cctx); HttpRequestSendHeaders(cctx); From 7b8ed08e881da531f68f425db8f50b0c1943e5ac Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:39:15 +0000 Subject: [PATCH 41/44] [ADD] Optionally allow instance admins to ignore m.notice events --- CHANGELOG.md | 2 ++ README.MD | 2 +- src/MatrixEventHandler.c | 8 ++++++++ src/Parsee/Config.c | 2 ++ src/include/Parsee.h | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b12530..f4ffcde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ TBD - The endpoint for media has been changed to '/media/[SERV]/[ID]?hmac=...' - Add parsee-plumb tool to manage plumbing from outside Parsee if it is not running. - Add packaging for Guix (see #18) +- Parsee is now dependent on authenticated media to function. + (Please, Matrix, do _not_ touch anything, I do _not_ want to mess with this anymore) #### Bugfixes - Fix potential infinite loops when processing some messages. - Parsee now handles pinging puppets from Matrix more sanely. diff --git a/README.MD b/README.MD index ee223de..f9522e8 100644 --- a/README.MD +++ b/README.MD @@ -22,7 +22,7 @@ Please scream at me if that fails(or just doesn't run on a overclocked Raspberry ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" -These two having the same answer should be enough information. Also can I *just* have fun? +These two having the same answer should be enough information. One could also argue that both sides need to migrate(onboard) the other side, so a bridge may be a good way to start. diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 17a3bb9..638b5a3 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -367,6 +367,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) DbRef *ref = NULL; HashMap *json = NULL; + char *msgtype = GrabString(event, 2, "content", "msgtype"); char *m_sender = GrabString(event, 1, "sender"); char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); @@ -391,6 +392,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) if (new_content) body = new_content; } + if (data->config->ignore_bots && StrEquals(msgtype, "m.notice")) + { + Free(reply_id); + Free(xepd); + Free(unedited_id); + return; + } if (ParseeIsPuppet(data->config, m_sender) || ParseeManageBan(data, m_sender, id)) { diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 45e6fe5..b95e5e1 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -85,6 +85,8 @@ ParseeConfigLoad(char *conf) config->max_stanza_size = 10000; } + CopyToBool(ignore_bots, "ignore_bots"); + CopyToStr(media_base, "media_base"); CopyToStr(db_path, "db"); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index ea5d18b..4b1f7aa 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -44,6 +44,7 @@ typedef struct ParseeConfig { int homeserver_tls; bool accept_pings; + bool ignore_bots; /* ------- JABBER -------- */ From e3749817d3585aa0218bb9602f222ecac85f526f Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 13 Apr 2025 10:49:40 +0000 Subject: [PATCH 42/44] [MOD/FIX] Be even more strict with Unicode filtering Now, only ASCII characters get to fit. --- src/MatrixEventHandler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 638b5a3..6311fac 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -21,7 +21,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char char *sender = GrabString(event, 1, "sender"); Unistr *uninick = UnistrCreate(name); - Unistr *filtered = UnistrFilter(uninick, UnistrIsBMP); + Unistr *filtered = UnistrFilter(uninick, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */ char *nick = UnistrC(filtered); char *rev = StrConcat(3, muc, "/", nick); int nonce = 0; From e617b5e8d47dfd8955c5e3565394e9f2b14a1204 Mon Sep 17 00:00:00 2001 From: LDA Date: Mon, 28 Apr 2025 02:18:57 +0200 Subject: [PATCH 43/44] [FIX/WIP] Unique redactions for moderation stanzas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, I'll consider Parsee dev to be dead until July 2026. J'ai le bac et déja un projet à faire, et si je peux même pas me concentrer sur mon futur en ne comptant pas Parsee. Feel free to make a good XMPP bridge, I don't really even want to touch either Matrix or XMPP anymore, you know? --- src/XMPPThread/Stanzas/Message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index c4066d9..13021df 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -277,7 +277,7 @@ end_error: Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime)); } - if (moderated) + if (moderated && !(!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))) { /* TODO: Parsee MUST check if it is a valid MUC */ char *resource = ParseeGetResource(from); From e331d110c7fa57e1a3fd96f8ff993cf59d714f9c Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 22 Jun 2025 14:00:12 +0200 Subject: [PATCH 44/44] [FIX] URL encode filenames --- src/Parsee/User.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Parsee/User.c b/src/Parsee/User.c index a9320d8..6e673bb 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -725,12 +726,14 @@ ParseeToUnauth(ParseeData *data, char *mxc, char *filename) } else { + char *encoded = HttpUrlEncode(filename); l = snprintf(NULL, 0, PATF, data->config->media_base, - url->host, url->path, filename, + url->host, url->path, encoded, hmac ); + Free(encoded); } ret = Malloc(l + 3); if (!filename)