diff --git a/.gitignore b/.gitignore index 09b6fd1..01d033d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,10 @@ parsee* parsee *.swp .* -data* -data*/* +data +data/* Makefile configure -gmon.out tools/out tools/out/* @@ -26,7 +25,10 @@ tags !.forgejo/* !.forgejo/** -!.guix -!.guix/* -!.guix/** - +# Janet +janet/* +janet/** +janet +extensions +extensions/* +extensions/** diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm deleted file mode 100644 index 75b8b64..0000000 --- a/.guix/modules/parsee.scm +++ /dev/null @@ -1,82 +0,0 @@ -(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 (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")) - (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)))) - -(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 diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ffcde..5999d7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,22 +13,6 @@ 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. -- 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. - ### 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/README.MD b/README.MD index f9522e8..9a7fc6f 100644 --- a/README.MD +++ b/README.MD @@ -18,11 +18,12 @@ 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)) +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.)) ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" -These two having the same answer should be enough information. +These two having the same answer should be enough information. Also can I *just* have fun? One could also argue that both sides need to migrate(onboard) the other side, so a bridge may be a good way to start. @@ -43,13 +44,6 @@ 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. diff --git a/build.conf b/build.conf index a2e17c1..9ebf253 100644 --- a/build.conf +++ b/build.conf @@ -1,6 +1,6 @@ -CODE=lunar-rainbow +CODE=star-of-hope NAME=Parsee -VERSION=0.3.0 +VERSION=0.2.0 BINARY=parsee SOURCE=src INCLUDES=src/include diff --git a/configure.c b/configure.c index d3a01d0..2f59bb5 100644 --- a/configure.c +++ b/configure.c @@ -90,6 +90,22 @@ 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; @@ -99,10 +115,6 @@ 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; @@ -204,6 +216,36 @@ 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) @@ -211,6 +253,17 @@ 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) { @@ -315,7 +368,7 @@ write_objects(FILE *makefile, str_array_t *sources) { char *src = str_array_get(sources, i); char *ofl = string_rep_ext(src, ".c", ".o"); - char *obj = string_cat("build", ofl + 3); + char *obj = string_cat("build", strchr(ofl, '/')); fprintf(makefile, " %s", obj); free(ofl); @@ -459,10 +512,12 @@ main_build(int argc, char *argv[]) { FILE *makefile; char *repo = cmd_stdout("git remote get-url origin"); - size_t i; + size_t size, i; + ssize_t nread; bool with_static = false, with_lmdb = false; int opt; - str_array_t *sources, *images, *utils, *aya; + str_array_t *sources, *janet = NULL, *images, *utils, *aya; + char *janet_dir = NULL; if (repo) { @@ -477,7 +532,7 @@ main_build(int argc, char *argv[]) repo = strdup("N/A"); } - while ((opt = getopt(argc, argv, "sl")) != -1) + while ((opt = getopt(argc, argv, "slJ:")) != -1) { switch (opt) { @@ -488,17 +543,13 @@ main_build(int argc, char *argv[]) with_lmdb = with_static; with_static = true; break; + case 'J': + janet_dir = strdup(optarg); + break; } } 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"); @@ -513,13 +564,22 @@ main_build(int argc, char *argv[]) /* Create all objects */ sources = collect_sources("src", true, ".c"); + if (janet_dir) + { + janet = collect_sources(janet_dir, true, ".c"); + for (i = 0; i < str_array_len(janet); i++) + { + str_array_add(sources, str_array_get(janet, i)); + } + str_array_free(janet); + } images = collect_sources("etc/media", true, ".png"); fprintf(makefile, "binary:"); write_objects(makefile, sources); write_images(makefile, images); fprintf(makefile, "\n\t"); { - fprintf(makefile, "$(CC) -o $(BINARY)"); + fprintf(makefile, "$(CC) -lm -o $(BINARY)"); if (with_static) { fprintf(makefile, " -static"); @@ -532,7 +592,7 @@ main_build(int argc, char *argv[]) fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); if (with_lmdb) fprintf(makefile, " -llmdb"); } - fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n"); + fprintf(makefile, " -lCytoplasm -lm $(LDFLAGS)\n"); } /* Write rules for every source */ @@ -540,7 +600,7 @@ main_build(int argc, char *argv[]) { char *src = str_array_get(sources, i); char *ofl = string_rep_ext(src, ".c", ".o"); - char *obj = string_cat("build", ofl + 3); + char *obj = string_cat("build", strchr(ofl, '/')); fprintf(makefile, "%s: %s", obj, src); analyse_dependencies(makefile, src); @@ -558,6 +618,10 @@ main_build(int argc, char *argv[]) str_array_free(s); fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) "); + if (janet_dir) + { + fprintf(makefile, "-DJANET -I %s ", janet_dir); + } fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" "); fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" "); fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" "); @@ -628,7 +692,7 @@ main_build(int argc, char *argv[]) fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); if (with_lmdb) fprintf(makefile, " -llmdb"); } - fprintf(makefile, " -lCytoplasm $(CFLAGS)\n"); + fprintf(makefile, " -lCytoplasm -lm $(CFLAGS)\n"); } free(ofl); @@ -716,6 +780,7 @@ main_build(int argc, char *argv[]) str_array_free(aya); fflush(makefile); fclose(makefile); + free(janet_dir); free(repo); return EXIT_SUCCESS; } diff --git a/etc/man/man1/parsee-config.1 b/etc/man/man1/parsee-config.1 index 5dc258b..a297075 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" "lunar-rainbow" +.TH parsee-config 1 "Parsee Utility" "star-of-hope" .SH NAME parsee-config - generate a basic configuration file @@ -11,7 +11,6 @@ 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] @@ -35,7 +34,6 @@ $ parsee-config \\ -H 'blow.hole' \\ -s 'The Dark Shared Secret' \\ -J 'xmpp.blow.hole' \\ - -j 'localhost' \\ -S 128 .fi .if n \{\ @@ -70,12 +68,6 @@ 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/etc/man/man7/.parsee-bridge-guidebook.7.swp b/etc/man/man7/.parsee-bridge-guidebook.7.swp deleted file mode 100644 index 50976ea..0000000 Binary files a/etc/man/man7/.parsee-bridge-guidebook.7.swp and /dev/null differ diff --git a/etc/man/man7/.parsee-cmd-syntax.7.swp b/etc/man/man7/.parsee-cmd-syntax.7.swp deleted file mode 100644 index 64990b7..0000000 Binary files a/etc/man/man7/.parsee-cmd-syntax.7.swp and /dev/null differ diff --git a/etc/man/man7/parsee-bridge-guidebook.7 b/etc/man/man7/parsee-bridge-guidebook.7 deleted file mode 100644 index b5932f2..0000000 --- a/etc/man/man7/parsee-bridge-guidebook.7 +++ /dev/null @@ -1,70 +0,0 @@ -." The last field is the codename, by the way. -." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN -.TH parsee-bridge-guidebook 7 "Parsee Utility" "star-of-hope" - -.SH NAME -parsee-bridge-guidebook - A short guidebook on running a Parsee bridge - -.SH INTRODUCTION -.P -This manpage is intended to be a guidebook for Parsee administrators. It -is meant to show how to create an instance with an XMPP-Matrix server -(though it cannot be specific, due to their ecosystem diversity), how to -plumb rooms, and moderate them through. -.P -It also assumes Parsee is properly built and installed, in which case you -are seeing this from -.I man -itself. - -.SH CONVENTIONS -This page shall assume a few things that -.B must -be changed to fit your configuration. Please read those carefully, or it -will come and bite at you! - -.P -First off, it assumes that you have a domain at -.I blow.hole -and that any other domains related to Parsee are it's subdomains, as -you'll see. - -.P -We also assume you're planning on making the XMPP component available at -.I j.blow.hole , -and that Parsee can reach it by using -.I localhost:1234 . -It is highly recommended that Parsee can reach the component locally, as -the stream cannot be encrypted! - -.P -The Parsee HTTP server (which is used for media and the appservice) shall -be reached through -.I https://p.blow.hole/ -via a reverse proxy. This manual shall only show you what port to make -available, as there are many reverse proxy options available. - -.P -Finally, the Matrix server will be publicly known as -.I m.blow.hole -. - -That is, if -.B bob -is in it, then they shall be known as -.I @bob:m.blow.hole . - -.SH SETTING UP -Setting up Parsee mainly involves creating a valid configuration file -and the database. Most of this however is dealt with by -.I parsee-config(1) -TODO - -.P - -.SH LICENSE -This document is under public domain, or CC0 if not allowed by local law. - -.SH SEE ALSO -.B parsee(1), parsee-cmd-syntax(7) - diff --git a/guix.scm b/guix.scm deleted file mode 120000 index 021acc5..0000000 --- a/guix.scm +++ /dev/null @@ -1 +0,0 @@ -.guix/modules/parsee.scm \ No newline at end of file diff --git a/src/AS/Media.c b/src/AS/Media.c index 5fbc659..65f049c 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/client/v1/media/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/media/v3/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/client/v1/media/download/", uri->host, uri->path); + path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path); cctx = ParseeCreateRequest(c, HTTP_GET, path); ASAuthenticateRequest(c, cctx); HttpRequestSendHeaders(cctx); diff --git a/src/AS/Room.c b/src/AS/Room.c index e9691e1..9e10579 100644 --- a/src/AS/Room.c +++ b/src/AS/Room.c @@ -25,13 +25,11 @@ 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, @@ -62,13 +60,11 @@ 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, @@ -99,13 +95,11 @@ 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, @@ -126,8 +120,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) { HttpClientContext *ctx = NULL; HashMap *json = NULL; - char *path, *ret, *serv; - int status; + char *path, *ret; if (!conf || !id) { return NULL; @@ -146,11 +139,6 @@ 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, "?", @@ -164,7 +152,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(path); json = HashMapCreate(); ASAuthenticateRequest(conf, ctx); - status = ParseeSetRequestJSON(ctx, json); + ParseeSetRequestJSON(ctx, json); JsonFree(json); json = JsonDecode(HttpClientStream(ctx)); @@ -175,8 +163,6 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade) Free(masquerade); Free(id); - (void) serv; // TODO - return ret; } void diff --git a/src/AS/Send.c b/src/AS/Send.c index 6811548..766f393 100644 --- a/src/AS/Send.c +++ b/src/AS/Send.c @@ -33,11 +33,6 @@ 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) { @@ -52,20 +47,18 @@ 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); ctx = ParseeCreateRequest(conf, HTTP_PUT, path); Free(path); ASAuthenticateRequest(conf, ctx); - status = ParseeSetRequestJSON(ctx, c); + ParseeSetRequestJSON(ctx, c); reply = JsonDecode(HttpClientStream(ctx)); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); diff --git a/src/Commands/BanUser.c b/src/Commands/BanUser.c index dd380ed..adc505e 100644 --- a/src/Commands/BanUser.c +++ b/src/Commands/BanUser.c @@ -28,25 +28,6 @@ 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/Commands/SetPL.c b/src/Commands/SetPL.c index ef2fdb6..95a2f04 100644 --- a/src/Commands/SetPL.c +++ b/src/Commands/SetPL.c @@ -12,7 +12,7 @@ CommandHead(CmdSetPL, cmd, argp) char *user = HashMapGet(cmd->arguments, "user"); char *room = HashMapGet(cmd->arguments, "room"); char *pl_str = HashMapGet(cmd->arguments, "pl"); - long pl = strtol(pl_str, NULL, 10); + long pl = pl_str ? strtol(pl_str, NULL, 10) : 0; HashMap *map; diff --git a/src/Extensions.c b/src/Extensions.c new file mode 100644 index 0000000..987ec91 --- /dev/null +++ b/src/Extensions.c @@ -0,0 +1,74 @@ +#ifndef JANET +#include +#include + +bool +ExtensionEnabled(void) +{ + return false; +} +Extensions * +ExtensionCreateContext(ParseeData *data) +{ + (void) data; + return NULL; +} +void +ExtensionDestroyContext(Extensions *ctx) +{ + (void) ctx; +} +Extension * +ExtensionLoadBasic(Extensions *ctx, char *id, char *file) +{ + (void) file; + (void) ctx; + (void) id; + return NULL; +} +void +ExtensionLoadDir(Extensions *ctx, char *dir) +{ + (void) ctx; + (void) dir; +} +bool +ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType type) +{ + (void) ctx; + (void) stanza; + + return false; +} +bool +ExtensionPushEvent(Extensions *ctx, HashMap *obj) +{ + (void) ctx; + (void) obj; +} +void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza) +{ + (void) ctx; + (void) stanza; +} +void +ExtensionRequestCommands(Extensions *ctx) +{ + (void) ctx; +} +void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza) +{ + (void) ctx; + (void) jid; + (void) query; + (void) stanza; +} +void +ExtensionReload(Extensions *ctx, char *id) +{ + (void) ctx; + (void) id; +} +#endif diff --git a/src/Extensions/DB.c b/src/Extensions/DB.c new file mode 100644 index 0000000..dd15f0c --- /dev/null +++ b/src/Extensions/DB.c @@ -0,0 +1,366 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +static int JanetDbGet(void *data0, Janet key, Janet *out); +static void JanetMarshalDb(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalDb(JanetMarshalContext *ctx); +static void JanetDbToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashDb(void *data0, size_t size); +static int JanetCloseDb(void *data0, size_t size); +static Janet JanetDbNext(void *data0, Janet next); + +static Janet JanetRefClose(int32_t argc, Janet *argv); +static Janet JanetRefGet(int32_t argc, Janet *argv); +static Janet JanetRefSet(int32_t argc, Janet *argv); +static const JanetMethod db_methods[] = { + { "get", JanetRefGet }, + { "set", JanetRefSet }, + { "close", JanetRefClose }, + + { NULL, NULL } +}; +const JanetAbstractType janet_db_type = { + .name = "db-ref", + .gc = JanetCloseDb, .gcmark = NULL, + + .get = JanetDbGet, .put = NULL, + .marshal = JanetMarshalDb, .unmarshal = JanetUnmarshalDb, + .tostring = JanetDbToString, + + .compare = NULL, + .hash = JanetHashDb, + + .next = JanetDbNext, + JANET_ATEND_NEXT +}; + +typedef struct DbAndRef { + Db *database; + DbRef *ref; + + bool valid; +} DbAndRef; + +static Janet +CreateFromDb(Db *db, DbRef *ref) +{ + JanetAbstract *abs; + + abs = janet_abstract(&janet_db_type, sizeof(DbAndRef)); + ((DbAndRef *)(abs))->database = db; + ((DbAndRef *)(abs))->ref = ref; + ((DbAndRef *)(abs))->valid = true; + + return janet_wrap_abstract(abs); +} + +static void +CloseFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + if (indirect) + { + indirect->valid = false; + indirect->database = NULL; + indirect->ref = NULL; + } +} +static DbRef * +RefFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + return (indirect && indirect->valid) ? indirect->ref : NULL; +} +static Db * +DbFromJanet(Janet v) +{ + DbAndRef *indirect = janet_checkabstract(v, &janet_db_type); + return (indirect && indirect->valid) ? indirect->database : NULL; +} + +static int +JanetDbGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, db_methods, out); +} +static void +JanetMarshalDb(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalDb(JanetMarshalContext *ctx) +{ + DbAndRef *data = janet_unmarshal_abstract(ctx, sizeof(DbAndRef)); + return data; +} +static int +JanetCloseDb(void *data0, size_t size) +{ + DbAndRef *data = data0; + if (data && data->valid) + { + data->valid = false; + DbUnlock(data->database, data->ref); + data->database = NULL; + data->ref = NULL; + } + return 0; +} +static void +JanetDbToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " database reference]"); +} +static int32_t +JanetHashDb(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* Hm. */ + return (int32_t) datap; +} +static Janet +JanetDbNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(db_methods, next); +} + +static Janet +JanetRefClose(int32_t argc, Janet *argv) +{ + Db *db; + DbRef *ref; + janet_fixarity(argc, 1); + + if (!(ref = RefFromJanet(argv[0])) || + !(db = DbFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + + DbUnlock(db, ref); + CloseFromJanet(argv[0]); + return janet_wrap_true(); + +} +static Janet +JanetRefGet(int32_t argc, Janet *argv) +{ + DbRef *ref; + HashMap *json; + janet_fixarity(argc, 1); + + if (!(ref = RefFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + + json = DbJson(ref); + return JsonToJanet(json); +} +static Janet +JanetRefSet(int32_t argc, Janet *argv) +{ + DbRef *ref; + JanetTable *table; + HashMap *json; + bool ret; + + janet_fixarity(argc, 2); + + if (!(ref = RefFromJanet(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a DbRef *") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_TABLE)) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("object to replace isnt JSON") + ); + return janet_wrap_nil(); + } + + table = janet_unwrap_table(argv[1]); + if (!(json = JanetToJson(table))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("object to replace isnt JSON") + ); + return janet_wrap_nil(); + } + + ret = DbJsonSet(ref, json); + JsonFree(json); + + return janet_wrap_boolean(ret); +} + + +Janet +JanetDbExists(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + int32_t i; + bool ret; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ret = DbExistsArgs(data->db, args); + ArrayFree(args); + + return janet_wrap_boolean(ret); +} +Janet +JanetDbList(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args, *list; + JanetArray *arr; + int32_t i; + janet_arity(argc, 1, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + list = DbListArgs(data->db, args); + arr = janet_array(0); + for (i = 0; i < (int32_t) ArraySize(list); i++) + { + char *ent = ArrayGet(list, i); + janet_array_push(arr, janet_cstringv(ent)); + } + + ArrayFree(args); + DbListFree(list); + return janet_wrap_array(arr); +} +Janet +JanetDbLock(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + DbRef *ref; + int32_t i; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ref = DbLockArgs(data->db, args); + ArrayFree(args); + + if (!ref) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("couldn't lock database") + ); + return janet_wrap_nil(); + } + + return CreateFromDb(data->db, ref); +} +Janet +JanetDbCreate(int32_t argc, Janet *argv) +{ + ParseeData *data; + Array *args; + DbRef *ref; + int32_t i; + janet_arity(argc, 2, -1); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("self isnt a ParseeData *") + ); + return janet_wrap_nil(); + } + + args = ArrayCreate(); + for (i = 1; i < argc; i++) + { + char *string = (char *) janet_to_string(argv[i]); + ArrayAdd(args, string); + } + + ref = DbCreateArgs(data->db, args); + ArrayFree(args); + + if (!ref) + { + janet_signalv( + JANET_SIGNAL_ERROR, + janet_cstringv("couldn't create database") + ); + return janet_wrap_nil(); + } + + return CreateFromDb(data->db, ref); +} +#endif diff --git a/src/Extensions/Data.c b/src/Extensions/Data.c new file mode 100644 index 0000000..71b8d0f --- /dev/null +++ b/src/Extensions/Data.c @@ -0,0 +1,222 @@ +#ifdef JANET + +#include "Extensions/Internal.h" +#include "Extensions/Functions.h" + +static int JanetDataGet(void *data0, Janet key, Janet *out); +static void JanetMarshalData(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalData(JanetMarshalContext *ctx); +static void JanetDataToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashData(void *data0, size_t size); +static Janet JanetDataNext(void *data0, Janet next); + +static Janet JanetShimMXC(int32_t argc, Janet *argv); +static Janet JanetMTO(int32_t argc, Janet *argv); +static Janet JanetIsAdmin(int32_t argc, Janet *argv); +static Janet JanetUptime(int32_t argc, Janet *argv); +static Janet JanetGlobalBan(int32_t argc, Janet *argv); +static const JanetMethod data_methods[] = { + /* Matrix */ + { "matrix/invite", JanetMatrixInvite }, + { "matrix/find", JanetMatrixFind }, + { "matrix/send", JanetMatrixSend }, + { "matrix/shim-mxc", JanetShimMXC }, + { "matrix/matrix-to", JanetMTO }, + + /* Bridge */ + { "bridge/get", JanetGetKey }, + { "bridge/set", JanetSetKey }, + { "bridge/stanza-info", JanetStanzaInfo }, + + /* XMPP */ + { "xmpp/write", JanetWriteStanza }, + + /* Database */ + { "db/lock", JanetDbLock }, + { "db/exists?", JanetDbExists }, + { "db/create", JanetDbCreate }, + { "db/list", JanetDbList }, + + /* General */ + { "admin?", JanetIsAdmin }, + { "uptime", JanetUptime }, + { "global-ban", JanetGlobalBan }, + + { NULL, NULL } +}; + +const JanetAbstractType janet_data_type = { + .name = "parsee-ctx", + .gc = NULL, .gcmark = NULL, + + .get = JanetDataGet, .put = NULL, + .marshal = JanetMarshalData, .unmarshal = JanetUnmarshalData, + .tostring = JanetDataToString, + + .compare = NULL, + .hash = JanetHashData, + + .next = JanetDataNext, + JANET_ATEND_NEXT +}; + +ParseeData * +GetParseeData(Janet v) +{ + ParseeData **indirect = janet_checkabstract(v, &janet_data_type); + return indirect ? *indirect : NULL; +} +Janet +FromParseeData(ParseeData *data) +{ + JanetAbstract abs; + if (!data) + { + return janet_wrap_nil(); + } + + abs = janet_abstract(&janet_data_type, sizeof(ParseeData *)); + *((ParseeData **) abs) = data; + return janet_wrap_abstract(abs); +} + +static int +JanetDataGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, data_methods, out); +} +static void +JanetMarshalData(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalData(JanetMarshalContext *ctx) +{ + ParseeData **data = janet_unmarshal_abstract(ctx, sizeof(ParseeData *)); + return data ? *data : NULL; +} +static void +JanetDataToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " context type]"); +} +static int32_t +JanetHashData(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* We're casting down, but in a context where there is really + * just one ParseeData, is that really such a crime? */ + return (int32_t) datap; +} +static Janet +JanetDataNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(data_methods, next); +} +static Janet +JanetIsAdmin(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *str; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + str = (char *) janet_to_string(argv[1]); + + return janet_wrap_boolean(ParseeIsAdmin(data, str)); +} +static Janet +JanetShimMXC(int32_t argc, Janet *argv) +{ + ParseeData *data; + Janet v; + char *mxc, *shimmed; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + mxc = (char *) janet_to_string(argv[1]); + shimmed = ParseeToUnauth(data, mxc); + if (!shimmed) + { + return janet_wrap_nil(); + } + v = janet_cstringv(shimmed); + Free(shimmed); + + return v; +} +static Janet +JanetMTO(int32_t argc, Janet *argv) +{ + ParseeData *data; + Janet v; + char *common, *mto; + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + common = (char *) janet_to_string(argv[1]); + mto = ParseeGenerateMTO(common); + if (!mto) + { + return janet_wrap_nil(); + } + v = janet_cstringv(mto); + Free(mto); + + (void) data; + return v; +} +static Janet +JanetUptime(int32_t argc, Janet *argv) +{ + ParseeData *data; + janet_fixarity(argc, 1); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + + (void) data; + return janet_wrap_number(ParseeUptime()/1000.); +} +static Janet +JanetGlobalBan(int32_t argc, Janet *argv) +{ + ParseeData *data; + const char *user, *reason = NULL; + janet_arity(argc, 2, 3); + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (!(user = janet_getcstring(argv, 1))) + { + return janet_wrap_nil(); + } + if (argc == 3 && janet_checktype(argv[2], JANET_STRING)) + { + reason = janet_getcstring(argv, 2); + } + + ParseeGlobalBan(data, (char *) user, (char *) reason); + return janet_wrap_true(); +} +#endif diff --git a/src/Extensions/Functions.c b/src/Extensions/Functions.c new file mode 100644 index 0000000..d159f99 --- /dev/null +++ b/src/Extensions/Functions.c @@ -0,0 +1,207 @@ +#ifdef JANET +#include "Extensions/Internal.h" + +#include "Extensions/Functions.h" + +Janet +JanetMilliseconds(int32_t argc, Janet *argv) +{ + janet_fixarity(argc, 0); + return janet_wrap_number(UtilTsMillis()/1000.); +} +Janet +JanetCytoVersion(int32_t argc, Janet *argv) +{ + janet_fixarity(argc, 0); + return janet_cstringv(CytoplasmGetVersionStr()); +} +static const JanetReg functions[] = { + /* Cytoplasm */ + { + "cyto/version", JanetCytoVersion, + "(cyto/version)\n\n" + "Returns the Cytoplasm version as a string." + }, + { + "cyto/ts", JanetMilliseconds, + "(cyto/ts)\n\n" + "Returns the timestamp as seconds since 01-01-1970." + }, + + /* XML */ + { + "xml/data", JanetCreateXMLData, + "(xml/data [PARENT?] [TEXT])\n\n" + "Creates a data XML element with freeform text." + }, + { + "xml/tag", JanetCreateXMLTag, + "(xml/tag [PARENT?] [NAME])\n\n" + "Creates a simple XML tag with a name" + }, + + /* JSON */ + { + "json/parse", JanetParseJSON, + "(json/parse [INPUT:buffer]) -> table|NIL\n\n" + "Parses a JSON buffer into a table." + }, + + /* HTTP */ + { + "http/request", JanetHttpRequest, + "(http/request " + "[METHOD:string] " + "[URL:string] " + "[HEADERS:table?] " + "[DATA:buffer])\n\n" + "Creates and sends an HTTP request, and returns an HTTP response" + }, + + {NULL, NULL, NULL} +}; +void +SetupBasicExtensionContext(Extension *ext, JanetTable *env) +{ + char *parsee; + /* Constants */ + janet_def(ext->env, + "parsee/ctx", FromParseeData(ext->source->data), + "The Parsee context used, which maps directly to a ParseeData " + "structure pointer in C99land. Used in various functions." + ); + + parsee = ParseeJID(ext->source->data); + janet_def(ext->env, + "parsee/jid", janet_cstringv(parsee), + "The Parsee bot's Jabber ID as a string" + ); + Free(parsee); + + parsee = ParseeMXID(ext->source->data); + janet_def(ext->env, + "parsee/mxid", janet_cstringv(parsee), + "The Parsee bot's MXID as a string" + ); + Free(parsee); + + janet_def(ext->env, + "parsee/id", janet_cstringv(ext->id), + "The extension's 'identifier'(reverse DNS) notation." + ); + + /* Functions */ + janet_cfuns(env, "parsee", functions); + + /* "Why bother with C99?" */ +#define CheckType(var, type, str) \ + "(if (not= (type " #var ") :" #type ") (error `" str "`))" + janet_dostring(ext->env, + "(defn xml/get-attr[xml key] " + CheckType(xml, table, "XML is not a table") + CheckType(key, string, "KEY is not a string") + "(get (get xml :xml-attrs) key) " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xml/set-attr[xml key val] " + CheckType(xml, table, "XML is not a table") + CheckType(key, string, "KEY is not a string") + CheckType(val, string, "VAL is not a string") + "(put (get xml :xml-attrs) key val) " + + /* Just to make sure this function returns NIL */ + "nil" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/create-note [type msg] " + "(def note (xml/tag `note`)) " + "(def mesg (xml/data note msg)) " + + "(xml/set-attr note `type` type) " + + "note" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/create-form [] " + "(def x (xml/tag `x`)) " + "(xml/set-attr x `xmlns` `jabber:x:data`) " + + "x" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/set-form-title [x title] " + "(def title-xml (xml/tag x `title`)) " + "(def title-txt (xml/data title-xml title)) " + + "title-xml" + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/generate-table [x fields elems] " + CheckType(x, table, "Form is not a table") + CheckType(fields, table, "Fields is not a table") + CheckType(elems, array, "Elems is not an array") + + "(def reported (xml/tag x `reported`)) " + + "(loop [[id name] :pairs fields] " + "(def field (xml/tag reported `field`)) " + "(xml/set-attr field `var` id) " + "(xml/set-attr field `label` name) " + ") " + + "(loop [entry :in elems] " + CheckType(entry, table, "Entry is not a table.") + "(def xml-entry (xml/tag x `item`)) " + + "(loop [[id val] :pairs entry] " + "(def xml-field (xml/tag xml-entry `field`)) " + "(def xml-value (xml/tag xml-field `value`)) " + "(xml/data xml-value val) " + "(xml/set-attr xml-field `var` id) " + ") " + ") " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/find-tag [xml tag] " + "(var ret nil) " + "(loop [xml-field :in (get xml :xml-children)] " + "(if (= (get xml-field :xml-name) tag) (do" + "(set ret xml-field) " + "(break)" + ")) " + ") " + "ret " + ")" + , "embed:" __FILE__, NULL + ); + janet_dostring(ext->env, + "(defn xmppcmd/get-field [submit key] " + CheckType(submit, table, "Input is not a table") + "(var value nil) " + "(loop [xml-field :in (get submit :xml-children)] " + "(if (and (= (get xml-field :xml-name) `field`) (= (get (get xml-field :xml-attrs) `var`) key)) (do" + "(set value (xmppcmd/find-tag xml-field `value`)) " + "(set value (get (get (get value :xml-children) 0) :xml-data)) " + "(break)" + ")) " + ") " + "value " + ") " + , "embed:" __FILE__, NULL + ); + +#undef CheckType +} +#endif diff --git a/src/Extensions/Functions.h b/src/Extensions/Functions.h new file mode 100644 index 0000000..0d0f4a6 --- /dev/null +++ b/src/Extensions/Functions.h @@ -0,0 +1,34 @@ +#ifdef JANET + +#include + +#define Bind(name) Janet Janet##name(int32_t argc, Janet *argv) + + +Bind(Milliseconds); +Bind(CytoVersion); + +Bind(WriteStanza); + +Bind(CreateXMLData); +Bind(CreateXMLTag); +Bind(SetXMLAttr); + +Bind(ParseJSON); + +Bind(MatrixInvite); +Bind(MatrixFind); +Bind(MatrixSend); + +Bind(StanzaInfo); +Bind(GetKey); +Bind(SetKey); + +Bind(DbLock); +Bind(DbCreate); +Bind(DbList); +Bind(DbExists); + +Bind(HttpRequest); + +#endif diff --git a/src/Extensions/Http.c b/src/Extensions/Http.c new file mode 100644 index 0000000..ffd95b8 --- /dev/null +++ b/src/Extensions/Http.c @@ -0,0 +1,258 @@ +#ifdef JANET + +#include "Extensions/Internal.h" +#include "Extensions/Functions.h" + +#include +#include + +static int JanetHttpGet(void *http0, Janet key, Janet *out); +static void JanetMarshalHttp(void *http0, JanetMarshalContext *ctx); +static void *JanetUnmarshalHttp(JanetMarshalContext *ctx); +static void JanetHttpToString(void *http0, JanetBuffer *buf); +static int32_t JanetHashHttp(void *http0, size_t size); +static Janet JanetHttpNext(void *http0, Janet next); +static int JanetCloseHttp(void *data0, size_t size); + +static Janet JanetHttpCode(int32_t argc, Janet *argv); +static Janet JanetHttpHeaders(int32_t argc, Janet *argv); +static Janet JanetHttpResponse(int32_t argc, Janet *argv); +static const JanetMethod http_methods[] = { + { "code", JanetHttpCode }, + { "headers", JanetHttpHeaders }, + { "response", JanetHttpResponse }, + + { NULL, NULL } +}; + +typedef struct HttpStruct { + int code; + HashMap *headers; + + uint8_t *data; + int32_t size; +} HttpStruct; + +const JanetAbstractType janet_http_type = { + .name = "http-response", + .gc = JanetCloseHttp, .gcmark = NULL, + + .get = JanetHttpGet, .put = NULL, + .marshal = JanetMarshalHttp, .unmarshal = JanetUnmarshalHttp, + .tostring = JanetHttpToString, + + .compare = NULL, + .hash = JanetHashHttp, + + .next = JanetHttpNext, + JANET_ATEND_NEXT +}; +Janet +JanetHttpRequest(int32_t argc, Janet *argv) +{ + const char *method_str = NULL; + const char *path_str = NULL; + JanetTable *headers = NULL; + JanetAbstract *abs = NULL; + JanetBuffer *data = NULL; + HttpClientContext *ctx; + HashMap *raw_headers; + char *header, *value; + HttpStruct *http; + int code, ch; + int32_t i; + Uri *url; + + janet_arity(argc, 2, 4); + if (!(method_str = janet_getcstring(argv, 0)) || + !(path_str = janet_getcstring(argv, 1))) + { + return janet_wrap_nil(); + } + if (argc >= 3) + { + headers = janet_gettable(argv, 2); + } + if (argc >= 4) + { + data = janet_getbuffer(argv, 3); + } + + url = UriParse(path_str); + if (!url || + (!StrEquals(url->proto, "http") && !StrEquals(url->proto, "https"))) + { + UriFree(url); + return janet_wrap_nil(); + } + if (!url->port || StrEquals(url->proto, "http")) + { + url->port = 80; + } + if (!url->port || StrEquals(url->proto, "https")) + { + url->port = 443; + } + ctx = HttpRequest( + HttpRequestMethodFromString(method_str), + StrEquals(url->proto, "https") ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, + url->port, url->host, url->path + ); + UriFree(url); + if (!ctx) + { + return janet_wrap_nil(); + } + + for (i = 0; headers && i < headers->capacity; i++) + { + JanetKV kv = headers->data[i]; + char *key, *value; + if (!janet_checktype(kv.key, JANET_STRING)) + { + continue; + } + + key = (char *) janet_unwrap_string(kv.key); + value = (char *) janet_to_string(kv.value); + HttpRequestHeader(ctx, key, value); + } + if (data) + { + char *len_str = StrInt((int) data->count); + HttpRequestHeader(ctx, "Content-Length", len_str); + Free(len_str); + } + HttpRequestSendHeaders(ctx); + if (data) + { + for (i = 0; i < data->count; i++) + { + uint8_t byte = data->data[i]; + StreamPutc(HttpClientStream(ctx), byte); + } + } + code = HttpRequestSend(ctx); + raw_headers = HttpResponseHeaders(ctx); + + abs = janet_abstract(&janet_http_type, sizeof(HttpStruct)); + http = (HttpStruct *) abs; + http->code = code; + http->headers = HashMapCreate(); + http->size = 0; + http->data = NULL; + while (HashMapIterate(raw_headers, &header, (void **) &value)) + { + HashMapSet(http->headers, header, StrDuplicate(value)); + } + + while ((ch = StreamGetc(HttpClientStream(ctx))) != EOF) + { + http->data = Realloc(http->data, ++(http->size)); + http->data[http->size-1] = ch; + } + HttpClientContextFree(ctx); + + return janet_wrap_abstract(abs); +} + +static int +JanetHttpGet(void *http0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) http0; + return janet_getmethod(kw, http_methods, out); +} +static void +JanetMarshalHttp(void *http0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, http0); +} +static void * +JanetUnmarshalHttp(JanetMarshalContext *ctx) +{ + HttpStruct *http = janet_unmarshal_abstract(ctx, sizeof(HttpStruct)); + return http; +} +static void +JanetHttpToString(void *http0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " HTTP object]"); +} +static int32_t +JanetHashHttp(void *http0, size_t size) +{ + uintptr_t httpp = (uintptr_t) http0; + + (void) size; + return (int32_t) httpp; +} +static Janet +JanetHttpNext(void *http0, Janet next) +{ + (void) http0; + return janet_nextmethod(http_methods, next); +} +static int +JanetCloseHttp(void *data0, size_t size) +{ + HttpStruct *http = data0; + char *key; + void *val; + + while (HashMapIterate(http->headers, &key, &val)) + { + Free(val); + } + HashMapFree(http->headers); + Free(http->data); + return 0; +} +static Janet +JanetHttpCode(int32_t argc, Janet *argv) +{ + JanetAbstract *abs; + HttpStruct *http; + janet_fixarity(argc, 1); + + abs = janet_getabstract(argv, 0, &janet_http_type); + if (!abs) + { + return janet_wrap_nil(); + } + http = (HttpStruct *) abs; + + return janet_wrap_integer(http->code); +} +static Janet +JanetHttpHeaders(int32_t argc, Janet *argv) +{ + return janet_wrap_nil(); +} +static Janet +JanetHttpResponse(int32_t argc, Janet *argv) +{ + JanetBuffer *buffer; + JanetAbstract *abs; + HttpStruct *http; + janet_fixarity(argc, 1); + + abs = janet_getabstract(argv, 0, &janet_http_type); + if (!abs) + { + return janet_wrap_nil(); + } + http = (HttpStruct *) abs; + + buffer = janet_buffer(http->size + 1); + janet_buffer_push_bytes(buffer, (const uint8_t *) http->data, http->size); + + return janet_wrap_buffer(buffer); +} +#endif diff --git a/src/Extensions/Internal.h b/src/Extensions/Internal.h new file mode 100644 index 0000000..db0af88 --- /dev/null +++ b/src/Extensions/Internal.h @@ -0,0 +1,98 @@ +#ifndef PARSEE_IEXTENSIONS_H +#define PARSEE_IEXTENSIONS_H +#ifdef JANET + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#define JANET_STANZA_PUSH 0 +#define JANET_REQUEST_XMPPCMD 1 +#define JANET_REQUEST_XMPPCMD_A 2 +#define JANET_REQUEST_XMPPCMD_F 3 + +struct Extensions { + HashMap *extensions; + size_t count; + + ParseeData *data; + + pthread_mutex_t mtx; +}; +struct Extension { + Extensions *source; + + JanetTable *env; + JanetVM *vm; + char *id; + + uint8_t *bytes; + size_t len; + char *file; + + JanetFiber *stanzas; + + pthread_t thr; + volatile bool halted; + + XMPPCommandManager *manager; + JanetTable *functions; +}; + +/** Creates a Janet table from an XML element. For all sensical + * usecases, set in to NULL. + * ----------------------- + * Returns: a wrapped Janet table[janet] */ +Janet ExtensionsFromXML(JanetArray *in, XMLElement *e); + +/** Takes a Janet table and tries to encode it as XML. + * ---------------- + * Returns: an XML element[HEAP] | NULL */ +XMLElement * ExtensionsToXML(JanetTable *t); + +/** Creates a Janet table from a JSON object. + * ---------- + * Returns: a wrapped Janet table[janet] | NULL */ +Janet JsonToJanet(HashMap *obj); + +/** Takes a Janet table and tries to encode it as JSON. + * ---------------- + * Returns: a JSON object[HEAP] | NULL */ +HashMap * JanetToJson(JanetTable *table); + +/** Configures some basic context for the embedded Janet environment. + * --------------------------- + * Returns: NOTHING + * Modifies: env */ +void SetupBasicExtensionContext(Extension *ext, JanetTable *env); + +/** Tries to get a ParseeData from a Janet value(see 'parsee/ctx'). + * ------------------ + * Returns: the data object | NULL */ +ParseeData * GetParseeData(Janet v); + +/** Generates a Janet value from a ParseeData. + * ------------ + * See-Also; GetParseeData */ +Janet FromParseeData(ParseeData *data); + +bool +TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out); +#endif +#endif diff --git a/src/Extensions/JSON.c b/src/Extensions/JSON.c new file mode 100644 index 0000000..1996b9d --- /dev/null +++ b/src/Extensions/JSON.c @@ -0,0 +1,170 @@ +#ifdef JANET +#include + +#include +#include +#include + +#include +#include + +Janet JsonToJanet(HashMap *obj); +static Janet +JsonValueToJanet(JsonValue *val) +{ + Janet jVal; + switch (JsonValueType(val)) + { + case JSON_STRING: + jVal = janet_cstringv(JsonValueAsString(val)); + break; + case JSON_NULL: + jVal = janet_wrap_nil(); + break; + case JSON_FLOAT: + jVal = janet_wrap_number(JsonValueAsFloat(val)); + break; + case JSON_INTEGER: + jVal = janet_wrap_s64(JsonValueAsInteger(val)); + break; + case JSON_BOOLEAN: + jVal = janet_wrap_boolean(JsonValueAsBoolean(val)); + break; + case JSON_OBJECT: + jVal = JsonToJanet(JsonValueAsObject(val)); + break; + case JSON_ARRAY: + { + JanetArray *arr = janet_array(0); + Array *jArr = JsonValueAsArray(val); + size_t i, len; + for (i = 0, len = ArraySize(jArr); i < len; i++) + { + janet_array_push(arr, JsonValueToJanet(ArrayGet(jArr, i))); + } + jVal = janet_wrap_array(arr); + } + break; + } + + return jVal; +} + +Janet +JsonToJanet(HashMap *obj) +{ + JanetTable *table; + char *key; + JsonValue *val; + if (!obj) + { + return janet_wrap_nil(); + } + + table = janet_table(0); + while (HashMapIterate(obj, &key, (void **) &val)) + { + Janet jVal = JsonValueToJanet(val); + janet_table_put(table, janet_cstringv(key), jVal); + } + + return janet_wrap_table(table); +} + +HashMap * JanetToJson(JanetTable *table); +static JsonValue * +JanetToJsonValue(Janet janet) +{ + JsonValue *ret = NULL; + if (janet_type(janet) == JANET_STRING) + { + ret = JsonValueString((char *) janet_unwrap_string(janet)); + } + else if (janet_type(janet) == JANET_BOOLEAN) + { + ret = JsonValueBoolean(janet_unwrap_boolean(janet)); + } + else if (janet_type(janet) == JANET_NIL) + { + ret = JsonValueNull(); + } + else if (janet_type(janet) == JANET_ARRAY) + { + JanetArray *arr = janet_unwrap_array(janet); + Array *jArr = ArrayCreate(); + size_t i; + + for (i = 0; i < arr->count; i++) + { + Janet child = arr->data[i]; + ArrayAdd(jArr, JanetToJsonValue(child)); + } + + ret = JsonValueArray(jArr); + } + else if (janet_type(janet) == JANET_TABLE) + { + ret = JsonValueObject(JanetToJson(janet_unwrap_table(janet))); + } + else if (janet_is_int(janet)) + { + ret = JsonValueInteger(janet_unwrap_s64(janet)); + } + else if (janet_type(janet) == JANET_NUMBER) + { + ret = JsonValueFloat(janet_unwrap_number(janet)); + } + + return ret; +} +HashMap * +JanetToJson(JanetTable *table) +{ + HashMap *ret; + size_t i; + if (!table) + { + return NULL; + } + + ret = HashMapCreate(); + for (i = 0; i < table->capacity; i++) + { + JanetKV *kv = &table->data[i]; + char *key; + if (!janet_checktype(kv->key, JANET_STRING)) + { + continue; + } + + key = (char *) janet_unwrap_string(kv->key); + + JsonValueFree(HashMapSet(ret, key, JanetToJsonValue(kv->value))); + } + + return ret; +} + +Janet +JanetParseJSON(int32_t argc, Janet *argv) +{ + JanetBuffer *buf = NULL; + Stream *buffer_stream; + HashMap *json; + Janet ret; + + janet_fixarity(argc, 1); + if (!(buf = janet_getbuffer(argv, 0))) + { + return janet_wrap_nil(); + } + + buffer_stream = StrStreamReaderN((char *) buf->data, buf->count); + json = JsonDecode(buffer_stream); + StreamClose(buffer_stream); + ret = JsonToJanet(json); + JsonFree(json); + + return ret; +} +#endif diff --git a/src/Extensions/Jabber.c b/src/Extensions/Jabber.c new file mode 100644 index 0000000..9adcaec --- /dev/null +++ b/src/Extensions/Jabber.c @@ -0,0 +1,30 @@ +#ifdef JANET +#include "Extensions/Internal.h" + +#include "Extensions/Functions.h" + +Janet +JanetWriteStanza(int32_t argc, Janet *argv) +{ + XMLElement *xml; + ParseeData *data; + + janet_fixarity(argc, 2); + if (janet_type(argv[0]) != JANET_POINTER) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_TABLE) + { + return janet_wrap_nil(); + } + + data = janet_unwrap_pointer(argv[0]); + xml = ExtensionsToXML(janet_unwrap_table(argv[1])); + + XMPPSendStanza(data->jabber, xml, data->config->max_stanza_size); + + XMLFreeElement(xml); + return janet_wrap_nil(); +} +#endif diff --git a/src/Extensions/Janet.c b/src/Extensions/Janet.c new file mode 100644 index 0000000..0442688 --- /dev/null +++ b/src/Extensions/Janet.c @@ -0,0 +1,304 @@ +#ifdef JANET +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Extensions/Internal.h" + +bool +ExtensionEnabled(void) +{ + return true; +} + +Extensions * +ExtensionCreateContext(ParseeData *data) +{ + Extensions *ctx = Malloc(sizeof(*ctx)); + + ctx->extensions = HashMapCreate(); + ctx->data = data; + ctx->count = 0; + + pthread_mutex_init(&ctx->mtx, NULL); + return ctx; +} +void +ExtensionDestroyContext(Extensions *ctx) +{ + char *key; + Extension *ext; + if (!ctx) + { + return; + } + + while (HashMapIterate(ctx->extensions, &key, (void **) &ext)) + { + /* TODO */ + ext->halted = true; + janet_loop1_interrupt(ext->vm); + + pthread_join(ext->thr, NULL); + XMPPFreeManager(ext->manager); + Free(ext->bytes); + Free(ext->file); + Free(ext->id); + Free(ext); + } + pthread_mutex_destroy(&ctx->mtx); + HashMapFree(ctx->extensions); + Free(ctx); +} + +Janet +GetGlobal(Extension *ext, char *key) +{ + Janet v; + + v = janet_table_get(ext->env, janet_csymbolv(key)); + if (janet_type(v) == JANET_NIL) + { + return janet_wrap_nil(); + } + return janet_table_get(janet_unwrap_table(v), janet_ckeywordv("value")); +} +bool +TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out) +{ + Janet v = GetGlobal(ext, func); + JanetFiber *fib = NULL; + JanetSignal sig = JANET_SIGNAL_OK; + if (janet_type(v) == JANET_FUNCTION) + { + sig = janet_pcall( + janet_unwrap_function(v), + argc, argv, + out, &fib + ); + } + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, *out); + } + + return sig == JANET_SIGNAL_OK; +} + +static void * +ExtensionLoop(void *p) +{ + Extension *ext = p; + Janet f_table; + + janet_init(); + ext->env = janet_core_env(NULL); + ext->functions = janet_table(0); + ext->vm = janet_local_vm(); + + f_table = janet_wrap_table(ext->functions); + janet_gcroot(f_table); + + /* Load the Janet environment */ + SetupBasicExtensionContext(ext, ext->env); + janet_dobytes(ext->env, ext->bytes, ext->len, ext->file, NULL); + + while (!ext->halted) + { + janet_loop(); + UtilSleepMillis(1); + } + + janet_gcunroot(f_table); + + //janet_table_deinit(ext->env); + janet_deinit(); + return NULL; +} + +void +ExtensionReload(Extensions *ctx, char *id) +{ + Extension *ext; + Log(LOG_INFO, "ctx=%p id=%s", ctx, id); + if (!ctx || !id) + { + return; + } + + pthread_mutex_lock(&ctx->mtx); + Log(LOG_INFO, "Reloading %s", id); + ext = HashMapGet(ctx->extensions, id); + if (!ext) + { + pthread_mutex_unlock(&ctx->mtx); + Log(LOG_INFO, ":("); + return; + } + ext->halted = true; + janet_loop1_interrupt(ext->vm); + + pthread_join(ext->thr, NULL); + XMPPFreeManager(ext->manager); + Free(ext->bytes); + Free(ext->id); + HashMapDelete(ctx->extensions, id); + + ExtensionLoadBasic(ctx, id, ext->file); + Free(ext->file); + Free(ext); + pthread_mutex_unlock(&ctx->mtx); +} +Extension * +ExtensionLoadBasic(Extensions *ctx, char *id, char *file) +{ + Extension *ext; + Stream *stream; + uint8_t *bytes = NULL; + size_t len = 0; + int c; + + if (!ctx || !id || !file) + { + return NULL; + } + + if (!(stream = StreamOpen(file, "r"))) + { + return NULL; + } + while ((c = StreamGetc(stream)) != EOF) + { + bytes = Realloc(bytes, len + 1); + bytes[len++] = c; + } + StreamClose(stream); + + ext = Malloc(sizeof(*ext)); + + /* will be set by the thread */ + ext->vm = NULL; + ext->stanzas = NULL; + + ext->file = StrDuplicate(file); + ext->id = StrDuplicate(id); + ext->bytes = bytes; + ext->source = ctx; + ext->len = len; + ext->halted = false; + ext->manager = XMPPCreateManager(ext); + HashMapSet(ctx->extensions, id, ext); + + pthread_create( + &ext->thr, + NULL, + ExtensionLoop, + ext + ); + return ext; +} + +static bool +EndsWith(char *str, const char *ext) +{ + size_t len_str, len_ext; + if (!str || !ext) + { + return false; + } + + len_str = strlen(str); + len_ext = strlen(ext); + if (len_ext > len_str) + { + return false; + } + + return StrEquals(str + (len_str - len_ext), ext); +} +void +ExtensionLoadDir(Extensions *ctx, char *dir) +{ + struct dirent *ent; + DIR *handle; + if (!ctx || !dir) + { + return; + } + if (!(handle = opendir(dir))) + { + return; + } + while ((ent = readdir(handle))) + { + const char *extension = ".janet"; + char *name = ent->d_name; + char *actual_path; + char *id = name; + if (!EndsWith(name, extension)) + { + continue; + } + id = StrDuplicate(id); + actual_path = StrConcat(3, dir, "/", name); + id[strlen(name) - strlen(extension)] = '\0'; + + ExtensionLoadBasic(ctx, id, actual_path); + + Free(actual_path); + Free(id); + } + + closedir(handle); +} +void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza) +{ + size_t i; + char *key; + Extension *val; + if (!ctx || !jid || !query || !stanza) + { + return; + } + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + XMPPShoveCommandList(val->manager, jid, query, stanza); + } + pthread_mutex_unlock(&ctx->mtx); +} +void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza) +{ + size_t i; + char *key; + Extension *val; + if (!ctx || !stanza) + { + return; + } + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + XMPPManageCommand(val->manager, stanza, ctx->data); + } + pthread_mutex_unlock(&ctx->mtx); +} +#endif diff --git a/src/Extensions/KV.c b/src/Extensions/KV.c new file mode 100644 index 0000000..69819c5 --- /dev/null +++ b/src/Extensions/KV.c @@ -0,0 +1,86 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +Janet +JanetGetKey(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *chat_id = NULL; + char *key = NULL, *val = NULL; + Janet ret; + + janet_fixarity(argc, 3); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string")); + return janet_wrap_nil(); + } + + if (!(data = janet_unwrap_pointer(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is NULL..")); + return janet_wrap_nil(); + } + chat_id = (char *) janet_unwrap_string(argv[1]); + key = (char *) janet_unwrap_string(argv[2]); + + if ((val = ParseeGetChatSetting(data, chat_id, key))) + { + ret = janet_cstringv(val); + Free(val); + } + else + { + ret = janet_wrap_nil(); + } + return ret; +} +Janet +JanetSetKey(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *chat_id = NULL; + char *key = NULL, *val = NULL; + + janet_fixarity(argc, 4); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[3], JANET_STRING)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("VAL is not a string")); + return janet_wrap_nil(); + } + + chat_id = (char *) janet_unwrap_string(argv[1]); + key = (char *) janet_unwrap_string(argv[2]); + val = (char *) janet_unwrap_string(argv[3]); + + ParseeSetChatSetting(data, chat_id, key, val); + return janet_wrap_nil(); +} +#endif + diff --git a/src/Extensions/Matrix.c b/src/Extensions/Matrix.c new file mode 100644 index 0000000..e0d33a8 --- /dev/null +++ b/src/Extensions/Matrix.c @@ -0,0 +1,149 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +#include + +Janet +JanetMatrixInvite(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *invitee, *room; + janet_fixarity(argc, 3); + + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + return janet_wrap_nil(); + } + + invitee = (char *) janet_unwrap_string(argv[1]); + room = (char *) janet_unwrap_string(argv[2]); + if (!data) + { + return janet_wrap_nil(); + } + + ASInvite(data->config, room, invitee); + return janet_wrap_nil(); +} +Janet +JanetMatrixFind(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *id, *room; + Janet returned; + HashMap *e; + janet_fixarity(argc, 3); + + if (!(data = GetParseeData(argv[0]))) + { + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + return janet_wrap_nil(); + } + + room = (char *) janet_unwrap_string(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + if (!data) + { + return janet_wrap_nil(); + } + + if (!(e = ASFind(data->config, room, id))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("couldnt find event")); + return janet_wrap_nil(); + } + returned = JsonToJanet(e); + JsonFree(e); + return returned; +} +Janet +JanetMatrixSend(int32_t argc, Janet *argv) +{ + ParseeData *data; + char *from, *room, *type, *id; + JanetTable *json; + Janet returned; + HashMap *e; + janet_fixarity(argc, 5); + + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[1]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("FROM is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[2]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("ROOM is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[3]) != JANET_STRING) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("TYPE is not a string.") + ); + return janet_wrap_nil(); + } + if (janet_type(argv[4]) != JANET_TABLE) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("JSON is not a table.") + ); + return janet_wrap_nil(); + } + + from = (char *) janet_unwrap_string(argv[1]); + room = (char *) janet_unwrap_string(argv[2]); + type = (char *) janet_unwrap_string(argv[3]); + json = janet_unwrap_table(argv[4]); + if (!data) + { + return janet_wrap_nil(); + } + + if (!(e = JanetToJson(json))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("JSON is not valid JSON.") + ); + return janet_wrap_nil(); + } + + if (!(id = ASSend(data->config, room, from, type, e, 0))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Could not send event") + ); + return janet_wrap_nil(); + } + returned = janet_cstringv(id); + Free(id); + return returned; +} +#endif diff --git a/src/Extensions/PushEvents.c b/src/Extensions/PushEvents.c new file mode 100644 index 0000000..40ae858 --- /dev/null +++ b/src/Extensions/PushEvents.c @@ -0,0 +1,89 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +typedef struct ExtensionEventReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + HashMap *obj; + + Extension *ext; + bool reply, ack; +} ExtensionEventReply; + +static void +OnEventResponse(JanetEVGenericMessage msg) +{ + ExtensionEventReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet args[1] = { + JsonToJanet(reply->obj), + }; + Janet ret; + + if (TryCall(ext, "on-event", 1, args, &ret)) + { + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = janet_truthy(ret); + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); + return; + } + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = false; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} +bool +ExtensionPushEvent(Extensions *ctx, HashMap *obj) +{ + bool veto = false; + size_t i; + + char *key; + Extension *val; + if (!ctx || !obj) + { + return false; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionEventReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + /* We except a veto boolean. */ + .tag = JANET_STANZA_PUSH, + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->reply = false; + reply->ack = false; + reply->obj = obj; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnEventResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + if (reply->reply) + { + veto = true; + } + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); + + return veto; +} +#endif diff --git a/src/Extensions/PushStanza.c b/src/Extensions/PushStanza.c new file mode 100644 index 0000000..4f61961 --- /dev/null +++ b/src/Extensions/PushStanza.c @@ -0,0 +1,98 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +typedef struct ExtensionStanzaReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + XMLElement *stanza; + + Extension *ext; + bool reply, ack; + char *type; +} ExtensionStanzaReply; + +static void +OnStanzaResponse(JanetEVGenericMessage msg) +{ + ExtensionStanzaReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet args[2] = { + ExtensionsFromXML(NULL, reply->stanza), + janet_cstringv(reply->type) + }; + Janet ret; + + if (TryCall(ext, "on-stanza", 2, args, &ret)) + { + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = janet_truthy(ret); + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); + return; + } + pthread_mutex_lock(&reply->lock); + reply->ack = true; + reply->reply = false; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} +bool +ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t) +{ + bool veto = false; + size_t i; + + char *table[STANZA_TYPE_COUNT] = { + "raw", + "message", + "iq", + "presence" + }; + char *key; + Extension *val; + if (!ctx || !stanza) + { + return false; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionStanzaReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + /* We except a veto boolean. */ + .tag = JANET_STANZA_PUSH, + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->reply = false; + reply->ack = false; + reply->type = table[t]; + reply->stanza = stanza; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnStanzaResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + if (reply->reply) + { + veto = true; + } + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); + + return veto; +} +#endif diff --git a/src/Extensions/RequestCmds.c b/src/Extensions/RequestCmds.c new file mode 100644 index 0000000..61a1bca --- /dev/null +++ b/src/Extensions/RequestCmds.c @@ -0,0 +1,580 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +#include +#include + +typedef struct ExtensionCommandReply { + pthread_mutex_t lock; + pthread_cond_t cond; + + XMLElement *form; + char *from; + char *node; + + XMLElement *out; + + XMPPCommand *cmd; + + Extension *ext; + bool ack; +} ExtensionCommandReply; + +static void OnCommandResponse(JanetEVGenericMessage msg); + +static int JanetCmdGet(void *data0, Janet key, Janet *out); +static void JanetMarshalCmd(void *data0, JanetMarshalContext *ctx); +static void *JanetUnmarshalCmd(JanetMarshalContext *ctx); +static void JanetCmdToString(void *data0, JanetBuffer *buf); +static int32_t JanetHashCmd(void *data0, size_t size); +static Janet JanetCmdNext(void *data0, Janet next); + +static Janet JanetCmdCreateText(int32_t argc, Janet *argv); +static Janet JanetCmdCreateBool(int32_t argc, Janet *argv); +static Janet JanetCmdCreateFixed(int32_t argc, Janet *argv); +static Janet JanetCmdSetTitle(int32_t argc, Janet *argv); +static const JanetMethod cmd_methods[] = { + { "create-text", JanetCmdCreateText }, + { "create-bool", JanetCmdCreateBool }, + { "create-fixed", JanetCmdCreateFixed }, + { "set-title", JanetCmdSetTitle }, + { NULL, NULL } +}; +const JanetAbstractType janet_cmd_type = { + .name = "parsee-xmppcmd", + .gc = NULL, .gcmark = NULL, + + .get = JanetCmdGet, .put = NULL, + .marshal = JanetMarshalCmd, .unmarshal = JanetUnmarshalCmd, + .tostring = JanetCmdToString, + + .compare = NULL, + .hash = JanetHashCmd, + + .next = JanetCmdNext, + JANET_ATEND_NEXT +}; + + +static XMPPCommand * +JanetToCommand(Janet v) +{ + XMPPCommand **indirect = janet_checkabstract(v, &janet_cmd_type); + return indirect ? *indirect : NULL; +} +static Janet +CommandToJanet(XMPPCommand *cmd) +{ + JanetAbstract abs; + if (!cmd) + { + return janet_wrap_nil(); + } + + abs = janet_abstract(&janet_cmd_type, sizeof(XMPPCommand *)); + *((XMPPCommand **) abs) = cmd; + return janet_wrap_abstract(abs); +} + +static void +PutFunction(JanetTable *functions, char *node, char *kind, JanetFunction *f) +{ + JanetTable *kinds; + Janet kindsv; + if (!functions || !node || !kind || !f) + { + return; + } + + kindsv = janet_table_get(functions, janet_cstringv(node)); + if (janet_checktype(kindsv, JANET_NIL)) + { + kinds = janet_table(0); + janet_table_put( + functions, + janet_cstringv(node), janet_wrap_table(kinds) + ); + } + else + { + kinds = janet_unwrap_table(kindsv); + } + + janet_table_put(kinds, janet_cstringv(kind), janet_wrap_function(f)); + +} +static JanetFunction * +FetchFunction(JanetTable *functions, char *node, char *kind) +{ + Janet f_tablev, fv; + JanetTable *ftable; + if (!functions || !node || !kind) + { + return NULL; + } + + f_tablev = janet_table_get( + functions, + janet_cstringv(node) + ); + if (!janet_checktype(f_tablev, JANET_TABLE)) + { + return NULL; + } + ftable = janet_unwrap_table(f_tablev); + + fv = janet_table_get( + ftable, + janet_cstringv(kind) + ); + if (!janet_checktype(fv, JANET_FUNCTION)) + { + return NULL; + } + + return janet_unwrap_function(fv); +} + +extern void +JanetFormCb(XMPPCommandManager *manager, XMPPCommand *cmd, char *from, char *node) +{ + Extension *ext = XMPPGetManagerCookie(manager); + ExtensionCommandReply *reply; + + reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD_F + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = ext; + reply->node = node; + reply->from = from; + reply->ack = false; + reply->cmd = cmd; + msg.argp = reply; + + janet_ev_post_event(ext->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); + +} +static void +JanetCmdCb(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) +{ + Extension *ext = XMPPGetManagerCookie(m); + ExtensionCommandReply *reply; + + reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD_A + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = ext; + reply->node = node; + reply->form = form; + reply->from = from; + reply->ack = false; + reply->out = out; + msg.argp = reply; + + janet_ev_post_event(ext->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); +} +static Janet +JanetRegisterCommand(int32_t argc, Janet *argv) +{ + JanetTable *self; + JanetFunction *func; + JanetFunction *form; + char *node, *name; + Janet v; + + Extension *ext; + XMPPCommand *cmd; + + janet_arity(argc, 4, 5); + + if (!janet_checktype(argv[0], JANET_TABLE) || + !janet_checktype(argv[1], JANET_STRING) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_FUNCTION) || + (argc == 5 && !janet_checktype(argv[4], JANET_FUNCTION))) + { + return janet_wrap_nil(); + } + self = janet_unwrap_table(argv[0]); + node = (char *) janet_unwrap_string(argv[1]); + name = (char *) janet_unwrap_string(argv[2]); + func = janet_unwrap_function(argv[3]); + form = argc == 5 ? janet_unwrap_function(argv[4]) : NULL; + + v = janet_table_get(self, janet_ckeywordv("ptr")); + if (!janet_checktype(v, JANET_POINTER)) + { + return janet_wrap_nil(); + } + ext = janet_unwrap_pointer(v); + + PutFunction(ext->functions, node, "act", func); + PutFunction(ext->functions, node, "form", form); + cmd = XMPPBasicCmd(XMPPCMD_ADMINS, node, name, JanetCmdCb); + if (form) + { + XMPPCmdOptionsCreator(cmd, JanetFormCb); + } + XMPPRegisterCommand(ext->manager, cmd); + + return janet_wrap_nil(); +} + +static void +OnCommandResponse(JanetEVGenericMessage msg) +{ + ExtensionCommandReply *reply = msg.argp; + Extension *ext = reply->ext; + Janet ret, in; + + if (msg.tag == JANET_REQUEST_XMPPCMD) + { + JanetTable *cmd = janet_table(0); + janet_table_put(cmd, + janet_ckeywordv("ptr"), + janet_wrap_pointer(reply->ext) + ); + janet_table_put(cmd, + janet_ckeywordv("register"), + janet_wrap_cfunction(JanetRegisterCommand) + ); + in = janet_wrap_table(cmd); + + TryCall(ext, "on-xmppcmd", 1, &in, &ret); + } + else if (msg.tag == JANET_REQUEST_XMPPCMD_F) + { + Janet in[2] = { + janet_cstringv(reply->from), + CommandToJanet(reply->cmd) + }; + JanetFiber *fib = NULL; + JanetFunction *func; + JanetSignal sig; + + func = FetchFunction(reply->ext->functions, reply->node, "form"); + if (func) + { + sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib); + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, ret); + } + } + } + else if (msg.tag == JANET_REQUEST_XMPPCMD_A) + { + JanetFunction *func; + + func = FetchFunction(reply->ext->functions, reply->node, "act"); + if (func) + { + Janet in[2] = { + janet_cstringv(reply->from), + ExtensionsFromXML(NULL, reply->form) + }; + JanetFiber *fib = NULL; + JanetSignal sig; + XMLElement *out = reply->out; + sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib); + if (sig != JANET_SIGNAL_OK) + { + janet_stacktrace(fib, ret); + SetNote("error", "Internal error in Janet extension."); + } + + if (janet_checktype(ret, JANET_ARRAY)) + { + JanetArray *arr = janet_unwrap_array(ret); + size_t i, added; + for (i = 0, added = 0; i < arr->count; i++) + { + Janet v = arr->data[i]; + JanetTable *source; + XMLElement *child = NULL; + + if (!reply->out || !janet_checktype(v, JANET_TABLE)) + { + continue; + } + source = janet_unwrap_table(v); + child = ExtensionsToXML(source); + XMLAddChild(reply->out, child); + if (child) + { + added++; + } + } + if (added == 0) + { + SetNote("error", "No values returned in Janet extension."); + } + } + else + { + SetNote("error", "Invalid output in Janet extension."); + } + } + } + + pthread_mutex_lock(&reply->lock); + reply->ack = true; + pthread_cond_signal(&reply->cond); + pthread_mutex_unlock(&reply->lock); +} + +void +ExtensionRequestCommands(Extensions *ctx) +{ + size_t i; + char *key; + Extension *val; + if (!ctx) + { + return; + } + + pthread_mutex_lock(&ctx->mtx); + for (i = 0; IterateReentrant(ctx->extensions, key, val, i); ) + { + ExtensionCommandReply *reply = Malloc(sizeof(*reply)); + JanetEVGenericMessage msg = { + .tag = JANET_REQUEST_XMPPCMD + }; + + pthread_mutex_init(&reply->lock, NULL); + pthread_cond_init(&reply->cond, NULL); + reply->ext = val; + reply->ack = false; + msg.argp = reply; + + janet_ev_post_event(val->vm, OnCommandResponse, msg); + pthread_mutex_lock(&reply->lock); + while (!reply->ack) + { + pthread_cond_wait(&reply->cond, &reply->lock); + } + pthread_mutex_unlock(&reply->lock); + pthread_mutex_destroy(&reply->lock); + pthread_cond_destroy(&reply->cond); + Free(reply); + } + pthread_mutex_unlock(&ctx->mtx); +} + +static int +JanetCmdGet(void *data0, Janet key, Janet *out) +{ + JanetKeyword kw; + if (!janet_checktype(key, JANET_KEYWORD)) + { + return 0; + } + + kw = janet_unwrap_keyword(key); + (void) data0; + return janet_getmethod(kw, cmd_methods, out); +} +static void +JanetMarshalCmd(void *data0, JanetMarshalContext *ctx) +{ + janet_marshal_abstract(ctx, data0); +} +static void * +JanetUnmarshalCmd(JanetMarshalContext *ctx) +{ + XMPPCommand **data = janet_unmarshal_abstract(ctx, sizeof(XMPPCommand *)); + return data ? *data : NULL; +} +static void +JanetCmdToString(void *data0, JanetBuffer *buf) +{ + janet_buffer_push_cstring(buf, "[" NAME " XMPP command type]"); +} +static int32_t +JanetHashCmd(void *data0, size_t size) +{ + uintptr_t datap = (uintptr_t) data0; + + (void) size; + /* We're casting down, but in a context where there is really + * just one ParseeData, is that really such a crime? */ + return (int32_t) datap; +} +static Janet +JanetCmdNext(void *data0, Janet next) +{ + (void) data0; + return janet_nextmethod(cmd_methods, next); +} + + +static Janet +JanetCmdCreateText(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + bool req; + char *id, *def, *desc; + + janet_arity(argc, 4, 5); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_BOOLEAN) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_STRING) || + ((argc == 5) && !janet_checktype(argv[4], JANET_STRING))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + req = janet_unwrap_boolean(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + def = (char *) janet_unwrap_string(argv[3]); + desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL; + + opt = XMPPCreateText(req, id, def); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} + +static Janet +JanetCmdCreateBool(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + bool req, def; + char *id, *desc; + + janet_arity(argc, 4, 5); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_BOOLEAN) || + !janet_checktype(argv[2], JANET_STRING) || + !janet_checktype(argv[3], JANET_BOOLEAN) || + ((argc == 5) && !janet_checktype(argv[4], JANET_STRING))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + req = janet_unwrap_boolean(argv[1]); + id = (char *) janet_unwrap_string(argv[2]); + def = janet_unwrap_boolean(argv[3]); + desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL; + + opt = XMPPCreateBool(req, id, def); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} +static Janet +JanetCmdCreateFixed(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + XMPPOption *opt; + + char *id, *desc; + + janet_fixarity(argc, 3); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING) || + !janet_checktype(argv[2], JANET_STRING)) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + id = (char *) janet_unwrap_string(argv[1]); + desc = (char *) janet_unwrap_string(argv[2]); + + opt = XMPPCreateFixed(id, desc); + XMPPSetDescription(opt, desc); + + XMPPAddOption(cmd, opt); + return janet_wrap_nil(); +} +static Janet +JanetCmdSetTitle(int32_t argc, Janet *argv) +{ + XMPPCommand *cmd; + + char *title; + + janet_fixarity(argc, 2); + if (!(cmd = JanetToCommand(argv[0]))) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command") + ); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_STRING)) + { + janet_signalv( + JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types") + ); + return janet_wrap_nil(); + } + + title = (char *) janet_unwrap_string(argv[1]); + + XMPPSetFormTitle(cmd, title); + return janet_wrap_nil(); +} +#endif diff --git a/src/Extensions/StanzaInfo.c b/src/Extensions/StanzaInfo.c new file mode 100644 index 0000000..1400aaa --- /dev/null +++ b/src/Extensions/StanzaInfo.c @@ -0,0 +1,98 @@ +#ifdef JANET + +#include "Extensions/Internal.h" + +Janet +JanetStanzaInfo(int32_t argc, Janet *argv) +{ + ParseeData *data; + XMLElement *stanza; + + JanetTable *ret; + + char *trimmed = NULL; + char *chat_id = NULL; + char *from = NULL; + char *room = NULL; + char *mxid = NULL; + + janet_fixarity(argc, 2); + if (!(data = GetParseeData(argv[0]))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr")); + return janet_wrap_nil(); + } + if (!janet_checktype(argv[1], JANET_TABLE)) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not a table")); + return janet_wrap_nil(); + } + + if (!(stanza = ExtensionsToXML(janet_unwrap_table(argv[1])))) + { + janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not XML.")); + return janet_wrap_nil(); + } + + from = HashMapGet(stanza->attrs, "from"); + trimmed = ParseeTrimJID(from); + chat_id = ParseeGetFromMUCID(data, trimmed); + + ret = janet_table(0); + janet_table_put(ret, + janet_ckeywordv("in-muc"), + janet_wrap_boolean(ParseeIsMUC(data, trimmed)) + ); + janet_table_put(ret, + janet_ckeywordv("in-dm"), + janet_wrap_boolean(StrEquals(HashMapGet(stanza->attrs, "type"), "chat")) + ); + if (chat_id && trimmed) + { + room = ParseeGetRoomID(data, chat_id); + janet_table_put(ret, + janet_ckeywordv("muc-jid"), janet_cstringv(trimmed) + ); + janet_table_put(ret, + janet_ckeywordv("id"), janet_cstringv(chat_id) + ); + } + else + { + char *to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); + char *dmid = ParseeGetDMID(to, from); + + room = ParseeFindDMRoom(data, to, from); + if (dmid) + { + janet_table_put(ret, + janet_ckeywordv("id"), janet_cstringv(dmid) + ); + } + + Free(dmid); + Free(to); + } + + if (room) + { + janet_table_put(ret, + janet_ckeywordv("room-id"), janet_cstringv(room) + ); + } + + if ((mxid = ParseeGetBridgedUser(data, stanza))) + { + janet_table_put(ret, + janet_ckeywordv("bridged-sender"), janet_cstringv(mxid) + ); + } + + Free(trimmed); + Free(chat_id); + Free(room); + Free(mxid); + XMLFreeElement(stanza); + return janet_wrap_table(ret); +} +#endif diff --git a/src/Extensions/XML.c b/src/Extensions/XML.c new file mode 100644 index 0000000..2567f82 --- /dev/null +++ b/src/Extensions/XML.c @@ -0,0 +1,357 @@ +#ifdef JANET +#include + +#include +#include + +#include +#include + +static Janet XMLToString(int32_t argc, Janet *argv); +static Janet JanetXMLAddAttr(int32_t argc, Janet *argv); +static void InitialiseBasicXML(JanetTable *table, char *type); + +XMLElement * +ExtensionsToXML(JanetTable *t) +{ + XMLElement *ret = NULL; + + char *type = NULL, *name, *data; + JanetArray *children; + JanetTable *attrs; + int32_t i; + Janet v; + + if (!t) + { + return NULL; + } +#define ChkType(ent, type) (janet_type((v = janet_table_get(t, janet_ckeywordv(ent)))) == type) + if (!ChkType("xml-type", JANET_STRING)) + { + return NULL; + } + type = (char *) janet_unwrap_string(v); + if (StrEquals(type, "data")) + { + if (!ChkType("xml-data", JANET_STRING)) + { + return NULL; + } + data = (char *) janet_unwrap_string(v); + + ret = XMLCreateText(data); + } + else if (StrEquals(type, "tag")) + { + if (!ChkType("xml-name", JANET_STRING)) + { + return NULL; + } + name = (char *) janet_unwrap_string(v); + + if (!ChkType("xml-attrs", JANET_TABLE)) + { + return NULL; + } + + ret = XMLCreateTag(name); + attrs = janet_unwrap_table(v); + for (i = 0; i < attrs->capacity; i++) + { + JanetKV *kv = &attrs->data[i]; + if (!janet_checktype(kv->key, JANET_STRING) || + !janet_checktype(kv->value, JANET_STRING)) + { + continue; + } + XMLAddAttr(ret, + (char *) janet_unwrap_string(kv->key), + (char *) janet_unwrap_string(kv->value) + ); + } + + if (!ChkType("xml-children", JANET_ARRAY)) + { + return ret; + } + children = janet_unwrap_array(v); + + for (i = 0; i < children->capacity; i++) + { + Janet cJanet = children->data[i]; + XMLElement *cXML = NULL; + if (!janet_checktype(cJanet, JANET_TABLE)) + { + continue; + } + + cXML = ExtensionsToXML(janet_unwrap_table(cJanet)); + XMLAddChild(ret, cXML); + } + } + + return ret; +} +Janet +ExtensionsFromXML(JanetArray *in, XMLElement *e) +{ + JanetArray *children = NULL; + JanetTable *table; + size_t i, len; + char *type = NULL; + if (!e) + { + return janet_wrap_nil(); + } + + table = janet_table(0); + switch (e->type) + { + case XML_ELEMENT_TAG: + type = "tag"; + break; + case XML_ELEMENT_DATA: + case XML_ELEMENT_CDATA: + type = "data"; + break; + case XML_ELEMENT_PI: + type = "pi"; + break; + case XML_ELEMENT_UNKNOWN: + type = "unknown"; + break; + } + + InitialiseBasicXML(table, type); + if (e->type == XML_ELEMENT_DATA || e->type == XML_ELEMENT_PI) + { + janet_table_put(table, + janet_ckeywordv("xml-data"), janet_cstringv(e->data) + ); + } + else if (e->type == XML_ELEMENT_TAG) + { + char *attr, *val; + JanetTable *attrs; + children = janet_array(ArraySize(e->children)); + janet_table_put(table, + janet_ckeywordv("xml-name"), janet_cstringv(e->name) + ); + for (i = 0, len = ArraySize(e->children); i < len; i++) + { + ExtensionsFromXML(children, ArrayGet(e->children, i)); + } + janet_table_put(table, + janet_ckeywordv("xml-children"), janet_wrap_array(children) + ); + + attrs = janet_table(0); + while (HashMapIterate(e->attrs, &attr, (void **) &val)) + { + janet_table_put(attrs, + janet_cstringv(attr), janet_cstringv(val) + ); + } + janet_table_put(table, + janet_ckeywordv("xml-attrs"), janet_wrap_table(attrs) + ); + } + if (children && in) + { + janet_array_push(in, janet_wrap_table(table)); + } + return janet_wrap_table(table); +} +static Janet +JanetXMLAddAttr(int32_t argc, Janet *argv) +{ + Janet self_v, key_v, val_v, attr_v; + JanetTable *self, *attrs; + char *key, *val; + + janet_fixarity(argc, 3); + + if (!janet_checktype((self_v = argv[0]), JANET_TABLE)) + { + return janet_wrap_nil(); + } + if (!janet_checktype((key_v = argv[1]), JANET_STRING)) + { + return janet_wrap_nil(); + } + if (!janet_checktype((val_v = argv[2]), JANET_STRING)) + { + return janet_wrap_nil(); + } + key = (char *) janet_unwrap_string(key_v); + val = (char *) janet_unwrap_string(val_v); + + self = janet_unwrap_table(self_v); + attr_v = janet_table_get(self, janet_ckeywordv("xml-attrs")); + if (!janet_checktype(attr_v, JANET_TABLE)) + { + return janet_wrap_nil(); + } + attrs = janet_unwrap_table(attr_v); + + janet_table_put(attrs, janet_cstringv(key), janet_cstringv(val)); + + return janet_wrap_nil(); +} +static Janet +XMLToString(int32_t argc, Janet *argv) +{ + XMLElement *xml; + Stream *fake; + Janet ret; + char *buf = NULL; + janet_fixarity(argc, 1); + + if (!janet_checktype(argv[0], JANET_TABLE)) + { + return janet_wrap_nil(); + } + xml = ExtensionsToXML(janet_gettable(argv, 0)); + if (!xml) + { + return janet_wrap_nil(); + } + + fake = StrStreamWriter(&buf); + XMLEncode(fake, xml); + StreamFlush(fake); + StreamClose(fake); + XMLFreeElement(xml); + + ret = janet_cstringv(buf); + Free(buf); + return ret; +} + +Janet +JanetCreateXMLData(int32_t argc, Janet *argv) +{ + Janet parent_v, text_v; + JanetTable *parent; + JanetTable *ret; + char *text; + + janet_arity(argc, 1, 2); + if (argc == 1) + { + text_v = argv[0]; + parent_v = janet_wrap_nil(); + } + else + { + parent_v = argv[0]; + text_v = argv[1]; + } + + if (janet_type(text_v) != JANET_STRING) + { + return janet_wrap_nil(); + } + + text = (char *) janet_unwrap_string(text_v); + ret = janet_table(0); + InitialiseBasicXML(ret, "data"); + janet_table_put(ret, + janet_ckeywordv("xml-data"), janet_cstringv(text) + ); + + if (janet_type(parent_v) == JANET_TABLE) + { + JanetArray *children; + Janet children_v; + + parent = janet_unwrap_table(parent_v); + children_v = janet_table_get(parent, janet_ckeywordv("xml-children")); + if (janet_type(children_v) != JANET_ARRAY) + { + goto end; + } + children = janet_unwrap_array(children_v); + + janet_array_push(children, janet_wrap_table(ret)); + } + +end: + return janet_wrap_table(ret); +} +Janet +JanetCreateXMLTag(int32_t argc, Janet *argv) +{ + Janet parent_v, text_v; + JanetTable *parent; + JanetTable *ret; + char *text; + + janet_arity(argc, 1, 2); + if (argc == 1) + { + text_v = argv[0]; + parent_v = janet_wrap_nil(); + } + else + { + parent_v = argv[0]; + text_v = argv[1]; + } + + if (janet_type(text_v) != JANET_STRING) + { + return janet_wrap_nil(); + } + + text = (char *) janet_unwrap_string(text_v); + ret = janet_table(0); + + InitialiseBasicXML(ret, "tag"); + janet_table_put(ret, + janet_ckeywordv("xml-name"), janet_cstringv(text) + ); + + if (janet_type(parent_v) == JANET_TABLE) + { + JanetArray *children; + Janet children_v; + + parent = janet_unwrap_table(parent_v); + children_v = janet_table_get(parent, janet_ckeywordv("xml-children")); + if (janet_type(children_v) != JANET_ARRAY) + { + goto end; + } + children = janet_unwrap_array(children_v); + + janet_array_push(children, janet_wrap_table(ret)); + } + +end: + return janet_wrap_table(ret); +} +void +InitialiseBasicXML(JanetTable *table, char *type) +{ + janet_table_put(table, + janet_ckeywordv("stringify"), janet_wrap_cfunction(XMLToString) + ); + janet_table_put(table, + janet_ckeywordv("set-attr"), janet_wrap_cfunction(JanetXMLAddAttr) + ); + janet_table_put(table, + janet_ckeywordv("xml-type"), janet_cstringv(type) + ); + if (StrEquals(type, "tag")) + { + janet_table_put(table, + janet_ckeywordv("xml-attrs"), janet_wrap_table(janet_table(0)) + ); + janet_table_put(table, + janet_ckeywordv("xml-children"), janet_wrap_array(janet_array(0)) + ); + } +} +#endif diff --git a/src/HttParsee.c b/src/HttParsee.c index 572487c..c47728b 100644 --- a/src/HttParsee.c +++ b/src/HttParsee.c @@ -22,6 +22,9 @@ ParseeRequest(HttpServerContext *ctx, void *argp) /* Basic headers */ HttpResponseStatus(ctx, HTTP_OK); HttpResponseHeader(ctx, "Server", NAME "/v" VERSION); +#ifdef PLATFORM_WINDOWS + HttpResponseHeader(ctx, "X-Powered-By", "some weirdows"); +#endif HttpResponseHeader(ctx, "Connection", "close"); arg.data = data; @@ -73,7 +76,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path ctx = HttpRequest( meth, - conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, + HTTP_FLAG_TLS, conf->homeserver_port, conf->homeserver_host, path ); diff --git a/src/Main.c b/src/Main.c index 10c809f..baeb984 100644 --- a/src/Main.c +++ b/src/Main.c @@ -13,11 +13,16 @@ #include #include -#include +#include #include #include #include +#ifdef JANET + #include +#endif +#include + static HttpServer *server = NULL; static pthread_t xmpp_thr; static XMPPComponent *jabber = NULL; @@ -63,39 +68,23 @@ ParseeCheckMatrix(void *datp) { static volatile uint64_t streak = 0; ParseeData *data = datp; - if (data->config->accept_pings && !ASPing(data->config)) + if (!ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); - if (++streak == 10) + if (++streak >= 5) { - DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); - HashMap *json = DbJson(ref); - HashMap *mucs = GrabObject(json, 1, "mucs"); - char *muc; - void *ignored; + 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); + pthread_mutex_lock(&data->halt_lock); + data->halted = true; + pthread_mutex_unlock(&data->halt_lock); - /* 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); + XMPPFinishCompStream(data->jabber); + pthread_join(xmpp_thr, NULL); + Log(LOG_INFO, "Stopping server..."); + HttpServerStop(data->server); } return; } @@ -115,6 +104,8 @@ Main(Array *args, HashMap *env) int http = 8; int verbose = 0; + Extensions *ext_ctx = NULL; + start = UtilTsMillis(); memset(&conf, 0, sizeof(conf)); @@ -124,8 +115,11 @@ Main(Array *args, HashMap *env) ); ParseePrintASCII(); Log(LOG_INFO, "======================="); - Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); + Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors"); Log(LOG_INFO, "(This program is free software, see LICENSE.)"); +#ifdef JANET + Log(LOG_INFO, "**Built with Janet!**"); +#endif #ifdef PLATFORM_IPHONE Log(LOG_WARNING, "Wait. Are you running this on an iPhone?"); @@ -223,11 +217,9 @@ 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 @@ -287,18 +279,22 @@ Main(Array *args, HashMap *env) Log(LOG_DEBUG, "Verbosity level: %d", verbose); ((ParseeData *) conf.handlerArgs)->verbosity = verbose; + ext_ctx = ExtensionCreateContext(conf.handlerArgs); + ((ParseeData *) conf.handlerArgs)->exts = ext_ctx; + ExtensionLoadDir(ext_ctx, parsee_conf->extensions); + Log(LOG_NOTICE, "Setting up local Matrix user..."); if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) { char *parsee = ParseeMXID(conf.handlerArgs); + /* TODO: An hardcoded avatar like this sucks. */ ASSetAvatar(parsee_conf, parsee, "mxc://tedomum.net/" "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" ); ASSetName(parsee_conf, parsee, "Parsee bridge"); - Free(parsee); } @@ -350,6 +346,8 @@ end: ParseeDestroyHeadTable(); ParseeDestroyJIDTable(); + ExtensionDestroyContext(ext_ctx); + (void) env; return 0; } diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index 6311fac..3d6a69e 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -21,11 +21,13 @@ 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, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */ + Unistr *filtered = UnistrFilter(uninick, UnistrIsBMP); char *nick = UnistrC(filtered); char *rev = StrConcat(3, muc, "/", nick); int nonce = 0; + Log(LOG_DEBUG, "MUCJOINER: filtered '%s' to '%s'", name, nick); + UnistrFree(uninick); UnistrFree(filtered); @@ -100,7 +102,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, NULL); + char *url = ParseeToUnauth(data, avatar); chat_id = ParseeGetFromRoomID(data, room_id); ASGetMIMESHA(data->config, avatar, &mime, &sha); @@ -255,9 +257,14 @@ ParseeBotHandler(ParseeData *data, HashMap *event) return; } - if (!body || *body != '!') + 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; } @@ -289,10 +296,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 = NULL; + char *user; - DbRef *room_data = NULL; - HashMap *data_json = NULL; + DbRef *room_data; + HashMap *data_json; bool direct = false; if (!data || !event || !from || !to) @@ -324,8 +331,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) } else { - char *matrix_name = NULL, *matrix_avatar = NULL; - char *mime = NULL, *sha = NULL; + char *matrix_name, *matrix_avatar; + char *mime, *sha; muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) @@ -358,47 +365,28 @@ 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; + StanzaBuilder *builder; DbRef *ref = NULL; - HashMap *json = NULL; + HashMap *json; - 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"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); - char *chat_id = NULL, *muc_id = NULL; + char *m_sender = GrabString(event, 1, "sender"); + char *chat_id, *muc_id; char *reply_id = MatrixGetReply(event); - char *xepd = ParseeXMPPify(data, event); + char *xepd = ParseeXMPPify(event); char *type, *user, *xmppified_user = NULL, *to = NULL; 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; - unedited_id = MatrixGetEdit(event); - if (unedited_id) - { - char *new_content = GrabString(event, 3, "content", "m.new_content", "body"); - 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)) { @@ -429,7 +417,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) type = direct ? "chat" : "groupchat"; user = GrabString(json, 1, "xmpp_user"); - unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename")); + unauth = ParseeToUnauth(data, url); encoded_from = ParseeEncodeMXID(m_sender); xmppified_user = StrConcat(3, @@ -466,7 +454,6 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) Free(name); Free(avatar); } - if (reply_id) { /* TODO: Monocles chat DM users HATE this trick! @@ -523,8 +510,8 @@ end: Free(stanza); Free(sender); Free(unauth); - Free(encoded_from); Free(unedited_id); + Free(encoded_from); DbUnlock(data->db, ref); ref = NULL; diff --git a/src/MatrixID.c b/src/MatrixID.c index bb7c584..4488fd4 100644 --- a/src/MatrixID.c +++ b/src/MatrixID.c @@ -1,9 +1,6 @@ #include #include -#include -#include -#include #include @@ -35,34 +32,3 @@ 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/Config.c b/src/Parsee/Config.c index b95e5e1..46b37fa 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -36,12 +36,6 @@ 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( \ @@ -50,9 +44,6 @@ 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; @@ -67,26 +58,19 @@ 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; - } - CopyToBool(accept_pings, "accept_pings"); 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"); + + CopyToStr(extensions, "extensions"); if (!config->max_stanza_size) { /* Standard XMPP "minimum" maximum */ config->max_stanza_size = 10000; } - CopyToBool(ignore_bots, "ignore_bots"); - CopyToStr(media_base, "media_base"); CopyToStr(db_path, "db"); @@ -147,7 +131,6 @@ ParseeConfigFree(void) return; } Free(config->component_host); - Free(config->component_addr); Free(config->shared_comp_secret); Free(config->db_path); Free(config->homeserver_host); @@ -159,6 +142,7 @@ ParseeConfigFree(void) Free(config->namespace_base); Free(config->media_base); Free(config->listen_as); + Free(config->extensions); Free(config); } const ParseeConfig * diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index 46d1433..afdf188 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -27,7 +27,6 @@ ParseeInitData(XMPPComponent *comp) data->config = ParseeConfigGet(); data->router = HttpRouterCreate(); data->jabber = comp; - data->muc = CreateMUCServer(data); data->handler = CommandCreateRouter(); data->oid_servers = HashMapCreate(); @@ -117,7 +116,6 @@ 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); @@ -132,6 +130,7 @@ ParseeCleanup(void *datp) char *chat; size_t i; uint64_t ts = UtilTsMillis(); + size_t entries = 0; chats = DbList(data->db, 1, "chats"); @@ -175,6 +174,7 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ DbDelete(data->db, 4, "chats", chat, #field"s", field); \ + entries++; \ } \ Free(field); \ } \ @@ -231,6 +231,7 @@ ParseeCleanup(void *datp) if (cleaned > threshold) \ { \ JsonValueFree(HashMapDelete(field##s, field)); \ + entries++; \ } \ Free(field); \ } \ @@ -725,3 +726,13 @@ ParseeIsMediaEnabled(ParseeData *data, char *chat_id) return ret; } +bool +ParseeIsMUC(ParseeData *data, char *jid) +{ + if (!data) + { + return false; + } + + return XMPPQueryMUC(data->jabber, jid, NULL); +} diff --git a/src/Parsee/User.c b/src/Parsee/User.c index 6e673bb..e77f20a 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -539,6 +538,7 @@ ParseeGetMUCID(ParseeData *data, char *chat_id) return ret; } + void ParseeSendPresence(ParseeData *data) { @@ -562,7 +562,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", rev); + Log(LOG_NOTICE, "Sending presence to %s last=%ds", rev, diff); XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false); DbUnlock(data->db, chat); @@ -688,13 +688,12 @@ end: #include char * -ParseeToUnauth(ParseeData *data, char *mxc, char *filename) +ParseeToUnauth(ParseeData *data, char *mxc) { 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" +#define PAT "%s/_matrix/client/v1/media/download/%s%s?hmac=%s" size_t l; if (!data || !mxc) { @@ -715,45 +714,19 @@ ParseeToUnauth(ParseeData *data, char *mxc, char *filename) hmac = ParseeHMACS(data->id, key); Free(key); - if (!filename) - { - l = snprintf(NULL, 0, - PAT, - data->config->media_base, - url->host, url->path, - hmac - ); - } - else - { - char *encoded = HttpUrlEncode(filename); - l = snprintf(NULL, 0, - PATF, - data->config->media_base, - url->host, url->path, encoded, - hmac - ); - Free(encoded); - } + l = snprintf(NULL, 0, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); ret = Malloc(l + 3); - 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 - ); - } + snprintf(ret, l + 1, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); UriFree(url); Free(hmac); return ret; diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c index ec95aef..ea97b2c 100644 --- a/src/Parsee/Utils/Formatting.c +++ b/src/Parsee/Utils/Formatting.c @@ -5,14 +5,11 @@ #include #include #include -#include -#include #include #include #include -#include #include #include @@ -20,7 +17,7 @@ typedef struct XMPPFlags { bool quote; } XMPPFlags; static char * -XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags) +XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) { char *xepd = NULL, *tmp = NULL; @@ -58,7 +55,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF } \ } \ while (0) - switch (elem ? elem->type : -1) + switch (elem->type) { case XML_ELEMENT_DATA: Concat(elem->data); @@ -70,7 +67,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); @@ -83,7 +80,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); @@ -96,7 +93,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); @@ -129,7 +126,7 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); @@ -144,70 +141,36 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); } - if (i != 0) - { - Concat("\n"); - } + Concat("\n"); } else if (StrEquals(elem->name, "a")) { char *href = HashMapGet(elem->attrs, "href"); - Uri *pref = UriParse(href); - if (pref && StrEquals(pref->host, "matrix.to")) + /* TODO: Check if the element here is a Matrix.TO + * pointing to a Parsee user. */ + for (i = 0; i < ArraySize(elem->children); i++) { - /* TODO: Check if the element here is a Matrix.TO - * pointing to a Parsee user. */ - UserID *id = MatrixParseIDFromMTO(pref); - if (id) - { - char *real_id = StrConcat(4, "@", id->localpart, ":", id->server); - /* TODO: Detect if it already is a Parsee user */ - 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 - { - Concat(href); - } - - Free(id); - } - else - { - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(event, child, flags); - Concat(subxep); - Free(subxep); - } - Concat(" < "); - Concat(href); - Concat(" >"); + Concat(subxep); + Free(subxep); } - UriFree(pref); + Concat("< "); + Concat(href); + Concat(" >"); } else { for (i = 0; i < ArraySize(elem->children); i++) { child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(conf, event, child, flags); + subxep = XMPPifyElement(event, child, flags); Concat(subxep); Free(subxep); @@ -219,45 +182,8 @@ XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPF } return xepd; } -static char * -GetRawBody(HashMap *event) -{ - void *id; - if ((id = MatrixGetEdit(event))) - { - char *new = GrabString(event, 3, "content", "m.new_content", "body"); - Free(id); - 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"); -} -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) +ParseeXMPPify(HashMap *event) { char *type, *format, *html; char *xepd = NULL; @@ -276,28 +202,20 @@ ParseeXMPPify(ParseeData *data, HashMap *event) return NULL; } - if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html")) + format = JsonValueAsString(JsonGet(event, 2, "content", "format")); + if (!StrEquals(format, "org.matrix.custom.html")) { /* Settle for the raw body instead. */ - char *body = GetRawBody(event); + char *body = JsonValueAsString(JsonGet(event, 2, "content", "body")); return StrDuplicate(body); } - html = GetHTMLBody(event); - + html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body")); 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(data ? data->config : NULL, event, elem, flags); + xepd = XMPPifyElement(event, elem, flags); XMLFreeElement(elem); Free(html); diff --git a/src/Parsee/Utils/Nofly.c b/src/Parsee/Utils/Nofly.c index 439c7d0..8a82c0f 100644 --- a/src/Parsee/Utils/Nofly.c +++ b/src/Parsee/Utils/Nofly.c @@ -6,28 +6,6 @@ #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/Routes/Media.c b/src/Routes/Media.c index 498eba2..7d6bc68 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -10,37 +10,6 @@ #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; @@ -48,7 +17,7 @@ RouteHead(RouteMedia, arr, argp) HashMap *reqh, *params; char *server = ArrayGet(arr, 0); char *identi = ArrayGet(arr, 1); - char *key, *val; + char *path, *key, *val; char *hmac, *chkmak = NULL; params = HttpRequestParams(args->ctx); @@ -75,7 +44,15 @@ RouteHead(RouteMedia, arr, argp) /* Proxy the media through an authenticated endpoint if the HMAC * is valid. */ - cctx = TryDownload(args->data, server, identi); + 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); reqh = HttpResponseHeaders(cctx); while (HashMapIterate(reqh, &key, (void **) &val)) { @@ -88,6 +65,8 @@ RouteHead(RouteMedia, arr, argp) } HttpClientContextFree(cctx); + Free(server); + Free(identi); return NULL; } diff --git a/src/Routes/Ping.c b/src/Routes/Ping.c index 1d4191e..a508336 100644 --- a/src/Routes/Ping.c +++ b/src/Routes/Ping.c @@ -10,6 +10,8 @@ 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/Routes/Transactions.c b/src/Routes/Transactions.c index 580e43c..4ef0bee 100644 --- a/src/Routes/Transactions.c +++ b/src/Routes/Transactions.c @@ -34,6 +34,10 @@ RouteHead(RouteTxns, arr, argp) for (i = 0; i < ArraySize(events); i++) { HashMap *event = JsonValueAsObject(ArrayGet(events, i)); + if (ExtensionPushEvent(args->data->exts, event)) + { + continue; + } ParseeEventHandler(args->data, event); } diff --git a/src/Signal.c b/src/Signal.c index 3f01dcb..e9d00e8 100644 --- a/src/Signal.c +++ b/src/Signal.c @@ -26,6 +26,11 @@ SignalHandler(int signal) HttpServerStop(data->server); return; } + if (signal == SIGPIPE) + { + Log(LOG_DEBUG, "Caught a SIGPIPE..."); + return; + } } bool @@ -41,7 +46,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)) + if (!Register(SIGTERM) || !Register(SIGINT) || !Register(SIGPIPE)) { Log(LOG_ERR, "Couldn't register signals..."); return false; diff --git a/src/Unistr.c b/src/Unistr.c index 7eab2e0..5eaae43 100644 --- a/src/Unistr.c +++ b/src/Unistr.c @@ -192,16 +192,6 @@ 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) @@ -277,38 +267,3 @@ 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/XEP-0393.c b/src/XEP-0393.c index 784bacf..a0f6be9 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(xmlparent, XMLCreateText("`")); + XMLAddChild(head, XMLCreateText("`")); break; case XEP393_SRKE: XMLAddChild(head, XMLCreateText("~")); diff --git a/src/XML/Parser.c b/src/XML/Parser.c index 350f0e4..506e49d 100644 --- a/src/XML/Parser.c +++ b/src/XML/Parser.c @@ -31,12 +31,6 @@ 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 defea6b..21430bb 100644 --- a/src/XML/SAX.c +++ b/src/XML/SAX.c @@ -58,7 +58,6 @@ 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); @@ -199,9 +198,7 @@ XMLCrank(XMLexer *lexer) else if (XMLookahead(lexer, "--", false)) { /* Throw error */ - XMLFreeEvent(event); - event = XMLCreateError(lexer); - break; + return NULL; } break; case XML_STATE_PI: @@ -218,9 +215,6 @@ XMLCrank(XMLexer *lexer) if (!attrname) { /* TODO: Throw error */ - XMLFreeEvent(event); - event = XMLCreateError(lexer); - break; } XMLPushElement(lexer, attrname); @@ -247,10 +241,7 @@ XMLCrank(XMLexer *lexer) } else if (XMLookahead(lexer, "'", true)) { - //while (true); uh oh - XMLFreeEvent(event); - event = XMLCreateError(lexer); - break; + while (true); } break; case XML_STATE_ATTRTAIL: @@ -259,8 +250,6 @@ XMLCrank(XMLexer *lexer) if (!XMLookahead(lexer, ">", true)) { /* TODO: Throw error. */ - XMLFreeEvent(event); - event = XMLCreateError(lexer); break; } lexer->state = XML_STATE_NONE; @@ -269,8 +258,6 @@ XMLCrank(XMLexer *lexer) break; default: /* TODO */ - XMLFreeEvent(event); - event = XMLCreateError(lexer); break; } /* TODO: Crank our XML parser. */ @@ -706,26 +693,6 @@ 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/XMPP/Component.c b/src/XMPP/Component.c index e3ff4e4..42e2992 100644 --- a/src/XMPP/Component.c +++ b/src/XMPP/Component.c @@ -19,7 +19,7 @@ #define DEFAULT_PROSODY_PORT 5347 XMPPComponent * -XMPPInitialiseCompStream(char *addr, char *host, int port) +XMPPInitialiseCompStream(char *host, int port) { int sd = -1; struct addrinfo hints, *res, *res0; @@ -28,17 +28,12 @@ XMPPInitialiseCompStream(char *addr, 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(addr, serv, &hints, &res0); + error = getaddrinfo(host, serv, &hints, &res0); if (error) { const char *error_str = gai_strerror(error); @@ -61,10 +56,6 @@ 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; @@ -75,10 +66,6 @@ XMPPInitialiseCompStream(char *addr, char *host, int port) if (sd < 0) { - Log(LOG_ERR, - "%s: cannot connect to '%s': no socket available", __func__, - host - ); return NULL; } freeaddrinfo(res0); @@ -86,10 +73,6 @@ 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; } @@ -182,49 +165,6 @@ 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: - LogConfigUnindent(LogConfigGlobal()); - Log(LOG_DEBUG, "", ev->element); - 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); diff --git a/src/XMPP/MUC.c b/src/XMPP/MUC.c index 9ecfcf2..7064698 100644 --- a/src/XMPP/MUC.c +++ b/src/XMPP/MUC.c @@ -8,8 +8,6 @@ #include #include -#include "XMPPThread/internal.h" - bool XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) { @@ -44,7 +42,6 @@ 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; } @@ -56,11 +53,6 @@ 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; } @@ -170,7 +162,6 @@ 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) { @@ -198,15 +189,7 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool XMLAddChild(x, history); XMLAddChild(presence, x); - features = LookupJIDFeatures(from); -#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); + XMPPAnnotatePresence(presence); if (hash) { @@ -242,7 +225,6 @@ void XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) { XMLElement *presence; - IQFeatures *features; char *from, *id; if (!comp || !fr || !muc) { @@ -264,16 +246,7 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMLAddChild(presence, status); } - features = LookupJIDFeatures(from); -#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); - + XMPPAnnotatePresence(presence); XMPPSendStanza(comp, presence, 10000); diff --git a/src/XMPP/MUCServ.c b/src/XMPP/MUCServ.c deleted file mode 100644 index 557c4a2..0000000 --- a/src/XMPP/MUCServ.c +++ /dev/null @@ -1,154 +0,0 @@ -#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) */ - (void) ret; - 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/XMPP/Stanza.c b/src/XMPP/Stanza.c index a2daf5a..451c16f 100644 --- a/src/XMPP/Stanza.c +++ b/src/XMPP/Stanza.c @@ -7,8 +7,6 @@ #include #include -#include - void XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact) @@ -161,31 +159,27 @@ XMPPGetReply(XMLElement *elem) return HashMapGet(rep->attrs, "id"); } -ssize_t -XMPPGetReplyOffset(XMLElement *elem) +void +XMPPAnnotatePresence(XMLElement *presence) { - if (!elem) + XMLElement *c; + char *ver; + if (!presence) { - return -1; + return; } - 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; + + 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/XMPPCommand/Commands.c b/src/XMPPCommand/Commands.c index 7b418c6..8d00598 100644 --- a/src/XMPPCommand/Commands.c +++ b/src/XMPPCommand/Commands.c @@ -4,20 +4,10 @@ #include #include -struct XMPPCommand { - XMPPCmdCallback callback; - char *node, *name; - - char *form_instruction; - char *form_title; - - /* TODO: On-the-fly generation of options */ - Array *options; - XMPPOptionWriter otf; -}; +#include "XMPPCommand/Internal.h" XMPPCommand * -XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) +XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback callback_funct) { XMPPCommand *cmd; @@ -32,6 +22,7 @@ XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct) cmd->name = StrDuplicate(name); cmd->form_instruction = NULL; cmd->form_title = NULL; + cmd->flags = flags; /* No options -> no form required */ cmd->options = NULL; @@ -127,7 +118,7 @@ XMPPFormifyCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from) ArrayFree(cmd->options); cmd->options = NULL; - cmd->otf(m, cmd, from); + cmd->otf(m, cmd, from, cmd->node); } if (!cmd->options) { @@ -184,13 +175,13 @@ XMPPCommandRequiresForm(XMPPCommand *cmd) return cmd ? (cmd->otf || !!cmd->options) : false; } void -XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form) +XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node) { - if (!m || !cmd || !from || !to) + if (!m || !cmd || !from || !to || !node) { return; } - cmd->callback(m, from, form, to); + cmd->callback(m, from, form, to, node); } bool diff --git a/src/XMPPCommand/Internal.h b/src/XMPPCommand/Internal.h new file mode 100644 index 0000000..264e83e --- /dev/null +++ b/src/XMPPCommand/Internal.h @@ -0,0 +1,17 @@ +#include + +#include + +struct XMPPCommand { + XMPPCmdCallback callback; + char *node, *name; + + char *form_instruction; + char *form_title; + + /* TODO: On-the-fly generation of options */ + Array *options; + XMPPOptionWriter otf; + + XMPPCommandFlags flags; +}; diff --git a/src/XMPPCommand/Manager.c b/src/XMPPCommand/Manager.c index 00a8c89..b272fb7 100644 --- a/src/XMPPCommand/Manager.c +++ b/src/XMPPCommand/Manager.c @@ -8,6 +8,8 @@ #include +#include "XMPPCommand/Internal.h" + typedef struct XMPPSession { char *identifier; @@ -323,7 +325,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) XMLAddAttr(command_xml, "node", node); XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "sessionid", session_id); - XMPPExecuteCommand(m, cmd, from, command_xml, NULL); + XMPPExecuteCommand(m, cmd, from, command_xml, NULL, node); XMLAddChild(iq, command_xml); XMPPSendStanza(jabber, iq, data->config->max_stanza_size); @@ -363,7 +365,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) XMLAddAttr(command_xml, "node", node); XMLAddAttr(command_xml, "status", "completed"); XMLAddAttr(command_xml, "sessionid", session_given); - XMPPExecuteCommand(m, cmd, from, command_xml, x_form); + XMPPExecuteCommand(m, cmd, from, command_xml, x_form, node); XMLAddChild(iq, command_xml); XMPPSendStanza(jabber, iq, data->config->max_stanza_size); @@ -385,3 +387,15 @@ XMPPGetManagerCookie(XMPPCommandManager *manager) { return manager ? manager->cookie : NULL; } +XMPPCommandFlags +XMPPGetCommandFlags(XMPPCommandManager *m, char *id) +{ + XMPPCommand *cmd; + if (!m || !id) + { + return XMPPCMD_ALL; + } + + cmd = HashMapGet(m->commands, id); + return cmd ? cmd->flags : XMPPCMD_ALL; +} diff --git a/src/XMPPCommands/AddAdmin.c b/src/XMPPCommands/AddAdmin.c index 321d18d..70f4479 100644 --- a/src/XMPPCommands/AddAdmin.c +++ b/src/XMPPCommands/AddAdmin.c @@ -12,7 +12,7 @@ #include void -AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -23,6 +23,8 @@ AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement GetFieldValue(glob, "glob", form); + (void) node; + if (!ParseeIsAdmin(data, trimmed)) { SetNote("error", "User is not authorised to execute command."); diff --git a/src/XMPPCommands/AddNofly.c b/src/XMPPCommands/AddNofly.c index 80f81e5..2b7f0ff 100644 --- a/src/XMPPCommands/AddNofly.c +++ b/src/XMPPCommands/AddNofly.c @@ -12,7 +12,7 @@ #include void -AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -21,6 +21,8 @@ AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement GetFieldValue(entity, "entity", form); GetFieldValue(reason, "reason", form); + (void) node; + if (!ParseeIsAdmin(data, trimmed)) { SetNote("error", "User is not authorised to execute command."); diff --git a/src/XMPPCommands/Admins.c b/src/XMPPCommands/Admins.c index 5749165..36c4f9b 100644 --- a/src/XMPPCommands/Admins.c +++ b/src/XMPPCommands/Admins.c @@ -12,7 +12,7 @@ #include void -AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); size_t i; @@ -20,6 +20,8 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement * XMLElement *title; XMLElement *reported, *item, *field, *value, *txt; + (void) node; + x = XMLCreateTag("x"); title = XMLCreateTag("title"); diff --git a/src/XMPPCommands/Cleanup.c b/src/XMPPCommands/Cleanup.c index 22780ff..a6fdb8b 100644 --- a/src/XMPPCommands/Cleanup.c +++ b/src/XMPPCommands/Cleanup.c @@ -12,7 +12,7 @@ #include void -CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -31,4 +31,5 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o SetNote("info", "Parsee data was sucessfully cleant up."); (void) form; + (void) node; } diff --git a/src/XMPPCommands/DelAdmin.c b/src/XMPPCommands/DelAdmin.c index 603b09f..f4ada63 100644 --- a/src/XMPPCommands/DelAdmin.c +++ b/src/XMPPCommands/DelAdmin.c @@ -31,7 +31,7 @@ DelAdmin(Array *admin_list, char *glob) return removed; } void -DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -84,9 +84,11 @@ DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement } SetNote("info", "Sucessfully removed admins"); /* TODO */ + + (void) node; } void -FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from) +FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from, char *node) { ParseeData *data = XMPPGetManagerCookie(m); DbRef *admins; @@ -120,4 +122,6 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from) DbUnlock(data->db, admins); XMPPAddOption(cmd, admin_opt); + + (void) node; } diff --git a/src/XMPPCommands/MUCInformation.c b/src/XMPPCommands/MUCInformation.c index 53dc684..c96b0ed 100644 --- a/src/XMPPCommands/MUCInformation.c +++ b/src/XMPPCommands/MUCInformation.c @@ -12,7 +12,7 @@ #include void -MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *muc = ParseeTrimJID(from); @@ -29,9 +29,10 @@ MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement Free(room_id); Free(chat_id); (void) form; + (void) node; } void -MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *muc = ParseeTrimJID(from); @@ -46,4 +47,5 @@ MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen Free(msg); Free(chat_id); (void) form; + (void) node; } diff --git a/src/XMPPCommands/MUCKV.c b/src/XMPPCommands/MUCKV.c index 80ac9c4..97bf7d2 100644 --- a/src/XMPPCommands/MUCKV.c +++ b/src/XMPPCommands/MUCKV.c @@ -15,12 +15,12 @@ #include void -MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); - char *affiliation = NULL, *role = NULL; - char *chat_id = NULL; - char *muc = NULL; + char *affiliation, *role; + char *chat_id; + char *muc; char *key = NULL, *val = NULL; @@ -51,9 +51,10 @@ end: Free(chat_id); Free(muc); (void) form; + (void) node; } void -MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *affiliation = NULL, *role = NULL; @@ -114,4 +115,5 @@ end: Free(chat_id); Free(muc); (void) form; + (void) node; } diff --git a/src/XMPPCommands/MUCUnlink.c b/src/XMPPCommands/MUCUnlink.c index f5560d3..a553824 100644 --- a/src/XMPPCommands/MUCUnlink.c +++ b/src/XMPPCommands/MUCUnlink.c @@ -14,7 +14,7 @@ #include void -MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *affiliation, *role; @@ -64,4 +64,5 @@ MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) Free(room); Free(muc); (void) form; + (void) node; } diff --git a/src/XMPPCommands/Nofly.c b/src/XMPPCommands/Nofly.c index dcbef67..05053b4 100644 --- a/src/XMPPCommands/Nofly.c +++ b/src/XMPPCommands/Nofly.c @@ -14,7 +14,7 @@ #define TITLE "Parsee global bans" void -NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -89,4 +89,5 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o } (void) form; + (void) node; } diff --git a/src/XMPPCommands/Status.c b/src/XMPPCommands/Status.c index 1b19129..e954eef 100644 --- a/src/XMPPCommands/Status.c +++ b/src/XMPPCommands/Status.c @@ -12,7 +12,7 @@ #include void -StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { char *trimmed = ParseeTrimJID(from); size_t alloc = MemoryAllocated(); @@ -69,5 +69,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement * XMLAddChild(out, x); (void) form; + (void) node; (void) m; } diff --git a/src/XMPPCommands/Whitelist.c b/src/XMPPCommands/Whitelist.c index 8e097a8..e9b4d01 100644 --- a/src/XMPPCommands/Whitelist.c +++ b/src/XMPPCommands/Whitelist.c @@ -12,7 +12,7 @@ #include void -ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -35,9 +35,10 @@ ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLE SetNote("info", "Parsee whitelist was removed."); (void) form; + (void) node; } void -AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -79,9 +80,10 @@ AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLEle DbUnlock(data->db, ref); SetNote("info", "Server successfully whitelisted."); + (void) node; } void -WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) +WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node) { ParseeData *data = XMPPGetManagerCookie(m); char *trimmed = ParseeTrimJID(from); @@ -139,4 +141,5 @@ WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen } (void) form; + (void) node; } diff --git a/src/XMPPThread/Caps.c b/src/XMPPThread/Caps.c index d79a30c..867adbd 100644 --- a/src/XMPPThread/Caps.c +++ b/src/XMPPThread/Caps.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,86 +12,33 @@ #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(IQFeatures *features) +XMPPGenerateVer(void) { char *S = NULL; unsigned char *Sha = NULL; + Array *identities = ArrayCreate(); + Array *features = ArrayCreate(); size_t i; /* Initialise identity table, to be sorted */ - ArraySort(features->identity, IdentitySort); - for (i = 0; i < ArraySize(features->identity); i++) +#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++) { - XMPPIdentity *identity = ArrayGet(features->identity, i); + XMPPIdentity *identity = ArrayGet(identities, i); char *id_chunk = StrConcat(7, identity->category, "/", identity->type, "/", @@ -104,10 +50,10 @@ XMPPGenerateVer(IQFeatures *features) Free(id_chunk); } - ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate)); - for (i = 0; i < ArraySize(features->adverts); i++) + ArraySort(features, ((int (*) (void *, void *)) ICollate)); + for (i = 0; i < ArraySize(features); i++) { - char *feature = ArrayGet(features->adverts, i); + char *feature = ArrayGet(features, i); char *tmp = S; S = StrConcat(3, S, feature, "<"); Free(tmp); @@ -118,64 +64,16 @@ XMPPGenerateVer(IQFeatures *features) 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); -} - - -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/PEPs/VCard.c b/src/XMPPThread/PEPs/VCard.c index 086e248..896d518 100644 --- a/src/XMPPThread/PEPs/VCard.c +++ b/src/XMPPThread/PEPs/VCard.c @@ -94,15 +94,13 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item) AddTextField(vcard, "fn", name); AddTextField(vcard, "nickname", mxid); - AddURIField(vcard, "url", REPOSITORY); AddURIField(vcard, "url", "https://kappach.at/parsee"); - AddURIField(vcard, "url", "https://matrix.org"); AddURIField(vcard, "url", m_to); AddTextField( vcard, "note", - "This is a bridged Matrix user, from Parsee." + "This is a bridged Matrix user, from " NAME "." ); Free(mxid); Free(name); diff --git a/src/XMPPThread/ReListener.c b/src/XMPPThread/ReListener.c index 44f22f8..3c86feb 100644 --- a/src/XMPPThread/ReListener.c +++ b/src/XMPPThread/ReListener.c @@ -56,8 +56,6 @@ XMPPDispatcher(void *argp) if (!stanza) { - /* TODO: We shouldn't be busywaiting. Even with a sleep call. - */ UtilSleepMillis(10); continue; } @@ -67,8 +65,12 @@ XMPPDispatcher(void *argp) Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); } - if (ManageMUCStanza(args->muc, stanza)) + if (ExtensionPushStanza(args->exts, stanza, STANZA_RAW)) { + if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Stanza was vetoed by an extension."); + } XMLFreeElement(stanza); continue; } @@ -138,6 +140,7 @@ bool XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza) { ParseeData *args = XMPPGetManagerCookie(m); + XMPPCommandFlags flags; char *trimmed_from; char *from; char *chat_id; @@ -152,28 +155,24 @@ XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza) is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from)); Free(trimmed_from); Free(chat_id); -#define XMPP_COMMAND(f,l,n,t,s) \ - if (StrEquals(n, id)) \ - { \ - if (l == XMPPCMD_ALL) \ - { \ - return true; \ - } \ - else if (l == XMPPCMD_MUC) \ - { \ - return is_muc; \ - } \ - else if (l == XMPPCMD_ADMINS) \ - { \ - bool is_admin; \ - trimmed_from = ParseeTrimJID(from); \ - is_admin = ParseeIsAdmin(args, trimmed_from); \ - Free(trimmed_from); \ - return is_admin; \ - } \ + flags = XMPPGetCommandFlags(m, id); + + if (flags == XMPPCMD_ALL) + { + return true; + } + else if (flags == XMPPCMD_MUC) + { + return is_muc; + } + else if (flags == XMPPCMD_ADMINS) + { + bool is_admin; + trimmed_from = ParseeTrimJID(from); + is_admin = ParseeIsAdmin(args, trimmed_from); + Free(trimmed_from); + return is_admin; } - XMPPCOMMANDS -#undef XMPP_COMMAND return false; } @@ -199,13 +198,14 @@ ParseeXMPPThread(void *argp) XMPPCommand *cmd; #define XMPP_COMMAND(f,l,n,t,s) \ cmd = XMPPBasicCmd( \ - n, t, f \ + l, n, t, f \ ); \ s \ XMPPRegisterCommand(info.m, cmd); XMPPCOMMANDS #undef XMPP_COMMAND } + ExtensionRequestCommands(args->exts); info.pep_manager = CreatePEPManager(args, &info); { PEPManagerAddEvent( @@ -244,96 +244,66 @@ ParseeXMPPThread(void *argp) } } - while (!args->halted) + while (true) { - while (true) + char *id; + + stanza = XMLDecode(jabber->stream, false); + if (!stanza) { - char *id; - - stanza = XMLDecode(jabber->stream, false); - if (!stanza) + /* Try to check if an error is abound */ + if (args->verbosity >= PARSEE_VERBOSE_COMICAL) { - /* Try to check if an error is abound */ - if (args->verbosity >= PARSEE_VERBOSE_COMICAL) - { - Log(LOG_DEBUG, "RECEIVED EOF."); - } - break; + Log(LOG_DEBUG, "RECEIVED EOF."); } + break; + } - if (args->verbosity >= PARSEE_VERBOSE_STANZA) + 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))) { - Stream *output = StreamStderr(); - StreamPrintf(output, "-------STANZA BEGIN-------" "\n"); - XMLEncode(output, stanza); - StreamPrintf(output, "\n--------STANZA END--------" "\n"); - StreamFlush(output); - } + pthread_mutex_lock(&await->cond_lock); + await->stanza = stanza; + pthread_cond_signal(&await->condition); + pthread_mutex_unlock(&await->cond_lock); - 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); - HashMapDelete(await_table, id); - - pthread_mutex_unlock(&await_lock); - continue; - } pthread_mutex_unlock(&await_lock); + continue; } - - /* 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_unlock(&await_lock); } - 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); + /* 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..."); + 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++) diff --git a/src/XMPPThread/Stanzas/IQ.c b/src/XMPPThread/Stanzas/IQ.c index 1b5087d..1ba5bd0 100644 --- a/src/XMPPThread/Stanzas/IQ.c +++ b/src/XMPPThread/Stanzas/IQ.c @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -116,37 +115,33 @@ end: #define DISCO "http://jabber.org/protocol/disco#info" static XMLElement * -IQGenerateQuery(IQFeatures *features) +IQGenerateQuery(void) { - XMLElement *query; - if (!features) - { - return NULL; - } - query = XMLCreateTag("query"); + XMLElement *query = XMLCreateTag("query"); XMLAddAttr(query, "xmlns", DISCO); { XMLElement *feature; - size_t i; - for (i = 0; i < ArraySize(features->identity); i++) - { - XMPPIdentity *identity = ArrayGet(features->identity, i); +#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); - 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); - } + IQ_ADVERT +#undef AdvertiseSimple } return query; @@ -156,7 +151,6 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) { char *from, *to, *id; XMLElement *iq_reply, *query; - IQFeatures *features; from = HashMapGet(stanza->attrs, "from"); to = HashMapGet(stanza->attrs, "to"); @@ -169,10 +163,9 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza) XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "id", id); - features = LookupJIDFeatures(to); - query = IQGenerateQuery(features); + query = IQGenerateQuery(); { - char *ver = XMPPGenerateVer(features); + char *ver = XMPPGenerateVer(); char *node = StrConcat(3, REPOSITORY, "#", ver); XMLAddAttr(query, "node", node); @@ -180,7 +173,6 @@ 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); @@ -374,7 +366,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr) bool IQIsCommandList(ParseeData *args, XMLElement *stanza) { - char *parsee = NULL, *to; + char *parsee = NULL; XMLElement *query = XMLookForTKV( stanza, "query", "xmlns", "http://jabber.org/protocol/disco#items" @@ -389,8 +381,7 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza) } parsee = ParseeJID(args); - to = HashMapGet(stanza->attrs, "to"); - ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host); + ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee); Free(parsee); return ret; @@ -430,10 +421,16 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); + XMPPShoveCommandList(thr->info->m, IsInMUC(args, from) ? parsee_muc_jid : to, q, stanza ); + ExtensionShoveCommands( + args->exts, + IsInMUC(args, from) ? parsee_muc_jid : to, + q, stanza + ); XMLAddChild(iq_reply, q); Free(parsee_muc_jid); } @@ -574,7 +571,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) { XMLElement *iq_reply, *query; - XMLElement *name, *version, *os; + XMLElement *name, *version; iq_reply = XMLCreateTag("iq"); XMLAddAttr(iq_reply, "to", from); @@ -585,19 +582,14 @@ 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(info.sysname)); } XMLAddChild(query, name); XMLAddChild(query, version); - XMLAddChild(query, os); XMLAddChild(iq_reply, query); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size); @@ -605,14 +597,10 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) } else { - char *buf = NULL; - Stream *s = StrStreamWriter(&buf); Log(LOG_WARNING, "Unknown I/Q received:"); - XMLEncode(s, stanza); - StreamFlush(s); - StreamClose(s); - Log(LOG_WARNING, "%s", buf); - Free(buf); + XMLEncode(StreamStdout(), stanza); + StreamPrintf(StreamStdout(),"\n"); + StreamFlush(StreamStdout()); } } @@ -629,6 +617,7 @@ IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) { XMPPCommandManager *manager = thr->info->m; XMPPManageCommand(manager, stanza, args); + ExtensionManageCommands(args->exts, stanza); } #undef DISCO diff --git a/src/XMPPThread/Stanzas/Message.c b/src/XMPPThread/Stanzas/Message.c index 13021df..220cca4 100644 --- a/src/XMPPThread/Stanzas/Message.c +++ b/src/XMPPThread/Stanzas/Message.c @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -277,7 +276,7 @@ end_error: Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime)); } - if (moderated && !(!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))) + if (moderated) { /* TODO: Parsee MUST check if it is a valid MUC */ char *resource = ParseeGetResource(from); @@ -325,9 +324,8 @@ end_error: { Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime)); } - char *trim = ParseeTrimJID(from); if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && - to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL)) + to && *to == '@') { DbRef *room_ref; HashMap *room_json; @@ -346,7 +344,6 @@ 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 @@ -397,16 +394,10 @@ 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; + Free(chat_id); + + pthread_mutex_unlock(&thr->info->chk_lock); LazyRegister(args, encoded, !chat ? res : NULL); if (args->verbosity >= PARSEE_VERBOSE_TIMINGS) @@ -420,10 +411,15 @@ end_error: /* Check if it is a media link */ oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); - if (chat_id && StrEquals(type, "chat") && !is_parsee) + + /* TODO: This may be way too late. I think a way to listen to *all* + * stanzas may be worthwhile. */ + if (ExtensionPushStanza(args->exts, stanza, STANZA_MESSAGE)) { - /* Very clearly a MUC DM. */ - event_id = NULL; + //if (args->verbosity >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Stanza was vetoed by an extension."); + } } else if (oob && data && media_enabled) { @@ -521,15 +517,6 @@ 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/XMPPThread/Stanzas/Presence.c b/src/XMPPThread/Stanzas/Presence.c index 947fc4b..65c2082 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 && !StrEquals(type, "unavailable")) + if (status) { XMLElement *status_data = ArrayGet(status->children, 0); char *decode_from = ParseeLookupJID(oid); @@ -309,8 +309,6 @@ 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/XMPPThread/internal.h b/src/XMPPThread/internal.h index efdd300..5931f3d 100644 --- a/src/XMPPThread/internal.h +++ b/src/XMPPThread/internal.h @@ -32,17 +32,13 @@ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("component", "generic", "Parsee's component") -typedef struct IQFeatures { - Array *identity; - Array *adverts; -} IQFeatures; +typedef struct PEPManager PEPManager; +typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); + 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 @@ -69,33 +65,9 @@ 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 *); - -/** - * 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] - * 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); -void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event); bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza); HashMap * ShoveStanza(HashMap *content, XMLElement *stanza); @@ -109,9 +81,6 @@ void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr); bool ServerHasXEP421(ParseeData *data, char *from); -char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force); -#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL) - PEPManager * CreatePEPManager(ParseeData *data, void *cookie); void * PEPManagerCookie(PEPManager *manager); void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event); diff --git a/src/include/Extension.h b/src/include/Extension.h new file mode 100644 index 0000000..54123d5 --- /dev/null +++ b/src/include/Extension.h @@ -0,0 +1,81 @@ +#ifndef PARSEE_EXTENSION_H +#define PARSEE_EXTENSION_H + +#include + +#include + +#include + +typedef struct ParseeData ParseeData; + +typedef struct Extensions Extensions; +typedef struct Extension Extension; + +typedef enum StanzaType { + STANZA_RAW = 0, + STANZA_MESSAGE = 1, + STANZA_IQ = 2, + STANZA_PRESENCE = 3, + + STANZA_TYPE_COUNT +} StanzaType; + +/** Verifies if extensions are enabled with the Parsee binary. + * Functions here WILL fail if this function + * returns false! + * ------------------- + * Returns: true IFF extensions are enabled */ +extern bool ExtensionEnabled(void); + +/* Creates an extension context. */ +extern Extensions * ExtensionCreateContext(struct ParseeData *data); + +/** Destroys the entire extension context, and all of its extensions. + * ------------------------------------- + * Returns: NOTHING + * Thrashes: ctx */ +extern void ExtensionDestroyContext(Extensions *ctx); + +/** Loads a basic extension from a simple Janet file. + * ---------------------------- + * Returns: An extension handle[ctx] | NULL */ +extern Extension * ExtensionLoadBasic(Extensions *ctx, char *id, char *file); + +/** Reloads an extension from an ID. + * --------------------------- + * Returns: NOTHING */ +extern void ExtensionReload(Extensions *ctx, char *id); + +/** Loads extensions from a directory. The directory's Janet files are loaded + * (with the part before the .janet extension denoting the identifier). + * ----------------------------------- + * Returns: NOTHING */ +extern void ExtensionLoadDir(Extensions *ctx, char *dir); + +/** Broadcasts a stanza received to all extensions(by calling the + * on-stanza function). + * ----------------------------------------- + * Returns: NOTHING + * Modifies: the extensions */ +extern bool ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t); + +/** Broadcasts a Matrix event to all extensions, akin to ExtensionPushStanza. + * ----------- + * Returns: NOTHING + * Modifies: the extensions */ +extern bool ExtensionPushEvent(Extensions *ctx, HashMap *obj); + +/** Calls "on-xmpp-cmd" on extensions' Janet side, with no arguments. + * ---------- + * Returns: NOTHING + * Modifies: the extensions */ +extern void ExtensionRequestCommands(Extensions *ctx); + +extern void +ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza); + +extern void +ExtensionManageCommands(Extensions *ctx, XMLElement *stanza); + +#endif diff --git a/src/include/MUCServ.h b/src/include/MUCServ.h deleted file mode 100644 index f9a2e4b..0000000 --- a/src/include/MUCServ.h +++ /dev/null @@ -1,39 +0,0 @@ -#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 fb0b5f7..a408389 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -2,7 +2,6 @@ #define PARSEE_MATRIX_H #include -#include #include "FileInfo.h" @@ -21,12 +20,6 @@ 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 4b1f7aa..5993877 100644 --- a/src/include/Parsee.h +++ b/src/include/Parsee.h @@ -15,11 +15,10 @@ typedef struct ParseeData ParseeData; #include +#include #include #include -#include - #define PARSEE_VERBOSE_NONE 0 #define PARSEE_VERBOSE_LOG 1 #define PARSEE_VERBOSE_TIMINGS 2 @@ -41,14 +40,9 @@ typedef struct ParseeConfig { /* Homeserver port info */ char *homeserver_host; int homeserver_port; - int homeserver_tls; - - bool accept_pings; - bool ignore_bots; /* ------- JABBER -------- */ - char *component_addr; char *component_host; char *shared_comp_secret; int component_port; @@ -57,6 +51,7 @@ typedef struct ParseeConfig { /* ------- DB -------- */ char *db_path; size_t db_size; + char *extensions; /* - COMMAND-LINE FLAGS - */ int xmpp_threads, http_threads; @@ -69,7 +64,6 @@ typedef struct ParseeData { HttpServer *server; XMPPComponent *jabber; - MUCServer *muc; Db *db; char *id; @@ -80,8 +74,10 @@ typedef struct ParseeData { pthread_mutex_t oidl; /* If Parsee was intentionally halted */ - bool halted; + volatile bool halted; pthread_mutex_t halt_lock; + + Extensions *exts; } ParseeData; typedef struct Argument { @@ -101,6 +97,8 @@ typedef struct Argument { #define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__)) #define GrabArray(obj, ...) JsonValueAsArray(JsonGet(obj, __VA_ARGS__)) +#define IterateReentrant(h, k, v, i) HashMapIterateReentrant(h, &k, (void **) &v, &i) + /* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTES * in timestamps */ #define SECONDS * 1000 @@ -129,6 +127,7 @@ extern const char *parsee_ascii[PARSEE_ASCII_LINES]; * Modifies: the logger output */ extern void ParseePrintASCII(void); + /** * Checks if two versions of Parsee can be considered "compatible". * This is mainly used for things like database operations. TODO: @@ -284,6 +283,12 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id); /* Finds the MUC JID from a chat ID */ extern char * ParseeGetMUCID(ParseeData *, char *chat_id); +/** Verifies if a JID maps to a chatroom through Service Discovery. + * ---------------- + * Returns: whenever the JID is a real MUC + * Modifies: the XMPP stream */ +extern bool ParseeIsMUC(ParseeData *data, char *jid); + /** 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. @@ -329,6 +334,14 @@ ParseeIsMediaEnabled(ParseeData *data, char *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); +/** Automatically pushes the link between a stanza and a bridged Matrix event. + * It behaves like {ParseePushStanza} and {ParseePushDMStanza}, but the routing + * is done automatically. + * ---------------------------- + * Returns: NOTHING + * Modifies: the database */ +extern void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event); + /* Checks if a stanza is not duplicated in a chat ID */ extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id); @@ -359,7 +372,7 @@ extern int ParseeFindDatastartU(char *data); /* XMPP-ifies a message event to XEP-0393 if possible. */ -extern char * ParseeXMPPify(ParseeData *data, HashMap *event); +extern char * ParseeXMPPify(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); @@ -369,7 +382,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, char *filename); +extern char * ParseeToUnauth(ParseeData *data, char *mxc); extern void ParseeInitialiseOIDTable(void); extern void ParseePushOIDTable(char *muc, char *occupant); @@ -400,17 +413,10 @@ extern void ParseeDestroyNickTable(void); * to ban them from rooms where Parsee has the ability to do so ("noflying"). * --------------- * Returns: NOTHING - * See-Also: ParseeManageBan, ParseeGlobalUnban + * See-Also: ParseeManageBan * 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. * --------------- @@ -503,4 +509,8 @@ extern void ParseeUnlinkRoom(ParseeData *data, char *chat_id); * Modifies: NOTHING */ extern bool ParseeIsMUCWhitelisted(ParseeData *data, char *muc); +/* TODO */ +extern char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force); +#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL) + #endif diff --git a/src/include/Routes.h b/src/include/Routes.h index 664032b..8d233e6 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -27,10 +27,6 @@ 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 " \ @@ -79,8 +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("/media/(.*)/(.*)", RouteMedia) \ - X_ROUTE("/media/(.*)/(.*)/(.*)", RouteMedia) + X_ROUTE("/_matrix/client/v1/media/download/(.*)/(.*)", 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 7eff7a1..6fbc296 100644 --- a/src/include/Unistring.h +++ b/src/include/Unistring.h @@ -64,12 +64,6 @@ 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. @@ -82,10 +76,4 @@ 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/XML.h b/src/include/XML.h index c7b6610..8fd108d 100644 --- a/src/include/XML.h +++ b/src/include/XML.h @@ -13,8 +13,6 @@ typedef struct XMLEvent { XML_LEXER_DATA, XML_LEXER_ELEM, /* Empty attr */ XML_LEXER_ENDELEM, - - XML_ERROR, XML_RELAX } type; diff --git a/src/include/XMPP.h b/src/include/XMPP.h index 3b53f04..b2c5293 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 *addr, char *host, int port); +extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port); /* Authenticates a component stream with a given shared secret, * with a stream ID from the server. This should be called right @@ -90,9 +90,6 @@ 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. * ---------------------------------------------------------------------- @@ -101,6 +98,16 @@ extern ssize_t XMPPGetReplyOffset(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 diff --git a/src/include/XMPPCommand.h b/src/include/XMPPCommand.h index 33b121f..8fa0a03 100644 --- a/src/include/XMPPCommand.h +++ b/src/include/XMPPCommand.h @@ -1,3 +1,4 @@ +typedef struct XMPPCommandManager XMPPCommandManager; #ifndef PARSEE_XMPPCOMMAND_H #define PARSEE_XMPPCOMMAND_H @@ -7,11 +8,16 @@ #include #include -typedef struct XMPPCommandManager XMPPCommandManager; +typedef enum XMPPCommandFlags { + XMPPCMD_ALL = 0, + XMPPCMD_MUC , /* Only for MUCs */ + XMPPCMD_ADMINS /* Only for administrators */ +} XMPPCommandFlags; + typedef struct XMPPCommand XMPPCommand; typedef struct XMPPOption XMPPOption; -typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *); -typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *); +typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); +typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *, char *); typedef bool (*XMPPCmdFilter)(XMPPCommandManager *, char *id, XMLElement *stanza); /** Creates a simple XMPP command manager, which routes commands @@ -39,7 +45,7 @@ XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter); * Modifies: NOTHING * See-Also: XMPPRegisterCommand */ extern XMPPCommand * - XMPPBasicCmd(char *node, char *name, XMPPCmdCallback cb); +XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback cb); extern void XMPPCmdOptionsCreator(XMPPCommand *cmd, XMPPOptionWriter writer); extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt); @@ -49,7 +55,7 @@ extern char * XMPPGetCommandDesc(XMPPCommand *cmd); extern void XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction); extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title); extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); -extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form); +extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node); /** Create a basic option. * ----------------------------------------------------- @@ -91,6 +97,12 @@ extern void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd); * See-Also: XMPPCreateManager */ extern void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s); +/** Returns the flags associated to a command. + * ---------------- + * Returns: some flags + * Modifies: NOTHING */ +extern XMPPCommandFlags XMPPGetCommandFlags(XMPPCommandManager *m, char *id); + /** Destroys all memory related to the command {manager}. * ----------------------------------------------------- * Returns: NOTHING @@ -111,11 +123,11 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD #define XMPP_COMMAND(f,l,n,t,s) \ extern void \ - f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); \ + f(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); \ /* This symbol might not exist. We do, however, not care, as * it is not always done. */ \ extern void \ - Form##f(XMPPCommandManager *, XMPPCommand *, char *); \ + Form##f(XMPPCommandManager *, XMPPCommand *, char *, char *); \ #include "XMPPCommands.x.h" diff --git a/src/include/XMPPCommands.x.h b/src/include/XMPPCommands.x.h index dba6f48..483aca9 100644 --- a/src/include/XMPPCommands.x.h +++ b/src/include/XMPPCommands.x.h @@ -1,9 +1,4 @@ /* C X-macro file */ -typedef enum XMPPCommandFlags { - XMPPCMD_ALL = 0, - XMPPCMD_MUC , /* Only for MUCs */ - XMPPCMD_ADMINS /* Only for administrators */ -} XMPPCommandFlags; #define XMPPCOMMANDS \ XMPP_COMMAND(StatusCallback, XMPPCMD_ALL, "stats", "Get Parsee statistics", {}) \ XMPP_COMMAND(CleanCallback, XMPPCMD_ADMINS, "clean", "Cleanup temporary Parsee data", {}) \ diff --git a/tools/adminify.c b/tools/adminify.c index 1fdd6cf..e87d97e 100644 --- a/tools/adminify.c +++ b/tools/adminify.c @@ -89,30 +89,31 @@ 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!*"); + 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()); DbClose(parsee); return EXIT_SUCCESS; } - /* List admins */ - Log(LOG_INFO, "Admin list:"); - LogConfigIndent(LogConfigGlobal()); - ListAdmins(parsee); - LogConfigUnindent(LogConfigGlobal()); - - 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; } diff --git a/tools/common.h b/tools/common.h index 9687ea3..eee4793 100644 --- a/tools/common.h +++ b/tools/common.h @@ -108,7 +108,7 @@ GetDB(char *config) { ret = DbOpenLMDB(db_path, lmdb_size); } - else + if (!ret) { ret = DbOpen(db_path, 0); } diff --git a/tools/config.c b/tools/config.c index d8e0aea..b949c14 100644 --- a/tools/config.c +++ b/tools/config.c @@ -20,7 +20,6 @@ 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; @@ -29,7 +28,7 @@ Main(Array *args, HashMap *env) listen = "localhost"; ArgParseStateInit(&state); - while ((flag = ArgParse(&state, args, "H:J:j:s:d:p:m:l:S:M:")) != -1) + while ((flag = ArgParse(&state, args, "H:J:s:d:p:m:l:S:M:")) != -1) { switch (flag) { @@ -46,9 +45,6 @@ Main(Array *args, HashMap *env) case 'J': jabber = state.optArg; break; - case 'j': - component_as = state.optArg; - break; case 'd': data = state.optArg; break; @@ -127,7 +123,6 @@ 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); diff --git a/tools/plumb.c b/tools/plumb.c deleted file mode 100644 index 1bc9e14..0000000 --- a/tools/plumb.c +++ /dev/null @@ -1,152 +0,0 @@ -/* 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; -}