diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml new file mode 100644 index 0000000..c649ab1 --- /dev/null +++ b/.forgejo/workflows/build-release.yaml @@ -0,0 +1,85 @@ +name: Builds static Parsee +on: [push] +jobs: + compile: + strategy: + matrix: + distro: + - debian + arch: + - amd64 + - arm64 + runs-on: + - ${{ matrix.distro }} + - ${{ matrix.arch }} + container: + image: ${{ matrix.distro }} + steps: + - name: Install LMDB+OpenSSL tools + run: | + echo $(uname -a) $(env | grep container) + apt update -y + apt install -y build-essential liblmdb-dev nodejs libssl-dev git wget python3 python3-pip bzip2 + - name: Clone everything + run: | + mkdir -p repos + git clone --depth=1 https://git.musl-libc.org/git/musl repos/musl + wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.1/mbedtls-3.6.1.tar.bz2 + tar -xvf mbedtls-3.6.1.tar.bz2 + mv mbedtls-3.6.1/ repos/mbed + git clone --depth=1 https://github.com/LMDB/lmdb repos/lmdb + git clone -b add-mbed --single-branch https://git.telodendria.io/lda/Cytoplasm repos/cyto + echo "PREFIX=$GITHUB_WORKSPACE/usr" >> $GITHUB_ENV + echo "PATH=$GITHUB_WORKSPACE/usr/bin:$PATH" >> $GITHUB_ENV + echo "INCLUDE_PATH=$GITHUB_WORKSPACE/usr/include" >> $GITHUB_ENV + echo "LIBRARY_PATH=$GITHUB_WORKSPACE/usr/lib" >> $GITHUB_ENV + - name: Build musl + run: | + cd repos/musl + ./configure --prefix=${PREFIX} + make -j$(nproc) + make install -j$(nproc) + alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib" + - name: Build MbedTLS + run: | + alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib" + cd repos/mbed + python3 -m pip install -r scripts/basic.requirements.txt --break-system-packages + make CC=musl-gcc -j$(nproc) + make install DESTDIR=${PREFIX} -j$(nproc) + - name: Build LMDB + run: | + alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib" + cd repos/lmdb/libraries/liblmdb + make CC=musl-gcc + make install prefix=${PREFIX} + - name: Build Cytoplasm + run: | + alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib" + cd repos/cyto + git fetch + rm configure + wget https://kappach.at/configure + chmod +x configure + ./configure --cc=musl-gcc --prefix=${PREFIX} --with-lmdb --with-mbed + make -j$(nproc) + make install + - name: Clone/Build Parsee + run: | + git clone https://git.kappach.at/${{ github.repository}} parsee + cd parsee + alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib" + musl-gcc -static configure.c -o configure && ./configure -s -l + make CC=musl-gcc CYTO_INC=${PREFIX}/include CYTO_LIB=${PREFIX}/lib LDFLAGS=-s + make PREFIX=bins install + - name: Create a final archive + run: | + cd parsee + echo "NAM=parsee-$(uname)-$(uname -m)" >> $GITHUB_ENV + echo "DIR=$PWD/bins" >> $GITHUB_ENV + - name: Upload it all(as a ZIP) + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + name: ${{ env.NAM }} + path: ${{ env.DIR }} + compression-level: 9 diff --git a/.forgejo/workflows/check-gcc.yaml b/.forgejo/workflows/check-gcc.yaml new file mode 100644 index 0000000..5404fa6 --- /dev/null +++ b/.forgejo/workflows/check-gcc.yaml @@ -0,0 +1,41 @@ +name: Checks Parsee's correctness on GCC+Debian +on: [push] +jobs: + compile: + strategy: + matrix: + distro: + - debian + arch: + - amd64 + runs-on: + - ${{ matrix.distro }} + - ${{ matrix.arch }} + container: + image: ${{ matrix.distro }} + steps: + - name: Install LMDB+OpenSSL tools + run: | + echo $(uname -a) $(env | grep container) + apt update -y + apt install -y build-essential liblmdb-dev nodejs libssl-dev git + - name: Clone/Configure Cytoplasm + run: | + git clone https://git.kappach.at/KappaChat/Cytoplasm --depth=1 + cd Cytoplasm + ./configure --with-lmdb --prefix=/usr + - name: Build Cytoplasm + run: 'cd Cytoplasm && make -j$(nproc)' + - name: Install Cytoplasm + run: 'cd Cytoplasm && make install' + - name: Clone Parsee + uses: actions/checkout@v3 + - name: Build Parsee + run: | + cc configure.c -o configure && ./configure + echo 'CFLAGS=-Werror -Wall -Wextra -pedantic -fanalyzer' >> build.conf + cat build.conf + make && make ayadoc + - name: Install Parsee + run: 'make install' + diff --git a/.gitignore b/.gitignore index ff7606f..09b6fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,29 @@ parsee* parsee *.swp .* -data -data/* +data* +data*/* +Makefile +configure +gmon.out tools/out tools/out/* ayaya/* ayaya + +#ctags +tags + +# Whitelists +!etc/* +!etc/** +!.forgejo +!.forgejo/* +!.forgejo/** + +!.guix +!.guix/* +!.guix/** + diff --git a/.guix/modules/parsee.scm b/.guix/modules/parsee.scm new file mode 100644 index 0000000..75b8b64 --- /dev/null +++ b/.guix/modules/parsee.scm @@ -0,0 +1,82 @@ +(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 new file mode 100644 index 0000000..f4ffcde --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,84 @@ +# Parsee changelogs + +This document holds changes logged between versions, ever +since `tomboyish-bridges-adventure`'s release. +Dates are to be written as DD/MM/YYYY. Please update the +changelog as you go, no one wants to keep track of every +commit done between releases. + +## Release +*There is currently no full releases of Parsee* + +## Beta +*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 +up Parsee just a bit. MbedTLS support is still highly unstable. +#### New things +- Start dealing with some basic PEP and vCard-based avatar +support from both sides. +- Banning Parsee from a XMPP MUC effectively unlinks it from +the MUC. +- Start adding basic documentation to Parsee, through the +wiki page. +- Add MUC whitelists for plumbing, alongside a `whitelist` tool +- Add parameter for setting the max stanza size allowed, with +the default being the XMPP minimum of 10000 bytes. +- Allows experimental MbedTLS through a specific Cytoplasm +patch (though still unstable AND slow). +- Does basic work towards NetBSD support(especially with DEC Alpha) +- Start contextualising XMPP commands(all/Parsee admins/MUCs). + +#### Bugfixes +- Adds more information to media events so that clients can +behave. +- Fixes issues where SIGPIPE can actually just kill Parsee. +- "Lone" XMPP messages no longer render weirdly on Element +Android's weird rendering. +- Start fixing bug where Parsee takes several seconds to send +a message coming from XMPP with MbedTLS(it is still slow and +unstable) +- Fix issue where the XMPP server would kill Parsee if avatars +are too large. +- Refactor some of the code to abstract sending stanzas down +the wire to a specific function, thus allowing us to check +for certain conditions more easily(for example, verifying the +size on the fly, or having extensions being able to postprocess +the stanza). +- Parsee now stops when a stream error is received, instead of +being in a limbo state. +- The format for links has been changed to be slighlty *less* +annoying. + +#### Deprecated features +- The old `build.c` and `Makefile`s used for building are removed, +and replaced by the `configure.c` C file(/script via TCC). + +### v0.1.0[tomboyish-bridges-adventure] <9/9/2024> +Nothing much to say, but this is the first alpha release +of Parsee. May occasionally deadlock. +#### New things +*NONE* +#### Bugfixes +*NONE* +#### Deprecated features +*NONE* diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..c7f8b75 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,14 @@ +# Lines starting with a '#' are ignored. +# This is a list of people who have contributed to Parsee. +# You *may* add some information about yourself after having made a change +# to the repository(or wiki!), with those fields: +# (N) - A nickname +# (L) - An approximate place of residence +# (X) - An XMPP URI +# (M) - A Matrix MXID +# (D) - A short description about yourself +(N): LDA +(L): France +(X): xmpp:lda@monocles.eu +(M): @fourier:ari.lt +(M): @fourier:ari.lt diff --git a/DATES.TXT b/DATES.TXT index 8e32ab3..4da14dc 100644 --- a/DATES.TXT +++ b/DATES.TXT @@ -1,7 +1,3 @@ Some dates for Parsee-related events. They mostly serve as LDA's TODOs with a strict deadline: - - ~September 2024[star-of-hope]: - Get Parsee into the _Phantasmagoria of Bug View_ stage (essentially - v0.0.1 for a public testing) once I can afford `yama`, and start - bridging the Matrix room alongside a shiny XMPP MUC, bridged by - yours truly. + - Get star-of-hope out by November 8 diff --git a/LICENSE b/LICENSE index 5d9765c..cb9aa73 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -For the files src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile, see COPYING.CC0 -to be present. -For any other file in src/, see COPYING.AGPL as the primary license. +For the files src/include/Unistring.h, src/Unistr.h rc/Parsee/HMAC.c, src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile, +see COPYING.CC0. +For any other file in src/, see COPYING.AGPL as the primary license(AGPL-3.0-or-later) As Parsee depends on Cytoplasm, its license is left here in COPYING.CYTO diff --git a/Makefile b/Makefile deleted file mode 100644 index 733bd71..0000000 --- a/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for building Parsee -# ================================ -# TODO: Consider making something akin to a configure script that checks -# for dependencies, or maybe even use *autoconf* (the devil!) - - -# =========================== Parsee Flags ============================= - -# phantasmagoria - test runs without an actual code -CODE=phantasmagoria -NAME=Parsee -VERSION=0.0.0 - -REPOSITORY=$(shell git remote get-url origin) - -# =========================== Compilation Flags ============================ -CYTO_INC ?=/usr/local/include/ # Where Cytoplasm's include path is - # located. -CYTO_LIB ?=/usr/local/lib # Where's Cytoplasm's library is - # located. -PREFIX ?=/usr/local - -SOURCE=src -OBJECT=build -AYAS=ayaya -ETC=etc -INCLUDES=src/include -CC=cc -CFLAGS=-I$(SOURCE) -I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -DCODE=\"$(CODE)\" -g -ggdb -Wall -Werror -LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -g -ggdb -AFLAGS=-C "$(ETC)/ayadoc/style.css" -p "$(NAME)" -BINARY=parsee -# ============================ Compilation ================================= -SRC_FILES:=$(shell find $(SOURCE) -name '*.c') -OBJ_FILES:=${subst $(SOURCE)/,$(OBJECT)/,$(patsubst %.c, %.o, $(SRC_FILES))} - -CPP_FILES:=$(shell find $(INCLUDES) -name '*.h') -AYA_FILES:=${subst $(INCLUDES)/,$(AYAS)/,$(patsubst %.h, %.html, $(CPP_FILES))} - -all: binary utils - -binary: $(OBJ_FILES) - $(CC) $(LDFLAGS) $(OBJ_FILES) -o $(BINARY) - -clean: - rm -rf $(OBJECT) $(BINARY) $(AYAS) - -$(OBJECT)/%.o: $(SOURCE)/%.c - @mkdir -p $(shell dirname "$@") - $(CC) -c $(CFLAGS) $< -o $@ - -utils: - (cd tools && make) - -ayadoc: utils $(AYA_FILES) - -$(AYAS)/%.html: $(INCLUDES)/%.h - @mkdir -p $(shell dirname "$@") - tools/out/aya $(AFLAGS) -i $< -o $@ - - -# Installs everything. -install: binary utils ayadoc install_setup install_parsee install_tools install_aya install_man - @echo Installed $(NAME) to $(PREFIX)! - -install_setup: - install -dm755 "$(PREFIX)/bin" - install -dm755 "$(PREFIX)/share/doc" - install -dm755 "$(PREFIX)/man" - -install_parsee: - install -Dm755 "$(BINARY)" "$(PREFIX)/bin/$(BINARY)" - -TOOLS:=$(shell find 'tools/out' -name '*') -ITOOL:=${subst tools/out/,$(PREFIX)/bin/,$(patsubst tools/out/%, tools/out/$(BINARY)-%, $(TOOLS))} -install_tools: $(ITOOL) -$(PREFIX)/bin/$(BINARY)-%: tools/out/% - install -Dm755 "$<" "$@" - -IHTML:=${subst $(AYAS)/,$(PREFIX)/share/doc/$(BINARY)/,$(AYA_FILES)} -install_aya: $(IHTML) -$(PREFIX)/share/doc/$(BINARY)/%: $(AYAS)/% - install -Dm644 "$<" "$@" - -MPAGE:=$(shell find 'etc/man' -name '*.*') -PPAGE:=${subst etc/man/,$(PREFIX)/man/,$(MPAGE)} -install_man: $(PPAGE) -$(PREFIX)/man/%: etc/man/% - install -Dm644 "$<" "$@" diff --git a/README.MD b/README.MD index 672d11b..f9522e8 100644 --- a/README.MD +++ b/README.MD @@ -1,39 +1,54 @@ # Parsee - the jealous XMPP<=>Matrix bridge -Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is NOT a drop-in replacment. +Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is +NOT a drop-in replacment. +Currently, it is *alpha* stage, which means that I wouldn't recommend using this in production, +as I can change anything, at any time, and it may behave strangely at times. ## Why? ### Naming -The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi), a -"*bridge* princess". +The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi), +a "*bridge* princess". The other name you actually can sometimes see explains itself, so I won't +be talking about it. ### Reasoning (personal to LDA) -I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister project to KappaChat, -this means that I can integrate Parsee with KappaChat however I wish it to be, which allows me to mess around with a -codebase I'm already familiar with. -A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*. -Well, I'm *trying* to do that, at least. Please scream at me if that fails(or just doesn't run -on a overclocked Raspberry Pi 4B, which, by the way, was literally where Parsee+XMPP ran for -a good chunk of Parsee's start.) +I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister +project to KappaChat, this means that I can integrate Parsee with KappaChat however I wish it +to be, which allows me to mess around with a codebase I'm already familiar with. +A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*, +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)) ### "Why not just use Matrix lol" ### "Why not just use XMPP lol" -These two having the same answer should be enough information. Also can I *just* have fun? +These two having the same answer should be enough information. One could also argue that both sides need to migrate(onboard) the other side, so a bridge may be a good way to start. ## BUILDING ```sh -$ make # This generates a 'parsee' executable. -$ cd tools # If you want to build more tools -$ make && cd .. -$ make ayadoc # If you want to build HTML documentation -$ make [PREFIX=(install path)] install # To install Parsee. +$ cc configure.c -o configure # that or use tcc -run to consolidate these two steps. +$ ./configure # use -s if you want static Parsee+MbedTLS, use -s -l if LMDB is needed +$ make +$ make [PREFIX=...] install # run as root if on a protected dir like /usr ``` If there are any Cytoplasm-related build failures, you may want to check the Makefile to -change a few variables (you can set `CYTO_INC` and `CYTO_LIB`) +change a few variables (you can set `CYTO_INC` and `CYTO_LIB` for Cytoplasm's include and +library paths specifically.) +If you build with MbedTLS, please mind setting the `CYTO_TLS_CA` env to Parsee. ### DEPENDENCIES -Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm). Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but you can get away without those). +Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm). +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 @@ -46,7 +61,8 @@ parsee-config \ -H 'blow.hole' \ # Matrix homeserver name -J 'parsee.blow.hole' \ # XMPP component host, must be reachable -s 'A very secure XMPP component secret' \ - -p 5347 + -p 5347 \ + -M 65535 # Maximum stanza size. Stanzas larger than this from Parsee will be dropped to avoid stream closures. Leave this empty if you're unsure. ``` If everything goes well, it should generate a `parsee.json` file. @@ -59,31 +75,44 @@ returns with a landing page, then this side works. You can read it for some more ## DOCS Currently, the main sources of documentation are the Ayadocs(for headers) and the manpages -(see `etc/man`) +(see `etc/man`). -## TODOS -- Add [libomemo](https://github.com/gkdr/libomemo) as an optional dependency. +## TODOS before 1.0 rolls around +- Make Parsee actually go *vroooooooooommmmmmm*. +- Make sure Parsee can easily run on just about any reasonable POSIX +system. +- Avoid making 'back-puppets' from Matrix as much as possible +- Extension support. I'd need to design a good system, and maybe do it +with either shared libraries(`dlopen`/`dlclose` on POSIX) or use a +language like Janet or Lua. +- Add [libomemo](https://github.com/gkdr/libomemo) or something as an optional dependency. - It depends on more stuff anyways, and I don't want to weigh down the dependency list of Parsee for that. - Matrix's libolm is deprecated. They replaced it with a Rust version that pulls in *way too many* dependencies, and that lacks a C binding. We may - put in the work of either forking off libolm or making a binding to KappaChat. + ~~put in the work of either forking off libolm or~~ be making a binding with + KappaChat(when I get around to remaking UI :p). + - Josh did infact tell me that maybe C bindings may happen. I'd be + willing to help out, but IDK. In any case, this will at best be an + extension packagers may integrate properly. - Get rid of the '?'-syntax and use another invalid Matrix char/valid XMPP char ('$'?) for escaped? -- PROPER FUCKING AVATARS - XEP-0084 IS THE WORST PIECE OF SHIT KNOWN TO MAN. If any Jabberbros want to - look at terrible code/XML and suggest things to have *proper* avatar support, - I'm all in. - Consider making room/MUC admins/owners be able to plumb instead of it being restricted to Parsee admins, with permission from MUC owners, too - Limiting to admins may be a way to "control" consent for both, but this is only if Parsee admins are good-willed, which we must assume such statment to be false by default. + - Currently, MUC owners may kick Parsee out, with the effect of unlinking the + MUC. - Look at XEPS-TBD.TXT for XEPs to be done - Add a MUC server to Parsee, such that it may be able to hook onto it and therefore support XMPP->Matrix bridging. - Manage MUC DMs in a reasonable manner. Thanks `@freeoffers4u:matrix.org` for being a fucking annoyance and DMing an old Parsee semi-anon user for no clear reason. +- Make Parsee cope with stream closures(i.e: XMPP server turning off) better. As of +now, it just kills itself when that happens, instead of trying to negociate a new +connection, which would be a better method that would actually fit Parsee's own +principles. ## DONATING/CONTRIBUTING If you know things about XMPP or Matrix, yet aren't familiar with C99, or just @@ -100,7 +129,7 @@ You may also donate to [the LiberaPay](https://en.liberapay.com/Parsee), alongsi currently maintaining Cytoplasm. ## IM chats -Please avoid asking for help/issues here. If you *really* want, you may just open +Please avoid asking for help/issues there. If you *really* want, you may just open an issue and link it over it. Basic respect for others/not being an asshat is required. (Also, these are temporary room aliases.) - [#parsee:tedomum.net](https://matrix.to/#/%23parsee:tedomum.net) diff --git a/XEPS-TBD.TXT b/XEPS-TBD.TXT index 02a68ca..9ed28cf 100644 --- a/XEPS-TBD.TXT +++ b/XEPS-TBD.TXT @@ -1,6 +1,19 @@ XEPs current supported are in src/XMPPThread, at the IQ disco advertising. Somewhat implemented XEPs: + v https://xmpp.org/extensions/xep-0050.html + Ad-hoc commands that bridge maintainers can deal with XMPP-style are + also a nice to have. + There are commands, but not a lot of them as of now, and localisation + is missing. + v https://xmpp.org/extensions/xep-0421.html + Using the occupant ID in semi-anonymous MUCs is a desirable property. + I dont know of a lot of places that don't use the occupant ID anymore + within Parsee. + v "also it [Bifrost] doesn't respect voice either" + As far as I am aware, works. + v https://xmpp.org/extensions/xep-0425.html + As mentionned in #2, moderation _needs_ to be done. ~ https://xmpp.org/extensions/xep-0085.html Only XMPP->Matrix at the moment. Still need to figure out how to get typing indicators as an AS. @@ -8,21 +21,14 @@ Somewhat implemented XEPs: This allows reactions, which Matrix also has support to. The two systems don't seem *too* restrictive on one-another (unlike some IM platforms I won't mention), so this doesn't sound too bad to do - HALF-IMPLEMENTED: Removing reacts won't work. + TODO: Add support from Matrix. ~ https://xmpp.org/extensions/xep-0184.html Only Matrix->XMPP as of now. Requesting data from Matrix ASes without /sync seems like a non-option as of now, which _sucks_. - ~ https://xmpp.org/extensions/xep-0050.html - Ad-hoc commands that bridge maintainers can deal with XMPP-style are - also a nice to have. - There are commands, but not a lot of them as of now, and localisation - is missing. - ~ https://xmpp.org/extensions/xep-0421.html - Using the occupant ID in semi-anonymous MUCs is a desirable property. - I dont know of a lot of places that don't use the occupant ID anymore - within Parsee. - ~ https://xmpp.org/extensions/xep-0425.html - As mentionned in #2, moderation _needs_ to be done. + ~ https://xmpp.org/extensions/xep-0084.html + Avatar support would be extremely useful, if just a QoL improvment. + Matrix and XMPP both have support for these. + XEP-0084 is a pain in the ass to implement and seems generally just For future XEPs: - https://xmpp.org/extensions/xep-0449.html @@ -32,22 +38,16 @@ For future XEPs: which is used along PEP, it seems, and meanwhile Matrix has ''support'' for packs too, tracking them is between "annoyance" and "yeah, no.". -ON STANDBY BECAUSE THESE HAVE BEEN TERRIBLE TO DEAL WITH AND WHO KEEPS WRITING -THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT: - x https://xmpp.org/extensions/xep-0084.html - Avatar support would be extremely useful, if just a QoL improvment. - Matrix and XMPP both have support for these. - XEP-0084 is a pain in the ass to implement and seems generally just +On Standby: unreliable, however. x https://xmpp.org/extensions/xep-0080.html Can't think of a good analogy to these... Not XEPs, but ideas that _needs_ to be added: - v "also it [Bifrost] doesn't respect voice either" -> Send a form on moderated - MUCs (which is standard, so its not too bad!). Currently WIP, and barely tested. ~ "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d - - Standalone/Static Parsee, ideally as small as it can be(if not as APE). + Happens on Matrix. I'll need to handle that on XMPP as well. + ~ Standalone/Static Parsee, ideally as small as it can be(if not as APE). - Kappa-like extension system(maybe bridging more than just Matrix-XMPP.) - https://www.youtube.com/watch?v=InL414iDZmY diff --git a/build.conf b/build.conf new file mode 100644 index 0000000..a2e17c1 --- /dev/null +++ b/build.conf @@ -0,0 +1,10 @@ +CODE=lunar-rainbow +NAME=Parsee +VERSION=0.3.0 +BINARY=parsee +SOURCE=src +INCLUDES=src/include +OBJECT=build +CC=cc +CFLAGS=-O3 +PREFIX=/usr diff --git a/configure.c b/configure.c new file mode 100644 index 0000000..d3a01d0 --- /dev/null +++ b/configure.c @@ -0,0 +1,727 @@ +/* configure.c - Simple, POSIX, non-Cytoplasm utility to build out + * a Parsee Makefile. + * To run it, just build it with: + * cc configure.c -o configure && configure + * -------------------------------- + * LICENSE: CC0 + * Written-By: LDA [lda@freetards.xyz] [@fourier:ari.lt] */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char * +string_rep_ext(char *in, char *ext1, char *ext2) +{ + char *end; + size_t inLen; + if (!in || !ext1 || !ext2) + { + return NULL; + } + + inLen = strlen(in); + end = inLen + in; + + while (end >= in) + { + if (!strcmp(end, ext1)) + { + size_t cpyLen = end - in; + size_t extLen = strlen(ext2); + char *ret = malloc(cpyLen + extLen + 1); + + memcpy(ret, in, cpyLen); + memcpy(ret + cpyLen, ext2, extLen); + ret[cpyLen + extLen] = '\0'; + return ret; + } + end--; + } + return NULL; +} +static char * +string_dup(char *in) +{ + char *out; + size_t len; + if (!in) + { + return NULL; + } + len = strlen(in); + out = malloc(len + 1); + memcpy(out, in, len); + out[len] = '\0'; + + return out; +} +static char * +string_cat(char *in1, char *in2) +{ + char *out; + size_t len1, len2; + if (!in1) + { + return string_dup(in2); + } + if (!in2) + { + return string_dup(in1); + } + + len1 = strlen(in1); + len2 = strlen(in2); + out = malloc(len1 + len2 + 1); + memcpy(out, in1, len1); + memcpy(out + len1, in2, len2); + out[len1 + len2] = '\0'; + + return out; +} + +typedef struct str_array { + char **values; + size_t quantity; +} str_array_t; +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; + return ret; +} +static void +str_array_free(str_array_t *arr) +{ + size_t i; + if (!arr) + { + return; + } + + for (i = 0; i < arr->quantity; i++) + { + if (arr->values[i]) free(arr->values[i]); + } + if (arr->values) free(arr->values); + free(arr); +} +static void +str_array_set(str_array_t *arr, size_t i, char *str) +{ + if (!arr) + { + return; + } + + if (i >= arr->quantity) + { + size_t size = (i+1) * sizeof(*arr->values); + size_t j; + arr->values = realloc(arr->values, size); + for (j = arr->quantity; j <= i; j++) + { + arr->values[j] = NULL; + } + arr->quantity = i + 1 ; + } + if (arr->values[i]) free(arr->values[i]); + arr->values[i] = string_dup(str); +} +static void +str_array_add(str_array_t *arr, char *str) +{ + if (!arr) + { + return; + } + str_array_set(arr, arr->quantity, str); +} +static char * +str_array_get(str_array_t *arr, size_t i) +{ + if (!arr) + { + return NULL; + } + return i < arr->quantity ? arr->values[i] : NULL; +} +static size_t +str_array_len(str_array_t *arr) +{ + return arr ? arr->quantity : 0; +} +static char * +cmd_stdout(char *cmd) +{ + FILE *f; + char *line = NULL; + size_t size; + int result; + if (!cmd) + { + goto failure; + } + if (!(f = popen(cmd, "r"))) + { + goto failure; + } + + getline(&line, &size, f); + result = pclose(f); + + if (result < 0) + { + perror("pclose(3)"); + goto failure; + } + else if (result) + { + fprintf(stderr, "command exited with status %d: %s\n", result, cmd); + goto failure; + } + return line; + +failure: + free(line); + return NULL; +} + +static char * +strchrn(char *s, char c) +{ + s = strchr(s, c); + return s ? s+1 : NULL; +} +static str_array_t * +split(char *dir) +{ + str_array_t *ret; + char *start; + if (!dir || strlen(dir) >= PATH_MAX - 1) + { + return NULL; + } + + ret = str_array_create(); + for (start = dir; start; start = strchrn(start, '/')) + { + char subtmp[PATH_MAX]; + char *next = strchr(start, '/'); + if (!next) + { + next = start + strlen(start); + } + memcpy(subtmp, start, next - start); + subtmp[next - start] = '\0'; + + str_array_add(ret, subtmp); + } + return ret; +} +static str_array_t * +collect_sources(char *dir, bool head, char *ext) +{ + DIR *handle; + str_array_t *ret; + struct dirent *ent; + if (!dir) + { + return NULL; + } + + ret = str_array_create(); + handle = opendir(dir); + if (!handle) + { + printf("error: cannot open directory '%s'\n", dir); + return ret; + } + while ((ent = readdir(handle))) + { + char *name = ent->d_name; + + if (!strcmp(name, ".") || !strcmp(name, "..")) continue; + + if (strlen(name) > strlen(ext) && + !strcmp(name + strlen(name) - strlen(ext), ext)) + { + char *d1 = string_cat(dir, "/"); + char *na = string_cat(d1, name); + str_array_add(ret, na); + free(d1); + free(na); + continue; + } + { + char *d1 = string_cat(dir, "/"); + char *d2 = string_cat(d1, name); + size_t i; + struct stat sb; + + if (stat(d2, &sb)) + { + fprintf(stderr, "stat(2) %s: %s\n", d2, strerror(errno)); + free(d2); + free(d1); + } + else if (S_ISDIR(sb.st_mode)) + { + str_array_t *sub = collect_sources(d2, false, ext); + for (i = 0; i < str_array_len(sub); i++) + { + char *file = str_array_get(sub, i); + str_array_add(ret, file); + } + str_array_free(sub); + } + + free(d2); + free(d1); + } + } + closedir(handle); + return ret; +} + +/* Builds the entirety of Parsee. */ +static void +write_objects(FILE *makefile, str_array_t *sources) +{ + size_t i; + if (!makefile || !sources) + { + return; + } + for (i = 0; i < str_array_len(sources); i++) + { + char *src = str_array_get(sources, i); + char *ofl = string_rep_ext(src, ".c", ".o"); + char *obj = string_cat("build", ofl + 3); + + fprintf(makefile, " %s", obj); + free(ofl); + free(obj); + } +} +static void +write_images(FILE *makefile, str_array_t *sources) +{ + size_t i; + if (!makefile || !sources) + { + return; + } + for (i = 0; i < str_array_len(sources); i++) + { + char *src = str_array_get(sources, i); + char *ofl = string_rep_ext(src, ".png", ".o"); + char *obj = string_cat("build", ofl + 3 + 1 + 5); + + fprintf(makefile, " %s", obj); + free(ofl); + free(obj); + } +} +static void +write_executable(FILE *makefile, str_array_t *sources) +{ + size_t i; + if (!makefile || !sources) + { + return; + } + for (i = 0; i < str_array_len(sources); i++) + { + char *src = str_array_get(sources, i); + char *ofl = string_rep_ext(src, ".c", ""); + char *obj = string_cat("tools/out", ofl + 5); + + fprintf(makefile, " %s", obj); + free(ofl); + free(obj); + } +} +static void +write_ayas(FILE *makefile, str_array_t *sources) +{ + size_t i; + if (!makefile || !sources) + { + return; + } + for (i = 0; i < str_array_len(sources); i++) + { + char *src = str_array_get(sources, i); + char *ofl = string_rep_ext(src, ".h", ".html"); + char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7); + + fprintf(makefile, " %s", obj); + free(ofl); + free(obj); + } +} + +static void +analyse_dependencies(FILE *makefile, char *src) +{ + FILE *source = fopen(src, "r"); + char *lineptr = NULL; + char *dn = strdup(src), *dirn; + size_t len = 0; + + dirn = dirname(dn); + + while (getline(&lineptr, &len, source) != -1) + { + char *corrptr = lineptr, *nl, *chevron, *quote, *cutoff; + char *basedir, *srcdir, *incdir, *tmp; + struct stat statinfo; + + /* try to parse the line */ + if ((nl = strchr(corrptr, '\n'))) + { + *nl = '\0'; + } + + while (*corrptr && isblank(*corrptr)) + { + corrptr++; + } + if (strncmp(corrptr, "#include \"", 10) && + strncmp(corrptr, "#include <", 10)) + { + continue; + } + corrptr += 10; + chevron = strchr(corrptr, '>'); + chevron = chevron ? chevron : corrptr + strlen(corrptr); + quote = strchr(corrptr, '"'); + quote = quote ? quote : corrptr + strlen(corrptr); + cutoff = chevron < quote ? chevron : quote; + + *cutoff = '\0'; + + /* if we found something, try to resolve it */ + tmp = string_cat(dirn, "/"); + basedir = string_cat(tmp, corrptr); + free(tmp); + if (!stat(basedir, &statinfo)) + { + fprintf(makefile, " %s", basedir); + free(basedir); + continue; + } + free(basedir); + + srcdir = string_cat("src/", corrptr); + if (!stat(srcdir, &statinfo)) + { + fprintf(makefile, " %s", srcdir); + free(srcdir); + continue; + } + free(srcdir); + + incdir = string_cat("src/include/", corrptr); + if (!stat(incdir, &statinfo)) + { + fprintf(makefile, " %s", incdir); + free(incdir); + continue; + } + free(incdir); + } + free(lineptr); + free(dn); + fclose(source); +} +static int +main_build(int argc, char *argv[]) +{ + FILE *makefile; + char *repo = cmd_stdout("git remote get-url origin"); + size_t i; + bool with_static = false, with_lmdb = false; + int opt; + str_array_t *sources, *images, *utils, *aya; + + if (repo) + { + char *lf = strchr(repo, '\n'); + if (lf) + { + *lf = '\0'; + } + } + else + { + repo = strdup("N/A"); + } + + while ((opt = getopt(argc, argv, "sl")) != -1) + { + switch (opt) + { + case 's': + with_static = true; + break; + case 'l': + with_lmdb = with_static; + with_static = true; + 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"); + fprintf(makefile, ".POSIX: \n"); + fprintf(makefile, "include build.conf\n\n"); + fprintf(makefile, "AYAYAS=ayaya\n"); + fprintf(makefile, "CYTO_LIB=/usr/local/lib\n"); + fprintf(makefile, "CYTO_INC=/usr/local/include\n"); + fprintf(makefile, "ETC=etc\n\n"); + + fprintf(makefile, "all: utils binary ayadoc\n\n"); + + /* Create all objects */ + sources = collect_sources("src", true, ".c"); + 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)"); + if (with_static) + { + fprintf(makefile, " -static"); + } + fprintf(makefile, " -L $(CYTO_LIB)"); + write_objects(makefile, sources); + write_images(makefile, images); + if (with_static) + { + fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); + if (with_lmdb) fprintf(makefile, " -llmdb"); + } + fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n"); + } + + /* Write rules for every source */ + for (i = 0; i < str_array_len(sources); i++) + { + char *src = str_array_get(sources, i); + char *ofl = string_rep_ext(src, ".c", ".o"); + char *obj = string_cat("build", ofl + 3); + + fprintf(makefile, "%s: %s", obj, src); + analyse_dependencies(makefile, src); + fprintf(makefile, "\n"); + { + str_array_t *s = split(obj); + ssize_t j; + + fprintf(makefile, "\t@mkdir -p "); + for (j = 0; j < str_array_len(s) - 1; j++) + { + fprintf(makefile, "%s/", str_array_get(s, j)); + } + fprintf(makefile, "\n"); + str_array_free(s); + + fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) "); + fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" "); + fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" "); + fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" "); + fprintf(makefile, "-DREPOSITORY=\"\\\"%s\\\"\" ", repo); + fprintf(makefile, "$(CFLAGS) %s -o %s\n", src, obj); + } + free(ofl); + free(obj); + } + for (i = 0; i < str_array_len(images); i++) + { + char *src = str_array_get(images, i); + char *ofl = string_rep_ext(src, ".png", ".o"); + char *obj = string_cat("build", ofl + 3 + 1 + 5); + char *cfl = string_cat("build", src + 3 + 1 + 5); + + fprintf(makefile, "%s: %s\n", obj, src); + + { + str_array_t *s = split(obj); + char *sym; + ssize_t j; + + fprintf(makefile, "\t@mkdir -p "); + for (j = 0; j < str_array_len(s) - 1; j++) + { + fprintf(makefile, "%s/", str_array_get(s, j)); + } + fprintf(makefile, "\n"); + sym = j != -1 ? str_array_get(s, j): NULL; + sym = string_rep_ext(sym, ".o", ""); + str_array_free(s); + + fprintf(makefile, "\ttools/out/b64 %s 'media_%s' '%s.c'\n", src, sym, obj); + fprintf(makefile, "\t$(CC) -c %s.c -o %s\n", obj, obj); + free(sym); + } + free(obj); + free(ofl); + free(cfl); + } + + /* Build utilities */ + utils = collect_sources("tools", true, ".c"); + fprintf(makefile, "utils:"); + write_executable(makefile, utils); + fprintf(makefile, "\n"); + for (i = 0; i < str_array_len(utils); i++) + { + char *src = str_array_get(utils, i); + char *ofl = string_rep_ext(src, ".c", ""); + char *obj = string_cat("tools/out", ofl + 5); + + fprintf(makefile, "%s: %s\n", obj, src); + { + fprintf(makefile, "\t@mkdir -p tools/out\n"); + fprintf(makefile, "\t$(CC) -o %s", obj); + if (with_static) + { + fprintf(makefile, " -static"); + } + fprintf(makefile, " %s", src); + fprintf(makefile, " -I $(CYTO_INC)"); + fprintf(makefile, " -L $(CYTO_LIB)"); + if (with_static) + { + fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto"); + fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto"); + if (with_lmdb) fprintf(makefile, " -llmdb"); + } + fprintf(makefile, " -lCytoplasm $(CFLAGS)\n"); + } + + free(ofl); + free(obj); + } + + /* Build Aya */ + aya = collect_sources("src/include", true, ".h"); + fprintf(makefile, "ayadoc:"); + write_ayas(makefile, aya); + fprintf(makefile, "\n"); + for (i = 0; i < str_array_len(aya); i++) + { + char *src = str_array_get(aya, i); + char *ofl = string_rep_ext(src, ".h", ".html"); + char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7); + + fprintf(makefile, "%s: %s\n", obj, src); + { + str_array_t *s = split(obj); + ssize_t j; + + fprintf(makefile, "\t@mkdir -p "); + for (j = 0; j < str_array_len(s) - 1; j++) + { + fprintf(makefile, "%s/", str_array_get(s, j)); + } + fprintf(makefile, "\n"); + str_array_free(s); + + fprintf(makefile, + "\ttools/out/aya " + "-C etc/ayadoc/style.css " + "-p $(NAME) " + "-i %s -o %s\n", src, obj + ); + } + + free(ofl); + free(obj); + } + + fprintf(makefile, "install-parsee: binary\n"); + { + fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n"); + fprintf(makefile, "\tcp $(BINARY) $(PREFIX)/bin\n"); + fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/$(BINARY)\n"); + } + + fprintf(makefile, "install-tools: utils\n"); + fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n"); + for (i = 0; i < str_array_len(utils); i++) + { + char *tool = str_array_get(utils, i); + char *ofl = string_rep_ext(tool, ".c", ""); + char *obj = string_cat("tools/out", ofl + 5); + char *bna = basename(obj); + + fprintf(makefile, "\tcp %s $(PREFIX)/bin/parsee-%s\n", obj, bna); + fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/parsee-%s\n", bna); + + free(ofl); + free(obj); + } + fprintf(makefile, "install-aya: ayadoc\n"); + fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/aya/$(BINARY)\n"); + fprintf(makefile, "\tcp -R $(AYAYAS)/* $(PREFIX)/aya/$(BINARY)\n"); + fprintf(makefile, "install-man:\n"); + fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/share/man\n"); + fprintf(makefile, "\tcp -R etc/man/* $(PREFIX)/share/man\n"); + + fprintf(makefile, + "install: " + "install-parsee " + "install-tools " + "install-aya " + "install-man\n" + ); + + fprintf(makefile, "clean:\n\trm -rf ayaya build $(BINARY) tools/out\n"); + + str_array_free(sources); + str_array_free(images); + str_array_free(utils); + str_array_free(aya); + fflush(makefile); + fclose(makefile); + free(repo); + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + return main_build(argc, argv); +} diff --git a/etc/man/man1/parsee-adminify.1 b/etc/man/man1/parsee-adminify.1 new file mode 100644 index 0000000..9015d20 --- /dev/null +++ b/etc/man/man1/parsee-adminify.1 @@ -0,0 +1,40 @@ +." The last field is the codename, by the way. +." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN +.TH parsee-adminify 1 "Parsee Utility" "star-of-hope" + +.SH NAME +parsee-adminify - bootstrap an admin to a new Parsee server + +.SH SYNOPSIS +parsee-adminify +.B [DB path] +.B [user] + +.SH DESCRIPTION +.I parsee-adminify +is a tool to add/list the Parsee administrator list. It currently only +allows you to see/add admins to it, using simplified globrules. + +.SH OPTIONS +.TP +.B [config] +The configuration file used by the Parsee daemon. +.TP +.B [user] +If set, adds the glob +.I [user] +as a Parsee administrator. Otherwise, lists all administrators in +the Parsee instance. + +.SH BUGS +.PP +None as I know of. If anyone notices any issues with this, please +let me know. + +.SH LICENSE +Unlike Parsee, +.B parsee-adminify +is under the CC0/PD. + +.SH SEE ALSO +.B parsee-config(1), parsee-aya(1), parsee(1) diff --git a/etc/man/man1/parsee-aya.1 b/etc/man/man1/parsee-aya.1 new file mode 100644 index 0000000..b51afa0 --- /dev/null +++ b/etc/man/man1/parsee-aya.1 @@ -0,0 +1,40 @@ +." The last field is the codename, by the way. +." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN +.TH parsee-aya 1 "Parsee Utility" "star-of-hope" + +.SH NAME +parsee-aya - generate some nice Ayaya! documentation + +.SH SYNOPSIS +parsee-aya +.B [-i HEADER] +.B [-o HTML] +.B [-C STYLESHEET] +.B [-p NAME] + +.SH DESCRIPTION +.I parsee-aya +is a tool to generate Ayadocs(HTML documentation) from a formatted +header file. See a Parsee header file for an example of the inner usage. + +.SH OPTIONS +.TP +.BR -i HEADER +The input header file to process out. +.TP +.BR -o HTML +The HTML file to write out the Ayadoc. +.TP +.BR -C STYLESHEET +A stylesheet file to use for Ayadocs. +.TP +.BR -p NAME +The project's name. If unavailable, defaults to Ayaya! + +.SH LICENSE +Unlike Parsee, +.B parsee-aya +is under the CC0/PD. + +.SH SEE ALSO +.B parsee-config(1), parsee-adminify(1), parsee(1) diff --git a/etc/man/man1/parsee-config.1 b/etc/man/man1/parsee-config.1 new file mode 100644 index 0000000..5dc258b --- /dev/null +++ b/etc/man/man1/parsee-config.1 @@ -0,0 +1,103 @@ +." 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" + +.SH NAME +parsee-config - generate a basic configuration file + +.SH SYNOPSIS +parsee-config +.B [-H HOMESERVER_NAME] +.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] +.B [-S DATABASE size] + +.SH DESCRIPTION +.I parsee-config +creates a basic configuration file to initialise a Parsee instance with the +given input flags, and makes a basic database. A basic example of +.I parsee-config +would be this(for a blow.hole server, with a xmpp.blow.hole JCP on Prosody, +and a 128MB LMDB backend) +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ parsee-config \\ + -d '/var/lib/parsee' \\ + -m 'https://pmedia.blow.hole' \\ + -H 'blow.hole' \\ + -s 'The Dark Shared Secret' \\ + -J 'xmpp.blow.hole' \\ + -j 'localhost' \\ + -S 128 +.fi +.if n \{\ +.RE +.\} +.sp + +.SH OPTIONS +.TP +.BR -H HOMESERVER_NAME +.I HOMESERVER_NAME +points to the homeserver name, without delegation (which is autosensed). +For example, if you except Parsee users to be on +.IR @something:blow.hole +, then it should be set to +.IR blow.hole . +.TP +.BR -s SHARED_SECRET +.I SHARED_SECRET +is a shared secret known by Parsee and the XMPP component to authenticate. +.TP +.BR -M MAX_STANZA +.I MAX_STANZA +is the maximum stanza size accepted by the XMPP host. If it is less than 10000 bytes, then it shall be set to that limit(the standardised value). +.TP +.BR -m MEDIA_URL +.I MEDIA_URL +is an optional field used by Parsee as an address that points to Matrix +media. It must be publicly accessible (behind a reverse proxy to HTTP:7642) +.TP +.BR -J JABBER_HOST +.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 +.I JABBER_HOST +to connect to +.I JABBER_HOST:JABBER_PORT +over TCP. +.TP +.BR -d DATABASE +The directory inwhich Parsee stores its database(LMDB/flat-file). Whenever the +database is flat or LMDB is dictated by the presence of the -S flag, and +whenever Cytoplasm has LMDB enabled. +.TP +.BR -S SIZE +If set, enables LMDB in Parsee, and sets its max DB size to +.BR SIZE MiB . + + +.SH LICENSE +Unlike Parsee, +.B parsee-config +is under the CC0/PD. + +.SH SEE ALSO +.B parsee-aya(1), parsee-adminify(1), parsee(1) diff --git a/etc/man/man1/parsee.1 b/etc/man/man1/parsee.1 new file mode 100644 index 0000000..d7eeedf --- /dev/null +++ b/etc/man/man1/parsee.1 @@ -0,0 +1,118 @@ +." The last field is the codename, by the way. +." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN +.TH parsee 1 "Parsee Utility" "star-of-hope" + +.SH NAME +parsee - the jealous XMPP-Matrix bridge + +.SH SYNOPSIS +parsee +.B [-J num] +.B [-H num] +.B [-C file] +.B [-g] +.B [-v] +.B [-h] + +.SH DESCRIPTION +Parsee is a +.B bridging program +, that is, it connects two chat protocols together (here, XMPP/Jabber, +and Matrix). +.PP +As such, a user on XMPP can communicate with Matrix users, and +vice-versa with it. +.PP +Currently, Parsee is under +.BI alpha . +This means that it is still subject to bugs, flaws, and may change in a +backwards-incompatible manner at any time. + +.SH FIRST RUN +To start with a new install of Parsee(assuming you have a homeserver +with AS support and a XMPP server with JCP support, and that you know +the shared secret/domain for that component. Guides for that are +outside the scope of this manpage.), start by running +.B parsee-config(1) +with the flags provided in it, according to your configuration. +.PP +This should generate a +.I parsee.json +file, which is the configuration files for Parsee, and a directory where +the Parsee database may reside. You can then run +.I parsee -g +in that directory, which should generate a +.I parsee.yaml +file, this time with the generated AS configuration file, which you can +apply to your homeserver(how is implementation-dependent, but it is +generally a matter of modifying a configuration file to add the path +to such file). + +.SH OPTIONS +.TP +.BR -J num +Set the number of threads used for XMPP stanza processing to +.I num\[char46] +.TP +.BR -H num +Set the number of threads used for HTTP request processing to +.I num\[char46] +.TP +.BR -C file +Sets the configuration JSON path to +.I file\[char46] +.PP +Defaults to +.I parsee.json +if none can be found. +.TP +.BR -g +Generates a +.I parsee.yaml +file to be used as an Application-Service entry within Matrix, and +exits. +.TP +.BR -v +Verbose logging of Parsee's behaviour. Recommended when testing Parsee +for bugs/hints. +.TP +.BR -h +Prints the command list with descriptions. + +.SH BUGS +.PP +Sometimes Parsee will not respond to ^C requests properly, which +causes a system administrator to have to invoke SIGKILL. +.PP +Parsee seems to grow slowly in memory usage when messages are bridged +which, in the long run, could cause a memory error. All of the memory is +tracked, however, which means that this isn't exactly a leak. +.PP +Some important features still aren't implemented yet(e.g being able to join +a MUC from XMPP) + +.SH CHATROOMS +You may talk about Parsee on these rooms(on Matrix and XMPP): +.RE +.IP xmpp:parsee@conference.monocles.eu?join +for XMPP, which is bridged along Matrix +.IP #parsee:tedomum.net +for Matrix, which is bridged along Parsee +.RS + +.SH AUTHORS +." Contributors, feel free to put your names here in a PR, as long as +." it is acceptable +. PP +.BR LDA: +main maintainer of Parsee, accessible over XMPP at lda@freetards.xyz +and over Matrix as @fourier:ari.lt. + +.SH LICENSE +Parsee is available under the AGPL3, but has some code under CC0/PD, and +some from Cytoplasm itself. Please see +.I https://git.kappach.at/lda/Parsee +for more information. + +.SH SEE ALSO +.B parsee-config(1), parsee-adminify(1), parsee-aya(1) diff --git a/etc/man/man7/.parsee-bridge-guidebook.7.swp b/etc/man/man7/.parsee-bridge-guidebook.7.swp new file mode 100644 index 0000000..50976ea Binary files /dev/null and b/etc/man/man7/.parsee-bridge-guidebook.7.swp differ diff --git a/etc/man/man7/.parsee-cmd-syntax.7.swp b/etc/man/man7/.parsee-cmd-syntax.7.swp new file mode 100644 index 0000000..64990b7 Binary files /dev/null and b/etc/man/man7/.parsee-cmd-syntax.7.swp differ diff --git a/etc/man/man7/parsee-bridge-guidebook.7 b/etc/man/man7/parsee-bridge-guidebook.7 new file mode 100644 index 0000000..b5932f2 --- /dev/null +++ b/etc/man/man7/parsee-bridge-guidebook.7 @@ -0,0 +1,70 @@ +." 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/etc/man/man7/parsee-cmd-syntax.7 b/etc/man/man7/parsee-cmd-syntax.7 new file mode 100644 index 0000000..ba11e67 --- /dev/null +++ b/etc/man/man7/parsee-cmd-syntax.7 @@ -0,0 +1,50 @@ +." The last field is the codename, by the way. +." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN +.TH parsee-cmd-syntax 7 "Parsee Utility" "star-of-hope" + +.SH NAME +parsee-cmd-syntax - Basic syntax information with Parsee Matrix commands + +.SH DESCRIPTION +Parsee uses a specific syntax for commands, which is generally different +from regular bots, but closer to +.B dd(1) 's +syntax. +.PP +A command is formatted as so. +.sp +.if n \{\ +.RS 4 +.\} +.nf +.B ![NAME] arg1=val1 arg2='val2' arg3="val\(rs\(dq3" +.fi +.if n \{\ +.RE +.\} +.sp + +.PP +The +.B arg1=val1 +syntax is to be used for simple values, with values containing no spaces. + +.PP +The +.B NAME +attribute defines the command to be called, and the +.B arg1='val1' , +and +.B arg2="val2" +syntax is to be used for simple values, with values containing spaces. If +the value needs to contain quotes, they may be escaped with +.B \(rs' +and +.B \(rs" +respectively. + +.SH LICENSE +This document is under public domain, or CC0 if not allowed by local law. + +.SH SEE ALSO +.B parsee(1) diff --git a/etc/media/README b/etc/media/README new file mode 100644 index 0000000..2ef7300 --- /dev/null +++ b/etc/media/README @@ -0,0 +1,5 @@ +This directory is for any PNG media that needs to be integrated into Parsee. +Each file here is Base64-encoded then defined as a const char[] symbol with +the name being a function of the PNG filename: N(FILE.png)=media_FILE + +NOTE: Medias should be about ~1024B MAX. Avoid to stride for anything larger. diff --git a/etc/media/parsee_logo.png b/etc/media/parsee_logo.png new file mode 100644 index 0000000..1e0a8a1 Binary files /dev/null and b/etc/media/parsee_logo.png differ diff --git a/etc/media/unknown.png b/etc/media/unknown.png new file mode 100644 index 0000000..00ccf91 Binary files /dev/null and b/etc/media/unknown.png differ diff --git a/guix.scm b/guix.scm new file mode 120000 index 0000000..021acc5 --- /dev/null +++ b/guix.scm @@ -0,0 +1 @@ +.guix/modules/parsee.scm \ No newline at end of file diff --git a/src/AS.c b/src/AS.c index 81ce4a9..f9d1700 100644 --- a/src/AS.c +++ b/src/AS.c @@ -55,976 +55,5 @@ ASAuthenticateRequest(const ParseeConfig *data, HttpClientContext *ctx) HttpRequestHeader(ctx, "Authorization", bearer); Free(bearer); } -bool -ASRegisterUser(const ParseeConfig *conf, char *user) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - HttpStatus status; - if (!conf || !user) - { - return false; - } - /* Create user. We don't actually care about the value as we can - * masquerade, as long as it exists. */ - ctx = ParseeCreateRequest( - conf, - HTTP_POST, "/_matrix/client/v3/register" - ); - json = HashMapCreate(); - HashMapSet(json,"type",JsonValueString("m.login.application_service")); - - user = ParseeGetLocal(user); - HashMapSet(json,"username",JsonValueString(user)); - - ASAuthenticateRequest(conf, ctx); - status = ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); - - Free(user); - - return status == HTTP_OK; -} - -void -ASPing(const ParseeConfig *conf) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path; - if (!conf) - { - return; - } - - path = StrConcat(3, - "/_matrix/client/v1/appservice/", - "Parsee%20XMPP", - "/ping" - ); - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - HttpClientContextFree(ctx); - JsonFree(json); -} -void -ASInvite(const ParseeConfig *conf, char *id, char *invited) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *bridge; - if (!conf || !id || !invited) - { - return; - } - - bridge = StrConcat(4, - "@", conf->sender_localpart, - ":", conf->server_base - ); - path = StrConcat(5, - "/_matrix/client/v3/rooms/", id, "/invite", - "?user_id=", bridge - ); - Free(bridge); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - HashMapSet(json, "user_id", JsonValueString(invited)); - HashMapSet(json, "reason", JsonValueString("Pass over.")); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); -} -void -ASBan(const ParseeConfig *conf, char *id, char *banned) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *bridge; - if (!conf || !id || !banned) - { - return; - } - - bridge = StrConcat(4, - "@", conf->sender_localpart, - ":", conf->server_base - ); - path = StrConcat(5, - "/_matrix/client/v3/rooms/", id, "/ban", - "?user_id=", bridge - ); - Free(bridge); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - HashMapSet(json, "user_id", JsonValueString(banned)); - HashMapSet(json, "reason", JsonValueString("Parsee felt jealous.")); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); -} -void -ASKick(const ParseeConfig *conf, char *id, char *banned) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *bridge; - if (!conf || !id || !banned) - { - return; - } - - bridge = StrConcat(4, - "@", conf->sender_localpart, - ":", conf->server_base - ); - path = StrConcat(5, - "/_matrix/client/v3/rooms/", id, "/kick", - "?user_id=", bridge - ); - Free(bridge); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - HashMapSet(json, "user_id", JsonValueString(banned)); - HashMapSet(json, "reason", JsonValueString("Parsee felt jealous.")); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); -} -char * -ASJoin(const ParseeConfig *conf, char *id, char *masquerade) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *ret; - if (!conf || !id) - { - return NULL; - } - - if (!masquerade) - { - char *raw = StrConcat(4, - "@", conf->sender_localpart, - ":", conf->server_base - ); - masquerade = HttpUrlEncode(raw); - Free(raw); - } - else - { - masquerade = HttpUrlEncode(masquerade); - } - id = HttpUrlEncode(id); - path = StrConcat(5, - "/_matrix/client/v3/join/", id, "?", - "user_id=", masquerade - ); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - JsonFree(json); - - json = JsonDecode(HttpClientStream(ctx)); - ret = StrDuplicate(GrabString(json, 1, "room_id")); - JsonFree(json); - - HttpClientContextFree(ctx); - Free(masquerade); - Free(id); - - return ret; -} -void -ASLeave(const ParseeConfig *conf, char *id, char *masquerade) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path; - if (!conf || !id) - { - return; - } - - if (!masquerade) - { - char *raw = StrConcat(4, - "@", conf->sender_localpart, - ":", conf->server_base - ); - masquerade = HttpUrlEncode(raw); - Free(raw); - } - else - { - masquerade = HttpUrlEncode(masquerade); - } - id = HttpUrlEncode(id); - path = StrConcat(5, - "/_matrix/client/v3/rooms/", id, "/leave?", - "user_id=", masquerade - ); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - JsonFree(json); - - HttpClientContextFree(ctx); - Free(masquerade); - Free(id); -} -void -ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state) -{ - HttpClientContext *ctx = NULL; - char *path; - if (!conf || !id || !type || !mask || !state) - { - JsonFree(state); - return; - } - - path = StrConcat(9, - "/_matrix/client/v3/rooms/", id, "/state/", - type, "/", key, "?", "user_id=", mask - ); - - ctx = ParseeCreateRequest(conf, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, state); - - HttpClientContextFree(ctx); - JsonFree(state); -} -char * -ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c) -{ - HttpClientContext *ctx = NULL; - char *path; - char *txn, *ret; - HashMap *reply; - if (!conf || !id || !type || !user || !c) - { - JsonFree(c); - return NULL; - } - - txn = StrRandom(16); - path = StrConcat(9, - "/_matrix/client/v3/rooms/", - id, "/send/", type, "/", txn, "?", - "user_id=", user - ); - Free(txn); - - ctx = ParseeCreateRequest(conf, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, c); - - reply = JsonDecode(HttpClientStream(ctx)); - ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); - JsonFree(reply); - - HttpClientContextFree(ctx); - JsonFree(c); - - return ret; -} -char * -ASCreateRoom(const ParseeConfig *conf, char *by, char *alias) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *id; - if (!conf || !by) - { - return NULL; - } - - path = StrConcat(3, - "/_matrix/client/v3/createRoom", - "?user_id=", by - ); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - if (alias) - { - char *trimmed = StrDuplicate(alias); - if (*alias == '#') - { - char *tmp, cb[2] = { 0 }; - alias++; - Free(trimmed); - trimmed = NULL; - - while (*alias && *alias != ':') - { - cb[0] = *alias; - tmp = trimmed; - trimmed = StrConcat(2, trimmed, cb); - Free(tmp); - alias ++; - } - } - HashMapSet(json, "room_alias_name", JsonValueString(trimmed)); - Free(trimmed); - } - HashMapSet(json, "visibility", JsonValueString("public")); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - JsonFree(json); - json = JsonDecode(HttpClientStream(ctx)); - id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id"))); - HttpClientContextFree(ctx); - JsonFree(json); - - return id; -} -char * -ASCreateDM(const ParseeConfig *conf, char *by, char *with) -{ - HttpClientContext *ctx = NULL; - HashMap *json = NULL; - char *path, *id; - if (!conf || !by || !with) - { - return NULL; - } - - path = StrConcat(3, - "/_matrix/client/v3/createRoom", - "?user_id=", by - ); - - ctx = ParseeCreateRequest( - conf, - HTTP_POST, path - ); - Free(path); - json = HashMapCreate(); - { - Array *invitees = ArrayCreate(); - - ArrayAdd(invitees, JsonValueString(with)); - HashMapSet(json, "invite", JsonValueArray(invitees)); - HashMapSet(json, "is_direct", JsonValueBoolean(true)); - } - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - JsonFree(json); - json = JsonDecode(HttpClientStream(ctx)); - id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id"))); - HttpClientContextFree(ctx); - JsonFree(json); - - return id; -} -void -ASSetAvatar(const ParseeConfig *conf, char *user, char *mxc) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path; - if (!conf || !user || !mxc) - { - return; - } - - user = HttpUrlEncode(user); - path = StrConcat(6, - "/_matrix/client/v3/profile/", - user, "/avatar_url", "?", - "user_id=", user - ); - - json = HashMapCreate(); - HashMapSet(json, "avatar_url", JsonValueString(mxc)); - ctx = ParseeCreateRequest(conf, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); - Free(user); -} -void -ASSetName(const ParseeConfig *conf, char *user, char *name) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path; - if (!conf || !user || !name) - { - return; - } - - user = HttpUrlEncode(user); - path = StrConcat(6, - "/_matrix/client/v3/profile/", - user, "/displayname", "?", - "user_id=", user - ); - - json = HashMapCreate(); - HashMapSet(json, "displayname", JsonValueString(name)); - ctx = ParseeCreateRequest(conf, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(conf, ctx); - ParseeSetRequestJSON(ctx, json); - - HttpClientContextFree(ctx); - JsonFree(json); - Free(user); -} -HashMap * -ASFind(const ParseeConfig *c, char *room, char *event) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path, *user; - if (!c || !room || !event) - { - return NULL; - } - - user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base); - path = StrConcat(7, - "/_matrix/client/v3/rooms/", - room, "/event/", event, "?", - "user_id=", user - ); - - ctx = ParseeCreateRequest(c, HTTP_GET, path); - Free(path); - ASAuthenticateRequest(c, ctx); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - json = JsonDecode(HttpClientStream(ctx)); - - HttpClientContextFree(ctx); - Free(user); - - return json; -} -char * -ASGetName(const ParseeConfig *c, char *room, char *user) -{ - HttpClientContext *ctx; - HashMap *reply; - char *path, *ret; - char *u2 = user; - if (!c || !user) - { - return NULL; - } - - if (!room) - { - user = HttpUrlEncode(user); - path = StrConcat(3, - "/_matrix/client/v3/profile/", user, "/displayname" - ); - ctx = ParseeCreateRequest(c, HTTP_GET, path); - Free(user); - ASAuthenticateRequest(c, ctx); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - reply = JsonDecode(HttpClientStream(ctx)); - - ret = StrDuplicate( - JsonValueAsString(HashMapGet(reply, "displayname")) - ); - HttpClientContextFree(ctx); - JsonFree(reply); - Free(path); - - if (!ret) - { - ret = StrDuplicate(u2); - } - return ret; - } - user = HttpUrlEncode(user); - room = HttpUrlEncode(room); - path = StrConcat(4, - "/_matrix/client/v3/rooms/", room, - "/state/m.room.member/", user - ); - ctx = ParseeCreateRequest(c, HTTP_GET, path); - Free(user); - Free(room); - ASAuthenticateRequest(c, ctx); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - reply = JsonDecode(HttpClientStream(ctx)); - - ret = StrDuplicate( - JsonValueAsString(HashMapGet(reply, "displayname")) - ); - HttpClientContextFree(ctx); - JsonFree(reply); - Free(path); - - if (!ret) - { - ret = StrDuplicate(u2); - } - return ret; -} -HashMap * -ASGetPL(const ParseeConfig *c, char *room) -{ - char *path; - HttpClientContext *ctx; - HashMap *reply; - if (!c || !room) - { - return NULL; - } - room = HttpUrlEncode(room); - path = StrConcat(4, - "/_matrix/client/v3/rooms/", room, - "/state/m.room.power_levels/", "" - ); - ctx = ParseeCreateRequest(c, HTTP_GET, path); - Free(room); - ASAuthenticateRequest(c, ctx); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - reply = JsonDecode(HttpClientStream(ctx)); - - HttpClientContextFree(ctx); - Free(path); - - return reply; -} -void -ASSetPL(const ParseeConfig *conf, char *id, HashMap *m) -{ - char *user; - if (!conf || !id || !m) - { - return; - } - user = StrConcat(4, - "@",conf->sender_localpart, - ":",conf->server_base - ); - ASSetState(conf, id, "m.room.power_levels", "", user, m); - Free(user); -} - -char * -ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime) -{ - char *size_str, *path, *ret, *user; - int i; - HttpClientContext *ctx; - HashMap *reply; - if (!c || !from) - { - return NULL; - } - - size_str = StrInt(size); - user = StrConcat(4, "@",c->sender_localpart,":",c->server_base); - path = StrConcat(2, - "/_matrix/media/v3/upload?user_id=", user - ); - ctx = ParseeCreateRequest(c, HTTP_POST, path); - ASAuthenticateRequest(c, ctx); - if (size) - { - HttpRequestHeader(ctx, "Content-Length", size_str); - } - if (mime) - { - HttpRequestHeader(ctx, "Content-Type", mime); - } - HttpRequestSendHeaders(ctx); - - for (i = 0; i < size; i++) - { - int ch = StreamGetc(from); - if (ch == EOF) - { - break; - } - StreamPutc(HttpClientStream(ctx), ch); - } - HttpRequestSend(ctx); - reply = JsonDecode(HttpClientStream(ctx)); - ret = StrDuplicate( - JsonValueAsString(HashMapGet(reply, "content_uri")) - ); - if (!ret) - { - JsonEncode(reply, StreamStdout(), JSON_PRETTY); - StreamFlush(StreamStdout()); - } - HttpClientContextFree(ctx); - JsonFree(reply); - Free(size_str); - Free(path); - Free(user); - - return ret; -} -char * -ASReupload(const ParseeConfig *c, char *from, char **mime) -{ - Uri *uri; - HttpClientContext *ctx; - unsigned short port; - int size = 0, flags = HTTP_FLAG_NONE; - char *ret, *content_len; - - if (!c || !from) - { - return NULL; - } - - uri = UriParse(from); - if (!uri) - { - return NULL; - } - if (uri->port) - { - port = uri->port; - } - else if (StrEquals(uri->proto, "https")) - { - port = 443; - } - else - { - port = 80; - } - - if (StrEquals(uri->proto, "https")) - { - flags |= HTTP_FLAG_TLS; - } - - ctx = HttpRequest( - HTTP_GET, flags, port, uri->host, uri->path - ); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - if (mime) - { - *mime = HashMapGet(HttpResponseHeaders(ctx), "content-type"); - *mime = StrDuplicate(*mime); - } - - content_len = HashMapGet(HttpResponseHeaders(ctx), "content-length"); - if (content_len) - { - size = strtol(content_len, NULL, 10); - } - ret = ASUpload(c, HttpClientStream(ctx), size, mime ? *mime : NULL); - - HttpClientContextFree(ctx); - UriFree(uri); - return ret; -} -void -ASType(const ParseeConfig *c, char *user, char *room, bool status) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path; - if (!c || !user || !room) - { - return; - } - - user = HttpUrlEncode(user); - path = StrConcat(6, - "/_matrix/client/v3/rooms/", - room, "/typing/", user, - "?user_id=", user - ); - - json = HashMapCreate(); - HashMapSet(json, "typing", JsonValueBoolean(status)); - /* If someone types for 10 minutes straight, they got something weird man. */ - HashMapSet(json, "timeout", JsonValueBoolean(10 MINUTES)); - ctx = ParseeCreateRequest(c, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(c, ctx); - ParseeSetRequestJSON(ctx, json); - JsonFree(json); - - HttpClientContextFree(ctx); - Free(user); -} - -void -ASPresence(const ParseeConfig *c, char *user, char *room, char *ev) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path; - if (!c || !user || !room || !ev) - { - return; - } - - user = HttpUrlEncode(user); - room = HttpUrlEncode(room); - ev = HttpUrlEncode(ev); - path = StrConcat(6, - "/_matrix/client/v3/rooms/", - room, "/receipt/m.read/", ev, - "?user_id=", user - ); - - json = HashMapCreate(); - ctx = ParseeCreateRequest(c, HTTP_POST, path); - Free(path); - ASAuthenticateRequest(c, ctx); - ParseeSetRequestJSON(ctx, json); - JsonFree(json); - - HttpClientContextFree(ctx); - Free(user); - Free(room); - Free(ev); -} - -HashMap * -ASGetUserConfig(const ParseeConfig *c, char *user, char *key) -{ - HttpClientContext *ctx = NULL; - HashMap *json; - char *path; - if (!c || !key) - { - return NULL; - } - - if (!user) - { - char *raw = StrConcat(4, - "@", c->sender_localpart, - ":", c->server_base - ); - user = HttpUrlEncode(raw); - Free(raw); - } - else - { - user = HttpUrlEncode(user); - } - path = StrConcat(7, - "/_matrix/client/v3/user/", - user, "/account_data/", key, "?", - "user_id=", user - ); - - ctx = ParseeCreateRequest(c, HTTP_GET, path); - Free(path); - ASAuthenticateRequest(c, ctx); - HttpRequestSendHeaders(ctx); - HttpRequestSend(ctx); - - json = JsonDecode(HttpClientStream(ctx)); - - HttpClientContextFree(ctx); - Free(user); - - return json; -} -void -ASSetUserConfig(const ParseeConfig *c, char *user, char *key, HashMap *map) -{ - HttpClientContext *ctx = NULL; - char *path; - if (!c || !key || !map) - { - JsonFree(map); - return; - } - - if (!user) - { - char *raw = StrConcat(4, - "@", c->sender_localpart, - ":", c->server_base - ); - user = HttpUrlEncode(raw); - Free(raw); - } - else - { - user = HttpUrlEncode(user); - } - - path = StrConcat(7, - "/_matrix/client/v3/user/", - user, "/account_data/", key, "?", - "user_id=", user - ); - - ctx = ParseeCreateRequest(c, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(c, ctx); - ParseeSetRequestJSON(ctx, map); - - HttpClientContextFree(ctx); - Free(user); - JsonFree(map); - - return; -} -void -ASRedact(const ParseeConfig *c, char *room, char *user, char *e_id) -{ - HttpClientContext *ctx = NULL; - HashMap *request; - char *path, *txn; - if (!c || !room || !e_id) - { - return; - } - - if (!user) - { - char *raw = StrConcat(4, - "@", c->sender_localpart, - ":", c->server_base - ); - user = HttpUrlEncode(raw); - Free(raw); - } - else - { - user = HttpUrlEncode(user); - } - room = HttpUrlEncode(room); - e_id = HttpUrlEncode(e_id); - txn = StrRandom(16); - - path = StrConcat(9, - "/_matrix/client/v3/rooms/", - room, "/redact/", e_id, "/", txn, - "?", "user_id=", user - ); - - request = HashMapCreate(); - ctx = ParseeCreateRequest(c, HTTP_PUT, path); - Free(path); - ASAuthenticateRequest(c, ctx); - ParseeSetRequestJSON(ctx, request); - JsonFree(request); - - HttpClientContextFree(ctx); - Free(user); - Free(room); - Free(e_id); - Free(txn); - - return; -} -void -ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, char *msg) -{ - HttpClientContext *ctx = NULL; - HashMap *request; - char *path; - char *status_str = NULL; - if (!c || !user) - { - return; - } - - switch (status) - { - case USER_STATUS_ONLINE: status_str = "online"; break; - case USER_STATUS_OFFLINE: status_str = "offline"; break; - case USER_STATUS_UNAVAILABLE: status_str = "unavailable"; break; - default: return; - } - - user = HttpUrlEncode(user); - path = StrConcat(5, - "/_matrix/client/v3/presence/",user,"/status", - "?user_id=", user - ); - Free(user); - - request = HashMapCreate(); - HashMapSet(request, "presence", JsonValueString(status_str)); - if (msg) - { - HashMapSet(request, "status_msg", JsonValueString(msg)); - } - - ctx = ParseeCreateRequest(c, HTTP_PUT, path); - ASAuthenticateRequest(c, ctx); - ParseeSetRequestJSON(ctx, request); - JsonFree(request); - - HttpClientContextFree(ctx); - Free(path); -} diff --git a/src/AS/Events.c b/src/AS/Events.c new file mode 100644 index 0000000..83ce147 --- /dev/null +++ b/src/AS/Events.c @@ -0,0 +1,93 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +HashMap * +ASFind(const ParseeConfig *c, char *room, char *event) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path, *user; + if (!c || !room || !event) + { + return NULL; + } + + user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base); + path = StrConcat(7, + "/_matrix/client/v3/rooms/", + room, "/event/", event, "?", + "user_id=", user + ); + + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(path); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + json = JsonDecode(HttpClientStream(ctx)); + + HttpClientContextFree(ctx); + Free(user); + + return json; +} + +void +ASRedact(const ParseeConfig *c, char *room, char *user, char *e_id) +{ + HttpClientContext *ctx = NULL; + HashMap *request; + char *path, *txn; + if (!c || !room || !e_id) + { + return; + } + + if (!user) + { + char *raw = StrConcat(4, + "@", c->sender_localpart, + ":", c->server_base + ); + user = HttpUrlEncode(raw); + Free(raw); + } + else + { + user = HttpUrlEncode(user); + } + room = HttpUrlEncode(room); + e_id = HttpUrlEncode(e_id); + txn = StrRandom(16); + + path = StrConcat(9, + "/_matrix/client/v3/rooms/", + room, "/redact/", e_id, "/", txn, + "?", "user_id=", user + ); + + request = HashMapCreate(); + ctx = ParseeCreateRequest(c, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(c, ctx); + ParseeSetRequestJSON(ctx, request); + JsonFree(request); + + HttpClientContextFree(ctx); + Free(user); + Free(room); + Free(e_id); + Free(txn); + + return; +} diff --git a/src/AS/Indicators.c b/src/AS/Indicators.c new file mode 100644 index 0000000..9797649 --- /dev/null +++ b/src/AS/Indicators.c @@ -0,0 +1,120 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +void +ASType(const ParseeConfig *c, char *user, char *room, bool status) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path; + if (!c || !user || !room) + { + return; + } + + user = HttpUrlEncode(user); + path = StrConcat(6, + "/_matrix/client/v3/rooms/", + room, "/typing/", user, + "?user_id=", user + ); + + json = HashMapCreate(); + HashMapSet(json, "typing", JsonValueBoolean(status)); + /* If someone types for 5 minutes straight, they got something + * weird man. */ + HashMapSet(json, "timeout", JsonValueInteger(5 MINUTES)); + ctx = ParseeCreateRequest(c, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(c, ctx); + ParseeSetRequestJSON(ctx, json); + JsonFree(json); + + HttpClientContextFree(ctx); + Free(user); +} + +void +ASPresence(const ParseeConfig *c, char *user, char *room, char *ev) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path; + if (!c || !user || !room || !ev) + { + return; + } + + user = HttpUrlEncode(user); + room = HttpUrlEncode(room); + ev = HttpUrlEncode(ev); + path = StrConcat(6, + "/_matrix/client/v3/rooms/", + room, "/receipt/m.read/", ev, + "?user_id=", user + ); + + json = HashMapCreate(); + ctx = ParseeCreateRequest(c, HTTP_POST, path); + Free(path); + ASAuthenticateRequest(c, ctx); + ParseeSetRequestJSON(ctx, json); + JsonFree(json); + + HttpClientContextFree(ctx); + Free(user); + Free(room); + Free(ev); +} + +void +ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, char *msg) +{ + HttpClientContext *ctx = NULL; + HashMap *request; + char *path; + char *status_str = NULL; + if (!c || !user) + { + return; + } + + switch (status) + { + case USER_STATUS_ONLINE: status_str = "online"; break; + case USER_STATUS_OFFLINE: status_str = "offline"; break; + case USER_STATUS_UNAVAILABLE: status_str = "unavailable"; break; + default: return; + } + + user = HttpUrlEncode(user); + path = StrConcat(5, + "/_matrix/client/v3/presence/",user,"/status", + "?user_id=", user + ); + Free(user); + + request = HashMapCreate(); + HashMapSet(request, "presence", JsonValueString(status_str)); + if (msg) + { + HashMapSet(request, "status_msg", JsonValueString(msg)); + } + + ctx = ParseeCreateRequest(c, HTTP_PUT, path); + ASAuthenticateRequest(c, ctx); + ParseeSetRequestJSON(ctx, request); + JsonFree(request); + + HttpClientContextFree(ctx); + Free(path); +} diff --git a/src/AS/Media.c b/src/AS/Media.c new file mode 100644 index 0000000..5fbc659 --- /dev/null +++ b/src/AS/Media.c @@ -0,0 +1,220 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +char * +ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime) +{ + char *size_str, *path, *ret, *user; + unsigned int i; + HttpClientContext *ctx; + HashMap *reply; + if (!c || !from) + { + return NULL; + } + + size_str = StrInt(size); + user = StrConcat(4, "@",c->sender_localpart,":",c->server_base); + path = StrConcat(2, + "/_matrix/media/v3/upload?user_id=", user + ); + ctx = ParseeCreateRequest(c, HTTP_POST, path); + ASAuthenticateRequest(c, ctx); + if (size) + { + HttpRequestHeader(ctx, "Content-Length", size_str); + } + if (mime) + { + HttpRequestHeader(ctx, "Content-Type", mime); + } + HttpRequestSendHeaders(ctx); + + for (i = 0; i < size; i++) + { + int ch = StreamGetc(from); + if (ch == EOF) + { + break; + } + StreamPutc(HttpClientStream(ctx), ch); + } + HttpRequestSend(ctx); + reply = JsonDecode(HttpClientStream(ctx)); + ret = StrDuplicate( + JsonValueAsString(HashMapGet(reply, "content_uri")) + ); + if (!ret) + { + JsonEncode(reply, StreamStdout(), JSON_PRETTY); + StreamFlush(StreamStdout()); + } + HttpClientContextFree(ctx); + JsonFree(reply); + Free(size_str); + Free(path); + Free(user); + + return ret; +} +char * +ASReupload(const ParseeConfig *c, char *from, char **mime) +{ + Uri *uri; + HttpClientContext *ctx; + unsigned short port; + int size = 0, flags = HTTP_FLAG_NONE; + char *ret, *content_len; + + if (!c || !from) + { + return NULL; + } + + uri = UriParse(from); + if (!uri) + { + return NULL; + } + if (uri->port) + { + port = uri->port; + } + else if (StrEquals(uri->proto, "https")) + { + port = 443; + } + else + { + port = 80; + } + + if (StrEquals(uri->proto, "https")) + { + flags |= HTTP_FLAG_TLS; + } + + ctx = HttpRequest( + HTTP_GET, flags, port, uri->host, uri->path + ); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + if (mime) + { + *mime = HashMapGet(HttpResponseHeaders(ctx), "content-type"); + *mime = StrDuplicate(*mime); + } + + content_len = HashMapGet(HttpResponseHeaders(ctx), "content-length"); + if (content_len) + { + size = strtol(content_len, NULL, 10); + } + ret = ASUpload(c, HttpClientStream(ctx), size, mime ? *mime : NULL); + + HttpClientContextFree(ctx); + UriFree(uri); + return ret; +} + +bool +ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha) +{ + HttpClientContext *cctx; + Stream *stream; + Stream *fake; + Uri *uri; + char *path, *buf = NULL; + unsigned char *sha1; + size_t len; + if (!c || !mxc || !mime || !sha) + { + return false; + } + *mime = NULL; + *sha = NULL; + + if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc")) + { + return false; + } + + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); + cctx = ParseeCreateRequest(c, HTTP_GET, path); + ASAuthenticateRequest(c, cctx); + HttpRequestSendHeaders(cctx); + HttpRequestSend(cctx); + + *mime = StrDuplicate( + HashMapGet(HttpResponseHeaders(cctx), "content-type") + ); + stream = HttpClientStream(cctx); + fake = StreamFile(open_memstream(&buf, &len)); + StreamCopy(stream, fake); + StreamClose(fake); + + sha1 = Sha1Raw((unsigned char *) buf, len); + free(buf); + *sha = ShaToHex(sha1, HASH_SHA1); + Free(sha1); + + HttpClientContextFree(cctx); + UriFree(uri); + Free(path); + return true; +} +bool +ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len) +{ + HttpClientContext *cctx; + Stream *stream; + Stream *fake; + Uri *uri; + char *path, *buf = NULL; + if (!c || !mxc || !mime || !out || !len) + { + return false; + } + *mime = NULL; + *out = NULL; + *len = 0; + + if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc")) + { + return false; + } + + path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path); + cctx = ParseeCreateRequest(c, HTTP_GET, path); + ASAuthenticateRequest(c, cctx); + HttpRequestSendHeaders(cctx); + HttpRequestSend(cctx); + + *mime = StrDuplicate( + HashMapGet(HttpResponseHeaders(cctx), "content-type") + ); + stream = HttpClientStream(cctx); + fake = StreamFile(open_memstream(&buf, len)); + StreamCopy(stream, fake); + StreamClose(fake); + + *out = Malloc(*len); + memcpy(*out, buf, *len); + free(buf); + + HttpClientContextFree(cctx); + UriFree(uri); + Free(path); + return true; +} diff --git a/src/AS/Ping.c b/src/AS/Ping.c new file mode 100644 index 0000000..3a8ffb7 --- /dev/null +++ b/src/AS/Ping.c @@ -0,0 +1,42 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +bool +ASPing(const ParseeConfig *conf) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path; + bool ret; + if (!conf) + { + return false; + } + + path = StrConcat(3, + "/_matrix/client/v1/appservice/", + "Parsee%20XMPP", + "/ping" + ); + ctx = ParseeCreateRequest( + conf, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + ASAuthenticateRequest(conf, ctx); + ret = ParseeSetRequestJSON(ctx, json) == HTTP_OK; + HttpClientContextFree(ctx); + JsonFree(json); + + return ret; +} diff --git a/src/AS/Profile.c b/src/AS/Profile.c new file mode 100644 index 0000000..1f1a0da --- /dev/null +++ b/src/AS/Profile.c @@ -0,0 +1,206 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +void +ASSetAvatar(const ParseeConfig *conf, char *user, char *mxc) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path; + if (!conf || !user || !mxc) + { + return; + } + + user = HttpUrlEncode(user); + path = StrConcat(6, + "/_matrix/client/v3/profile/", + user, "/avatar_url", "?", + "user_id=", user + ); + + json = HashMapCreate(); + HashMapSet(json, "avatar_url", JsonValueString(mxc)); + ctx = ParseeCreateRequest(conf, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); + Free(user); +} +void +ASSetName(const ParseeConfig *conf, char *user, char *name) +{ + HttpClientContext *ctx = NULL; + HashMap *json; + char *path; + if (!conf || !user || !name) + { + return; + } + + user = HttpUrlEncode(user); + path = StrConcat(6, + "/_matrix/client/v3/profile/", + user, "/displayname", "?", + "user_id=", user + ); + + json = HashMapCreate(); + HashMapSet(json, "displayname", JsonValueString(name)); + ctx = ParseeCreateRequest(conf, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); + Free(user); +} + +char * +ASGetName(const ParseeConfig *c, char *room, char *user) +{ + HttpClientContext *ctx; + HashMap *reply; + char *path, *ret; + char *u2 = user; + if (!c || !user) + { + return NULL; + } + + if (!room) + { + user = HttpUrlEncode(user); + path = StrConcat(3, + "/_matrix/client/v3/profile/", user, "/displayname" + ); + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(user); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + reply = JsonDecode(HttpClientStream(ctx)); + + ret = StrDuplicate( + JsonValueAsString(HashMapGet(reply, "displayname")) + ); + HttpClientContextFree(ctx); + JsonFree(reply); + Free(path); + + if (!ret) + { + ret = StrDuplicate(u2); + } + return ret; + } + user = HttpUrlEncode(user); + room = HttpUrlEncode(room); + path = StrConcat(4, + "/_matrix/client/v3/rooms/", room, + "/state/m.room.member/", user + ); + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(user); + Free(room); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + reply = JsonDecode(HttpClientStream(ctx)); + + ret = StrDuplicate( + JsonValueAsString(HashMapGet(reply, "displayname")) + ); + HttpClientContextFree(ctx); + JsonFree(reply); + Free(path); + + if (!ret) + { + ret = StrDuplicate(u2); + } + return ret; +} +char * +ASGetAvatar(const ParseeConfig *c, char *room, char *user) +{ + HttpClientContext *ctx; + HashMap *reply; + char *path = NULL, *ret = NULL; + char *u2 = user; + if (!c || !user) + { + return NULL; + } + + if (room) + { + user = HttpUrlEncode(user); + room = HttpUrlEncode(room); + path = StrConcat(4, + "/_matrix/client/v3/rooms/", room, + "/state/m.room.member/", user + ); + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(user); + Free(room); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + reply = JsonDecode(HttpClientStream(ctx)); + + ret = StrDuplicate( + JsonValueAsString(HashMapGet(reply, "avatar_url")) + ); + HttpClientContextFree(ctx); + JsonFree(reply); + Free(path); + + user = u2; + + Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from room, got %s", ret); + } + + if (!ret) + { + user = HttpUrlEncode(user); + path = StrConcat(3, + "/_matrix/client/v3/profile/", user, "/avatar_url" + ); + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(user); + user = u2; + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + reply = JsonDecode(HttpClientStream(ctx)); + + ret = StrDuplicate( + JsonValueAsString(HashMapGet(reply, "avatar_url")) + ); + HttpClientContextFree(ctx); + JsonFree(reply); + Free(path); + + Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from profile, got %s", ret); + } + + return ret; +} diff --git a/src/AS/Register.c b/src/AS/Register.c new file mode 100644 index 0000000..cbb8881 --- /dev/null +++ b/src/AS/Register.c @@ -0,0 +1,46 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +bool +ASRegisterUser(const ParseeConfig *conf, char *user) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + HttpStatus status; + if (!conf || !user) + { + return false; + } + + /* Create user. We don't actually care about the value as we can + * masquerade, as long as it exists. */ + ctx = ParseeCreateRequest( + conf, + HTTP_POST, "/_matrix/client/v3/register" + ); + json = HashMapCreate(); + + HashMapSet(json,"type",JsonValueString("m.login.application_service")); + + user = ParseeGetLocal(user); + HashMapSet(json,"username",JsonValueString(user)); + + ASAuthenticateRequest(conf, ctx); + status = ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); + + Free(user); + + return status == HTTP_OK; +} diff --git a/src/AS/Relations.c b/src/AS/Relations.c new file mode 100644 index 0000000..e9baa5a --- /dev/null +++ b/src/AS/Relations.c @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +Array * +ASGetRelations(const ParseeConfig *c, size_t n, char *room, char *event, char *type) +{ + HttpClientContext *ctx = NULL; + Array *ret, *chunk; + HashMap *json = NULL; + char *path; + char *user; + size_t i; + if (!c || !n || !room || !event) + { + return NULL; + } + + user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base); + if (type) + { + path = StrConcat(8, + "/_matrix/client/v1/rooms/", room, + "/relations/", event, "/", type, + "?user_id=", user + ); + } + else + { + path = StrConcat(6, + "/_matrix/client/v1/rooms/", room, + "/relations/", event, + "?user_id=", user + ); + } + Free(user); + + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(path); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + + json = JsonDecode(HttpClientStream(ctx)); + ret = ArrayCreate(); + chunk = GrabArray(json, 1, "chunk"); + for (i = 0; i < ArraySize(chunk); i++) + { + HashMap *obj = JsonValueAsObject(ArrayGet(chunk, i)); + ArrayAdd(ret, JsonDuplicate(obj)); + } + + HttpClientContextFree(ctx); + JsonFree(json); + return ret; +} + +void +ASFreeRelations(Array *relations) +{ + size_t i; + if (!relations) + { + return; + } + + for (i = 0; i < ArraySize(relations); i++) + { + JsonFree(ArrayGet(relations, i)); + } + + ArrayFree(relations); +} diff --git a/src/AS/Room.c b/src/AS/Room.c new file mode 100644 index 0000000..e9691e1 --- /dev/null +++ b/src/AS/Room.c @@ -0,0 +1,366 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +void +ASInvite(const ParseeConfig *conf, char *id, char *invited) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *bridge; + if (!conf || !id || !invited) + { + return; + } + + bridge = StrConcat(4, + "@", 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, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + HashMapSet(json, "user_id", JsonValueString(invited)); + HashMapSet(json, "reason", JsonValueString("Pass over.")); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); +} +void +ASBan(const ParseeConfig *conf, char *id, char *banned) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *bridge; + if (!conf || !id || !banned) + { + return; + } + + bridge = StrConcat(4, + "@", 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, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + HashMapSet(json, "user_id", JsonValueString(banned)); + HashMapSet(json, "reason", JsonValueString(NAME " felt jealous.")); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); +} +void +ASKick(const ParseeConfig *conf, char *id, char *banned) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *bridge; + if (!conf || !id || !banned) + { + return; + } + + bridge = StrConcat(4, + "@", 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, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + HashMapSet(json, "user_id", JsonValueString(banned)); + HashMapSet(json, "reason", JsonValueString(NAME " felt jealous.")); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + HttpClientContextFree(ctx); + JsonFree(json); +} +char * +ASJoin(const ParseeConfig *conf, char *id, char *masquerade) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *ret, *serv; + int status; + if (!conf || !id) + { + return NULL; + } + + if (!masquerade) + { + char *raw = StrConcat(4, + "@", conf->sender_localpart, + ":", conf->server_base + ); + masquerade = HttpUrlEncode(raw); + Free(raw); + } + else + { + masquerade = HttpUrlEncode(masquerade); + } + serv = strchr(id, ':'); + if (serv) + { + serv = serv + 1; + } + id = HttpUrlEncode(id); + path = StrConcat(5, + "/_matrix/client/v3/join/", id, "?", + "user_id=", masquerade + ); + + ctx = ParseeCreateRequest( + conf, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + ASAuthenticateRequest(conf, ctx); + status = ParseeSetRequestJSON(ctx, json); + JsonFree(json); + + json = JsonDecode(HttpClientStream(ctx)); + ret = StrDuplicate(GrabString(json, 1, "room_id")); + JsonFree(json); + + HttpClientContextFree(ctx); + Free(masquerade); + Free(id); + + (void) serv; // TODO + + return ret; +} +void +ASLeave(const ParseeConfig *conf, char *id, char *masquerade) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path; + if (!conf || !id) + { + return; + } + + if (!masquerade) + { + char *raw = StrConcat(4, + "@", conf->sender_localpart, + ":", conf->server_base + ); + masquerade = HttpUrlEncode(raw); + Free(raw); + } + else + { + masquerade = HttpUrlEncode(masquerade); + } + id = HttpUrlEncode(id); + path = StrConcat(5, + "/_matrix/client/v3/rooms/", id, "/leave?", + "user_id=", masquerade + ); + + ctx = ParseeCreateRequest( + conf, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + JsonFree(json); + + HttpClientContextFree(ctx); + Free(masquerade); + Free(id); +} + +char * +ASCreateRoom(const ParseeConfig *conf, char *by, char *alias) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *id; + if (!conf || !by) + { + return NULL; + } + + path = StrConcat(3, + "/_matrix/client/v3/createRoom", + "?user_id=", by + ); + + ctx = ParseeCreateRequest( + conf, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + if (alias) + { + char *trimmed = StrDuplicate(alias); + if (*alias == '#') + { + char *tmp, cb[2] = { 0 }; + alias++; + Free(trimmed); + trimmed = NULL; + + while (*alias && *alias != ':') + { + cb[0] = *alias; + tmp = trimmed; + trimmed = StrConcat(2, trimmed, cb); + Free(tmp); + alias ++; + } + } + HashMapSet(json, "room_alias_name", JsonValueString(trimmed)); + Free(trimmed); + } + HashMapSet(json, "visibility", JsonValueString("public")); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + JsonFree(json); + json = JsonDecode(HttpClientStream(ctx)); + id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id"))); + HttpClientContextFree(ctx); + JsonFree(json); + + return id; +} +char * +ASCreateDM(const ParseeConfig *conf, char *by, char *with) +{ + HttpClientContext *ctx = NULL; + HashMap *json = NULL; + char *path, *id; + if (!conf || !by || !with) + { + return NULL; + } + + path = StrConcat(3, + "/_matrix/client/v3/createRoom", + "?user_id=", by + ); + + ctx = ParseeCreateRequest( + conf, + HTTP_POST, path + ); + Free(path); + json = HashMapCreate(); + { + Array *invitees = ArrayCreate(); + + ArrayAdd(invitees, JsonValueString(with)); + HashMapSet(json, "invite", JsonValueArray(invitees)); + HashMapSet(json, "is_direct", JsonValueBoolean(true)); + } + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, json); + + JsonFree(json); + json = JsonDecode(HttpClientStream(ctx)); + id = StrDuplicate(JsonValueAsString(HashMapGet(json, "room_id"))); + HttpClientContextFree(ctx); + JsonFree(json); + + return id; +} + +HashMap * +ASGetPL(const ParseeConfig *c, char *room) +{ + char *path; + HttpClientContext *ctx; + HashMap *reply; + if (!c || !room) + { + return NULL; + } + room = HttpUrlEncode(room); + path = StrConcat(4, + "/_matrix/client/v3/rooms/", room, + "/state/m.room.power_levels/", "" + ); + ctx = ParseeCreateRequest(c, HTTP_GET, path); + Free(room); + ASAuthenticateRequest(c, ctx); + HttpRequestSendHeaders(ctx); + HttpRequestSend(ctx); + + reply = JsonDecode(HttpClientStream(ctx)); + + HttpClientContextFree(ctx); + Free(path); + + return reply; +} +void +ASSetPL(const ParseeConfig *conf, char *id, HashMap *m) +{ + char *user; + if (!conf || !id || !m) + { + return; + } + user = StrConcat(4, + "@",conf->sender_localpart, + ":",conf->server_base + ); + ASSetState(conf, id, "m.room.power_levels", "", user, m); + Free(user); +} diff --git a/src/AS/Send.c b/src/AS/Send.c new file mode 100644 index 0000000..6811548 --- /dev/null +++ b/src/AS/Send.c @@ -0,0 +1,79 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +static char * +TSToStr(uint64_t ts) +{ + size_t len; + char *str; + + len = snprintf(NULL, 0, "%"PRIu64, ts); + str = Malloc(len+1); + snprintf(str, len+1, "%"PRIu64, ts); + + return str; +} + +char * +ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, uint64_t ts) +{ + HttpClientContext *ctx = NULL; + char *path; + char *txn, *ret; + char *ts_str; + HttpStatus status; + if (!ret) + { + Log(LOG_ERR, "%", ret); + } + HashMap *reply; + if (!conf || !id || !type || !user || !c) + { + JsonFree(c); + return NULL; + } + + if (!ts) + { + ts = UtilTsMillis(); + } + 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); + + reply = JsonDecode(HttpClientStream(ctx)); + ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); + JsonFree(reply); + + HttpClientContextFree(ctx); + JsonFree(c); + + return ret; +} + diff --git a/src/AS/State.c b/src/AS/State.c new file mode 100644 index 0000000..ef2ff14 --- /dev/null +++ b/src/AS/State.c @@ -0,0 +1,37 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +void +ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state) +{ + HttpClientContext *ctx = NULL; + char *path; + if (!conf || !id || !type || !mask || !state) + { + JsonFree(state); + return; + } + + path = StrConcat(9, + "/_matrix/client/v3/rooms/", id, "/state/", + type, "/", key, "?", "user_id=", mask + ); + + ctx = ParseeCreateRequest(conf, HTTP_PUT, path); + Free(path); + ASAuthenticateRequest(conf, ctx); + ParseeSetRequestJSON(ctx, state); + + HttpClientContextFree(ctx); + JsonFree(state); +} + diff --git a/src/Command/Parser.c b/src/Command/Parser.c index 6ff55d3..65f295b 100644 --- a/src/Command/Parser.c +++ b/src/Command/Parser.c @@ -25,7 +25,7 @@ CommandParse(char *cmd) } end_data = strchr(cmd, ' '); - if (!end_data) + if (!end_data || (cmd > end_data)) { ret = Malloc(sizeof(*ret)); ret->command = StrDuplicate(cmd); @@ -49,11 +49,11 @@ CommandParse(char *cmd) char c = *cur; char *tmp; bool type; - char char_type; + char char_type = '\0'; switch (state) { case STATE_WHITE: - if (!isblank(c)) + if (!isblank((int) c)) { state = STATE_NAME; namestart = cur; @@ -84,7 +84,7 @@ CommandParse(char *cmd) { char c = *cur; char cb[2] = { c, '\0' }; - if ((type && c == char_type) || (!type && isblank(c))) + if ((type && c == char_type) || (!type && isblank((int) c))) { break; } diff --git a/src/Command/Router.c b/src/Command/Router.c index d188640..0a09d28 100644 --- a/src/Command/Router.c +++ b/src/Command/Router.c @@ -15,35 +15,47 @@ CommandCreateRouter(void) void CommandAddCommand(CommandRouter *rter, char *c, CommandRoute rte) { + CommandRoute *indirect; if (!rter || !c || !rte) { return; } - HashMapSet(rter->routes, c, rte); + + /* Little dirty trick to force C99 into submission, and since + * some architectures may separate data/code. Still don't like it... */ + indirect = Malloc(sizeof(rte)); + *indirect = rte; + HashMapSet(rter->routes, c, (void *) indirect); } void RouteCommand(CommandRouter *rter, Command *cmd, void *d) { - CommandRoute route; + CommandRoute *route; if (!rter || !cmd) { return; } route = HashMapGet(rter->routes, cmd->command); - if (route) + if (route && *route) { - route(cmd, d); + (*route)(cmd, d); } } void CommandFreeRouter(CommandRouter *rter) { + char *key; + CommandRoute *val; if (!rter) { return; } + while (HashMapIterate(rter->routes, &key, (void **) &val)) + { + Free(val); + } HashMapFree(rter->routes); Free(rter); } diff --git a/src/Commands/BanUser.c b/src/Commands/BanUser.c index 6e42356..dd380ed 100644 --- a/src/Commands/BanUser.c +++ b/src/Commands/BanUser.c @@ -22,11 +22,28 @@ CommandHead(CmdBanUser, cmd, argp) BotDestroy(); return; } - ASBan(data->config, room, user); - ReplySprintf("Banning %s from '%s'...", - user, room - ); + ASBan(data->config, room, user); + ReplySprintf("Banning %s from '%s'...", user, room); + + 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(); } diff --git a/src/Commands/Help.c b/src/Commands/Help.c index f60980c..0e25009 100644 --- a/src/Commands/Help.c +++ b/src/Commands/Help.c @@ -25,4 +25,6 @@ CommandHead(CmdHelp, cmd, argp) ); ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*"); BotDestroy(); + + (void) cmd; } diff --git a/src/Commands/ListBans.c b/src/Commands/ListBans.c index 109c5d4..50d9d31 100644 --- a/src/Commands/ListBans.c +++ b/src/Commands/ListBans.c @@ -32,4 +32,6 @@ CommandHead(CmdListBans, cmd, argp) DbUnlock(data->db, listed); BotDestroy(); + + (void) cmd; } diff --git a/src/Commands/Plumb.c b/src/Commands/Plumb.c index d7b3ba0..f51e4e6 100644 --- a/src/Commands/Plumb.c +++ b/src/Commands/Plumb.c @@ -24,7 +24,8 @@ CommandHead(CmdPlumb, cmd, argp) BotRequired(room); /* Check MUC viability */ - if (ParseeManageBan(args->data, muc, NULL)) + if (ParseeManageBan(args->data, muc, NULL) || + !ParseeIsMUCWhitelisted(args->data, muc)) { ReplySprintf("MUC '%s' is not allowed on this bridge.", muc); goto end; @@ -62,7 +63,7 @@ CommandHead(CmdPlumb, cmd, argp) if (chat_id) { char *rev = StrConcat(2, muc, "/parsee"); - XMPPJoinMUC(args->data->jabber, "parsee", rev); + XMPPJoinMUC(args->data->jabber, "parsee", rev, NULL, -1, false); Free(rev); } diff --git a/src/Commands/Stats.c b/src/Commands/Stats.c index 1102f89..0df0b44 100644 --- a/src/Commands/Stats.c +++ b/src/Commands/Stats.c @@ -24,7 +24,6 @@ CommandHead(CmdStats, cmd, argp) BotInitialise(); - /* TODO: Separate these into different "categories" */ ReplySprintf("Information for %s v%s (Cytoplasm %s)", NAME, VERSION, CytoplasmGetVersionStr() ); @@ -41,4 +40,6 @@ CommandHead(CmdStats, cmd, argp) ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*"); BotDestroy(); + + (void) cmd; } diff --git a/src/Commands/UnlinkMUC.c b/src/Commands/UnlinkMUC.c index 3e2ae81..7f7cc50 100644 --- a/src/Commands/UnlinkMUC.c +++ b/src/Commands/UnlinkMUC.c @@ -9,43 +9,60 @@ #include +static bool +Grab(ParseeData *data, Command *cmd, char **muc, char **chat_id, char **room) +{ + if (HashMapGet(cmd->arguments, "muc")) + { + *muc = HashMapGet(cmd->arguments, "muc"); + + *chat_id = ParseeGetFromMUCID(data, *muc); + *room = ParseeGetRoomID(data, *chat_id); + if (!chat_id || !room) + { + return false; + } + return true; + } + else if (HashMapGet(cmd->arguments, "room")) + { + *room = HashMapGet(cmd->arguments, "room"); + + *chat_id = ParseeGetFromRoomID(data, *room); + *muc = ParseeGetMUCID(data, *chat_id); + if (!chat_id || !muc) + { + return false; + } + return true; + } + return false; +} + CommandHead(CmdUnlinkMUC, cmd, argp) { ParseeCmdArg *args = argp; ParseeData *data = args->data; - HashMap *json, *event = args->event, *mucs; - DbRef *ref; + HashMap *event = args->event; char *muc = NULL, *chat_id = NULL, *room = NULL; BotInitialise(); - muc = HashMapGet(cmd->arguments, "muc"); - if (!muc) + if (!Grab(data, cmd, &muc, &chat_id, &room)) { - ReplyBasic("`muc` field REQUIRED."); + ReplyBasic("`muc`|`room` REQUIRED"); goto end; } - ref = DbLock(data->db, 1, "chats"); - json = DbJson(ref); - chat_id = StrDuplicate(GrabString(json, 2, "mucs", muc)); + chat_id = ParseeGetFromMUCID(data, muc); + room = ParseeGetRoomID(data, chat_id); if (!chat_id) { ReplySprintf("No internal mapping to '%s'.", muc); goto end; } - mucs = GrabObject(json, 1, "mucs"); - JsonValueFree(HashMapDelete(mucs, muc)); - DbUnlock(data->db, ref); - room = ParseeGetRoomID(data, chat_id); - ref = DbLock(data->db, 1, "chats"); - json = DbJson(ref); - mucs = GrabObject(json, 1, "rooms"); - JsonValueFree(HashMapDelete(mucs, room)); - DbUnlock(data->db, ref); - - DbDelete(data->db, 2, "chats", chat_id); + ParseeUnlinkRoom(data, chat_id); /* TODO: Do it automatically, if *not plumbed* */ ReplySprintf("The MUC %s is now *unlinked*.", muc); diff --git a/src/Events.c b/src/Events.c index 2a3f5b3..e9edf12 100644 --- a/src/Events.c +++ b/src/Events.c @@ -37,12 +37,10 @@ MatrixCreateMessage(char *body) HashMapSet(map, "msgtype", JsonValueString("m.text")); HashMapSet(map, "body", JsonValueString(body)); { - /* TODO */ XEP393Element *e = XEP393(body); text = XEP393ToXMLString(e); XEP393FreeElement(e); - HashMapSet(map, "formatted_body", JsonValueString(text)); HashMapSet(map, "format", JsonValueString("org.matrix.custom.html")); Free(text); @@ -81,7 +79,7 @@ MatrixCreateNickChange(char *nick) return map; } HashMap * -MatrixCreateMedia(char *mxc, char *body, char *mime) +MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info) { HashMap *map; char *mime_type = NULL, *matrix_type = NULL; @@ -93,9 +91,10 @@ MatrixCreateMedia(char *mxc, char *body, char *mime) matrix_type = "m.file"; if (mime) { - size_t i; + size_t i, len; mime_type = StrDuplicate(mime); - for (i = 0; i < strlen(mime); i++) + len = strlen(mime); + for (i = 0; i < len; i++) { if (mime_type[i] == '/') { @@ -121,6 +120,12 @@ MatrixCreateMedia(char *mxc, char *body, char *mime) } map = HashMapCreate(); + JsonSet(map, JsonValueString(mime), 2, "info", "mimetype"); + if (info && info->width && info->height) + { + JsonSet(map, JsonValueInteger(info->width), 2, "info", "w"); + JsonSet(map, JsonValueInteger(info->height), 2, "info", "h"); + } HashMapSet(map, "msgtype", JsonValueString(matrix_type)); HashMapSet(map, "mimetype", JsonValueString(mime)); HashMapSet(map, "body", JsonValueString(body)); @@ -182,6 +187,18 @@ MatrixCreateReplace(char *event, char *body) HashMapSet(map, "m.new_content", JsonValueObject(new)); HashMapSet(map, "m.relates_to", JsonValueObject(rel)); + { + XEP393Element *e = XEP393(body); + char *text = XEP393ToXMLString(e); + XEP393FreeElement(e); + + HashMapSet(map, "formatted_body", JsonValueString(text)); + HashMapSet(map, "format", JsonValueString("org.matrix.custom.html")); + HashMapSet(new, "formatted_body", JsonValueString(text)); + HashMapSet(new, "format", JsonValueString("org.matrix.custom.html")); + Free(text); + } + return map; } HashMap * diff --git a/src/FileInfo.c b/src/FileInfo.c new file mode 100644 index 0000000..17974d0 --- /dev/null +++ b/src/FileInfo.c @@ -0,0 +1,68 @@ +#include + +#include +#include + +#include +#include + +static int +GetField(XMLElement *elem) +{ + XMLElement *child; + if (!elem || ArraySize(elem->children) != 1) + { + return 0; + } + + child = ArrayGet(elem->children, 0); + + return strtol(child->data, NULL, 10); +} + +FileInfo * +FileInfoFromXMPP(XMLElement *stanza) +{ + FileInfo *info; + XMLElement *reference, *sims, *file; + + if (!stanza) + { + return NULL; + } + + reference = XMLookForTKV(stanza, + "reference", "xmlns", "urn:xmpp:reference:0" + ); + sims = XMLookForTKV(reference, + "media-sharing", "xmlns", "urn:xmpp:sims:1" + ); + file = XMLookForTKV(sims, + "file", "xmlns", "urn:xmpp:jingle:apps:file-transfer:5" + ); + /* TODO: We'll definitely need MIME types to do things like + * WebXDC */ + if (!file) + { + return NULL; + } + + info = Malloc(sizeof(*info)); + + + info->width = GetField(XMLookForUnique(file, "width")); + info->height = GetField(XMLookForUnique(file, "height")); + info->size = GetField(XMLookForUnique(file, "size")); + return info; +} + +void +FileInfoFree(FileInfo *info) +{ + if (!info) + { + return; + } + + Free(info); +} diff --git a/src/Glob.c b/src/Glob.c index 805f5a6..02765dd 100644 --- a/src/Glob.c +++ b/src/Glob.c @@ -19,7 +19,6 @@ GlobMatches(char *rule, char *string) switch (c1) { case '*': - /* TODO */ while ((c2 = *string) && (c2 != next)) { string++; diff --git a/src/HttParsee.c b/src/HttParsee.c index d6fd86b..572487c 100644 --- a/src/HttParsee.c +++ b/src/HttParsee.c @@ -29,19 +29,18 @@ ParseeRequest(HttpServerContext *ctx, void *argp) arg.stream = stream; - Log(LOG_NOTICE, "%s %s", + Log(LOG_DEBUG, "%s %s", HttpRequestMethodToString(HttpRequestMethodGet(ctx)), path ); if (!HttpRouterRoute(data->router, path, &arg, (void **) &response)) { - Log(LOG_NOTICE, "Couldn't route %s", path); + Log(LOG_DEBUG, "Couldn't route %s", path); HttpResponseStatus(ctx, HTTP_NOT_FOUND); JsonFree(response); response = MatrixCreateError("M_NOT_FOUND", "Route not found."); - /* TODO: Set a thing */ } /* Whatever, we routed a thing. */ @@ -56,8 +55,11 @@ ParseeRequest(HttpServerContext *ctx, void *argp) HttpSendHeaders(ctx); JsonEncode(response, stream, JSON_DEFAULT); JsonFree(response); - return; } + Log(LOG_DEBUG, "%s %s (%d)", + HttpRequestMethodToString(HttpRequestMethodGet(ctx)), + path, HttpResponseStatusGet(ctx) + ); } HttpClientContext * @@ -71,7 +73,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path ctx = HttpRequest( meth, - HTTP_FLAG_TLS, + conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE, conf->homeserver_port, conf->homeserver_host, path ); diff --git a/src/Main.c b/src/Main.c index 226a9e4..10c809f 100644 --- a/src/Main.c +++ b/src/Main.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -27,6 +29,79 @@ ParseeUptime(void) return UtilTsMillis() - start; } +static const Argument arguments[] = +{ +#define Arg(c, req, vdesc, desc) \ + { \ + .end = false, \ + .argument = c, .value_req = req, \ + .value_descr = vdesc, \ + .description = desc \ + }, +#define EndOfArgs { .end = true } + + Arg('H', true, "number(=8)", "Sets the number of HTTP threads") + Arg('J', true, "number(=8)", "Sets the number of XMPP threads") + Arg('C', true, "file(='parsee.json')", "Sets the JSON config to use") + + Arg('g', false, NULL, + "Generates a parsee.yaml AS file before exiting") + Arg('v', false, NULL, + "Forces Parsee to print in a more verbose fashion " + "(-vvv prints stanzas to stderr)") + Arg('h', false, NULL, + "Generates an help screen(this one!)") + + EndOfArgs + +#undef EndOfArgs +#undef Argument +}; + +void +ParseeCheckMatrix(void *datp) +{ + static volatile uint64_t streak = 0; + ParseeData *data = datp; + if (data->config->accept_pings && !ASPing(data->config)) + { + Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); + if (++streak == 10) + { + DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); + HashMap *json = DbJson(ref); + HashMap *mucs = GrabObject(json, 1, "mucs"); + char *muc; + void *ignored; + + + /* Notify any potential MUCs about this */ + while (HashMapIterate(mucs, &muc, &ignored)) + { + char *id = StrRandom(32); + char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee"); + StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id); + SetStanzaType(b, "groupchat"); + SetStanzaBody(b, + "This bridge hasn't been able to reach the Matrix host, and " + "as such, some messages may not have been sent over." + ); + + WriteoutStanza(b, data->jabber, 0); + DestroyStanzaBuilder(b); + + Free(sender); + Free(id); + } + (void) ignored; + + DbUnlock(data->db, ref); + } + return; + } + streak = 0; +} + int Main(Array *args, HashMap *env) { @@ -35,6 +110,11 @@ Main(Array *args, HashMap *env) Stream *yaml; Cron *cron = NULL; + char *configuration = "parsee.json"; + int xmpp = 8; + int http = 8; + int verbose = 0; + start = UtilTsMillis(); memset(&conf, 0, sizeof(conf)); @@ -42,24 +122,32 @@ Main(Array *args, HashMap *env) "%s - v%s[%s] (Cytoplasm %s)", NAME, VERSION, CODE, CytoplasmGetVersionStr() ); + ParseePrintASCII(); Log(LOG_INFO, "======================="); - LogConfigIndent(LogConfigGlobal()); + Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); + Log(LOG_INFO, "(This program is free software, see LICENSE.)"); - ParseeConfigLoad("parsee.json"); - ParseeConfigInit(); - parsee_conf = ParseeConfigGet(); +#ifdef PLATFORM_IPHONE + Log(LOG_WARNING, "Wait. Are you running this on an iPhone?"); + Log(LOG_WARNING, "You *ought* to have spoofed this, haven't you?"); + Log(LOG_WARNING, "Simply jealous of you for doing this."); +#endif + + LogConfigIndent(LogConfigGlobal()); { ArgParseState state; + char *opts = ParseeGenerateGetopt(arguments); int flag; - int xmpp = 8; - int http = 8; - ArgParseStateInit(&state); - while ((flag = ArgParse(&state, args, "gH:J:")) != -1) + while ((flag = ArgParse(&state, args, opts)) != -1) { switch (flag) { + case 'h': + ParseeGenerateHelp(arguments); + Free(opts); + goto end; case 'H': http = strtol(state.optArg, NULL, 10); break; @@ -70,46 +158,155 @@ Main(Array *args, HashMap *env) /* Write out the config file to a YAML document */ Log(LOG_INFO, "Generating YAML..."); yaml = StreamOpen("parsee.yaml", "w"); + ParseeConfigLoad(configuration); + ParseeConfigInit(); ParseeExportConfigYAML(yaml); StreamClose(yaml); + Free(opts); + goto end; + case 'v': + switch (++verbose) + { + case PARSEE_VERBOSE_LOG: + LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG); + break; + case PARSEE_VERBOSE_TIMINGS: + Log(LOG_DEBUG, "Logging bench information."); + break; + case PARSEE_VERBOSE_STANZA: + Log(LOG_DEBUG, "Enabling stanza printing."); + break; + case PARSEE_VERBOSE_COMICAL: + Log(LOG_DEBUG, "What?"); + Log(LOG_DEBUG, "No, but like, what do you except?"); + Log(LOG_DEBUG, "Like do you want to log _every_ instruction?"); + Log(LOG_DEBUG, "Like just every single thing %s does?", NAME); + Log(LOG_DEBUG, " ( why??? )"); + Log(LOG_DEBUG, "....................................."); + Log(LOG_DEBUG, "Argh."); + Log(LOG_DEBUG, "Alright. I'll do my best."); + Log(LOG_DEBUG, "Get what you paid for."); + break; + } + break; + case 'C': + if (!UtilLastModified(state.optArg)) + { + Log(LOG_ERR, "Invalid config: %s", state.optArg); + Log(LOG_ERR, "Ignoring."); + break; + } + configuration = state.optArg; + break; + case '?': + Log(LOG_ERR, "INVALID ARGUMENT GIVEN"); + Free(opts); goto end; } } - ParseeSetThreads(xmpp, http); + Free(opts); } + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Loading configuration..."); + } + ParseeConfigLoad(configuration); + ParseeConfigInit(); + parsee_conf = ParseeConfigGet(); + if (!parsee_conf) + { + goto end; + } + ParseeSetThreads(xmpp, http); + + 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 )) { Log(LOG_ERR, "Could not connect to XMPP..."); + + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Destroying component..."); + } XMPPEndCompStream(jabber); goto end; } Log(LOG_NOTICE, "Creating volatile tables..."); + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising JID table"); + } ParseeInitialiseJIDTable(); + + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising OID table"); + } ParseeInitialiseOIDTable(); + + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising head table"); + } ParseeInitialiseHeadTable(); - Log(LOG_NOTICE, "Setting up local Matrix user..."); - ASRegisterUser(parsee_conf, parsee_conf->sender_localpart); + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising nick table"); + } + ParseeInitialiseNickTable(); + + if (verbose >= PARSEE_VERBOSE_COMICAL) + { + Log(LOG_DEBUG, "Initialising affiliation table"); + } + ParseeInitialiseAffiliationTable(); conf.port = parsee_conf->port; conf.threads = parsee_conf->http_threads; conf.maxConnections = conf.threads << 2; conf.handlerArgs = ParseeInitData(jabber); conf.handler = ParseeRequest; + if (!conf.handlerArgs) + { + goto end; + } + + Log(LOG_DEBUG, "Verbosity level: %d", verbose); + ((ParseeData *) conf.handlerArgs)->verbosity = verbose; + + Log(LOG_NOTICE, "Setting up local Matrix user..."); + if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) + { + char *parsee = ParseeMXID(conf.handlerArgs); + + ASSetAvatar(parsee_conf, + parsee, + "mxc://tedomum.net/" + "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" + ); + ASSetName(parsee_conf, parsee, "Parsee bridge"); + + Free(parsee); + } Log(LOG_NOTICE, "Starting up local cronjobs..."); - cron = CronCreate( 10 SECONDS ); - CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs); + cron = CronCreate(10 SECONDS); + CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs); + CronEvery(cron, 10 SECONDS, ParseeCheckMatrix, conf.handlerArgs); + ParseeCleanup(conf.handlerArgs); CronStart(cron); @@ -121,8 +318,9 @@ Main(Array *args, HashMap *env) } server = HttpServerCreate(&conf); + ((ParseeData *) conf.handlerArgs)->server = server; - if (!ParseeInitialiseSignals(server, xmpp_thr, jabber)) + if (!ParseeInitialiseSignals(conf.handlerArgs, xmpp_thr)) { goto end; } @@ -146,8 +344,12 @@ end: CronStop(cron); CronFree(cron); ParseeFreeData(conf.handlerArgs); + ParseeDestroyAffiliationTable(); + ParseeDestroyNickTable(); ParseeDestroyOIDTable(); ParseeDestroyHeadTable(); ParseeDestroyJIDTable(); + + (void) env; return 0; } diff --git a/src/MatrixEventHandler.c b/src/MatrixEventHandler.c index ce24854..6311fac 100644 --- a/src/MatrixEventHandler.c +++ b/src/MatrixEventHandler.c @@ -8,9 +8,56 @@ #include #include +#include #include #include +static const char * +GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to); + +static char * +JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char *hash) +{ + 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. */ + char *nick = UnistrC(filtered); + char *rev = StrConcat(3, muc, "/", nick); + int nonce = 0; + + UnistrFree(uninick); + UnistrFree(filtered); + + /* TODO: vCards! */ + while (!XMPPJoinMUC(data->jabber, jid, rev, hash, -1, true) && nonce < 32) + { + char *nonce_str = StrInt(nonce); + char *input = StrConcat(3, sender, name, nonce_str); + char *hex = ParseeHMACS(data->id, input); + + if (strlen(hex) >= 8) + { + hex[8] = '\0'; + } + + Free(nick); + Free(rev); + + nick = StrConcat(4, name, "[", hex, "]"); + rev = StrConcat(3, muc, "/", nick); + nonce++; + + Free(nonce_str); + Free(input); + Free(hex); + } + + ParseePushNickTable(muc, sender, nick); + Free(nick); + return (rev); +} + static void ParseeMemberHandler(ParseeData *data, HashMap *event) { @@ -51,20 +98,95 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key)) { char *jid = ParseeEncodeMXID(state_key); + char *sha = NULL, *mime = NULL; + char *avatar = ASGetAvatar(data->config, NULL, state_key); + char *url = ParseeToUnauth(data, avatar, NULL); chat_id = ParseeGetFromRoomID(data, room_id); + + ASGetMIMESHA(data->config, avatar, &mime, &sha); + Free(avatar); + avatar = NULL; if (chat_id) { char *muc = ParseeGetMUCID(data, chat_id); - char *rev = StrConcat(2, muc, "/parsee"); + char *name = ASGetName(data->config, room_id, state_key); + char *jabber = JoinMUC(data, event, jid, muc, name, sha); + avatar = ASGetAvatar(data->config, NULL, state_key); + Log(LOG_DEBUG, "MATRIX: Joining as '%s' (avatar=%s)", jabber, avatar); - XMPPJoinMUC(data->jabber, jid, rev); - Free(rev); + Free(jabber); + Free(avatar); + Free(name); Free(muc); + avatar = NULL; /* TODO: XEP-0084 magic to advertise a new avatar if possible. */ } - Free(jid); + else + { + char *full_jid = StrConcat(3, + jid, "@", data->config->component_host + ); + XMLElement *elem, *pevent, *items, *item, *meta, *info; + + Log(LOG_DEBUG, "MATRIX: Got local user '%s'(mxid=%s)", jid, state_key); + + elem = XMLCreateTag("message"); + { +#define PUBSUB "http://jabber.org/protocol/pubsub" +#define AVATAR "urn:xmpp:avatar:metadata" + pevent = XMLCreateTag("event"); + XMLAddAttr(pevent, "xmlns", PUBSUB "#event"); + { + items = XMLCreateTag("items"); + item = XMLCreateTag("item"); + XMLAddAttr(items, "node", AVATAR); + XMLAddAttr(item, "id", sha); + { + meta = XMLCreateTag("metadata"); + info = XMLCreateTag("info"); + XMLAddAttr(meta, "xmlns", AVATAR); + + XMLAddAttr(info, "id", sha); + XMLAddAttr(info, "url", url); + XMLAddAttr(info, "type", mime); + + XMLAddChild(meta, info); + XMLAddChild(item, meta); + } + XMLAddChild(items, item); + XMLAddChild(pevent, items); + } + XMLAddChild(elem, pevent); +#undef PUBSUB + } + + /* TODO: Broadcast PEP avatar change */ + ParseeBroadcastStanza(data, full_jid, elem); + XMLFreeElement(elem); + + elem = XMLCreateTag("presence"); + { + XMLElement *x = XMLCreateTag("x"); + XMLElement *photo; + + XMLAddAttr(x, "xmlns", "vcard-temp:x:update"); + photo = XMLCreateTag("photo"); + XMLAddChild(photo, XMLCreateText(sha)); + XMLAddChild(x, photo); + XMLAddChild(elem, x); + } + ParseeBroadcastStanza(data, full_jid, elem); + XMLFreeElement(elem); + + Free(full_jid); + } Free(chat_id); + Free(avatar); + Free(mime); + Free(sha); + Free(jid); + Free(url); } else if ((StrEquals(membership, "leave") || StrEquals(membership, "ban")) @@ -82,14 +204,29 @@ ParseeMemberHandler(ParseeData *data, HashMap *event) muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { + /* If it can't be found, try to see if it's as a DM */ + char *info_from = NULL, *info_to = NULL; + const char *type = GetXMPPInformation(data, event, &info_from, &info_to); + + if (StrEquals(type, "chat")) + { + char *jid_to = ParseeTrimJID(info_to); + Log(LOG_DEBUG, "('%s'->'%s') is gone.", state_key, info_to); + /* TODO: Send a last DM, signifying that all is gone. */ + ParseeDeleteDM(data, state_key, jid_to); + Free(jid_to); + } + + Free(info_from); + Free(info_to); goto end; } - /* TODO: Check the name's validity */ - name = ASGetName(data->config, room_id, state_key); - rev = StrConcat(4, muc_id, "/", name, "[p]"); + name = StrDuplicate(ParseeLookupNick(muc_id, sender)); + rev = StrConcat(3, muc_id, "/", name); XMPPLeaveMUC(jabber, jid, rev, reason); + ParseePushNickTable(muc_id, sender, NULL); end: Free(chat_id); Free(muc_id); @@ -118,14 +255,9 @@ ParseeBotHandler(ParseeData *data, HashMap *event) return; } - if (*body != '!') + if (!body || *body != '!') { /* All commands are to be marked with a ! */ - Free(ASSend( - data->config, id, profile, - "m.room.message", - MatrixCreateNotice("Please enter a valid command") - )); Free(profile); return; } @@ -135,7 +267,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event) Free(ASSend( data->config, id, profile, "m.room.message", - MatrixCreateNotice("You are not authorised to do this.") + MatrixCreateNotice("You are not authorised to do this."), 0 )); Free(profile); return; @@ -157,12 +289,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) char *room_id = GrabString(event, 1, "room_id"); char *matrix_sender = GrabString(event, 1, "sender"); char *chat_id = NULL, *muc_id = NULL; - char *user; + char *user = NULL; - DbRef *room_data; - HashMap *data_json; - - XMPPComponent *jabber = data ? data->jabber : NULL; + DbRef *room_data = NULL; + HashMap *data_json = NULL; bool direct = false; if (!data || !event || !from || !to) @@ -194,7 +324,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) } else { - char *matrix_name, *muc_join_as; + char *matrix_name = NULL, *matrix_avatar = NULL; + char *mime = NULL, *sha = NULL; muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) @@ -208,18 +339,16 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to) } matrix_name = ASGetName(data->config, room_id, matrix_sender); - muc_join_as = StrConcat(4, muc_id, "/", matrix_name, "[p]"); - - /* TODO: Manage name conflicts. That would have been an easy - * task(try the original one, and use a counter if it fails), - * but that'd involve modifying the rest of the code, which - * I'm not doing at 01:39 ... */ - XMPPJoinMUC(jabber, *from, muc_join_as); + matrix_avatar = ASGetAvatar(data->config, NULL, matrix_sender); + ASGetMIMESHA(data->config, matrix_avatar, &mime, &sha); + Free(JoinMUC(data, event, *from, muc_id, matrix_name, sha)); *to = muc_id; + Free(matrix_avatar); Free(matrix_name); - Free(muc_join_as); + Free(mime); + Free(sha); } Free(chat_id); @@ -229,27 +358,47 @@ 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; + StanzaBuilder *builder = NULL; DbRef *ref = NULL; - HashMap *json; + HashMap *json = NULL; + char *msgtype = GrabString(event, 2, "content", "msgtype"); + char *m_sender = GrabString(event, 1, "sender"); + char *unedited_id = NULL; char *body = GrabString(event, 2, "content", "body"); char *id = GrabString(event, 1, "room_id"); char *ev_id = GrabString(event, 1, "event_id"); - char *m_sender = GrabString(event, 1, "sender"); - char *chat_id, *muc_id; + char *chat_id = NULL, *muc_id = NULL; char *reply_id = MatrixGetReply(event); - char *xepd = ParseeXMPPify(event); + char *xepd = ParseeXMPPify(data, 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)) { @@ -261,9 +410,6 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) chat_id = ParseeGetFromRoomID(data, id); - /* TODO: This ref should be marked as read-only, - * as LMDB doesn't seem to like having two concurrent RW - * transactions running. */ ref = DbLockIntent(data->db, DB_HINT_READONLY, 3, "rooms", id, "data"); json = DbJson(ref); direct = JsonValueAsBoolean(HashMapGet(json, "is_direct")); @@ -283,38 +429,44 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) type = direct ? "chat" : "groupchat"; user = GrabString(json, 1, "xmpp_user"); - unauth = ParseeToUnauth(data, url); + unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename")); + + encoded_from = ParseeEncodeMXID(m_sender); + xmppified_user = StrConcat(3, + encoded_from, "@", jabber->host + ); if (direct) { - xmppified_user = ParseeEncodeMXID(m_sender); to = StrDuplicate(user); Free(chat_id); } else { - char *name, *rev; + char *name, *mime = NULL, *sha = NULL; + char *avatar; /* Try to find the chat ID */ muc_id = ParseeGetMUCID(data, chat_id); if (!chat_id) { goto end; } - xmppified_user = ParseeEncodeMXID(m_sender); - /* TODO: Check the name's validity. - * Is there a good way to check for that that isn't - * just "await on join and try again?" */ + /* TODO: Avoid using the AS endpoints */ name = ASGetName(data->config, id, m_sender); - rev = StrConcat(4, muc_id, "/", name, "[p]"); + avatar = ASGetAvatar(data->config, NULL, m_sender); + ASGetMIMESHA(data->config, avatar, &mime, &sha); - XMPPJoinMUC(jabber, xmppified_user, rev); + Free(JoinMUC(data, event, encoded_from, muc_id, name, sha)); to = muc_id; + Free(sha); + Free(mime); Free(name); - Free(rev); + Free(avatar); } + if (reply_id) { /* TODO: Monocles chat DM users HATE this trick! @@ -342,13 +494,13 @@ ParseeMessageHandler(ParseeData *data, HashMap *event) char *xmpp_ident = StrRandom(32); builder = CreateStanzaBuilder(xmppified_user, to, xmpp_ident); SetStanzaType(builder, type); - SetStanzaBody(builder, xepd ? xepd : body); + SetStanzaBody(builder, unauth ? unauth : (xepd ? xepd : body)); SetStanzaReply(builder, stanza, sender); SetStanzaLink(builder, unauth); SetStanzaEdit(builder, origin_id); SetStanzaXParsee(builder, event); - WriteoutStanza(builder, jabber); + WriteoutStanza(builder, jabber, data->config->max_stanza_size); DestroyStanzaBuilder(builder); if (direct) @@ -371,6 +523,7 @@ end: Free(stanza); Free(sender); Free(unauth); + Free(encoded_from); Free(unedited_id); DbUnlock(data->db, ref); @@ -402,8 +555,7 @@ ParseeEventHandler(ParseeData *data, HashMap *event) return; } else if (StrEquals(event_type, "m.room.message") || - StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker - * support here... */ + StrEquals(event_type, "m.sticker")) { ParseeMessageHandler(data, event); Free(parsee); diff --git a/src/MatrixID.c b/src/MatrixID.c new file mode 100644 index 0000000..bb7c584 --- /dev/null +++ b/src/MatrixID.c @@ -0,0 +1,68 @@ +#include + +#include +#include +#include +#include + +#include + +UserID * +MatrixParseID(char *user) +{ + UserID *ret = NULL; + char *localstart, *serverstart; + if (!user || *user != '@') + { + return NULL; + } + + localstart = user + 1; + serverstart = strchr(user, ':'); + if (!*localstart || !serverstart || localstart == serverstart) + { + return NULL; + } + if (!*++serverstart) + { + return NULL; + } + + ret = Malloc(sizeof(*ret)); + memset(ret, '\0', sizeof(*ret)); + memcpy(ret->localpart, localstart, serverstart - localstart - 1); + memcpy(ret->server, serverstart, strlen(serverstart)); + + 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 4bad59f..b95e5e1 100644 --- a/src/Parsee/Config.c +++ b/src/Parsee/Config.c @@ -11,210 +11,15 @@ static ParseeConfig *config = NULL; -static char * -GetLine(void) -{ - Stream *input = StreamStdin(); - char *out = NULL; - size_t length; - UtilGetLine(&out, &length, input); - - if (out) - { - char *line = strchr(out, '\n'); - if (line) - { - *line = '\0'; - } - } - - return out; -} - -#include -static char * -PromptString(const char *expression, const char *def, ...) -{ - Stream *output = StreamStdout(); - char *out = NULL; - va_list ap; - - while (!out) - { - va_start(ap, def); - - StreamVprintf(output, expression, ap); - if (def) - { - StreamPrintf(output, " [%s]", def); - } - StreamPrintf(output, ": "); - StreamFlush(output); - - va_end(ap); - - out = GetLine(); - if (!*out) - { - Free(out); - out = NULL; - if (def) - { - return StrDuplicate(def); - } - } - Log(LOG_INFO, "R=%s", out); - } - - return out; -} -static int -PromptInteger(const char *expression, int def, ...) -{ - Stream *output = StreamStdout(); - char *out; - long l; - va_list ap; - - va_start(ap, def); - - StreamVprintf(output, expression, ap); - if (def >= 0) - { - StreamPrintf(output, " [%d]", def); - } - StreamPrintf(output, ": "); - StreamFlush(output); - - va_end(ap); - while (true) - { - char *inval; - out = GetLine(); - l = strtol(out, &inval, 10); - Free(out); - - /* Not a use-after-free, as we reference only the addresses. */ - if (l != 0 || inval != out) - { - break; - } - - if (def >= 0) - { - return def; - } - } - - return l; -} - -/* TODO: Memleaks, galore! */ void ParseeConfigInit(void) { - Stream *stream; - HashMap *json; if (config) { return; } - - /* TODO: Give the user an achievement at the end, just because they're - * cool. */ - Log(LOG_NOTICE, "It seems like it is the first time you have configured "); - Log(LOG_NOTICE, "Parsee."); - Log(LOG_NOTICE, "As such, I need to ask you a couple of questions before "); - Log(LOG_NOTICE, "being able to use it."); - Log(LOG_NOTICE, "(don't worry; it won't take too long.)"); - Log(LOG_NOTICE, ""); - Log(LOG_NOTICE, ""); - - config = Malloc(sizeof(*config)); - config->as_token = StrRandom(32); - config->hs_token = StrRandom(32); - config->http_threads = 8; - config->xmpp_threads = 8; - config->db_size = 64 MB; - - /* TODO: This is NOT user friendly, and I know it! */ - config->sender_localpart = PromptString( - "Name of the bridge bot, used for commands and bridged rooms", - "_parsee_bridge" - ); - config->namespace_base = PromptString( - "Base namespace for Parsee (so foo@bar.com => @[NS]_foo=40bar.com)", - "_jabber" - ); - - config->listen_as = StrDuplicate("localhost"); - config->port = PromptInteger( - "Matrix port for the AS service to use", - 7642 /* proposed by Saint */ - ); - - - config->component_host = PromptString( - "XMPP component to be used for the configuration", - NULL - ); - config->component_port = PromptInteger( - "XMPP port for to use for '%s'", - 5347, config->component_host - ); - config->shared_comp_secret = PromptString( - "%s's shared secret", - NULL, config->component_host - ); - - config->homeserver_host = PromptString( - "Delegated homeserver to be used for the configuration", - NULL - ); - config->homeserver_port = PromptInteger( - "HTTP port for to use for '%s'", - 443, config->homeserver_host - ); - - config->db_path = PromptString( - "Base directory for Parsee data", - NULL - ); - config->media_base = PromptString( - "Base media URL for bridged media", - NULL - ); - - /* TODO: Make that configurable. */ - config->server_base = StrDuplicate(config->homeserver_host); - - Log(LOG_NOTICE, "Done! Please look over to the parsee.yaml file, "); - Log(LOG_NOTICE, "and follow the instructions listed in it. Then, "); - Log(LOG_NOTICE, "restart Parsee. "); - Log(LOG_NOTICE, "------------------------------------------------"); - - stream = StreamOpen("parsee.json", "w"); - json = HashMapCreate(); - HashMapSet(json, "as_token", JsonValueString(config->as_token)); - HashMapSet(json, "hs_token", JsonValueString(config->hs_token)); - - HashMapSet(json, "sender", JsonValueString(config->sender_localpart)); - HashMapSet(json, "namespace", JsonValueString(config->namespace_base)); - HashMapSet(json, "listen_as", JsonValueString(config->listen_as)); - HashMapSet(json, "port", JsonValueInteger(config->port)); - - HashMapSet(json, "hs_base", JsonValueString(config->server_base)); - HashMapSet(json, "hs_host", JsonValueString(config->homeserver_host)); - HashMapSet(json, "hs_port", JsonValueInteger(config->homeserver_port)); - - HashMapSet(json, "component_host", JsonValueString(config->component_host)); - HashMapSet(json, "component_port", JsonValueInteger(config->component_port)); - HashMapSet(json, "shared_comp_secret", JsonValueString(config->shared_comp_secret)); - HashMapSet(json, "db", JsonValueString(config->db_path)); - - JsonEncode(json, stream, JSON_PRETTY); - JsonFree(json); - StreamClose(stream); + Log(LOG_ERR, "No config file found."); + Log(LOG_ERR, "Please use parsee-config to initialise %s.", NAME); } void ParseeConfigLoad(char *conf) @@ -225,12 +30,18 @@ ParseeConfigLoad(char *conf) { return; } - stream = StreamOpen("parsee.json", "r"); + stream = StreamOpen(conf ? conf : "parsee.json", "r"); if (!stream) { 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( \ @@ -239,6 +50,9 @@ ParseeConfigLoad(char *conf) #define CopyToInt(to, str) config->to = (int) ( \ JsonValueAsInteger(HashMapGet(json, str)) \ ) +#define CopyToBool(to, str) config->to = (int) ( \ + JsonValueAsBoolean(HashMapGet(json, str)) \ + ) config->http_threads = 8; config->xmpp_threads = 8; @@ -253,10 +67,25 @@ 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"); + if (!config->max_stanza_size) + { + /* Standard XMPP "minimum" maximum */ + config->max_stanza_size = 10000; + } + + CopyToBool(ignore_bots, "ignore_bots"); CopyToStr(media_base, "media_base"); @@ -272,6 +101,7 @@ ParseeSetThreads(int xmpp, int http) { if (!config) { + Achievement("THREAD COUNT REQUEST WITHOUT CONFIG", true); return; } config->http_threads = http; @@ -283,6 +113,7 @@ ParseeExportConfigYAML(Stream *stream) { if (!stream || !config) { + Achievement("YAML EXPORT REQUEST WITHOUT CONFIG", true); return; } StreamPrintf(stream, "# Autogenerated YAML AS entry for %s\n", NAME); @@ -296,6 +127,7 @@ ParseeExportConfigYAML(Stream *stream) StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token); StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart); StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n"); + StreamPrintf(stream, "receive_ephemeral: true\n"); /* TODO: Actually use that field */ StreamPrintf(stream, "\n"); StreamPrintf(stream, "namespaces: \n"); StreamPrintf(stream, " users:\n"); @@ -304,6 +136,7 @@ ParseeExportConfigYAML(Stream *stream) StreamPrintf(stream, " aliases:\n"); StreamPrintf(stream, " - exclusive: true\n"); StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base); + StreamFlush(stream); } void @@ -314,6 +147,7 @@ ParseeConfigFree(void) return; } Free(config->component_host); + Free(config->component_addr); Free(config->shared_comp_secret); Free(config->db_path); Free(config->homeserver_host); diff --git a/src/Parsee/Data.c b/src/Parsee/Data.c index e92e3e4..46d1433 100644 --- a/src/Parsee/Data.c +++ b/src/Parsee/Data.c @@ -6,17 +6,18 @@ #include #include -#include #include +#include #include -#include #include ParseeData * ParseeInitData(XMPPComponent *comp) { + char *version; ParseeData *data; + DbRef *ref; if (!ParseeConfigGet()) { return NULL; @@ -26,8 +27,15 @@ ParseeInitData(XMPPComponent *comp) data->config = ParseeConfigGet(); data->router = HttpRouterCreate(); data->jabber = comp; + data->muc = CreateMUCServer(data); data->handler = CommandCreateRouter(); + data->oid_servers = HashMapCreate(); + pthread_mutex_init(&data->oidl, NULL); + + data->halted = false; + pthread_mutex_init(&data->halt_lock, NULL); + if (data->config->db_size) { data->db = DbOpenLMDB(data->config->db_path, data->config->db_size); @@ -35,10 +43,47 @@ ParseeInitData(XMPPComponent *comp) if (!data->db) { Log(LOG_WARNING, "LMDB doesn't seem to be setup."); - Log(LOG_WARNING, "Falling back to flat-file."); + if (data->config->db_size) + { + Log(LOG_WARNING, "Falling back to flat-file."); + } data->db = DbOpen(data->config->db_path, 0); } + if (!(ref = DbLock(data->db, 1, "info"))) + { + char *id = StrRandom(64); + ref = DbCreate(data->db, 1, "info"); + HashMapSet(DbJson(ref), "identifier", JsonValueString(id)); + HashMapSet(DbJson(ref), "version", JsonValueString(VERSION)); + Free(id); + } + + version = GrabString(DbJson(ref), 1, "version"); + if (version && !ParseeIsCompatible(VERSION, version)) + { + Log(LOG_WARNING, "Version mismatch(curr=%s db=%s).", VERSION, version); + Log(LOG_WARNING, "Yeah. You may want to _not_ do that."); + Log(LOG_WARNING, "(Parsee still needs an upgradepath mechanism.)"); + + DbUnlock(data->db, ref); + DbClose(data->db); + + HashMapFree(data->oid_servers); + pthread_mutex_destroy(&data->oidl); + + XMPPEndCompStream(data->jabber); + + HttpRouterFree(data->router); + CommandFreeRouter(data->handler); + + Free(data); + return NULL; + } + + data->id = StrDuplicate(GrabString(DbJson(ref), 1, "identifier")); + DbUnlock(data->db, ref); + #define X_ROUTE(path, func) do {\ if (!HttpRouterAdd(data->router, path, func))\ {\ @@ -56,12 +101,23 @@ ParseeInitData(XMPPComponent *comp) void ParseeFreeData(ParseeData *data) { + char *entity; + XMLElement *disco; if (!data) { return; } + while (HashMapIterate(data->oid_servers, &entity, (void **) &disco)) + { + XMLFreeElement(disco); + } + HashMapFree(data->oid_servers); + pthread_mutex_destroy(&data->oidl); + pthread_mutex_destroy(&data->halt_lock); + Free(data->id); XMPPEndCompStream(data->jabber); + FreeMUCServer(data->muc); DbClose(data->db); HttpRouterFree(data->router); CommandFreeRouter(data->handler); @@ -77,8 +133,6 @@ ParseeCleanup(void *datp) size_t i; uint64_t ts = UtilTsMillis(); - Log(LOG_NOTICE, "Cleaning up..."); - chats = DbList(data->db, 1, "chats"); for (i = 0; i < ArraySize(chats); i++) @@ -128,9 +182,12 @@ ParseeCleanup(void *datp) } \ while (0) - CleanupField(stanza, 30 MINUTES, 50); - CleanupField(event, 30 MINUTES, 50); - CleanupField(id, 30 MINUTES, 50); + /* TODO: Custom retention period for any 1.0 */ + CleanupField(stanza, 30 MINUTES, 500); + CleanupField(event, 30 MINUTES, 500); + CleanupField(id, 30 MINUTES, 500); + + /* TODO: Also cleanup user cache information */ #undef CleanupField } DbListFree(chats); @@ -181,252 +238,15 @@ ParseeCleanup(void *datp) } \ while (0) - CleanupField(stanza, 3 HOURS, 50); - CleanupField(event, 3 HOURS, 50); - CleanupField(id, 3 HOURS, 50); + CleanupField(stanza, 3 HOURS, 500); + CleanupField(event, 3 HOURS, 500); + CleanupField(id, 3 HOURS, 500); DbUnlock(data->db, ref); } DbListFree(chats); } -int -ParseeFindDatastart(char *data) -{ - char *startline; - bool found = false; - if (!data) - { - return 0; - } - startline = data; - while (startline) - { - char *endline = strchr(startline, '\n'); - - if (*startline != '>') - { - found = true; - break; - } - - startline = endline ? endline + 1 : NULL; - } - - if (!found) - { - return 0; - } - - return (int) (startline - data); -} - -#include -typedef struct XMPPFlags { - bool quote; -} XMPPFlags; -static char * -XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags) -{ - char *xepd = NULL, *tmp = NULL; - - size_t i; - XMLElement *child; - char *reply_id = JsonValueAsString( - JsonGet(event, 4, - "content", "m.relates_to", "m.in_reply_to", "event_id" - )); - char *room_id = JsonValueAsString(HashMapGet(event, "room_id")); - HashMap *referenced; - char *subxep; -#define Concat(strp) do \ - { \ - size_t cidx; \ - size_t len = strp ? strlen(strp) : 0; \ - for (cidx = 0; cidx < len; cidx++) \ - { \ - char cch[2] = { strp[cidx], 0 }; \ - char nch = *cch ? strp[cidx+1] : '\0'; \ - bool c = *cch == '\n' && nch != '>'; \ - if (c && flags.quote) \ - { \ - tmp = xepd; \ - xepd = StrConcat(2, xepd, "\n>"); \ - Free(tmp); \ - continue; \ - } \ - tmp = xepd; \ - xepd = StrConcat(2, xepd, cch); \ - Free(tmp); \ - } \ - } \ - while (0) - switch (elem->type) - { - case XML_ELEMENT_DATA: - Concat(elem->data); - break; - case XML_ELEMENT_TAG: - if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong")) - { - Concat("*"); - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - Concat("*"); - } - else if (StrEquals(elem->name, "em")) - { - Concat("_"); - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - Concat("_"); - } - else if (StrEquals(elem->name, "code")) - { - Concat("`"); - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - Concat("`"); - } - else if (StrEquals(elem->name, "mx-reply")) - { - char *str; - referenced = ASFind(ParseeConfigGet(), room_id, reply_id); - str = JsonValueAsString( - JsonGet(referenced, 2, "content", "body") - ); - if (!str) - { - JsonFree(referenced); - return xepd; - } - Concat(">"); - flags.quote = true; - Concat(str); - flags.quote = false; - Concat("\n"); - JsonFree(referenced); - } - else if (StrEquals(elem->name, "blockquote")) - { - Concat(">"); - flags.quote = true; - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - flags.quote = false; - Concat("\n"); - } - else if (StrEquals(elem->name, "br")) - { - Concat("\n"); - /* HTML fucking SUCKS */ - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - Concat("\n"); - } - else if (StrEquals(elem->name, "a")) - { - char *href = HashMapGet(elem->attrs, "href"); - Concat("("); - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - Concat(" points to "); - Concat(href); - Concat(" )"); - } - else - { - for (i = 0; i < ArraySize(elem->children); i++) - { - child = ArrayGet(elem->children, i); - subxep = XMPPifyElement(event, child, flags); - - Concat(subxep); - Free(subxep); - } - } - break; - default: - break; - } - return xepd; -} -char * -ParseeXMPPify(HashMap *event) -{ - char *type, *format, *html; - char *xepd = NULL; - XMLElement *elem; - - XMPPFlags flags; - if (!event) - { - return NULL; - } - - /* Check if it is a message event. */ - type = JsonValueAsString(HashMapGet(event, "type")); - if (!StrEquals(type, "m.room.message")) - { - return NULL; - } - - format = JsonValueAsString(JsonGet(event, 2, "content", "format")); - if (!StrEquals(format, "org.matrix.custom.html")) - { - /* Settle for the raw body instead. */ - char *body = JsonValueAsString(JsonGet(event, 2, "content", "body")); - return StrDuplicate(body); - } - - html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body")); - html = StrConcat(3, "", html, ""); - elem = XMLCDecode(StrStreamReader(html), true, true); - - flags.quote = false; - xepd = XMPPifyElement(event, elem, flags); - - XMLFreeElement(elem); - Free(html); - - return xepd; -} void ParseePushDMStanza(ParseeData *data, char *room_id, char *stanza_id, char *id, char *ev, char *sender) { @@ -519,6 +339,15 @@ ParseePushStanza(ParseeData *data, char *chat_id, char *stanza_id, char *id, cha } /* TODO */ + { + ref = DbLock(data->db, 2, "chats", chat_id); + j = DbJson(ref); + if (j) + { + JsonValueFree(HashMapSet(j, "ts", JsonValueInteger(age))); + } + DbUnlock(data->db, ref); + } { ref = DbCreate(data->db, 4, "chats", chat_id, "stanzas", stanza_id); j = DbJson(ref); @@ -721,140 +550,178 @@ end: return ret; } -void -ParseeGlobalBan(ParseeData *data, char *glob, char *reason) + +void +ParseeUnlinkRoom(ParseeData *data, char *chat_id) { + char *muc, *room; DbRef *ref; - HashMap *j, *obj; - if (!data || !glob) + if (!data || !chat_id) { return; } - ref = DbLock(data->db, 1, "global_bans"); - if (!ref) + muc = ParseeGetMUCID(data, chat_id); + room = ParseeGetRoomID(data, chat_id); + if (!muc || !room) { - ref = DbCreate(data->db, 1, "global_bans"); + Free(muc); + Free(room); + return; } - j = DbJson(ref); - - obj = HashMapCreate(); - if (reason) - { - HashMapSet(obj, "reason", JsonValueString(reason)); - } - HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis())); - JsonValueFree(HashMapSet(j, glob, JsonValueObject(obj))); - + ref = DbLock(data->db, 1, "chats"); + JsonValueFree(HashMapDelete( + GrabObject(DbJson(ref), 1, "rooms"), + room + )); + JsonValueFree(HashMapDelete( + GrabObject(DbJson(ref), 1, "mucs"), + muc + )); DbUnlock(data->db, ref); + DbDelete(data->db, 2, "chats", chat_id); + + Free(muc); + Free(room); } -bool -ParseeManageBan(ParseeData *data, char *user, char *room) +bool +ParseeIsMUCWhitelisted(ParseeData *data, char *muc) { + char *server, *serv_start, *postserv; DbRef *ref; - HashMap *j; - char *key; - JsonValue *val; - bool banned = false , matches = false; - if (!data || !user) + bool ret; + if (!data || !muc) { return false; } - ref = DbLock(data->db, 1, "global_bans"); - j = DbJson(ref); - while (HashMapIterate(j, &key, (void **) &val)) + if (!DbExists(data->db, 1, "whitelist")) { - HashMap *obj = JsonValueAsObject(val); - if (matches) - { - continue; - } - if (GlobMatches(key, user)) - { - banned = true; - matches = true; - if (room) - { - /* TODO: Use the object to set the reason */ - ASBan(data->config, room, user); - (void) obj; - } - } + return true; + } + + serv_start = strchr(muc, '@'); + serv_start = serv_start ? serv_start : muc; + server = StrDuplicate(serv_start + 1); + postserv = server ? strchr(server, '/') : NULL; + if (postserv) /* GCC doesn't know strchr is pure. */ + { + *postserv = '\0'; } - DbUnlock(data->db, ref); - - return banned; -} -char * -ParseeStringifyDate(uint64_t millis) -{ - uint64_t rest = millis; - uint64_t hours, minutes, seconds; - char *hs, *ms, *ss, *out; - hours = rest / (1 HOURS); - rest = rest % (1 HOURS); - - minutes = rest / (1 MINUTES); - rest = rest % (1 MINUTES); - - seconds = rest / (1 SECONDS); - - hs = StrInt(hours); - ms = StrInt(minutes); - ss = StrInt(seconds); - - out = StrConcat(8, - hours ? hs : "", - hours ? " hours" : "", - (hours && minutes) ? ", " : "", - - minutes ? ms : "", - minutes ? " minutes" : "", - (minutes && seconds) ? ", " : "", - - seconds ? ss : "", - seconds ? " seconds" : "" + ref = DbLockIntent(data->db, + DB_HINT_READONLY, + 1, "whitelist" ); - Free(hs); - Free(ms); - Free(ss); + ret = HashMapGet(DbJson(ref), server); + DbUnlock(data->db, ref); + Free(server); - return out; + return ret; } -void -ParseeAchievement(const char *func, const char *msg, bool die) +extern HashMap * +ParseeGetChatSettings(ParseeData *data, char *chat) { - Log(LOG_ERR, "=========== Achievement GET! ==========="); - Log(LOG_ERR, "%s: %s.", func, msg); - Log(LOG_ERR, "THIS IS, LET'S SAY, NOT GOOD, AND A SIGN OF "); - Log(LOG_ERR, "A PROGRAMMER ERROR. PLEASE KILLALL -9 PARSEE."); - Log(LOG_ERR, ""); - Log(LOG_ERR, "YOU, HOWEVER, GET TO WIN AN AWARD FOR THIS."); - Log(LOG_ERR, "I AM SIMPLY JEALOUS OF YOU GETTING SUCH A "); - Log(LOG_ERR, "GOOD ERROR MESSAGE."); - Log(LOG_ERR, "=========== Achievement GET! ==========="); + HashMap *ret, *json; + DbRef *ref; - if (die) - { - abort(); - } -} -char * -ParseeGenerateMTO(char *common_id) -{ - char *matrix_to; - if (!common_id) + char *key; + JsonValue *value; + if (!data || !chat) { return NULL; } - common_id = HttpUrlEncode(common_id); - matrix_to = StrConcat(2, "https://matrix.to/#/", common_id); - Free(common_id); + ref = DbLockIntent(data->db, DB_HINT_READONLY, + 3, "chats", chat, "settings" + ); + json = DbJson(ref); + if (!ref) + { + return HashMapCreate(); + } - return matrix_to; + ret = HashMapCreate(); + while (HashMapIterate(json, &key, (void **) &value)) + { + char *str = JsonValueAsString(value); + HashMapSet(ret, key, StrDuplicate(str)); + } + DbUnlock(data->db, ref); + return ret; +} +void +ParseeFreeChatSettings(HashMap *settings) +{ + char *key; + void *val; + if (!settings) + { + return; + } + while (HashMapIterate(settings, &key, &val)) + { + Free(val); + } + HashMapFree(settings); +} +char * +ParseeGetChatSetting(ParseeData *data, char *chat, char *key) +{ + HashMap *map; + char *ret; + if (!data || !chat || !key) + { + return NULL; + } + + map = ParseeGetChatSettings(data, chat); + ret = StrDuplicate(HashMapGet(map, key)); + ParseeFreeChatSettings(map); + + return ret; +} +void +ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val) +{ + DbRef *ref; + HashMap *json; + if (!data || !chat || !key || !val) + { + return; + } + + ref = DbLockIntent(data->db, DB_HINT_WRITE, + 3, "chats", chat, "settings" + ); + if (!ref) + { + ref = DbCreate(data->db, 3, "chats", chat, "settings"); + } + json = DbJson(ref); + + JsonValueFree(HashMapSet(json, key, JsonValueString(val))); + + DbUnlock(data->db, ref); + return; +} +bool +ParseeIsMediaEnabled(ParseeData *data, char *chat_id) +{ + char *value; + bool ret; + if (!data || !chat_id) + { + return false; + } + + ret = !StrEquals( + (value = ParseeGetChatSetting(data, chat_id, "p.media.enabled")), + "false" + ); + Free(value); + + return ret; } diff --git a/src/Parsee/HMAC.c b/src/Parsee/HMAC.c new file mode 100644 index 0000000..abb65ef --- /dev/null +++ b/src/Parsee/HMAC.c @@ -0,0 +1,74 @@ +/* CC0 implementation of a HMAC system based on Cytoplasm. + * Ignore the scary "Parsee.h", its practically unused. */ +#include + +#include +#include +#include + +#include + +static void +ComputeKPad(char *key, uint8_t pad, uint8_t *kopad) +{ + size_t klen; + uint8_t *kp; + uint8_t kpi[64] = { 0 }; + size_t i; + if ((klen = strlen(key)) <= 64) + { + kp = Malloc(klen * sizeof(uint8_t)); + memcpy(kp, key, klen); + } + else + { + kp = (uint8_t *) Sha256(key); + klen = 32; + } + + memset(kpi, 0x00, 64); + memcpy(kpi, kp, klen); + Free(kp); + + /* Now that we have K', lets compute it XORd with opad */ + for (i = 0; i < 64; i++) + { + uint8_t byte = kpi[i]; + kopad[i] = byte ^ pad; + } +} + +char * +ParseeHMAC(char *key, uint8_t *msg, size_t msglen) +{ + uint8_t opad[64], ipad[64 + msglen]; + uint8_t outer[64 + 32]; + + uint8_t *innersha; + uint8_t *sha; + char *str; + if (!key || !msg || !msglen) + { + return NULL; + } + + /* Initialise K' XOR opad and K' XOR ipad */ + ComputeKPad(key, 0x5C, opad); + ComputeKPad(key, 0x36, ipad); + + /* Compute H((K' XOR ipad) || msg) */ + memcpy(ipad + 64, msg, msglen); + innersha = Sha256Raw(ipad, 64 + msglen); + + /* Compute (K' XOR opad) || H((K' XOR ipad) || msg) */ + memcpy(outer, opad, 64); + memcpy(outer + 64, innersha, 32); + + /* Compute H((K' XOR opad) || H((K' XOR ipad) || msg)) */ + sha = Sha256Raw(outer, 64 + 32); + str = ShaToHex(sha, HASH_SHA256); + + Free(innersha); + Free(sha); + return str; +} diff --git a/src/Parsee/JIDTable.c b/src/Parsee/JIDTable.c deleted file mode 100644 index 772b975..0000000 --- a/src/Parsee/JIDTable.c +++ /dev/null @@ -1,194 +0,0 @@ -#include - -#include -#include -#include -#include - -static pthread_mutex_t lock; -static HashMap *jid_table = NULL; - -void -ParseeInitialiseJIDTable(void) -{ - if (jid_table) - { - return; - } - pthread_mutex_init(&lock, NULL); - pthread_mutex_lock(&lock); - jid_table = HashMapCreate(); - pthread_mutex_unlock(&lock); -} -void -ParseePushJIDTable(char *muc, char *bare) -{ - if (!muc || !bare || !jid_table) - { - return; - } - pthread_mutex_lock(&lock); - bare = ParseeTrimJID(bare); - Free(HashMapSet(jid_table, muc, bare)); - pthread_mutex_unlock(&lock); -} -char * -ParseeLookupJID(char *muc) -{ - char *bare; - if (!muc || !jid_table) - { - return NULL; - } - pthread_mutex_lock(&lock); - bare = StrDuplicate(HashMapGet(jid_table, muc)); - pthread_mutex_unlock(&lock); - - if (!bare) - { - bare = StrDuplicate(muc); - } - return bare; -} -void -ParseeDestroyJIDTable(void) -{ - char *key; - void *val; - if (!jid_table) - { - return; - } - pthread_mutex_lock(&lock); - while (HashMapIterate(jid_table, &key, &val)) - { - Free(val); - } - HashMapFree(jid_table); - jid_table = NULL; - pthread_mutex_unlock(&lock); - pthread_mutex_destroy(&lock); -} - -static pthread_mutex_t head_lock; -static HashMap *head_table = NULL; -void -ParseeInitialiseHeadTable(void) -{ - if (head_table) - { - return; - } - pthread_mutex_init(&head_lock, NULL); - pthread_mutex_lock(&head_lock); - head_table = HashMapCreate(); - pthread_mutex_unlock(&head_lock); -} -void -ParseePushHeadTable(char *room, char *event) -{ - if (!room || !event || !head_table) - { - return; - } - pthread_mutex_lock(&head_lock); - event = StrDuplicate(event); - Free(HashMapSet(head_table, room, event)); - pthread_mutex_unlock(&head_lock); -} -char * -ParseeLookupHead(char *room) -{ - char *event; - if (!room || !head_table) - { - return NULL; - } - pthread_mutex_lock(&head_lock); - event = StrDuplicate(HashMapGet(head_table, room)); - pthread_mutex_unlock(&head_lock); - - return event; -} -void -ParseeDestroyHeadTable(void) -{ - char *key; - void *val; - if (!head_table) - { - return; - } - pthread_mutex_lock(&head_lock); - while (HashMapIterate(head_table, &key, &val)) - { - Free(val); - } - HashMapFree(head_table); - head_table = NULL; - pthread_mutex_unlock(&head_lock); - pthread_mutex_destroy(&head_lock); -} - - -static pthread_mutex_t oid_lock; -static HashMap *oid_table = NULL; - -void -ParseeInitialiseOIDTable(void) -{ - if (oid_table) - { - return; - } - pthread_mutex_init(&oid_lock, NULL); - pthread_mutex_lock(&oid_lock); - oid_table = HashMapCreate(); - pthread_mutex_unlock(&oid_lock); -} -void -ParseePushOIDTable(char *muc, char *bare) -{ - if (!muc || !bare || !oid_table) - { - return; - } - pthread_mutex_lock(&oid_lock); - bare = StrDuplicate(bare); - Free(HashMapSet(oid_table, muc, bare)); - pthread_mutex_unlock(&oid_lock); -} -char * -ParseeLookupOID(char *muc) -{ - char *bare; - if (!muc || !oid_table) - { - return NULL; - } - pthread_mutex_lock(&oid_lock); - bare = StrDuplicate(HashMapGet(oid_table, muc)); - pthread_mutex_unlock(&oid_lock); - - return bare; -} -void -ParseeDestroyOIDTable(void) -{ - char *key; - void *val; - if (!oid_table) - { - return; - } - pthread_mutex_lock(&oid_lock); - while (HashMapIterate(oid_table, &key, &val)) - { - Free(val); - } - HashMapFree(oid_table); - oid_table = NULL; - pthread_mutex_unlock(&oid_lock); - pthread_mutex_destroy(&oid_lock); -} - diff --git a/src/Parsee/Logo.c b/src/Parsee/Logo.c new file mode 100644 index 0000000..30885d9 --- /dev/null +++ b/src/Parsee/Logo.c @@ -0,0 +1,25 @@ +#include + +#include + +const char *parsee_ascii[PARSEE_ASCII_LINES] = +{ + " =+======", + " || | _ _/__----", + " / || \\ ==+= _/_____\\_", + " | || | -|- L___J ", + "_/ || \\_ ||| .______\\", + " || | | | |.____.|", + " || / | \\ |L____||", + " _// | | J" +}; + +void +ParseePrintASCII(void) +{ + size_t i; + for (i = 0; i < PARSEE_ASCII_LINES; i++) + { + Log(LOG_INFO, "%s", parsee_ascii[i]); + } +} diff --git a/src/Parsee/SHA.c b/src/Parsee/SHA.c new file mode 100644 index 0000000..9cd4484 --- /dev/null +++ b/src/Parsee/SHA.c @@ -0,0 +1,57 @@ +#include + +#include +#include + +static char * +ToHex(unsigned char *input, size_t length) +{ + char *hex = Malloc(length * 2 + 1); + size_t i; + + for (i = 0; i < length; i++) + { + const char *table = + "0123456789abcdef"; + unsigned char byte = input[i]; + hex[2 * i] = table[(byte >> 4) & 0xF]; + hex[2*i+1] = table[(byte >> 0) & 0xF]; + } + + hex[length * 2] = '\0'; + + return hex; +} + +char * +ParseeSHA256(char *string) +{ + unsigned char *sha; + char *returnString; + if (!string) + { + return NULL; + } + + sha = Sha256(string); + returnString = ToHex(sha, 32); + Free(sha); + + return returnString; +} +char * +ParseeSHA1(char *string) +{ + unsigned char *sha; + char *returnString; + if (!string) + { + return NULL; + } + + sha = Sha1(string); + returnString = ToHex(sha, 20); + Free(sha); + + return returnString; +} diff --git a/src/Parsee/Tables/Affiliation.c b/src/Parsee/Tables/Affiliation.c new file mode 100644 index 0000000..e3b5f55 --- /dev/null +++ b/src/Parsee/Tables/Affiliation.c @@ -0,0 +1,106 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t affi_lock; +static HashMap *affi_table = NULL; + +typedef struct XMPPStatus { + char *role; + char *affiliation; +} XMPPStatus; +static XMPPStatus * +CreateStatus(char *role, char *affiliation) +{ + XMPPStatus *ret; + if (!role || !affiliation) + { + return NULL; + } + + ret = Malloc(sizeof(*ret)); + ret->role = StrDuplicate(role); + ret->affiliation = StrDuplicate(affiliation); + + return ret; +} +static void +FreeStatus(XMPPStatus *status) +{ + if (!status) + { + return; + } + + Free(status->affiliation); + Free(status->role); + Free(status); +} + +void +ParseeInitialiseAffiliationTable(void) +{ + if (affi_table) + { + return; + } + pthread_mutex_init(&affi_lock, NULL); + pthread_mutex_lock(&affi_lock); + affi_table = HashMapCreate(); + pthread_mutex_unlock(&affi_lock); +} +void +ParseePushAffiliationTable(char *user, char *affi, char *role) +{ + XMPPStatus *status; + if (!user || !affi || !role) + { + return; + } + pthread_mutex_lock(&affi_lock); + + status = CreateStatus(role, affi); + FreeStatus(HashMapSet(affi_table, user, status)); + + pthread_mutex_unlock(&affi_lock); +} +bool +ParseeLookupAffiliation(char *user, char **affiliation, char **role) +{ + XMPPStatus *status; + if (!user || !affiliation || !role) + { + return false; + } + pthread_mutex_lock(&affi_lock); + + status = HashMapGet(affi_table, user); + *affiliation = StrDuplicate(status ? status->affiliation : NULL); + *role = StrDuplicate(status ? status->role : NULL); + + pthread_mutex_unlock(&affi_lock); + return !!status; +} +void +ParseeDestroyAffiliationTable(void) +{ + char *key; + void *val; + if (!affi_table) + { + return; + } + pthread_mutex_lock(&affi_lock); + while (HashMapIterate(affi_table, &key, &val)) + { + FreeStatus(val); + } + HashMapFree(affi_table); + affi_table = NULL; + pthread_mutex_unlock(&affi_lock); + pthread_mutex_destroy(&affi_lock); +} + diff --git a/src/Parsee/Tables/HeadTable.c b/src/Parsee/Tables/HeadTable.c new file mode 100644 index 0000000..f910af5 --- /dev/null +++ b/src/Parsee/Tables/HeadTable.c @@ -0,0 +1,67 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t head_lock; +static HashMap *head_table = NULL; +void +ParseeInitialiseHeadTable(void) +{ + if (head_table) + { + return; + } + pthread_mutex_init(&head_lock, NULL); + pthread_mutex_lock(&head_lock); + head_table = HashMapCreate(); + pthread_mutex_unlock(&head_lock); +} +void +ParseePushHeadTable(char *room, char *event) +{ + if (!room || !event || !head_table) + { + return; + } + pthread_mutex_lock(&head_lock); + event = StrDuplicate(event); + Free(HashMapSet(head_table, room, event)); + pthread_mutex_unlock(&head_lock); +} +char * +ParseeLookupHead(char *room) +{ + char *event; + if (!room || !head_table) + { + return NULL; + } + pthread_mutex_lock(&head_lock); + event = StrDuplicate(HashMapGet(head_table, room)); + pthread_mutex_unlock(&head_lock); + + return event; +} +void +ParseeDestroyHeadTable(void) +{ + char *key; + void *val; + if (!head_table) + { + return; + } + pthread_mutex_lock(&head_lock); + while (HashMapIterate(head_table, &key, &val)) + { + Free(val); + } + HashMapFree(head_table); + head_table = NULL; + pthread_mutex_unlock(&head_lock); + pthread_mutex_destroy(&head_lock); +} + diff --git a/src/Parsee/Tables/JIDTable.c b/src/Parsee/Tables/JIDTable.c new file mode 100644 index 0000000..e5663c9 --- /dev/null +++ b/src/Parsee/Tables/JIDTable.c @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t lock; +static HashMap *jid_table = NULL; + +void +ParseeInitialiseJIDTable(void) +{ + if (jid_table) + { + return; + } + pthread_mutex_init(&lock, NULL); + pthread_mutex_lock(&lock); + jid_table = HashMapCreate(); + pthread_mutex_unlock(&lock); +} +void +ParseePushJIDTable(char *muc, char *bare) +{ + if (!muc || !bare || !jid_table) + { + return; + } + pthread_mutex_lock(&lock); + bare = ParseeTrimJID(bare); + Free(HashMapSet(jid_table, muc, bare)); + pthread_mutex_unlock(&lock); +} +char * +ParseeLookupJID(char *muc) +{ + char *bare; + if (!muc || !jid_table) + { + return NULL; + } + pthread_mutex_lock(&lock); + bare = StrDuplicate(HashMapGet(jid_table, muc)); + pthread_mutex_unlock(&lock); + + if (!bare) + { + bare = StrDuplicate(muc); + } + return bare; +} +void +ParseeDestroyJIDTable(void) +{ + char *key; + void *val; + if (!jid_table) + { + return; + } + pthread_mutex_lock(&lock); + while (HashMapIterate(jid_table, &key, &val)) + { + Free(val); + } + HashMapFree(jid_table); + jid_table = NULL; + pthread_mutex_unlock(&lock); + pthread_mutex_destroy(&lock); +} + diff --git a/src/Parsee/Tables/NickTable.c b/src/Parsee/Tables/NickTable.c new file mode 100644 index 0000000..1778927 --- /dev/null +++ b/src/Parsee/Tables/NickTable.c @@ -0,0 +1,100 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t nick_lock; +static HashMap *nick_table = NULL; + +static char * +GenerateKey(char *muc, char *mxid) +{ + char *concatStr; + char *hexDigest; + if (!muc || !mxid) + { + return NULL; + } + + concatStr = StrConcat(3, muc, ":", mxid); + hexDigest = ParseeSHA256(concatStr); + + Free (concatStr); + return hexDigest; +} + +void +ParseeInitialiseNickTable(void) +{ + if (nick_table) + { + return; + } + pthread_mutex_init(&nick_lock, NULL); + pthread_mutex_lock(&nick_lock); + nick_table = HashMapCreate(); + pthread_mutex_unlock(&nick_lock); +} +void +ParseePushNickTable(char *muc, char *mxid, char *nick) +{ + char *key; + if (!muc || !mxid || !nick_table) + { + return; + } + pthread_mutex_lock(&nick_lock); + + key = GenerateKey(muc, mxid); + nick = StrDuplicate(nick); + if (nick) + { + Free(HashMapSet(nick_table, key, nick)); + } + else + { + Free(HashMapDelete(nick_table, key)); + } + Free(key); + + pthread_mutex_unlock(&nick_lock); +} +char * +ParseeLookupNick(char *muc, char *mxid) +{ + char *ret, *key; + if (!muc || !nick_table) + { + return NULL; + } + pthread_mutex_lock(&nick_lock); + + key = GenerateKey(muc, mxid); + ret = HashMapGet(nick_table, key); + Free(key); + + pthread_mutex_unlock(&nick_lock); + return ret; +} +void +ParseeDestroyNickTable(void) +{ + char *key; + void *val; + if (!nick_table) + { + return; + } + pthread_mutex_lock(&nick_lock); + while (HashMapIterate(nick_table, &key, &val)) + { + Free(val); + } + HashMapFree(nick_table); + nick_table = NULL; + pthread_mutex_unlock(&nick_lock); + pthread_mutex_destroy(&nick_lock); +} + diff --git a/src/Parsee/Tables/OIDTable.c b/src/Parsee/Tables/OIDTable.c new file mode 100644 index 0000000..29569c8 --- /dev/null +++ b/src/Parsee/Tables/OIDTable.c @@ -0,0 +1,68 @@ +#include + +#include +#include +#include +#include + +static pthread_mutex_t oid_lock; +static HashMap *oid_table = NULL; + +void +ParseeInitialiseOIDTable(void) +{ + if (oid_table) + { + return; + } + pthread_mutex_init(&oid_lock, NULL); + pthread_mutex_lock(&oid_lock); + oid_table = HashMapCreate(); + pthread_mutex_unlock(&oid_lock); +} +void +ParseePushOIDTable(char *muc, char *bare) +{ + if (!muc || !bare || !oid_table) + { + return; + } + pthread_mutex_lock(&oid_lock); + bare = StrDuplicate(bare); + Free(HashMapSet(oid_table, muc, bare)); + pthread_mutex_unlock(&oid_lock); +} +char * +ParseeLookupOID(char *muc) +{ + char *bare; + if (!muc || !oid_table) + { + return NULL; + } + pthread_mutex_lock(&oid_lock); + bare = StrDuplicate(HashMapGet(oid_table, muc)); + pthread_mutex_unlock(&oid_lock); + + return bare; +} +void +ParseeDestroyOIDTable(void) +{ + char *key; + void *val; + if (!oid_table) + { + return; + } + pthread_mutex_lock(&oid_lock); + while (HashMapIterate(oid_table, &key, &val)) + { + Free(val); + } + HashMapFree(oid_table); + oid_table = NULL; + pthread_mutex_unlock(&oid_lock); + pthread_mutex_destroy(&oid_lock); +} + diff --git a/src/Parsee/User.c b/src/Parsee/User.c index e1e2710..6e673bb 100644 --- a/src/Parsee/User.c +++ b/src/Parsee/User.c @@ -1,42 +1,46 @@ #include #include +#include #include #include #include -#include #include #include #include #include +#include + bool ParseeIsPuppet(const ParseeConfig *conf, char *user) { - char *localpart; + UserID *id; bool flag = true; size_t len; - if (!user || !conf || *user != '@') + if (!user || + !conf || + *user != '@' || + !(id = MatrixParseID(user))) { return false; } - localpart = user + 1; len = strlen(conf->namespace_base); - if (strncmp(localpart, conf->namespace_base, len)) + if (strncmp(id->localpart, conf->namespace_base, len)) { flag = false; } - len = strlen(conf->sender_localpart); - if (!strncmp(localpart, conf->sender_localpart, len)) + if (StrEquals(id->localpart, conf->sender_localpart)) { flag = true; } - /* TODO: Check serverpart. 2 Parsee instances may be running in the same - * room. */ + flag = flag && StrEquals(id->server, conf->server_base); + + Free(id); return flag; } char * @@ -93,7 +97,7 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid) data_start = jid_flags; while (*data_start && *data_start != '_') { - /* TODO: Make this a macro */ + /* TODO: Get rid of this */ if (*data_start == 'l') { plain_jid = true; @@ -123,7 +127,7 @@ ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias) data_start = jid_flags; while (*data_start && *data_start != '_') { - /* TODO: Make this a macro */ + /* TODO: Get rid of this */ if (*data_start == 'm') { plain_jid = true; @@ -144,14 +148,15 @@ char * ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim) { char *ret, *tmp; - size_t i; + size_t i, len; if (!c || !jid) { return NULL; } ret = StrConcat(2, c->namespace_base, "_l_"); - for (i = 0; i < strlen(jid); i++) + len = strlen(jid); + for (i = 0; i < len; i++) { char cpy = jid[i]; char cs[4] = { 0 }; @@ -161,7 +166,7 @@ ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim) /* RID: Break everything and die. */ break; } - if (islower(*cs) || isalnum(*cs) || *cs == '_' || + if (islower((int) *cs) || isalnum((int) *cs) || *cs == '_' || *cs == '=' || *cs == '-' || *cs == '/' || *cs == '+' || *cs == '.') { @@ -190,7 +195,7 @@ char * ParseeGetLocal(char *mxid) { char *cpy; - size_t i; + size_t i, len; if (!mxid) { return NULL; @@ -200,12 +205,14 @@ ParseeGetLocal(char *mxid) return StrDuplicate(mxid); } - mxid++; - cpy = Malloc(strlen(mxid) + 1); - memset(cpy, '\0', strlen(mxid) + 1); - memcpy(cpy, mxid, strlen(mxid)); + len = strlen(mxid); - for (i = 0; i < strlen(mxid); i++) + mxid++; + cpy = Malloc(len + 1); + memset(cpy, '\0', len + 1); + memcpy(cpy, mxid, len); + + for (i = 0; i < len; i++) { if (cpy[i] == ':') { @@ -221,19 +228,19 @@ char * ParseeEncodeMXID(char *mxid) { char *ret; - size_t i, j; + size_t i, j, len; if (!mxid) { return NULL; } /* Worst case scenario of 3-bytes the char */ - ret = Malloc(strlen(mxid) * 3 + 1); - for (i = 0, j = 0; i < strlen(mxid); i++) + len = strlen(mxid); + ret = Malloc(len * 3 + 1); + for (i = 0, j = 0; i < len; i++) { char src = mxid[i]; - /* TODO: More robust system */ if (src <= 0x20 || (src == '"' || src == '&' || src == '\'' || src == '/' || @@ -295,19 +302,17 @@ char * ParseeGetDMID(char *mxid, char *jid) { char *concat, *sha; - unsigned char *raw; if (!mxid || !jid) { return NULL; } + /* We really don't care about safety here. */ jid = ParseeTrimJID(jid); concat = StrConcat(2, mxid, jid); - raw = Sha1(concat); - sha = ShaToHex(raw); + sha = ParseeSHA1(concat); Free(concat); - Free(raw); Free(jid); return sha; @@ -353,19 +358,34 @@ ParseePushDMRoom(ParseeData *d, char *mxid, char *jid, char *r) Free(dmid); return; } +void +ParseeDeleteDM(ParseeData *d, char *mxid, char *jid) +{ + char *dmid; + if (!d || !mxid || !jid) + { + return; + } + + dmid = ParseeGetDMID(mxid, jid); + DbDelete(d->db, 2, "users", dmid); + Free(dmid); + return; +} char * ParseeTrimJID(char *jid) { char *ret; - size_t i; + size_t i, len; if (!jid) { return NULL; } ret = StrDuplicate(jid); - for (i = 0; i < strlen(ret); i++) + len = strlen(ret); + for (i = 0; i < len; i++) { if (ret[i] == '/') { @@ -519,7 +539,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id) return ret; } - void ParseeSendPresence(ParseeData *data) { @@ -538,10 +557,16 @@ ParseeSendPresence(ParseeData *data) while (HashMapIterate(mucs, &muc, (void **) &val)) { char *rev = StrConcat(2, muc, "/parsee"); + char *chat_id = ParseeGetFromMUCID(data, muc); + DbRef *chat = DbLockIntent(data->db, DB_HINT_READONLY, 2, "chats", chat_id); + 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); - XMPPJoinMUC(data->jabber, "parsee", rev); + XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false); + DbUnlock(data->db, chat); + Free(chat_id); Free(rev); } DbUnlock(data->db, ref); @@ -663,11 +688,13 @@ end: #include char * -ParseeToUnauth(ParseeData *data, char *mxc) +ParseeToUnauth(ParseeData *data, char *mxc, char *filename) { Uri *url = NULL; char *ret; -#define PAT "%s/_matrix/client/v1/media/download/%s%s" + char *key, *hmac; +#define PAT "%s/media/%s%s?hmac=%s" +#define PATF "%s/media/%s%s/%s?hmac=%s" size_t l; if (!data || !mxc) { @@ -684,19 +711,51 @@ ParseeToUnauth(ParseeData *data, char *mxc) return NULL; } - /* TODO: HTTPS */ - l = snprintf(NULL, 0, - PAT, - data->config->media_base, - url->host, url->path - ); + key = StrConcat(2, url->host, url->path); + 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); + } ret = Malloc(l + 3); - snprintf(ret, l + 1, - PAT, - data->config->media_base, - url->host, url->path - ); + if (!filename) + { + snprintf(ret, l + 1, + PAT, + data->config->media_base, + url->host, url->path, + hmac + ); + } + else + { + snprintf(ret, l + 1, + PATF, + data->config->media_base, + url->host, url->path, filename, + hmac + ); + } UriFree(url); + Free(hmac); return ret; } diff --git a/src/Parsee/Utils/Achievement.c b/src/Parsee/Utils/Achievement.c new file mode 100644 index 0000000..136f6a0 --- /dev/null +++ b/src/Parsee/Utils/Achievement.c @@ -0,0 +1,26 @@ +#include + +#include + +#include + +void +ParseeAchievement(const char *func, const char *msg, bool die) +{ + Log(LOG_ERR, "=========== Achievement GET! ==========="); + Log(LOG_ERR, "%s: %s.", func, msg); + Log(LOG_ERR, "THIS IS, LET'S SAY, NOT GOOD, AND A SIGN OF "); + Log(LOG_ERR, "A PROGRAMMER ERROR. PLEASE KILLALL -9 PARSEE."); + Log(LOG_ERR, ""); + Log(LOG_ERR, "YOU, HOWEVER, GET TO WIN AN AWARD FOR THIS."); + Log(LOG_ERR, "I AM SIMPLY JEALOUS OF YOU GETTING SUCH A "); + Log(LOG_ERR, "GOOD ERROR MESSAGE."); + Log(LOG_ERR, "=========== Achievement GET! ==========="); + + if (die) + { + abort(); + } +} + + diff --git a/src/Parsee/Utils/Arguments.c b/src/Parsee/Utils/Arguments.c new file mode 100644 index 0000000..9d4c897 --- /dev/null +++ b/src/Parsee/Utils/Arguments.c @@ -0,0 +1,53 @@ +#include + +#include +#include +#include + +void +ParseeGenerateHelp(const Argument *list) +{ + if (!list) + { + return; + } + + while (!list->end) + { + char *str = list->value_req ? + StrConcat(3, " [", list->value_descr, "]") : + StrDuplicate(""); + Log(LOG_INFO, "-%c%s", list->argument, str); + LogConfigIndent(LogConfigGlobal()); + Log(LOG_INFO, "%s", list->description); + LogConfigUnindent(LogConfigGlobal()); + list++; + + Free(str); + } + return; +} +char * +ParseeGenerateGetopt(const Argument *list) +{ + char *ret = NULL, *tmp = NULL; + if (!list) + { + return NULL; + } + + while (!list->end) + { + char buffer[3] = { + list->argument, list->value_req ? ':' : '\0', + '\0' + }; + + tmp = ret; + ret = StrConcat(2, ret, buffer); + Free(tmp); + + list++; + } + return ret; +} diff --git a/src/Parsee/Utils/Formatting.c b/src/Parsee/Utils/Formatting.c new file mode 100644 index 0000000..ec95aef --- /dev/null +++ b/src/Parsee/Utils/Formatting.c @@ -0,0 +1,323 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +typedef struct XMPPFlags { + bool quote; +} XMPPFlags; +static char * +XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags) +{ + char *xepd = NULL, *tmp = NULL; + + size_t i; + XMLElement *child; + char *reply_id = GrabString( + event, + 4, "content", + "m.relates_to", "m.in_reply_to", "event_id" + ); + char *room_id = JsonValueAsString(HashMapGet(event, "room_id")); + HashMap *referenced; + char *subxep; +#define Concat(strp) do \ + { \ + size_t cidx; \ + size_t len = strp ? strlen(strp) : 0; \ + for (cidx = 0; cidx < len; cidx++) \ + { \ + char cch[2]; \ + cch[0] = strp[cidx]; \ + cch[1] = '\0'; \ + char nch = *cch ? strp[cidx+1] : '\0'; \ + bool c = *cch == '\n' && nch != '>'; \ + if (c && flags.quote) \ + { \ + tmp = xepd; \ + xepd = StrConcat(2, xepd, "\n>"); \ + Free(tmp); \ + continue; \ + } \ + tmp = xepd; \ + xepd = StrConcat(2, xepd, cch); \ + Free(tmp); \ + } \ + } \ + while (0) + switch (elem ? elem->type : -1) + { + case XML_ELEMENT_DATA: + Concat(elem->data); + break; + case XML_ELEMENT_TAG: + if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong")) + { + Concat("*"); + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + Concat("*"); + } + else if (StrEquals(elem->name, "em")) + { + Concat("_"); + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + Concat("_"); + } + else if (StrEquals(elem->name, "code")) + { + Concat("`"); + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + Concat("`"); + } + else if (StrEquals(elem->name, "mx-reply")) + { + char *str; + referenced = ASFind(ParseeConfigGet(), room_id, reply_id); + str = JsonValueAsString( + JsonGet(referenced, 2, "content", "body") + ); + if (!str) + { + JsonFree(referenced); + return xepd; + } + Concat(">"); + flags.quote = true; + Concat(str); + flags.quote = false; + Concat("\n"); + JsonFree(referenced); + } + else if (StrEquals(elem->name, "blockquote")) + { + Concat(">"); + flags.quote = true; + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + flags.quote = false; + Concat("\n"); + } + else if (StrEquals(elem->name, "br")) + { + Concat("\n"); + /* HTML fucking SUCKS */ + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + if (i != 0) + { + 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. */ + 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); + + Concat(subxep); + Free(subxep); + } + Concat(" < "); + Concat(href); + Concat(" >"); + } + UriFree(pref); + } + else + { + for (i = 0; i < ArraySize(elem->children); i++) + { + child = ArrayGet(elem->children, i); + subxep = XMPPifyElement(conf, event, child, flags); + + Concat(subxep); + Free(subxep); + } + } + break; + default: + break; + } + 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) +{ + char *type, *format, *html; + char *xepd = NULL; + XMLElement *elem; + + XMPPFlags flags; + if (!event) + { + return NULL; + } + + /* Check if it is a message event. */ + type = JsonValueAsString(HashMapGet(event, "type")); + if (!StrEquals(type, "m.room.message")) + { + return NULL; + } + + if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html")) + { + /* Settle for the raw body instead. */ + char *body = GetRawBody(event); + return StrDuplicate(body); + } + + html = GetHTMLBody(event); + + html = StrConcat(3, "", html, ""); + elem = XMLCDecode(StrStreamReader(html), true, true); + if (!elem) + { + /* Settle for the raw body instead. + * TODO: Have the parser be more leinent on errors in HTML mode. */ + char *body = GetRawBody(event); + Free(html); + return StrDuplicate(body); + } + + flags.quote = false; + xepd = XMPPifyElement(data ? data->config : NULL, event, elem, flags); + + XMLFreeElement(elem); + Free(html); + + return xepd; +} + +char * +ParseeGenerateMTO(char *common_id) +{ + char *matrix_to; + if (!common_id) + { + return NULL; + } + + /* TODO: Is HttpUrlEncode okay? */ + common_id = HttpUrlEncode(common_id); + matrix_to = StrConcat(2, "https://matrix.to/#/", common_id); + Free(common_id); + + return matrix_to; +} diff --git a/src/Parsee/Utils/Nofly.c b/src/Parsee/Utils/Nofly.c new file mode 100644 index 0000000..439c7d0 --- /dev/null +++ b/src/Parsee/Utils/Nofly.c @@ -0,0 +1,97 @@ +#include + +#include +#include + +#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) +{ + DbRef *ref; + HashMap *j, *obj; + if (!data || !glob) + { + return; + } + + ref = DbLock(data->db, 1, "global_bans"); + if (!ref) + { + ref = DbCreate(data->db, 1, "global_bans"); + } + + j = DbJson(ref); + + obj = HashMapCreate(); + if (reason) + { + HashMapSet(obj, "reason", JsonValueString(reason)); + } + HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis())); + JsonValueFree(HashMapSet(j, glob, JsonValueObject(obj))); + + DbUnlock(data->db, ref); +} +bool +ParseeManageBan(ParseeData *data, char *user, char *room) +{ + DbRef *ref; + HashMap *j; + char *key; + JsonValue *val; + bool banned = false , matches = false; + if (!data || !user) + { + return false; + } + + ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "global_bans"); + j = DbJson(ref); + while (HashMapIterate(j, &key, (void **) &val)) + { + HashMap *obj = JsonValueAsObject(val); + if (matches) + { + continue; + } + if (GlobMatches(key, user)) + { + banned = true; + matches = true; + if (room) + { + /* TODO: Use the object to set the reason */ + ASBan(data->config, room, user); + (void) obj; + } + } + } + DbUnlock(data->db, ref); + + return banned; +} + diff --git a/src/Parsee/Utils/String.c b/src/Parsee/Utils/String.c new file mode 100644 index 0000000..c054d13 --- /dev/null +++ b/src/Parsee/Utils/String.c @@ -0,0 +1,95 @@ +#include + +#include +#include + +#include + +#include +#include + +int +ParseeFindDatastart(char *data) +{ + char *startline; + bool found = false; + if (!data) + { + return 0; + } + + startline = data; + while (startline) + { + char *endline = strchr(startline, '\n'); + + if (*startline != '>') + { + found = true; + break; + } + + startline = endline ? endline + 1 : NULL; + } + + if (!found) + { + return 0; + } + + return (int) (startline - data); +} +int +ParseeFindDatastartU(char *data) +{ + Unistr *str; + size_t ret; + if (!data) + { + return 0; + } + + str = UnistrCreate(data); + ret = UnistrGetOffset(str, (uint32_t) '>'); + UnistrFree(str); + + return (int) ret; +} + +char * +ParseeStringifyDate(uint64_t millis) +{ + uint64_t rest = millis; + uint64_t hours, minutes, seconds; + char *hs, *ms, *ss, *out; + + hours = rest / (1 HOURS); + rest = rest % (1 HOURS); + + minutes = rest / (1 MINUTES); + rest = rest % (1 MINUTES); + + seconds = rest / (1 SECONDS); + + hs = StrInt(hours); + ms = StrInt(minutes); + ss = StrInt(seconds); + + out = StrConcat(8, + hours ? hs : "", + hours ? " hours" : "", + (hours && minutes) ? ", " : "", + + minutes ? ms : "", + minutes ? " minutes" : "", + (minutes && seconds) ? ", " : "", + + seconds ? ss : "", + seconds ? " seconds" : "" + ); + Free(hs); + Free(ms); + Free(ss); + + return out; +} diff --git a/src/Parsee/Versions.c b/src/Parsee/Versions.c new file mode 100644 index 0000000..b5398c0 --- /dev/null +++ b/src/Parsee/Versions.c @@ -0,0 +1,53 @@ +#include + +#include +#include + +bool +ParseeIsCompatible(char *ver1, char *ver2) +{ + char *major1 = NULL; + char *major2 = NULL; + char *tmp; + if (!ver1 || !ver2) + { + return false; + } + + /* Check if one of them is a "flipped"(joke) version. If so, + * then the user should definitely NOT try funny things with + * their data. */ + if (*ver1 == '-' || *ver2 == '-') + { + return false; + } + +#define GetMajor(v) do \ + { \ + while (*ver##v != '.') \ + { \ + char cb[2]; \ + cb[0] = *ver##v; \ + cb[1] = '\0'; \ + tmp = major##v; \ + major##v = StrConcat(2, major##v, cb); \ + Free(tmp); \ + ver##v++; \ + } \ + } \ + while (0) + + GetMajor(1); + GetMajor(2); + + if (!StrEquals(major1, major2)) + { + Free(major1); + Free(major2); + return false; + } + + Free(major1); + Free(major2); + return true; +} diff --git a/src/Routes/Media.c b/src/Routes/Media.c index 45d79d0..498eba2 100644 --- a/src/Routes/Media.c +++ b/src/Routes/Media.c @@ -2,37 +2,80 @@ #include #include +#include #include #include #include +#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; HttpClientContext *cctx; - HashMap *reqh; + HashMap *reqh, *params; char *server = ArrayGet(arr, 0); char *identi = ArrayGet(arr, 1); - char *path, *key, *val; + char *key, *val; + char *hmac, *chkmak = NULL; - /* TODO: Make it check the DB for its validicity. "Purging" would be useful. - */ - if (!server || !identi) + params = HttpRequestParams(args->ctx); + hmac = HashMapGet(params, "hmac"); + + /* TODO: Make it check the DB for its validicity. "Purging" would be + * useful, alongside checking if someone isn't just a little idiotic. */ { - HttpResponseStatus(args->ctx, HTTP_BAD_REQUEST); - return MatrixCreateError("M_NOT_YET_UPLOADED", "No server/identifier"); + char *concat = StrConcat(3, server, "/", identi); + chkmak = ParseeHMACS(args->data->id, concat); + Free(concat); } + if (!server || !identi || !hmac || !StrEquals(hmac, chkmak)) + { + char *err = + hmac && StrEquals(hmac, chkmak) ? + "No server/identifier/HMAC code" : + "Hah! You _dirty_ little liar! Try a little harder!"; + Free(chkmak); + HttpResponseStatus(args->ctx, HTTP_BAD_REQUEST); + return MatrixCreateError("M_NOT_YET_UPLOADED", err); + } + Free(chkmak); - 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); + /* Proxy the media through an authenticated endpoint if the HMAC + * is valid. */ + cctx = TryDownload(args->data, server, identi); reqh = HttpResponseHeaders(cctx); while (HashMapIterate(reqh, &key, (void **) &val)) { @@ -45,8 +88,6 @@ 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 88dc533..1d4191e 100644 --- a/src/Routes/Ping.c +++ b/src/Routes/Ping.c @@ -25,13 +25,12 @@ RouteHead(RoutePing, arr, argp) ); goto end; } - Log(LOG_INFO, "Pong!"); RequestJSON(); - /* TODO: Load ping info */ response = HashMapCreate(); end: + (void) arr; JsonFree(request); return response; } diff --git a/src/Routes/Root.c b/src/Routes/Root.c index 578dc20..0ddb731 100644 --- a/src/Routes/Root.c +++ b/src/Routes/Root.c @@ -41,13 +41,26 @@ GetRandomQuote(void) "We truly live in a " CODE "...", "You truly lack the Desire Drive for this!", "Eeh? Bifrost mode? Only bad servers use Bifrost mode!", + "'Wow parsee can save the world' - said a wise guy", "As small as a dwarf, and can run on your pie!", "It's all wicked unsafe, memory slow 🚀🚀🚀🚀", "XMPP kinda sucks.", "Matrix kinda sucks.", - "Throw jabs!" + "Throw jabs!", + "'Won't you hold my <presence/> safe?' - Kanako", + + NAME ": the federated world's little little kobashi", + + "Go take a look at your stanzas!", + "Go take a look at your objects!", + + "DEC Alpha AXP-Certified!", + + "this is the moment parsee started parsing or smth idk" + " - another wise person", + "Ah, merde, mon TGV est en retard de 53 minutes !" }; const size_t count = sizeof(quotes)/sizeof(*quotes); @@ -69,6 +82,7 @@ RouteHead(RouteRoot, arr, argp) { P("%s Lander", NAME); P(""); + P("", media_parsee_logo); P("