Compare commits

...

46 commits

Author SHA1 Message Date
LDA
e331d110c7 [FIX] URL encode filenames 2025-06-22 14:00:12 +02:00
LDA
e617b5e8d4 [FIX/WIP] Unique redactions for moderation stanzas
Also, I'll consider Parsee dev to be dead until July 2026. J'ai le bac
et déja un projet à faire, et si je peux même pas me concentrer sur mon
futur en ne comptant pas Parsee.

Feel free to make a good XMPP bridge, I don't really even want to touch
either Matrix or XMPP anymore, you know?
2025-04-28 02:18:57 +02:00
LDA
e3749817d3 [MOD/FIX] Be even more strict with Unicode filtering
Now, only ASCII characters get to fit.
2025-04-13 10:49:40 +00:00
LDA
7b8ed08e88 [ADD] Optionally allow instance admins to ignore m.notice events 2025-04-13 10:39:15 +00:00
LDA
45250096ad [FIX] argh 2025-04-13 10:23:51 +00:00
LDA
f19ff2274b Merge branch 'master' of https://git.kappach.at/lda/Parsee 2025-03-09 11:06:42 +00:00
LDA
7454c8c597 [FIX] Fix a few bugs about edits and XEP-393 formatting 2025-03-09 11:05:55 +00:00
lda
e21ebed134 [META/ADD] Add Guix to the CHANGELOG
quick und dirty
2025-02-23 22:38:38 +00:00
lda
4eaec4687e [MERGE] Add Guix support (see #18)
Reviewed-on: https://git.kappach.at/lda/Parsee/pulls/18
2025-02-23 22:36:36 +00:00
Hank
680b4261c2 [ADD] small documentation paragraph 2025-02-23 21:48:15 +01:00
Hank
44473878d0 [ADD] add parsee guix package 2025-02-23 21:48:15 +01:00
Hank
b50b9bd615 [FIX] pin cytoplasm to the latest commit 2025-02-23 21:48:13 +01:00
Hank
a2c1de52dc [ADD] new guix.scm with cytoplasm 2025-02-23 21:33:42 +01:00
Hank
6762d1c1ce [FIX/WIP] remove .guix from gitignore 2025-02-23 21:33:42 +01:00
lda
40e3242465 [ADD] Try to renegociate an XMPP stream on failure 2025-02-19 16:14:30 +00:00
lda
b78f7b6ab3 [ADD/TOOL] Add -j flag for parsee-config 2025-02-19 15:38:22 +00:00
lda
fd1b3499b6 [ADD] Allow the main component to be used to issue commands 2025-02-19 15:32:52 +00:00
lda
fb44ad4bf6 [FIX] Go back to using room IDs
I already gave up on urandom anyways.
2025-02-18 22:52:44 +00:00
lda
0a4aa45de5 [FIX] Add proper includes 2025-02-17 20:24:06 +00:00
lda
cdbdc9345a [MOD] Don't always add two newlines 2025-02-17 17:44:09 +00:00
lda
c96f0486ff [FIX/WIP] Chrck URI before doing anything funny 2025-02-17 08:30:18 +00:00
lda
e2b014d000 [FIX/WIP] Fix replies and try fixing a bug with MUCs 2025-02-15 08:44:25 +00:00
lda
d12255b226 [FIX/WIP] Do not use room ID for plumbing 2025-02-14 21:58:42 +00:00
lda
43175e32e5 [FIX/WIP] Log out error info on ASSend 2025-02-14 20:28:49 +00:00
lda
3bef6afa5d [FIX/WIP] Try escaping room IDs
First attempt at dealing with #20
2025-02-14 19:10:26 +00:00
lda
71f3836ee1 [ADD] Use actual OS as information 2025-02-12 16:47:48 +00:00
lda
9a2d4188e2 [FIX] Do not put version in the OS field 2025-02-12 16:43:43 +00:00
lda
7f396a0379 [FIX] Write config to the proper field! 2025-02-12 08:33:05 +00:00
lda
1c51d57355 [ADD] Add accept_pings parameters
This is off by default, but highly encouraged for Synapse deployments
2025-02-12 08:24:18 +00:00
lda
176f390c4b [FIX] Use the puppet's name when pinged if possible 2025-02-11 17:18:01 +00:00
lda
f94db460ac [FIX/WIP] Fix annoying formatting bug
Fixes #16
2025-02-09 21:51:38 +00:00
lda
1936be0078 [FIX] Fix minor DB fuckup with tools
Used to open a flatfile database whenever it couldn't find an LMDB one,
which obviously caused *problems*
2025-02-08 08:45:21 +00:00
lda
c2536c2e84 [FIX] Log out some MUC information 2025-02-07 18:52:58 +00:00
lda
4f694129fc [FIX] Fix silly SEGV
Thanks, EP
2025-02-07 18:34:45 +00:00
LDA
110a1b695f [FIX] Fix the indenting 2025-02-05 08:58:25 +01:00
LDA
1e7d71f9f6 [ADD] Allow people to see component errors 2025-02-05 08:54:45 +01:00
LDA
b485298fbc [FIX] Log error if the config was not parsed 2025-01-29 19:15:23 +01:00
lda
f9de7f1750 [MOD/FIX] Be a bit more specific with component errors 2025-01-28 15:01:57 +00:00
lda
389870c5d3 [FIX] Ignore unavailable statuses
They aren't that useful, especially in MUCs.
2025-01-25 12:20:47 +00:00
lda
0facbaa5e5 [ADD/FIX/WIP] Allow reverting noflys, ignore MUC DMs (for now) 2025-01-07 15:14:47 +00:00
lda
5e1931a19f [MOD] Change media path, connect to component through another address 2025-01-06 17:09:36 +00:00
LDA
c433e31461 [DEL] Please enter a valid command
Please enter a valid command
2025-01-03 16:00:24 +01:00
LDA
c365681fcb [MOD] Notify MUCs about errors 2025-01-03 15:01:35 +01:00
LDA
86deab29af [FIX] Properly handle new contents with Matrix
No more ugly asterisks. It annoyed me enough.
2024-12-28 20:17:03 +01:00
LDA
5ddc5d3e5c [WIP/ADD] Try to separate discovery, make links nicer 2024-12-19 20:59:12 +01:00
LDA
2e566c73fc [MOD] Start not hardcoding discovery features
The eventual goal here is to have different disco replies for specific
kinds of JID, effectively allowing us to have MUCs. Yeah.
2024-11-29 13:58:49 +01:00
53 changed files with 1537 additions and 358 deletions

10
.gitignore vendored
View file

@ -4,10 +4,11 @@ parsee*
parsee
*.swp
.*
data
data/*
data*
data*/*
Makefile
configure
gmon.out
tools/out
tools/out/*
@ -24,3 +25,8 @@ tags
!.forgejo
!.forgejo/*
!.forgejo/**
!.guix
!.guix/*
!.guix/**

82
.guix/modules/parsee.scm Normal file
View file

@ -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

View file

@ -13,6 +13,22 @@ commit done between releases.
*There is currently no beta releases of Parsee*
## Alpha
### v0.3.0[lunar-rainbow] <XX/XX/2025>
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

View file

@ -18,12 +18,11 @@ A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solutio
and maybe as a testing ground for Cytoplasm features I sometimes add.
(Well, I'm *trying* to do that, at least.
Please scream at me if that fails(or just doesn't run on a overclocked Raspberry
Pi 4B, which, by the way, is literally where Parsee+XMPP is running for now.))
Please scream at me if that fails(or just doesn't run on a overclocked Raspberry Pi 4B))
### "Why not just use Matrix lol"
### "Why not just use XMPP lol"
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.
@ -44,6 +43,13 @@ Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendri
Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but
you can get away without those if you're adventurous).
### BUILDING WITH GUIX
If you have [Guix](https://guix.gnu.org/) installed, you can build Parsee using `guix package -f guix.scm`, or test it
using `guix shell -f guix.scm`. You can also generate a Docker/OCI image.
```sh
guix pack -f docker -S /bin=bin -L.guix/modules parsee
```
## RUNNING
First off, you may want to configure Parsee by running the `config` tool(generally named
`parsee-config` in most cases), with the correct flags, like here.

View file

@ -1,6 +1,6 @@
CODE=star-of-hope
CODE=lunar-rainbow
NAME=Parsee
VERSION=0.2.0
VERSION=0.3.0
BINARY=parsee
SOURCE=src
INCLUDES=src/include

View file

@ -90,22 +90,6 @@ string_cat(char *in1, char *in2)
return out;
}
static char *
trim_nl(char *in)
{
char *tc;
if (!in)
{
return NULL;
}
while ((tc = strrchr(in, '\n')))
{
*tc = '\0';
}
return in;
}
typedef struct str_array {
char **values;
@ -115,6 +99,10 @@ static str_array_t *
str_array_create(void)
{
str_array_t *ret = malloc(sizeof(*ret));
if (!ret)
{
return NULL;
}
ret->values = NULL;
ret->quantity = 0;
@ -216,36 +204,6 @@ failure:
free(line);
return NULL;
}
static int
exec_code(char *program, char *argv[])
{
pid_t forkRet;
if (!program || !argv)
{
return -1;
}
forkRet = fork();
if (forkRet == 0)
{
/* Child */
execvp(program, argv);
exit(0);
}
else
{
/* Parent */
int status;
if (waitpid(forkRet, &status, 0) == -1)
{
return -1;
}
return status;
}
/* We're not meant to ever be there, but TCC is stupid. */
return -1;
}
static char *
strchrn(char *s, char c)
@ -253,17 +211,6 @@ strchrn(char *s, char c)
s = strchr(s, c);
return s ? s+1 : NULL;
}
static char *
strchrl(char *s, char c)
{
char *s1 = NULL;
while ((s = strchr(s, c)))
{
s1 = s;
s++;
}
return s1;
}
static str_array_t *
split(char *dir)
{
@ -512,8 +459,7 @@ main_build(int argc, char *argv[])
{
FILE *makefile;
char *repo = cmd_stdout("git remote get-url origin");
size_t size, i;
ssize_t nread;
size_t i;
bool with_static = false, with_lmdb = false;
int opt;
str_array_t *sources, *images, *utils, *aya;
@ -546,6 +492,13 @@ main_build(int argc, char *argv[])
}
makefile = fopen("Makefile", "w");
if (!makefile)
{
fprintf(stderr, "Couldn't create Makefile.\n");
fprintf(stderr, "This isn't good, actually.\n");
return EXIT_FAILURE;
}
fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n");
fprintf(makefile, "# Ideally do not touch, unless you have a very ");
fprintf(makefile, "good reason to do it. \n\n");

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-config 1 "Parsee Utility" "star-of-hope"
.TH parsee-config 1 "Parsee Utility" "lunar-rainbow"
.SH NAME
parsee-config - generate a basic configuration file
@ -11,6 +11,7 @@ parsee-config
.B [-s SHARED_SECRET]
.B [-m MEDIA_URL]
.B [-J JABBER_HOST]
.B [-j JABBER_ADDR]
.B [-p JABBER_PORT]
.B [-d DATABASE]
.B [-M MAX_STANZA]
@ -34,6 +35,7 @@ $ parsee-config \\
-H 'blow.hole' \\
-s 'The Dark Shared Secret' \\
-J 'xmpp.blow.hole' \\
-j 'localhost' \\
-S 128
.fi
.if n \{\
@ -68,6 +70,12 @@ media. It must be publicly accessible (behind a reverse proxy to HTTP:7642)
.I JABBER_HOST
is used as the component host for Parsee.
.TP
.BR -j JABBER_ADDR
.I JABBER_ADDR
can optionally be used to change the hostname Parsee will try to contact
for XMPP. Users should ideally use localhost (or a hostname pointing to
the server itself), as XMPP component streams are not encrypted.
.TP
.BR -p JABBER_PORT
.I JABBER_PORT
is used as the component post for Parsee. Parsee uses it alongside

Binary file not shown.

Binary file not shown.

View file

@ -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)

1
guix.scm Symbolic link
View file

@ -0,0 +1 @@
.guix/modules/parsee.scm

View file

@ -150,7 +150,7 @@ ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha)
return false;
}
path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
@ -195,7 +195,7 @@ ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len)
return false;
}
path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);

View file

@ -25,11 +25,13 @@ ASInvite(const ParseeConfig *conf, char *id, char *invited)
"@", conf->sender_localpart,
":", conf->server_base
);
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/invite",
"?user_id=", bridge
);
Free(bridge);
Free(id);
ctx = ParseeCreateRequest(
conf,
@ -60,11 +62,13 @@ ASBan(const ParseeConfig *conf, char *id, char *banned)
"@", conf->sender_localpart,
":", conf->server_base
);
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/ban",
"?user_id=", bridge
);
Free(bridge);
Free(id);
ctx = ParseeCreateRequest(
conf,
@ -95,11 +99,13 @@ ASKick(const ParseeConfig *conf, char *id, char *banned)
"@", conf->sender_localpart,
":", conf->server_base
);
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/rooms/", id, "/kick",
"?user_id=", bridge
);
Free(bridge);
Free(id);
ctx = ParseeCreateRequest(
conf,
@ -120,7 +126,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{
HttpClientContext *ctx = NULL;
HashMap *json = NULL;
char *path, *ret;
char *path, *ret, *serv;
int status;
if (!conf || !id)
{
return NULL;
@ -139,6 +146,11 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{
masquerade = HttpUrlEncode(masquerade);
}
serv = strchr(id, ':');
if (serv)
{
serv = serv + 1;
}
id = HttpUrlEncode(id);
path = StrConcat(5,
"/_matrix/client/v3/join/", id, "?",
@ -152,7 +164,7 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json);
status = ParseeSetRequestJSON(ctx, json);
JsonFree(json);
json = JsonDecode(HttpClientStream(ctx));
@ -163,6 +175,8 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
Free(masquerade);
Free(id);
(void) serv; // TODO
return ret;
}
void

View file

@ -33,6 +33,11 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u
char *path;
char *txn, *ret;
char *ts_str;
HttpStatus status;
if (!ret)
{
Log(LOG_ERR, "%", ret);
}
HashMap *reply;
if (!conf || !id || !type || !user || !c)
{
@ -47,18 +52,20 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, u
ts_str = TSToStr(ts);
txn = StrRandom(16);
id = HttpUrlEncode(id);
path = StrConcat(11,
"/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?",
"user_id=", user, "&ts=", ts_str
);
Free(id);
Free(txn);
Free(ts_str);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path);
ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, c);
status = ParseeSetRequestJSON(ctx, c);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id")));

View file

@ -28,6 +28,25 @@ CommandHead(CmdBanUser, cmd, argp)
BotDestroy();
}
CommandHead(CmdNoFlyListDel, cmd, argp)
{
ParseeCmdArg *args = argp;
ParseeData *data = args->data;
HashMap *event = args->event;
char *user = HashMapGet(cmd->arguments, "user");
BotInitialise();
if (!user)
{
BotDestroy();
return;
}
ReplySprintf("Unbanning %s", user);
ParseeGlobalUnban(data, user);
BotDestroy();
}
CommandHead(CmdNoFlyList, cmd, argp)
{
ParseeCmdArg *args = argp;

View file

@ -73,7 +73,7 @@ ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path
ctx = HttpRequest(
meth,
HTTP_FLAG_TLS,
conf->homeserver_tls ? HTTP_FLAG_TLS : HTTP_FLAG_NONE,
conf->homeserver_port, conf->homeserver_host,
path
);

View file

@ -13,7 +13,7 @@
#include <signal.h>
#include <stdlib.h>
#include <Unistring.h>
#include <StanzaBuilder.h>
#include <Parsee.h>
#include <XMPP.h>
#include <AS.h>
@ -63,23 +63,39 @@ ParseeCheckMatrix(void *datp)
{
static volatile uint64_t streak = 0;
ParseeData *data = datp;
if (!ASPing(data->config))
if (data->config->accept_pings && !ASPing(data->config))
{
Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host);
if (++streak >= 5)
if (++streak == 10)
{
Log(LOG_ERR, "This has been at least the fifth time in a row.");
Log(LOG_ERR, "Please check if your homeserver is active.");
Log(LOG_ERR, "%s shall now exit...", NAME);
DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats");
HashMap *json = DbJson(ref);
HashMap *mucs = GrabObject(json, 1, "mucs");
char *muc;
void *ignored;
pthread_mutex_lock(&data->halt_lock);
data->halted = true;
pthread_mutex_unlock(&data->halt_lock);
XMPPFinishCompStream(data->jabber);
pthread_join(xmpp_thr, NULL);
Log(LOG_INFO, "Stopping server...");
HttpServerStop(data->server);
/* Notify any potential MUCs about this */
while (HashMapIterate(mucs, &muc, &ignored))
{
char *id = StrRandom(32);
char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee");
StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id);
SetStanzaType(b, "groupchat");
SetStanzaBody(b,
"This bridge hasn't been able to reach the Matrix host, and "
"as such, some messages may not have been sent over."
);
WriteoutStanza(b, data->jabber, 0);
DestroyStanzaBuilder(b);
Free(sender);
Free(id);
}
(void) ignored;
DbUnlock(data->db, ref);
}
return;
}
@ -108,7 +124,7 @@ Main(Array *args, HashMap *env)
);
ParseePrintASCII();
Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors");
Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
#ifdef PLATFORM_IPHONE
@ -207,9 +223,11 @@ Main(Array *args, HashMap *env)
Log(LOG_NOTICE, "Connecting to XMPP...");
jabber = XMPPInitialiseCompStream(
parsee_conf->component_addr,
parsee_conf->component_host,
parsee_conf->component_port
);
Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber);
if (!XMPPAuthenticateCompStream(
jabber,
parsee_conf->shared_comp_secret
@ -274,13 +292,13 @@ Main(Array *args, HashMap *env)
{
char *parsee = ParseeMXID(conf.handlerArgs);
/* TODO: An hardcoded avatar like this sucks. */
ASSetAvatar(parsee_conf,
parsee,
"mxc://tedomum.net/"
"7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136"
);
ASSetName(parsee_conf, parsee, "Parsee bridge");
Free(parsee);
}

View file

@ -21,13 +21,11 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char
char *sender = GrabString(event, 1, "sender");
Unistr *uninick = UnistrCreate(name);
Unistr *filtered = UnistrFilter(uninick, UnistrIsBMP);
Unistr *filtered = UnistrFilter(uninick, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */
char *nick = UnistrC(filtered);
char *rev = StrConcat(3, muc, "/", nick);
int nonce = 0;
Log(LOG_DEBUG, "MUCJOINER: filtered '%s' to '%s'", name, nick);
UnistrFree(uninick);
UnistrFree(filtered);
@ -102,7 +100,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
char *jid = ParseeEncodeMXID(state_key);
char *sha = NULL, *mime = NULL;
char *avatar = ASGetAvatar(data->config, NULL, state_key);
char *url = ParseeToUnauth(data, avatar);
char *url = ParseeToUnauth(data, avatar, NULL);
chat_id = ParseeGetFromRoomID(data, room_id);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
@ -257,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"), 0
));
Free(profile);
return;
}
@ -296,10 +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;
DbRef *room_data = NULL;
HashMap *data_json = NULL;
bool direct = false;
if (!data || !event || !from || !to)
@ -331,8 +324,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
else
{
char *matrix_name, *matrix_avatar;
char *mime, *sha;
char *matrix_name = NULL, *matrix_avatar = NULL;
char *mime = NULL, *sha = NULL;
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
@ -365,28 +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))
{
@ -417,7 +429,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
type = direct ? "chat" : "groupchat";
user = GrabString(json, 1, "xmpp_user");
unauth = ParseeToUnauth(data, url);
unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename"));
encoded_from = ParseeEncodeMXID(m_sender);
xmppified_user = StrConcat(3,
@ -454,6 +466,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
Free(name);
Free(avatar);
}
if (reply_id)
{
/* TODO: Monocles chat DM users HATE this trick!
@ -510,8 +523,8 @@ end:
Free(stanza);
Free(sender);
Free(unauth);
Free(unedited_id);
Free(encoded_from);
Free(unedited_id);
DbUnlock(data->db, ref);
ref = NULL;

View file

@ -1,6 +1,9 @@
#include <Matrix.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h>
@ -32,3 +35,34 @@ MatrixParseID(char *user)
return ret;
}
UserID *
MatrixParseIDFromMTO(Uri *uri)
{
UserID *id = NULL;
char *path, *params, *decoded;
if (!uri)
{
return NULL;
}
if (!StrEquals(uri->proto, "https") || !StrEquals(uri->host, "matrix.to"))
{
return NULL;
}
if (strncmp(uri->path, "/#/", 3))
{
return NULL;
}
path = StrDuplicate(uri->path + 3);
params = path ? strchr(path, '?') : NULL;
if (params)
{
*params = '\0';
}
decoded = HttpUrlDecode(path);
id = MatrixParseID(decoded);
Free(decoded);
Free(path);
return id;
}

View file

@ -36,6 +36,12 @@ ParseeConfigLoad(char *conf)
return;
}
json = JsonDecode(stream);
if (!json)
{
Log(LOG_ERR, "Could not parse config JSON");
StreamClose(stream);
return;
}
config = Malloc(sizeof(*config));
#define CopyToStr(to, str) config->to = StrDuplicate( \
@ -44,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;
@ -58,8 +67,15 @@ 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");
@ -69,6 +85,8 @@ ParseeConfigLoad(char *conf)
config->max_stanza_size = 10000;
}
CopyToBool(ignore_bots, "ignore_bots");
CopyToStr(media_base, "media_base");
CopyToStr(db_path, "db");
@ -129,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);

View file

@ -27,6 +27,7 @@ ParseeInitData(XMPPComponent *comp)
data->config = ParseeConfigGet();
data->router = HttpRouterCreate();
data->jabber = comp;
data->muc = CreateMUCServer(data);
data->handler = CommandCreateRouter();
data->oid_servers = HashMapCreate();
@ -116,6 +117,7 @@ ParseeFreeData(ParseeData *data)
pthread_mutex_destroy(&data->halt_lock);
Free(data->id);
XMPPEndCompStream(data->jabber);
FreeMUCServer(data->muc);
DbClose(data->db);
HttpRouterFree(data->router);
CommandFreeRouter(data->handler);
@ -130,7 +132,6 @@ ParseeCleanup(void *datp)
char *chat;
size_t i;
uint64_t ts = UtilTsMillis();
size_t entries = 0;
chats = DbList(data->db, 1, "chats");
@ -174,7 +175,6 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \
{ \
DbDelete(data->db, 4, "chats", chat, #field"s", field); \
entries++; \
} \
Free(field); \
} \
@ -231,7 +231,6 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \
{ \
JsonValueFree(HashMapDelete(field##s, field)); \
entries++; \
} \
Free(field); \
} \

View file

@ -1,6 +1,7 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
@ -538,7 +539,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id)
return ret;
}
void
ParseeSendPresence(ParseeData *data)
{
@ -562,7 +562,7 @@ ParseeSendPresence(ParseeData *data)
uint64_t ts = GrabInteger(DbJson(chat), 1, "ts");
int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1;
/* Make a fake user join the MUC */
Log(LOG_NOTICE, "Sending presence to %s last=%ds", rev, diff);
Log(LOG_NOTICE, "Sending presence to %s", rev);
XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false);
DbUnlock(data->db, chat);
@ -688,12 +688,13 @@ end:
#include <Cytoplasm/Uri.h>
char *
ParseeToUnauth(ParseeData *data, char *mxc)
ParseeToUnauth(ParseeData *data, char *mxc, char *filename)
{
Uri *url = NULL;
char *ret;
char *key, *hmac;
#define PAT "%s/_matrix/client/v1/media/download/%s%s?hmac=%s"
#define PAT "%s/media/%s%s?hmac=%s"
#define PATF "%s/media/%s%s/%s?hmac=%s"
size_t l;
if (!data || !mxc)
{
@ -714,19 +715,45 @@ ParseeToUnauth(ParseeData *data, char *mxc)
hmac = ParseeHMACS(data->id, key);
Free(key);
l = snprintf(NULL, 0,
PAT,
data->config->media_base,
url->host, url->path,
hmac
);
if (!filename)
{
l = snprintf(NULL, 0,
PAT,
data->config->media_base,
url->host, url->path,
hmac
);
}
else
{
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,
hmac
);
if (!filename)
{
snprintf(ret, l + 1,
PAT,
data->config->media_base,
url->host, url->path,
hmac
);
}
else
{
snprintf(ret, l + 1,
PATF,
data->config->media_base,
url->host, url->path, filename,
hmac
);
}
UriFree(url);
Free(hmac);
return ret;

View file

@ -5,11 +5,14 @@
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/Log.h>
#include <stdbool.h>
#include <string.h>
#include <StringStream.h>
#include <Matrix.h>
#include <XML.h>
#include <AS.h>
@ -17,7 +20,7 @@ typedef struct XMPPFlags {
bool quote;
} XMPPFlags;
static char *
XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
XMPPifyElement(const ParseeConfig *conf, HashMap *event, XMLElement *elem, XMPPFlags flags)
{
char *xepd = NULL, *tmp = NULL;
@ -55,7 +58,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
} \
} \
while (0)
switch (elem->type)
switch (elem ? elem->type : -1)
{
case XML_ELEMENT_DATA:
Concat(elem->data);
@ -67,7 +70,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
@ -80,7 +83,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
@ -93,7 +96,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
@ -126,7 +129,7 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
@ -141,37 +144,70 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("\n");
if (i != 0)
{
Concat("\n");
}
}
else if (StrEquals(elem->name, "a"))
{
char *href = HashMapGet(elem->attrs, "href");
/* TODO: Check if the element here is a Matrix.TO
* pointing to a Parsee user. */
Concat("(");
for (i = 0; i < ArraySize(elem->children); i++)
Uri *pref = UriParse(href);
if (pref && StrEquals(pref->host, "matrix.to"))
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
/* TODO: Check if the element here is a Matrix.TO
* pointing to a Parsee user. */
UserID *id = MatrixParseIDFromMTO(pref);
if (id)
{
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);
}
Concat(" points to ");
Concat(href);
Concat(" )");
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(event, child, flags);
subxep = XMPPifyElement(conf, event, child, flags);
Concat(subxep);
Free(subxep);
@ -183,8 +219,45 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
}
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(HashMap *event)
ParseeXMPPify(ParseeData *data, HashMap *event)
{
char *type, *format, *html;
char *xepd = NULL;
@ -203,20 +276,28 @@ ParseeXMPPify(HashMap *event)
return NULL;
}
format = JsonValueAsString(JsonGet(event, 2, "content", "format"));
if (!StrEquals(format, "org.matrix.custom.html"))
if (!StrEquals(GetBodyFormat(event), "org.matrix.custom.html"))
{
/* Settle for the raw body instead. */
char *body = JsonValueAsString(JsonGet(event, 2, "content", "body"));
char *body = GetRawBody(event);
return StrDuplicate(body);
}
html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body"));
html = GetHTMLBody(event);
html = StrConcat(3, "<html>", html, "</html>");
elem = XMLCDecode(StrStreamReader(html), true, true);
if (!elem)
{
/* Settle for the raw body instead.
* TODO: Have the parser be more leinent on errors in HTML mode. */
char *body = GetRawBody(event);
Free(html);
return StrDuplicate(body);
}
flags.quote = false;
xepd = XMPPifyElement(event, elem, flags);
xepd = XMPPifyElement(data ? data->config : NULL, event, elem, flags);
XMLFreeElement(elem);
Free(html);

View file

@ -6,6 +6,28 @@
#include <Glob.h>
#include <AS.h>
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)
{

View file

@ -10,6 +10,37 @@
#include <string.h>
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;
@ -17,7 +48,7 @@ RouteHead(RouteMedia, arr, argp)
HashMap *reqh, *params;
char *server = ArrayGet(arr, 0);
char *identi = ArrayGet(arr, 1);
char *path, *key, *val;
char *key, *val;
char *hmac, *chkmak = NULL;
params = HttpRequestParams(args->ctx);
@ -44,15 +75,7 @@ RouteHead(RouteMedia, arr, argp)
/* Proxy the media through an authenticated endpoint if the HMAC
* is valid. */
server = HttpUrlEncode(server);
identi = HttpUrlEncode(identi);
path = StrConcat(4, "/_matrix/media/v3/download/", server, "/", identi);
cctx = ParseeCreateRequest(args->data->config, HTTP_GET, path);
ASAuthenticateRequest(args->data->config, cctx);
Free(path);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
cctx = TryDownload(args->data, server, identi);
reqh = HttpResponseHeaders(cctx);
while (HashMapIterate(reqh, &key, (void **) &val))
{
@ -65,8 +88,6 @@ RouteHead(RouteMedia, arr, argp)
}
HttpClientContextFree(cctx);
Free(server);
Free(identi);
return NULL;
}

View file

@ -10,8 +10,6 @@ RouteHead(RoutePing, arr, argp)
ParseeHttpArg *args = argp;
HashMap *request = NULL;
HashMap *response = NULL;
Array *events;
size_t i;
response = ASVerifyRequest(args);
if (response)

View file

@ -26,11 +26,6 @@ SignalHandler(int signal)
HttpServerStop(data->server);
return;
}
if (signal == SIGPIPE)
{
Log(LOG_DEBUG, "Caught a SIGPIPE...");
return;
}
}
bool
@ -46,7 +41,7 @@ ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp)
sa.sa_flags = SA_RESTART;
#define Register(act) (sigaction(act, &sa, NULL) >= 0)
if (!Register(SIGTERM) || !Register(SIGINT) || !Register(SIGPIPE))
if (!Register(SIGTERM) || !Register(SIGINT))
{
Log(LOG_ERR, "Couldn't register signals...");
return false;

View file

@ -192,6 +192,16 @@ UnistrGetch(Unistr *unistr, size_t i)
return i < unistr->length ? unistr->codepoints[i] : 0;
}
bool
UnistrIsASCII(uint32_t u)
{
if (u == 0)
{
return NULL;
}
return u < 0x7F;
}
bool
UnistrIsBMP(uint32_t u)
{
if (u == 0)
@ -267,3 +277,38 @@ UnistrGetOffset(Unistr *str, uint32_t sep)
}
return 0;
}
size_t
UnistrGetUTFOffset(char *cstr, size_t unicode)
{
Unistr *tmp;
size_t ret = 0;
if (!cstr)
{
return 0;
}
tmp = UnistrCreate(cstr);
for (size_t i = 0; i < unicode && i < tmp->length; i++)
{
uint32_t codepoint = tmp->codepoints[i];
if (codepoint >= 0x0000 && codepoint <= 0x007F)
{
ret += 1;
}
else if (codepoint >= 0x0080 && codepoint <= 0x07FF)
{
ret += 2;
}
else if (codepoint >= 0x0800 && codepoint <= 0xFFFF)
{
ret += 3;
}
else if (codepoint >= 0x010000 && codepoint <= 0x10FFFF)
{
ret += 4;
}
}
end:
Free(tmp);
return ret;
}

View file

@ -329,8 +329,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
break;
case XEP393_MONO:
head = XMLCreateTag("code");
XMLAddChild(xmlparent, XMLCreateText("`"));
XMLAddChild(xmlparent, head);
XMLAddChild(head, XMLCreateText("`"));
break;
case XEP393_SRKE:
head = XMLCreateTag("s");
@ -372,7 +372,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
XMLAddChild(head, XMLCreateText("_"));
break;
case XEP393_MONO:
XMLAddChild(head, XMLCreateText("`"));
XMLAddChild(xmlparent, XMLCreateText("`"));
break;
case XEP393_SRKE:
XMLAddChild(head, XMLCreateText("~"));

View file

@ -31,6 +31,12 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
bool flag = false;
switch (event->type)
{
case XML_ERROR:
XMLFreeEvent(event);
XMLFreeElement(ret);
ArrayFree(stack);
XMLFreeLexer(lexer);
return NULL;
case XML_LEXER_STARTELEM:
/* Create a new element that will populated. */
top = XMLCreateTag(event->element);

View file

@ -58,6 +58,7 @@ static char * XMLPopElement(XMLexer *lexer);
static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateRelax(XMLexer *lexer);
static XMLEvent * XMLCreateError(XMLexer *lexer);
static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end);
static XMLEvent * XMLCreateData(XMLexer *lexer);
@ -198,7 +199,9 @@ XMLCrank(XMLexer *lexer)
else if (XMLookahead(lexer, "--", false))
{
/* Throw error */
return NULL;
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
break;
case XML_STATE_PI:
@ -215,6 +218,9 @@ XMLCrank(XMLexer *lexer)
if (!attrname)
{
/* TODO: Throw error */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
XMLPushElement(lexer, attrname);
@ -241,7 +247,10 @@ XMLCrank(XMLexer *lexer)
}
else if (XMLookahead(lexer, "'", true))
{
while (true);
//while (true); uh oh
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
break;
case XML_STATE_ATTRTAIL:
@ -250,6 +259,8 @@ XMLCrank(XMLexer *lexer)
if (!XMLookahead(lexer, ">", true))
{
/* TODO: Throw error. */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
lexer->state = XML_STATE_NONE;
@ -258,6 +269,8 @@ XMLCrank(XMLexer *lexer)
break;
default:
/* TODO */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
}
/* TODO: Crank our XML parser. */
@ -693,6 +706,26 @@ XMLCreateData(XMLexer *lexer)
return event;
}
static XMLEvent *
XMLCreateError(XMLexer *lexer)
{
XMLEvent *event = Malloc(sizeof(*event));
size_t elements = ArraySize(lexer->data.elements);
event->type = XML_ERROR;
event->element = elements ?
StrDuplicate(ArrayGet(lexer->data.elements, elements - 1)) :
NULL;
event->attrs = NULL;
event->data = NULL;
/* TODO */
event->line = 0;
event->col = 0;
event->offset = 0;
return event;
}
static XMLEvent *
XMLCreateRelax(XMLexer *lexer)
{
XMLEvent *event = Malloc(sizeof(*event));

View file

@ -19,7 +19,7 @@
#define DEFAULT_PROSODY_PORT 5347
XMPPComponent *
XMPPInitialiseCompStream(char *host, int port)
XMPPInitialiseCompStream(char *addr, char *host, int port)
{
int sd = -1;
struct addrinfo hints, *res, *res0;
@ -28,12 +28,17 @@ XMPPInitialiseCompStream(char *host, int port)
Stream *stream;
XMPPComponent *comp;
if (!addr)
{
addr = host;
}
snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, serv, &hints, &res0);
error = getaddrinfo(addr, serv, &hints, &res0);
if (error)
{
const char *error_str = gai_strerror(error);
@ -56,6 +61,10 @@ XMPPInitialiseCompStream(char *host, int port)
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, strerror(errno)
);
close(sd);
sd = -1;
continue;
@ -66,6 +75,10 @@ XMPPInitialiseCompStream(char *host, int port)
if (sd < 0)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': no socket available", __func__,
host
);
return NULL;
}
freeaddrinfo(res0);
@ -73,6 +86,10 @@ XMPPInitialiseCompStream(char *host, int port)
stream = StreamFd(sd);
if (!stream)
{
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, "couldn't create a Cytoplasm stream"
);
close(sd);
return NULL;
}
@ -165,6 +182,49 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
{
Log(LOG_ERR, "Excepted empty handshake reply, got nonsense.");
Log(LOG_ERR, "Another service (possibly Parsee) may have taken over.");
while ((ev = XMLCrank(sax)))
{
char *key, *val;
switch (ev->type)
{
case XML_LEXER_STARTELEM:
Log(LOG_DEBUG, "<%s>", ev->element);
LogConfigIndent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
/* TODO: Log out attributes a little better */
while (HashMapIterate(ev->attrs, &key, (void **) &val))
{
Log(LOG_DEBUG, "(%s=%s)", key, val);
}
LogConfigUnindent(LogConfigGlobal());
LogConfigUnindent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
break;
case XML_LEXER_ELEM:
Log(LOG_DEBUG, "<%s/>", ev->element);
LogConfigIndent(LogConfigGlobal());
LogConfigIndent(LogConfigGlobal());
/* TODO: Log out attributes a little better */
while (HashMapIterate(ev->attrs, &key, (void **) &val))
{
Log(LOG_DEBUG, "(%s=%s)", key, val);
}
LogConfigUnindent(LogConfigGlobal());
LogConfigUnindent(LogConfigGlobal());
break;
case XML_LEXER_ENDELEM:
LogConfigUnindent(LogConfigGlobal());
Log(LOG_DEBUG, "</%s>", ev->element);
break;
case XML_LEXER_DATA:
Log(LOG_DEBUG, "%s", ev->data);
break;
}
XMLFreeEvent(ev);
}
LogConfigIndentSet(LogConfigGlobal(), 0);
Log(LOG_ERR, "");
Log(LOG_ERR, "Simply jealous of that other service...");
Free(stream_id);

View file

@ -8,6 +8,8 @@
#include <Parsee.h>
#include <XML.h>
#include "XMPPThread/internal.h"
bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{
@ -42,6 +44,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
Free(uuid);
if (!iq_query || !StrEquals(iq_query->name, "iq"))
{
Log(LOG_ERR, "Didn't receive an <iq> stanza");
XMLFreeElement(iq_query);
return false;
}
@ -53,6 +56,11 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
"conference"))
{
XMLFreeElement(iq_query);
Log(LOG_DEBUG, "MUC INFO ERROR");
Log(LOG_DEBUG,
"identityp=%p category=%s", identity,
identity ? HashMapGet(identity->attrs, "category") : NULL
);
return false;
}
@ -162,6 +170,7 @@ bool
XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret)
{
XMLElement *presence, *x, *reply, *history, *photo;
IQFeatures *features;
char *from, *id, *stime = "3600";
if (!comp || !fr || !muc)
{
@ -189,7 +198,15 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool
XMLAddChild(x, history);
XMLAddChild(presence, x);
XMPPAnnotatePresence(presence);
features = LookupJIDFeatures(from);
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
XMPPAnnotatePresence(presence, features);
FreeIQFeatures(features);
if (hash)
{
@ -225,6 +242,7 @@ void
XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
{
XMLElement *presence;
IQFeatures *features;
char *from, *id;
if (!comp || !fr || !muc)
{
@ -246,7 +264,16 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
XMLAddChild(presence, status);
}
XMPPAnnotatePresence(presence);
features = LookupJIDFeatures(from);
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
XMPPAnnotatePresence(presence, features);
FreeIQFeatures(features);
XMPPSendStanza(comp, presence, 10000);

154
src/XMPP/MUCServ.c Normal file
View file

@ -0,0 +1,154 @@
#include <MUCServ.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <pthread.h>
#include <string.h>
#define MUCNS "http://jabber.org/protocol/muc"
struct MUCServer {
pthread_mutex_t mut;
ParseeData *data;
};
static char *
GetMUCName(char *jid)
{
char *name, *at;
size_t len;
if (!jid || *jid != '#')
{
return NULL;
}
jid++;
at = strchr(jid, '@');
if (!at)
{
return StrDuplicate(jid);
}
len = at - jid;
name = Malloc(len + 1);
memset(name, '\0', len + 1);
memcpy(name, jid, len);
return name;
}
MUCServer *
CreateMUCServer(ParseeData *data)
{
MUCServer *server;
if (!data)
{
return NULL;
}
server = Malloc(sizeof(*server));
pthread_mutex_init(&server->mut, NULL);
server->data = data;
return server;
}
static bool
MUCManagePresence(MUCServer *serv, XMLElement *stanza, char *from, char *to)
{
char *name;
XMLElement *x = XMLookForTKV(stanza, "x", "xmlns", MUCNS);
char *type = HashMapGet(stanza->attrs, "type");
if (x && !type)
{
/* The user is trying to join the MUC */
name = GetMUCName(to);
Log(LOG_WARNING, "%s is trying to join MUC '%s'", from, name);
/* TODO: Check if the user should be joining. If so, make them.
* Implementing MUCs is gonna be fun. */
/* TODO: Presence broadcast hell. */
Free(name);
}
return false;
}
static bool
MUCManageMessage(MUCServer *serv, XMLElement *stanza, char *from, char *to)
{
Log(LOG_WARNING, "MUCSERV: got a message %s->%s", from, to);
return false;
}
bool
ManageMUCStanza(MUCServer *serv, XMLElement *stanza)
{
char *stype, *from, *to;
bool ret;
if (!serv || !stanza)
{
return false;
}
from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to");
stype = stanza->name;
if (to && *to != '#')
{
/* We aren't interacting with a MUC at all. Don't do anything. */
return false;
}
if (StrEquals(stype, "iq"))
{
/* TODO: Worry about IQ later */
return false;
}
ret = false;
pthread_mutex_lock(&serv->mut);
if (StrEquals(stype, "presence"))
{
ret = MUCManagePresence(serv, stanza, from, to);
}
else if (StrEquals(stype, "message"))
{
ret = MUCManageMessage(serv, stanza, from, to);
}
/* TODO: Do stuff while locked */
pthread_mutex_unlock(&serv->mut);
/* TODO: Verify the destination, and make sure we aren't doing
* anything stupid(especially with discovery) */
(void) ret;
return false;
}
bool
MUCServerExists(MUCServer *serv, char *muc)
{
if (!serv || !muc)
{
return false;
}
return false;
}
void
FreeMUCServer(MUCServer *serv)
{
if (!serv)
{
return;
}
pthread_mutex_lock(&serv->mut);
/* TODO */
pthread_mutex_unlock(&serv->mut);
pthread_mutex_destroy(&serv->mut);
Free(serv);
}

View file

@ -7,6 +7,8 @@
#include <Parsee.h>
#include <XML.h>
#include <stdlib.h>
void
XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
@ -159,27 +161,31 @@ XMPPGetReply(XMLElement *elem)
return HashMapGet(rep->attrs, "id");
}
void
XMPPAnnotatePresence(XMLElement *presence)
ssize_t
XMPPGetReplyOffset(XMLElement *elem)
{
XMLElement *c;
char *ver;
if (!presence)
if (!elem)
{
return;
return -1;
}
ver = XMPPGenerateVer();
c = XMLCreateTag("c");
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
XMLAddAttr(c, "hash", "sha-1");
XMLAddAttr(c, "node", REPOSITORY);
XMLAddAttr(c, "ver", ver);
Free(ver);
XMLAddChild(presence, c);
for (size_t i = 0; i < ArraySize(elem->children); i++)
{
XMLElement *child = ArrayGet(elem->children, i);
char *xmlns = HashMapGet(child->attrs, "xmlns");
char *xfor = HashMapGet(child->attrs, "for");
if (StrEquals(child->name, "fallback") &&
StrEquals(xmlns, "urn:xmpp:feature-fallback:0") &&
StrEquals(xfor, "urn:xmpp:reply:0"))
{
XMLElement *body = XMLookForUnique(child, "body");
if (body && HashMapGet(body->attrs, "end"))
{
return strtol(HashMapGet(body->attrs, "end"), NULL, 10);
}
}
}
return -1;
}
char *
XMPPGetModeration(XMLElement *stanza)
{

View file

@ -18,9 +18,9 @@ void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *muc;
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
char *key = NULL, *val = NULL;

View file

@ -3,6 +3,7 @@
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Log.h>
#include <StringStream.h>
#include <XMPPCommand.h>
@ -12,33 +13,86 @@
#include "XMPPThread/internal.h"
IQFeatures *
CreateIQFeatures(void)
{
IQFeatures *ret = Malloc(sizeof(*ret));
ret->identity = ArrayCreate();
ret->adverts = ArrayCreate();
return ret;
}
void
FreeIQFeatures(IQFeatures *features)
{
size_t i;
if (!features)
{
return;
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
Free(ArrayGet(features->adverts, i));
}
ArrayFree(features->adverts);
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
Free(identity->category);
Free(identity->type);
Free(identity->lang);
Free(identity->name);
Free(identity);
}
ArrayFree(features->identity);
Free(features);
}
void
AdvertiseIQFeature(IQFeatures *f, char *feature)
{
if (!f || !feature)
{
return;
}
ArrayAdd(f->adverts, StrDuplicate(feature));
}
void
AddIQIdentity(IQFeatures *f, char *cat, char *lang, char *type, char *name)
{
XMPPIdentity *identity;
if (!f)
{
return;
}
identity = Malloc(sizeof(*identity));
identity->category = StrDuplicate(cat);
identity->type = StrDuplicate(type);
identity->lang = StrDuplicate(lang);
identity->name = StrDuplicate(name);
ArrayAdd(f->identity, identity);
}
/* Generates a SHA-256 hash of the ver field. */
char *
XMPPGenerateVer(void)
XMPPGenerateVer(IQFeatures *features)
{
char *S = NULL;
unsigned char *Sha = NULL;
Array *identities = ArrayCreate();
Array *features = ArrayCreate();
size_t i;
/* Initialise identity table, to be sorted */
#define IdentitySimple(cat, Type, Name) { \
XMPPIdentity *id = Malloc(sizeof(*id)); \
id->category = cat; \
id->lang = NULL; \
id->type = Type; \
id->name = Name; \
ArrayAdd(identities, id); }
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) ArrayAdd(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
ArraySort(identities, IdentitySort);
for (i = 0; i < ArraySize(identities); i++)
ArraySort(features->identity, IdentitySort);
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(identities, i);
XMPPIdentity *identity = ArrayGet(features->identity, i);
char *id_chunk = StrConcat(7,
identity->category, "/",
identity->type, "/",
@ -50,10 +104,10 @@ XMPPGenerateVer(void)
Free(id_chunk);
}
ArraySort(features, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features); i++)
ArraySort(features->adverts, ((int (*) (void *, void *)) ICollate));
for (i = 0; i < ArraySize(features->adverts); i++)
{
char *feature = ArrayGet(features, i);
char *feature = ArrayGet(features->adverts, i);
char *tmp = S;
S = StrConcat(3, S, feature, "<");
Free(tmp);
@ -64,16 +118,64 @@ XMPPGenerateVer(void)
S = Base64Encode((const char *) Sha, 20);
Free(Sha);
ArrayFree(features);
for (i = 0; i < ArraySize(identities); i++)
{
XMPPIdentity *identity = ArrayGet(identities, i);
/* We don't have to do anything here. */
Free(identity);
}
ArrayFree(identities);
return S;
}
void
XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features)
{
XMLElement *c;
char *ver;
if (!presence || !features)
{
return;
}
ver = XMPPGenerateVer(features);
c = XMLCreateTag("c");
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps");
XMLAddAttr(c, "hash", "sha-1");
XMLAddAttr(c, "node", REPOSITORY);
XMLAddAttr(c, "ver", ver);
Free(ver);
XMLAddChild(presence, c);
}
IQFeatures *
LookupJIDFeatures(char *jid)
{
IQFeatures *features;
if (!jid)
{
return NULL;
}
features = CreateIQFeatures();
if (*jid == '#')
{
/* This is a MUC. As such, we need to advertise MUCs */
#define ID(...) AddIQIdentity(features, __VA_ARGS__)
#define AD(var) AdvertiseIQFeature(features, var)
ID("gateway", NULL, "matrix", "Parsee MUC gateway");
ID("conference", NULL, "text", "Parsee MUC gateway");
ID("component", NULL, "generic", "Parsee component");
AD("http://jabber.org/protocol/muc");
#undef AD
#undef ID
}
else
{
#define IdentitySimple(cat, Type, Name) AddIQIdentity(features, cat, NULL, Type, Name);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(feature) AdvertiseIQFeature(features, feature);
IQ_ADVERT
#undef AdvertiseSimple
}
return features;
}

View file

@ -56,6 +56,8 @@ XMPPDispatcher(void *argp)
if (!stanza)
{
/* TODO: We shouldn't be busywaiting. Even with a sleep call.
*/
UtilSleepMillis(10);
continue;
}
@ -65,6 +67,12 @@ XMPPDispatcher(void *argp)
Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name);
}
if (ManageMUCStanza(args->muc, stanza))
{
XMLFreeElement(stanza);
continue;
}
if (StrEquals(stanza->name, "presence"))
{
PresenceStanza(args, stanza, thread);
@ -236,66 +244,96 @@ ParseeXMPPThread(void *argp)
}
}
while (true)
while (!args->halted)
{
char *id;
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
while (true)
{
/* Try to check if an error is abound */
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
char *id;
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{
Log(LOG_DEBUG, "RECEIVED EOF.");
/* Try to check if an error is abound */
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "RECEIVED EOF.");
}
break;
}
break;
}
if (args->verbosity >= PARSEE_VERBOSE_STANZA)
{
Stream *output = StreamStderr();
StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
XMLEncode(output, stanza);
StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
if (args->verbosity >= PARSEE_VERBOSE_STANZA)
{
pthread_mutex_lock(&await->cond_lock);
await->stanza = stanza;
pthread_cond_signal(&await->condition);
pthread_mutex_unlock(&await->cond_lock);
Stream *output = StreamStderr();
StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
XMLEncode(output, stanza);
StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
HashMapDelete(await_table, id);
id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
{
pthread_mutex_lock(&await->cond_lock);
await->stanza = stanza;
pthread_cond_signal(&await->condition);
pthread_mutex_unlock(&await->cond_lock);
HashMapDelete(await_table, id);
pthread_mutex_unlock(&await_lock);
continue;
}
pthread_mutex_unlock(&await_lock);
continue;
}
pthread_mutex_unlock(&await_lock);
}
/* Push it into the stanza FIFO. A dispatcher thread should then
* be able to freely grab a value(locked by a mutex). We can't make
* dispatchers read stanzas on their own, since that's _not_ how
* streams work, but this should mitigate some issues, and allow a
* few threads to be busy, while the rest of Parsee works. */
PushStanza(&info, stanza);
/* Push it into the stanza FIFO. A dispatcher thread should then
* be able to freely grab a value(locked by a mutex). We can't make
* dispatchers read stanzas on their own, since that's _not_ how
* streams work, but this should mitigate some issues, and allow a
* few threads to be busy, while the rest of Parsee works. */
PushStanza(&info, stanza);
}
pthread_mutex_lock(&args->halt_lock);
if (!args->halted)
{
Log(LOG_WARNING, "XMPP server is closing stream...");
for (size_t i = 0; i < 50; i++)
{
UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */
Log(LOG_WARNING, "Restarting XMPP stream.");
/* This is the part where a new connection is being considered */
XMPPFinishCompStream(jabber);
XMPPEndCompStream(jabber);
args->jabber = XMPPInitialiseCompStream(
args->config->component_addr,
args->config->component_host,
args->config->component_port
);
jabber = args->jabber;
if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret))
{
/* Oops, there is something wrong! */
Log(LOG_ERR, "Couldn't authenticate to XMPP server");
XMPPEndCompStream(jabber);
args->jabber = NULL;
jabber = NULL;
if (i == 4)
{
pthread_mutex_unlock(&args->halt_lock);
break;
}
}
}
}
pthread_mutex_unlock(&args->halt_lock);
}
pthread_mutex_lock(&args->halt_lock);
if (!args->halted)
{
Log(LOG_WARNING, "XMPP server is closing stream...");
Log(LOG_WARNING, "Stopping %s...", NAME);
error = true;
}
pthread_mutex_unlock(&args->halt_lock);
info.running = false;
for (i = 0; i < info.available_dispatchers; i++)

View file

@ -9,6 +9,7 @@
#include <Matrix.h>
#include <AS.h>
#include <sys/utsname.h>
#include <string.h>
#include <ctype.h>
@ -115,33 +116,37 @@ end:
#define DISCO "http://jabber.org/protocol/disco#info"
static XMLElement *
IQGenerateQuery(void)
IQGenerateQuery(IQFeatures *features)
{
XMLElement *query = XMLCreateTag("query");
XMLElement *query;
if (!features)
{
return NULL;
}
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO);
{
XMLElement *feature;
#define IdentitySimple(c,t,n) do \
{ \
feature = XMLCreateTag("identity"); \
XMLAddAttr(feature, "category", c); \
XMLAddAttr(feature, "type", t); \
XMLAddAttr(feature, "name", n); \
XMLAddChild(query, feature); \
} \
while (0);
IQ_IDENTITY
#undef IdentitySimple
#define AdvertiseSimple(f) do \
{ \
feature = XMLCreateTag("feature"); \
XMLAddAttr(feature, "var", f); \
XMLAddChild(query, feature); \
} \
while (0);
size_t i;
for (i = 0; i < ArraySize(features->identity); i++)
{
XMPPIdentity *identity = ArrayGet(features->identity, i);
IQ_ADVERT
#undef AdvertiseSimple
feature = XMLCreateTag("identity");
XMLAddAttr(feature, "category", identity->category);
XMLAddAttr(feature, "type", identity->type);
XMLAddAttr(feature, "name", identity->name);
XMLAddChild(query, feature);
}
for (i = 0; i < ArraySize(features->adverts); i++)
{
char *var = ArrayGet(features->adverts, i);
feature = XMLCreateTag("feature");
XMLAddAttr(feature, "var", var);
XMLAddChild(query, feature);
}
}
return query;
@ -151,6 +156,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
{
char *from, *to, *id;
XMLElement *iq_reply, *query;
IQFeatures *features;
from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to");
@ -163,9 +169,10 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "id", id);
query = IQGenerateQuery();
features = LookupJIDFeatures(to);
query = IQGenerateQuery(features);
{
char *ver = XMPPGenerateVer();
char *ver = XMPPGenerateVer(features);
char *node = StrConcat(3, REPOSITORY, "#", ver);
XMLAddAttr(query, "node", node);
@ -173,6 +180,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
Free(ver);
}
XMLAddChild(iq_reply, query);
FreeIQFeatures(features);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLFreeElement(iq_reply);
@ -366,7 +374,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
bool
IQIsCommandList(ParseeData *args, XMLElement *stanza)
{
char *parsee = NULL;
char *parsee = NULL, *to;
XMLElement *query = XMLookForTKV(
stanza, "query", "xmlns",
"http://jabber.org/protocol/disco#items"
@ -381,7 +389,8 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza)
}
parsee = ParseeJID(args);
ret = StrEquals(HashMapGet(stanza->attrs, "to"), parsee);
to = HashMapGet(stanza->attrs, "to");
ret = StrEquals(to, parsee) || StrEquals(to, args->config->component_host);
Free(parsee);
return ret;
@ -565,7 +574,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version"))
{
XMLElement *iq_reply, *query;
XMLElement *name, *version;
XMLElement *name, *version, *os;
iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "to", from);
@ -576,14 +585,19 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "jabber:iq:version");
{
struct utsname info;
name = XMLCreateTag("name");
version = XMLCreateTag("version");
os = XMLCreateTag("os");
uname(&info);
XMLAddChild(name, XMLCreateText(NAME));
XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]"));
XMLAddChild(os, XMLCreateText(info.sysname));
}
XMLAddChild(query, name);
XMLAddChild(query, version);
XMLAddChild(query, os);
XMLAddChild(iq_reply, query);
XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
@ -591,10 +605,14 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
}
else
{
char *buf = NULL;
Stream *s = StrStreamWriter(&buf);
Log(LOG_WARNING, "Unknown I/Q received:");
XMLEncode(StreamStdout(), stanza);
StreamPrintf(StreamStdout(),"\n");
StreamFlush(StreamStdout());
XMLEncode(s, stanza);
StreamFlush(s);
StreamClose(s);
Log(LOG_WARNING, "%s", buf);
Free(buf);
}
}

View file

@ -5,6 +5,7 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Unistring.h>
#include <Matrix.h>
#include <AS.h>
@ -276,7 +277,7 @@ end_error:
Log(LOG_DEBUG, "Error management: %fs", Elapsed(rectime));
}
if (moderated)
if (moderated && !(!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7)))
{
/* TODO: Parsee MUST check if it is a valid MUC */
char *resource = ParseeGetResource(from);
@ -324,8 +325,9 @@ end_error:
{
Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime));
}
char *trim = ParseeTrimJID(from);
if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) &&
to && *to == '@')
to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL))
{
DbRef *room_ref;
HashMap *room_json;
@ -344,6 +346,7 @@ end_error:
ParseePushDMRoom(args, to, from, room);
}
}
Free(trim);
/* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER
* SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED
@ -394,11 +397,17 @@ end_error:
bool media_enabled = chat_id ?
ParseeIsMediaEnabled(args, chat_id) :
true ;
bool is_parsee;
/* Note that chat_id still has meaningful info as a boolean
* despite being freed */
Free(chat_id);
pthread_mutex_unlock(&thr->info->chk_lock);
parsee = ParseeJID(args);
is_parsee = StrEquals(to, parsee);
Free(parsee);
parsee = NULL;
LazyRegister(args, encoded, !chat ? res : NULL);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
@ -411,7 +420,12 @@ end_error:
/* Check if it is a media link */
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
if (oob && data && media_enabled)
if (chat_id && StrEquals(type, "chat") && !is_parsee)
{
/* Very clearly a MUC DM. */
event_id = NULL;
}
else if (oob && data && media_enabled)
{
char *mxc, *mime = NULL;
HashMap *content = NULL;
@ -507,6 +521,15 @@ end_error:
* too. Go figure. */
size_t off =
reply_to ? ParseeFindDatastart(data->data) : 0;
size_t stanzaoff = XMPPGetReplyOffset(stanza);
if (reply_to && stanzaoff != -1)
{
off = UnistrGetUTFOffset(data->data, stanzaoff);
}
if (data->data && off >= strlen(data->data))
{
off = 0;
}
HashMap *ev = MatrixCreateMessage(data->data + off);
if (reply_to)
{

View file

@ -298,7 +298,7 @@ end_item:
Free(room);
FreeStatuses(statuses);
}
if (status)
if (status && !StrEquals(type, "unavailable"))
{
XMLElement *status_data = ArrayGet(status->children, 0);
char *decode_from = ParseeLookupJID(oid);
@ -309,6 +309,8 @@ end_item:
status_str = status_data->data;
}
/* TODO: "The server will automatically set a user's presence to
* unavailable if their last active time was over a threshold value
* (e.g. 5 minutes)."

View file

@ -32,13 +32,17 @@
IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \
IdentitySimple("component", "generic", "Parsee's component")
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct IQFeatures {
Array *identity;
Array *adverts;
} IQFeatures;
typedef struct XMPPIdentity {
char *category, *type, *lang, *name;
} XMPPIdentity;
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct XMPPThread XMPPThread;
typedef struct XMPPThreadInfo {
/* A FIFO of stanzas inbound, to be read by dispatcher
@ -65,6 +69,29 @@ struct XMPPThread {
int ICollate(unsigned char *cata, unsigned char *catb);
int IdentitySort(void *idap, void *idbp);
IQFeatures * CreateIQFeatures(void);
void AddIQIdentity(IQFeatures *, char *category, char *lang, char *type, char *name);
void AdvertiseIQFeature(IQFeatures *, char *feature);
void FreeIQFeatures(IQFeatures *);
/**
* Looks up the features supported by a JID
* ----------------------
* Returns: an IQ featureset[LA:HEAP] | NULL
* Thrasher: FreeIQFeatures
* Modifies: NOTHING */
IQFeatures * LookupJIDFeatures(char *jid);
/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps.
* --------
* Returns: A base64 encoded ver hash[LA:HEAP]
* Modifies: NOTHING
* See-Also: https://xmpp.org/extensions/xep-0115.html */
char * XMPPGenerateVer(IQFeatures *features);
/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */
void XMPPAnnotatePresence(XMLElement *presence, IQFeatures *features);
char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza);
char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id);
char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza);

39
src/include/MUCServ.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef PARSEE_MUCSERV_H
#define PARSEE_MUCSERV_H
/*-* An easy interface for handling MUCs.
*/
typedef struct MUCServer MUCServer;
#include <Parsee.h>
#include <stdbool.h>
/** Creates a MUC server handle given a ParseeData handle.
* This handle shall be used for all MUC-related operations.
* ----------------------------------------------------------
* Returns: a MUC server handle[HEAP] | NULL
* Thrasher: FreeMUCServer
* See-Also: https://xmpp.org/extensions/xep-0045.html */
extern MUCServer * CreateMUCServer(ParseeData *data);
/** Manages a stanza from a MUC, and returns true if the stanza
* was actually processed by the MUC server.
* -------------------------------------------------------------
* See-Also: CreateMUCServer */
extern bool ManageMUCStanza(MUCServer *serv, XMLElement *stanza);
/** Checks if a MUC(from its localpart, so
* <code>'#foobar@bridge.blow.hole' -> 'foobar'</code>) exists.
* --------------
* Returns: whenever the MUC exists */
extern bool MUCServerExists(MUCServer *serv, char *muc);
/** Destroys all memory and references from a MUC server handle.
* --------------
* Thrashes: CreateMUCServer
* See-Also: https://xmpp.org/extensions/xep-0045.html */
extern void FreeMUCServer(MUCServer *serv);
#endif

View file

@ -2,6 +2,7 @@
#define PARSEE_MATRIX_H
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Uri.h>
#include "FileInfo.h"
@ -20,6 +21,12 @@ typedef struct UserID {
* Thrasher: Free */
extern UserID * MatrixParseID(char *user);
/** Attempts to parse a Matrix ID from a matrix.to URL.
* -------------------------------------------------
* Returns: a valid user ID[HEAP] | NULL
* Thrasher: Free */
extern UserID * MatrixParseIDFromMTO(Uri *uri);
/* Creates an error message JSON, with the specified code and message. */
extern HashMap * MatrixCreateError(char *err, char *msg);

View file

@ -18,6 +18,8 @@ typedef struct ParseeData ParseeData;
#include <Command.h>
#include <XMPP.h>
#include <MUCServ.h>
#define PARSEE_VERBOSE_NONE 0
#define PARSEE_VERBOSE_LOG 1
#define PARSEE_VERBOSE_TIMINGS 2
@ -39,9 +41,14 @@ typedef struct ParseeConfig {
/* Homeserver port info */
char *homeserver_host;
int homeserver_port;
int homeserver_tls;
bool accept_pings;
bool ignore_bots;
/* ------- JABBER -------- */
char *component_addr;
char *component_host;
char *shared_comp_secret;
int component_port;
@ -62,6 +69,7 @@ typedef struct ParseeData {
HttpServer *server;
XMPPComponent *jabber;
MUCServer *muc;
Db *db;
char *id;
@ -351,7 +359,7 @@ extern int ParseeFindDatastartU(char *data);
/* XMPP-ifies a message event to XEP-0393 if possible. */
extern char * ParseeXMPPify(HashMap *event);
extern char * ParseeXMPPify(ParseeData *data, HashMap *event);
/* Finds an event ID from an ID in the stanza's attributes */
extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id);
@ -361,7 +369,7 @@ extern char * ParseeDMEventFromID(ParseeData *d, char *r_id, char *ori_id);
extern char * ParseeDMEventFromSID(ParseeData *d, char *r_id, char *ori_id);
/* Gets a Parsee "shim" link to an MXC, usable as unauth for a limited time */
extern char * ParseeToUnauth(ParseeData *data, char *mxc);
extern char * ParseeToUnauth(ParseeData *data, char *mxc, char *filename);
extern void ParseeInitialiseOIDTable(void);
extern void ParseePushOIDTable(char *muc, char *occupant);
@ -392,10 +400,17 @@ extern void ParseeDestroyNickTable(void);
* to ban them from rooms where Parsee has the ability to do so ("noflying").
* ---------------
* Returns: NOTHING
* See-Also: ParseeManageBan
* See-Also: ParseeManageBan, ParseeGlobalUnban
* Modifies: the database */
extern void ParseeGlobalBan(ParseeData *, char *user, char *reason);
/** Revokes a user/room/MUC's nofly status
* ---------------
* Returns: NOTHING
* See-Also: ParseeManageBan, ParseeGlobalBan
* Modifies: the database */
extern void ParseeGlobalUnban(ParseeData *, char *user);
/** Verifies if a user was banned globally. If so (and if {room} is set),
* tries to ban the user from it.
* ---------------

View file

@ -27,6 +27,10 @@ typedef struct ParseeCmdArg {
"ban-list", CmdNoFlyList, \
"Globally bans a user from using Parsee" \
) \
X_COMMAND( \
"unban-list", CmdNoFlyListDel, \
"Globally unbans a user from using Parsee" \
) \
X_COMMAND( \
"nuke-muc", CmdUnlinkMUC, \
"Removes a MUC. Users should then run " \
@ -75,7 +79,8 @@ typedef struct ParseeCmdArg {
X_ROUTE("/_matrix/app/v1/users/(.*)", RouteUserAck) \
X_ROUTE("/_matrix/app/v1/rooms/(.*)", RouteRoomAck) \
X_ROUTE("/_matrix/app/v1/ping", RoutePing) \
X_ROUTE("/_matrix/client/v1/media/download/(.*)/(.*)", RouteMedia)
X_ROUTE("/media/(.*)/(.*)", RouteMedia) \
X_ROUTE("/media/(.*)/(.*)/(.*)", RouteMedia)
#define X_ROUTE(path, name) extern void * name(Array *, void *);
ROUTES

View file

@ -64,6 +64,12 @@ extern void UnistrFree(Unistr *unistr);
* Returns: whenever the character is within the BMP */
extern bool UnistrIsBMP(uint32_t u);
/** Returns true IFF the character is within the 7-bit ASCII range
* not 0x0000
* ------------------------------------------------------------
* Returns: whenever the character is within ASCII */
extern bool UnistrIsASCII(uint32_t u);
typedef bool (*UnistrFilterFunc)(uint32_t u);
/** "Filters" characters in a Unistring by codepoint, removing
* those with callbacks which return false into a new unistring.
@ -76,4 +82,10 @@ extern Unistr * UnistrFilter(Unistr *str, UnistrFilterFunc filter);
* --------
* Returns: an offset of the first line to not start by {c} | 0 */
extern size_t UnistrGetOffset(Unistr *str, uint32_t sep);
/** Locates the new index within a regular UTF-8 sequence from an index
* within all codepoints.
* -----------------------------------
* Returns: an offset */
extern size_t UnistrGetUTFOffset(char *cstr, size_t unicode);
#endif

View file

@ -13,6 +13,8 @@ typedef struct XMLEvent {
XML_LEXER_DATA,
XML_LEXER_ELEM, /* Empty attr */
XML_LEXER_ENDELEM,
XML_ERROR,
XML_RELAX
} type;

View file

@ -22,7 +22,7 @@ typedef struct XMPPComponent {
/* Initialises a raw component stream to host, with an optional port.
* If said port is 0, then it is set to the default Prosody port */
extern XMPPComponent * XMPPInitialiseCompStream(char *host, int port);
extern XMPPComponent * XMPPInitialiseCompStream(char *addr, char *host, int port);
/* Authenticates a component stream with a given shared secret,
* with a stream ID from the server. This should be called right
@ -90,6 +90,9 @@ extern char * XMPPGetRetractedID(XMLElement *);
/* Get the replied-to stanza ID, if existent. */
extern char * XMPPGetReply(XMLElement *elem);
/* Get the replied-to stanza offset, if existent. */
extern ssize_t XMPPGetReplyOffset(XMLElement *elem);
/** Get the moderated message ID(as a stanza ID/plain ID) from a moderation
* stanza, that lives *alongside* the stanza itself.
* ----------------------------------------------------------------------
@ -98,16 +101,6 @@ extern char * XMPPGetReply(XMLElement *elem);
* See-Also: https://xmpp.org/extensions/xep-0425.html */
extern char * XMPPGetModeration(XMLElement *stanza);
/** Generate the B64-encoded SHA-256 hash for the 'ver' field in caps.
* --------
* Returns: A base64 encoded ver hash[LA:HEAP]
* Modifies: NOTHING
* See-Also: https://xmpp.org/extensions/xep-0115.html */
extern char * XMPPGenerateVer(void);
/* Annotates a presence with https://xmpp.org/extensions/xep-0115.html */
extern void XMPPAnnotatePresence(XMLElement *presence);
extern bool XMPPHasError(XMLElement *stanza, char *type);
#include <Parsee.h>

View file

@ -89,31 +89,30 @@ Main(Array *args, HashMap *env)
glob = ArrayGet(args, 2);
parsee = GetDB(db_path);
if (parsee)
if (!parsee)
{
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
}
if (glob)
{
Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path);
AddAdmin(parsee, glob);
Log(LOG_INFO, "Successfully added glob %s.", glob);
Log(LOG_INFO, "*I'm jealous of all these admins!*");
DbClose(parsee);
return EXIT_SUCCESS;
}
/* List admins */
Log(LOG_INFO, "Admin list:");
LogConfigIndent(LogConfigGlobal());
ListAdmins(parsee);
LogConfigUnindent(LogConfigGlobal());
if (glob)
{
Log(LOG_NOTICE, "Adding glob '%s' to database %s", glob, db_path);
AddAdmin(parsee, glob);
Log(LOG_INFO, "Successfully added glob %s.", glob);
Log(LOG_INFO, "*I'm jealous of all these admins!*");
DbClose(parsee);
return EXIT_SUCCESS;
}
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env;
return EXIT_FAILURE;
/* List admins */
Log(LOG_INFO, "Admin list:");
LogConfigIndent(LogConfigGlobal());
ListAdmins(parsee);
LogConfigUnindent(LogConfigGlobal());
DbClose(parsee);
return EXIT_SUCCESS;
}

View file

@ -108,7 +108,7 @@ GetDB(char *config)
{
ret = DbOpenLMDB(db_path, lmdb_size);
}
if (!ret)
else
{
ret = DbOpen(db_path, 0);
}

View file

@ -20,6 +20,7 @@ Main(Array *args, HashMap *env)
Uri *api_base;
char *homeserver = NULL, *jcp = NULL, *jabber = NULL;
char *data = NULL, *media = NULL, *listen = NULL;
char *component_as = NULL;
int flag, code = EXIT_FAILURE;
int port = 5347;
size_t lmdb_size = 0;
@ -28,7 +29,7 @@ Main(Array *args, HashMap *env)
listen = "localhost";
ArgParseStateInit(&state);
while ((flag = ArgParse(&state, args, "H:J:s:d:p:m:l:S:M:")) != -1)
while ((flag = ArgParse(&state, args, "H:J:j:s:d:p:m:l:S:M:")) != -1)
{
switch (flag)
{
@ -45,6 +46,9 @@ Main(Array *args, HashMap *env)
case 'J':
jabber = state.optArg;
break;
case 'j':
component_as = state.optArg;
break;
case 'd':
data = state.optArg;
break;
@ -123,6 +127,7 @@ Main(Array *args, HashMap *env)
JsonSet(json, JsonValueString(media), 1, "media_base");
JsonSet(json, JsonValueString(listen), 1, "listen_as");
JsonSet(json, JsonValueString(component_as), 1, "component_addr");
JsonEncode(json, file, JSON_PRETTY);
StreamFlush(file);

152
tools/plumb.c Normal file
View file

@ -0,0 +1,152 @@
/* plumb.c - Small utility to manage plumbings from a shutoff instance.
* ============================================================
* TODO: write other commands, and move some code to common.h
*
* Under CC0, as its a rather useful example of a Parsee tool.
* See LICENSE for more information about Parsee's licensing. */
#include "common.h"
#include <string.h>
static void
DeletePlumbID(Db *parsee, char *chat_id)
{
DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id);
DbRef *chats = DbLock(parsee, 1, "chats");
char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id");
char *jabber_id = GrabString(DbJson(chat_id_ref), 1, "jabber_id");
HashMap *rooms = GrabObject(DbJson(chats), 1, "rooms");
HashMap *mucs = GrabObject(DbJson(chats), 1, "mucs");
JsonValueFree(HashMapDelete(rooms, matrix_id));
JsonValueFree(HashMapDelete(mucs, jabber_id));
DbUnlock(parsee, chat_id_ref);
DbUnlock(parsee, chats);
DbDelete(parsee, 2, "chats", chat_id);
}
static void
DeletePlumb(Db *parsee, char *potential_id)
{
if (!parsee || !potential_id)
{
return;
}
if (!strncmp(potential_id, "xmpp:", 5))
{
DbRef *ref;
HashMap *mucs;
char *chat_id;
/* Try to parse it as an XMPP address */
potential_id += 5;
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
mucs = GrabObject(DbJson(ref), 1, "mucs");
chat_id = StrDuplicate(GrabString(mucs, 1, potential_id));
DbUnlock(parsee, ref);
DeletePlumbID(parsee, chat_id);
Free(chat_id);
return;
}
if (*potential_id == '!')
{
/* Try to parse it as a Matrix room ID */
DbRef *ref;
HashMap *rooms;
char *chat_id;
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
rooms = GrabObject(DbJson(ref), 1, "rooms");
chat_id = StrDuplicate(GrabString(rooms, 1, potential_id));
DbUnlock(parsee, ref);
DeletePlumbID(parsee, chat_id);
Free(chat_id);
return;
}
/* Try to parse it as a chat ID */
DeletePlumbID(parsee, potential_id);
}
static void
ListPlumbs(Db *parsee)
{
DbRef *ref;
HashMap *mucs;
char *muc;
JsonValue *value;
if (!parsee)
{
return;
}
ref = DbLockIntent(parsee, DB_HINT_READONLY, 1, "chats");
mucs = GrabObject(DbJson(ref), 1, "mucs");
while (HashMapIterate(mucs, &muc, (void **) &value))
{
char *chat_id = JsonValueAsString(value);
DbRef *chat_id_ref = DbLockIntent(parsee, DB_HINT_READONLY, 2, "chats", chat_id);
char *matrix_id = GrabString(DbJson(chat_id_ref), 1, "room_id");
/* TODO */
Log(LOG_INFO, "- Plumb xmpp:%s <=> %s", muc, matrix_id);
Log(LOG_INFO, " - ID=%s", chat_id);
DbUnlock(parsee, chat_id_ref);
}
DbUnlock(parsee, ref);
}
int
Main(Array *args, HashMap *env)
{
char *db_path, *action, *exec;
int ret = EXIT_SUCCESS;
Db *parsee;
exec = ArrayGet(args, 0);
if (ArraySize(args) < 3)
{
Log(LOG_ERR, "Usage: %s [config] [action] <args>", exec);
return EXIT_FAILURE;
}
db_path = ArrayGet(args, 1);
action = ArrayGet(args, 2);
parsee = GetDB(db_path);
if (!parsee)
{
Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
return EXIT_FAILURE;
}
if (StrEquals(action, "list") || StrEquals(action, "ls"))
{
ListPlumbs(parsee);
}
else if (StrEquals(action, "del"))
{
if (ArraySize(args) != 4)
{
Log(LOG_ERR, "%s: please show a !roomid:matrix.org, xmpp:mucid@jabber.org, or local hash", exec);
ret = EXIT_FAILURE;
goto end;
}
DeletePlumb(parsee, ArrayGet(args, 3));
}
end:
DbClose(parsee);
return ret;
}