diff --git a/DATES.TXT b/DATES.TXT index f54dbc0..4da14dc 100644 --- a/DATES.TXT +++ b/DATES.TXT @@ -1,7 +1,3 @@ Some dates for Parsee-related events. They mostly serve as LDA's TODOs with a strict deadline: - - ~September 2024[tomboyish-bridges-adventure]: - Get Parsee into the _Phantasmagoria of Bug View_ stage (essentially - v0.0.1 for a public testing) once I can afford `yama`, and start - bridging the Matrix room alongside a shiny XMPP MUC, bridged by - yours truly. + - Get star-of-hope out by November 8 diff --git a/README.MD b/README.MD index e02db11..1d4b711 100644 --- a/README.MD +++ b/README.MD @@ -98,10 +98,6 @@ restricted to Parsee admins, with permission from MUC owners, too be false by default. - Currently, MUC owners may kick Parsee out, with the effect of unlinking the MUC. -- Rewrite the XMPP command management to actually be aware of context, instead of -being a baked-in X-macro. It could be useful for MUC admins, which may use commands -specifically within the MUC's own context rather than the global Parsee context(for -Parsee admins). - Look at XEPS-TBD.TXT for XEPs to be done - Add a MUC server to Parsee, such that it may be able to hook onto it and therefore support XMPP->Matrix bridging. diff --git a/etc/man/man1/parsee-adminify.1 b/etc/man/man1/parsee-adminify.1 index fd4a582..9015d20 100644 --- a/etc/man/man1/parsee-adminify.1 +++ b/etc/man/man1/parsee-adminify.1 @@ -1,6 +1,6 @@ ." The last field is the codename, by the way. ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee-adminify 1 "Parsee Utility" "tomboyish-bridges-adventure" +.TH parsee-adminify 1 "Parsee Utility" "star-of-hope" .SH NAME parsee-adminify - bootstrap an admin to a new Parsee server diff --git a/etc/man/man1/parsee-aya.1 b/etc/man/man1/parsee-aya.1 index bb5caac..b51afa0 100644 --- a/etc/man/man1/parsee-aya.1 +++ b/etc/man/man1/parsee-aya.1 @@ -1,6 +1,6 @@ ." The last field is the codename, by the way. ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee-aya 1 "Parsee Utility" "tomboyish-bridges-adventure" +.TH parsee-aya 1 "Parsee Utility" "star-of-hope" .SH NAME parsee-aya - generate some nice Ayaya! documentation diff --git a/etc/man/man1/parsee.1 b/etc/man/man1/parsee.1 index 093af7b..d7eeedf 100644 --- a/etc/man/man1/parsee.1 +++ b/etc/man/man1/parsee.1 @@ -1,6 +1,6 @@ ." The last field is the codename, by the way. ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee 1 "Parsee Utility" "tomboyish-bridges-adventure" +.TH parsee 1 "Parsee Utility" "star-of-hope" .SH NAME parsee - the jealous XMPP-Matrix bridge diff --git a/src/Main.c b/src/Main.c index cd1f089..cebb055 100644 --- a/src/Main.c +++ b/src/Main.c @@ -222,6 +222,12 @@ Main(Array *args, HashMap *env) } ParseeInitialiseNickTable(); + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising affiliation table"); + } + ParseeInitialiseAffiliationTable(); + conf.port = parsee_conf->port; conf.threads = parsee_conf->http_threads; conf.maxConnections = conf.threads << 2; @@ -291,6 +297,7 @@ end: CronStop(cron); CronFree(cron); ParseeFreeData(conf.handlerArgs); + ParseeDestroyAffiliationTable(); ParseeDestroyNickTable(); ParseeDestroyOIDTable(); ParseeDestroyHeadTable(); diff --git a/src/Parsee/Config.c b/src/Parsee/Config.c index 5361d2a..0898063 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -109,6 +109,7 @@ ParseeExportConfigYAML(Stream *stream) StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token); StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart); StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n"); + StreamPrintf(stream, "receive_ephemeral: true\n"); /* TODO: Actually use that field */ StreamPrintf(stream, "\n"); StreamPrintf(stream, "namespaces: \n"); StreamPrintf(stream, " users:\n"); diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index f858060..180f389 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -620,3 +620,90 @@ ParseeIsMUCWhitelisted(ParseeData *data, char *muc) return ret; } + +extern HashMap * +ParseeGetChatSettings(ParseeData *data, char *chat) +{ + HashMap *ret, *json; + DbRef *ref; + + char *key; + JsonValue *value; + if (!data || !chat) + { + return NULL; + } + + ref = DbLockIntent(data->db, DB_HINT_READONLY, + 3, "chats", chat, "settings" + ); + json = DbJson(ref); + if (!ref) + { + return HashMapCreate(); + } + + ret = HashMapCreate(); + while (HashMapIterate(json, &key, (void **) &value)) + { + char *str = JsonValueAsString(value); + HashMapSet(ret, key, StrDuplicate(str)); + } + DbUnlock(data->db, ref); + return ret; +} +void +ParseeFreeChatSettings(HashMap *settings) +{ + char *key; + void *val; + if (!settings) + { + return; + } + while (HashMapIterate(settings, &key, &val)) + { + Free(val); + } + HashMapFree(settings); +} +char * +ParseeGetChatSetting(ParseeData *data, char *chat, char *key) +{ + HashMap *map; + char *ret; + if (!data || !chat || !key) + { + return NULL; + } + + map = ParseeGetChatSettings(data, chat); + ret = StrDuplicate(HashMapGet(map, key)); + ParseeFreeChatSettings(map); + + return ret; +} +void +ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val) +{ + DbRef *ref; + HashMap *json; + if (!data || !chat || !key || !val) + { + return; + } + + ref = DbLockIntent(data->db, DB_HINT_WRITE, + 3, "chats", chat, "settings" + ); + if (!ref) + { + ref = DbCreate(data->db, 3, "chats", chat, "settings"); + } + json = DbJson(ref); + + JsonValueFree(HashMapSet(json, key, JsonValueString(val))); + + DbUnlock(data->db, ref); + return; +} diff --git a/src/Parsee/Tables/Affiliation.c b/src/Parsee/Tables/Affiliation.c new file mode 100644 index 0000000..e3b5f55 --- /dev/null +++ b/src/Parsee/Tables/Affiliation.c @@ -0,0 +1,106 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t affi_lock; +static HashMap *affi_table = NULL; + +typedef struct XMPPStatus { + char *role; + char *affiliation; +} XMPPStatus; +static XMPPStatus * +CreateStatus(char *role, char *affiliation) +{ + XMPPStatus *ret; + if (!role || !affiliation) + { + return NULL; + } + + ret = Malloc(sizeof(*ret)); + ret->role = StrDuplicate(role); + ret->affiliation = StrDuplicate(affiliation); + + return ret; +} +static void +FreeStatus(XMPPStatus *status) +{ + if (!status) + { + return; + } + + Free(status->affiliation); + Free(status->role); + Free(status); +} + +void +ParseeInitialiseAffiliationTable(void) +{ + if (affi_table) + { + return; + } + pthread_mutex_init(&affi_lock, NULL); + pthread_mutex_lock(&affi_lock); + affi_table = HashMapCreate(); + pthread_mutex_unlock(&affi_lock); +} +void +ParseePushAffiliationTable(char *user, char *affi, char *role) +{ + XMPPStatus *status; + if (!user || !affi || !role) + { + return; + } + pthread_mutex_lock(&affi_lock); + + status = CreateStatus(role, affi); + FreeStatus(HashMapSet(affi_table, user, status)); + + pthread_mutex_unlock(&affi_lock); +} +bool +ParseeLookupAffiliation(char *user, char **affiliation, char **role) +{ + XMPPStatus *status; + if (!user || !affiliation || !role) + { + return false; + } + pthread_mutex_lock(&affi_lock); + + status = HashMapGet(affi_table, user); + *affiliation = StrDuplicate(status ? status->affiliation : NULL); + *role = StrDuplicate(status ? status->role : NULL); + + pthread_mutex_unlock(&affi_lock); + return !!status; +} +void +ParseeDestroyAffiliationTable(void) +{ + char *key; + void *val; + if (!affi_table) + { + return; + } + pthread_mutex_lock(&affi_lock); + while (HashMapIterate(affi_table, &key, &val)) + { + FreeStatus(val); + } + HashMapFree(affi_table); + affi_table = NULL; + pthread_mutex_unlock(&affi_lock); + pthread_mutex_destroy(&affi_lock); +} + diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index 77a3d9b..da6194f 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -233,6 +233,7 @@ ParseeGenerateMTO(char *common_id) return NULL; } + /* TODO: Is HttpUrlEncode okay? */ common_id = HttpUrlEncode(common_id); matrix_to = StrConcat(2, "https://matrix.to/#/", common_id); Free(common_id); diff --git a/src/XMPPCommands/MUCKV.c b/src/XMPPCommands/MUCKV.c new file mode 100644 index 0000000..4bcf010 --- /dev/null +++ b/src/XMPPCommands/MUCKV.c @@ -0,0 +1,117 @@ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void +MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +{ + ParseeData *data = XMPPGetManagerCookie(m); + char *affiliation, *role; + char *chat_id; + char *muc; + + char *key, *val; + + ParseeLookupAffiliation(from, &affiliation, &role); + Free(role); + if (!StrEquals(affiliation, "owner")) + { + SetNote("error", "Setting MUC properties requires the 'owner' affiliation."); + Free(affiliation); + return; + } + + GetFieldValue(key, "key", form); + GetFieldValue(val, "val", form); + if (!key || !val) + { + SetNote("error", "No keys or no value given."); + goto end; + } + + muc = ParseeTrimJID(from); + chat_id = ParseeGetFromMUCID(data, muc); + ParseeSetChatSetting(data, chat_id, key, val); + + SetNote("info", "Set key-value pair!"); +end: + Free(affiliation); + Free(chat_id); + Free(muc); + (void) form; +} +void +MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +{ + ParseeData *data = XMPPGetManagerCookie(m); + char *affiliation = NULL, *role = NULL; + char *chat_id = NULL; + char *muc = NULL; + + XMLElement *x; + XMLElement *title; + XMLElement *reported, *item, *field, *value, *txt; + + HashMap *settings = NULL; + + ParseeLookupAffiliation(from, &affiliation, &role); + Free(role); + if (!StrEquals(affiliation, "owner")) + { + SetNote("error", "Getting MUC roperties requires the 'owner' affiliation."); + goto end; + } + + + muc = ParseeTrimJID(from); + chat_id = ParseeGetFromMUCID(data, muc); + settings = ParseeGetChatSettings(data, chat_id); + + x = XMLCreateTag("x"); + title = XMLCreateTag("title"); + + SetTitle(x, "MUC/room settings"); + + XMLAddChild(x, title); + + XMLAddAttr(x, "xmlns", "jabber:x:data"); + XMLAddAttr(x, "type", "result"); + { + char *key, *val; + reported = XMLCreateTag("reported"); + XMLAddChild(x, reported); + + /* Report */ + Report("key", "Setting's key"); + Report("val", "Setting's value"); + + /* Set */ + while (HashMapIterate(settings, &key, (void **) &val)) + { + BeginItem(); + SetField("key", key); + SetField("val", val); + EndItem(); + } + } + XMLAddChild(out, x); + +end: + ParseeFreeChatSettings(settings); + Free(affiliation); + Free(chat_id); + Free(muc); + (void) form; +} diff --git a/src/XMPPCommands/MUCUnlink.c b/src/XMPPCommands/MUCUnlink.c new file mode 100644 index 0000000..f5560d3 --- /dev/null +++ b/src/XMPPCommands/MUCUnlink.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void +MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +{ + ParseeData *data = XMPPGetManagerCookie(m); + char *affiliation, *role; + char *chat_id; + char *parsee; + char *room; + char *muc; + + ParseeLookupAffiliation(from, &affiliation, &role); + Free(role); + if (!StrEquals(affiliation, "owner")) + { + SetNote("error", "Unlinking a MUC requires the 'owner' affiliation."); + Free(affiliation); + return; + } + + muc = ParseeTrimJID(from); + chat_id = ParseeGetFromMUCID(data, muc); + room = ParseeGetRoomID(data, chat_id); + if (!chat_id) + { + SetNote("error", "Couldn't fetch chat ID."); + Free(muc); + return; + } + ParseeUnlinkRoom(data, chat_id); + + parsee = ParseeMXID(data); + Free(ASSend( + data->config, room, parsee, + "m.room.message", + MatrixCreateNotice("This room has been unlinked."), + 0 + )); + ASLeave(data->config, room, parsee); + + XMPPLeaveMUC(data->jabber, "parsee", muc, "Unlinked by MUC admin."); + + /* Setting an error here won't work, as we're communicating through + * the MUC, which we *left*. I guess we can try to defer the leave. */ + SetNote("info", "Unlinked MUC."); + + Free(affiliation); + Free(chat_id); + Free(parsee); + Free(room); + Free(muc); + (void) form; +} diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 0962fc0..839b930 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -189,6 +189,7 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) time = UtilTsMillis(); rectime = time; + from = HashMapGet(stanza->attrs, "from"); if (ParseeManageBan(args, from, NULL)) { @@ -202,7 +203,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) return false; } - if (ServerHasXEP421(args, from)) { XMLElement *occupant = XMLookForTKV( diff --git a/src/XMPPThread/Stanzas/Presence.c b/src/XMPPThread/Stanzas/Presence.c index 5c7c21d..fb6011b 100644 --- a/src/XMPPThread/Stanzas/Presence.c +++ b/src/XMPPThread/Stanzas/Presence.c @@ -174,6 +174,7 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { ParseePushJIDTable(oid, jid); } + ParseePushAffiliationTable(oid, affiliation, role); decode_from = ParseeLookupJID(oid); real_matrix = ParseeDecodeMXID(decode_from); diff --git a/src/include/Parsee.h b/src/include/Parsee.h index 6b2d9cc..456c4fe 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -276,6 +276,44 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id); /* Finds the MUC JID from a chat ID */ extern char * ParseeGetMUCID(ParseeData *, char *chat_id); +/** Fetches a configuration value from a key in a chat(given a Chat ID), + * as a string or NULL. Keys are to be stored like Java packages(reveres DNS). + * Parsee has the right over any key with the 'p.' prefix. + * ----------------------------------- + * Returns: a valid string[HEAP] | NULL + * Modifies: NOTHING + * See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting */ +extern char * +ParseeGetChatSetting(ParseeData *data, char *chat, char *key); + +/** Fetches the entire configuration in a chat(given a Chat ID), as an hashmap + * of strings. + * ----------------------------------- + * Returns: a valid string[HEAP] | NULL + * Modifies: NOTHING + * Thrasher: ParseeFreeChatSettings + * See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting, ParseeGetChatSetting */ +extern HashMap * +ParseeGetChatSettings(ParseeData *data, char *chat); + +/** Destroys memory allocated from a call to {ParseeGetChatSettings}. + * ----------------------- + * Returns: NOTHING + * Modifies: {settings} + * Thrashes: {settings} + * See-Also: ParseeGetChatSettings */ +extern void +ParseeFreeChatSettings(HashMap *settings); + +/** Replaces a configuration key-value pair within the chat's context, which + * can be read with {ParseeGetChatSetting}. + * ------------------------------------- + * Returns: NOTHING + * Modifies: the chat context + * See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeGetChatSetting */ +extern void +ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val); + /* Pushes a stanza ID to a chat ID */ extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender); @@ -327,6 +365,11 @@ extern void ParseePushOIDTable(char *muc, char *occupant); extern char *ParseeLookupOID(char *muc); extern void ParseeDestroyOIDTable(void); +extern void ParseeInitialiseAffiliationTable(void); +extern void ParseePushAffiliationTable(char *user, char *affiliation, char *role); +extern bool ParseeLookupAffiliation(char *muc, char **affiliation, char **role); +extern void ParseeDestroyAffiliationTable(void); + extern void ParseeInitialiseJIDTable(void); extern void ParseePushJIDTable(char *muc, char *bare); extern char *ParseeLookupJID(char *muc); diff --git a/src/include/XMPPCommands.x.h b/src/include/XMPPCommands.x.h index 01b27fb..dba6f48 100644 --- a/src/include/XMPPCommands.x.h +++ b/src/include/XMPPCommands.x.h @@ -45,5 +45,18 @@ typedef enum XMPPCommandFlags { XMPP_COMMAND(WhitelistCallback, XMPPCMD_ADMINS, "wl", "Get Parsee's chat whitelist", {}) \ XMPP_COMMAND(MUCInformationID, XMPPCMD_MUC, "muc-info-id", "Get bridged Matrix room ID", {}) \ XMPP_COMMAND(MUCInformationCID, XMPPCMD_MUC, "muc-info-cid", "Get MUC's internal ID", {}) \ + XMPP_COMMAND(MUCUnlink, XMPPCMD_MUC, "muc-unlink", "Unlinks MUC", {}) \ + XMPP_COMMAND(MUCSetKey, XMPPCMD_MUC, "muc-set-key", "Sets a key within the MUC/room's context", { \ + XMPPOption *key = XMPPCreateText(true, "key", ""); \ + XMPPOption *val = XMPPCreateText(true, "val", ""); \ + XMPPSetDescription(key, "Key"); \ + XMPPSetDescription(val, "Value"); \ + XMPPAddOption(cmd, key); \ + XMPPAddOption(cmd, val); \ + \ + XMPPSetFormTitle(cmd, "Set a key-value pair"); \ + XMPPSetFormInstruction(cmd, "Replace a key with a specific value"); \ + }) \ + XMPP_COMMAND(MUCGetKeys, XMPPCMD_MUC, "muc-get-keys", "Get all key-values in the MUC/room.", {}) \ XMPPCOMMANDS diff --git a/tools/adminify.c b/tools/adminify.c index ee9e749..e87d97e 100644 --- a/tools/adminify.c +++ b/tools/adminify.c @@ -113,7 +113,7 @@ Main(Array *args, HashMap *env) return EXIT_SUCCESS; } - Log(LOG_ERR, "%s: couldn't open DB '%s'", exec, db_path); + Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path); (void) env; return EXIT_FAILURE; }