mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 16:55:10 +00:00
Compare commits
46 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e331d110c7 | ||
|
|
e617b5e8d4 | ||
|
|
e3749817d3 | ||
|
|
7b8ed08e88 | ||
|
|
45250096ad | ||
|
|
f19ff2274b | ||
|
|
7454c8c597 | ||
|
|
e21ebed134 | ||
|
|
4eaec4687e | ||
|
|
680b4261c2 | ||
|
|
44473878d0 | ||
|
|
b50b9bd615 | ||
|
|
a2c1de52dc | ||
|
|
6762d1c1ce | ||
|
|
40e3242465 | ||
|
|
b78f7b6ab3 | ||
|
|
fd1b3499b6 | ||
|
|
fb44ad4bf6 | ||
|
|
0a4aa45de5 | ||
|
|
cdbdc9345a | ||
|
|
c96f0486ff | ||
|
|
e2b014d000 | ||
|
|
d12255b226 | ||
|
|
43175e32e5 | ||
|
|
3bef6afa5d | ||
|
|
71f3836ee1 | ||
|
|
9a2d4188e2 | ||
|
|
7f396a0379 | ||
|
|
1c51d57355 | ||
|
|
176f390c4b | ||
|
|
f94db460ac | ||
|
|
1936be0078 | ||
|
|
c2536c2e84 | ||
|
|
4f694129fc | ||
|
|
110a1b695f | ||
|
|
1e7d71f9f6 | ||
|
|
b485298fbc | ||
|
|
f9de7f1750 | ||
|
|
389870c5d3 | ||
|
|
0facbaa5e5 | ||
|
|
5e1931a19f | ||
|
|
c433e31461 | ||
|
|
c365681fcb | ||
|
|
86deab29af | ||
|
|
5ddc5d3e5c | ||
|
|
2e566c73fc |
53 changed files with 1537 additions and 358 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -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
82
.guix/modules/parsee.scm
Normal 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
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
12
README.MD
12
README.MD
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
71
configure.c
71
configure.c
|
|
@ -90,22 +90,6 @@ string_cat(char *in1, char *in2)
|
|||
|
||||
return out;
|
||||
}
|
||||
static char *
|
||||
trim_nl(char *in)
|
||||
{
|
||||
char *tc;
|
||||
if (!in)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while ((tc = strrchr(in, '\n')))
|
||||
{
|
||||
*tc = '\0';
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
typedef struct str_array {
|
||||
char **values;
|
||||
|
|
@ -115,6 +99,10 @@ static str_array_t *
|
|||
str_array_create(void)
|
||||
{
|
||||
str_array_t *ret = malloc(sizeof(*ret));
|
||||
if (!ret)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret->values = NULL;
|
||||
ret->quantity = 0;
|
||||
|
|
@ -216,36 +204,6 @@ failure:
|
|||
free(line);
|
||||
return NULL;
|
||||
}
|
||||
static int
|
||||
exec_code(char *program, char *argv[])
|
||||
{
|
||||
pid_t forkRet;
|
||||
if (!program || !argv)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
forkRet = fork();
|
||||
if (forkRet == 0)
|
||||
{
|
||||
/* Child */
|
||||
execvp(program, argv);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Parent */
|
||||
int status;
|
||||
if (waitpid(forkRet, &status, 0) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* We're not meant to ever be there, but TCC is stupid. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *
|
||||
strchrn(char *s, char c)
|
||||
|
|
@ -253,17 +211,6 @@ strchrn(char *s, char c)
|
|||
s = strchr(s, c);
|
||||
return s ? s+1 : NULL;
|
||||
}
|
||||
static char *
|
||||
strchrl(char *s, char c)
|
||||
{
|
||||
char *s1 = NULL;
|
||||
while ((s = strchr(s, c)))
|
||||
{
|
||||
s1 = s;
|
||||
s++;
|
||||
}
|
||||
return s1;
|
||||
}
|
||||
static str_array_t *
|
||||
split(char *dir)
|
||||
{
|
||||
|
|
@ -512,8 +459,7 @@ main_build(int argc, char *argv[])
|
|||
{
|
||||
FILE *makefile;
|
||||
char *repo = cmd_stdout("git remote get-url origin");
|
||||
size_t size, i;
|
||||
ssize_t nread;
|
||||
size_t i;
|
||||
bool with_static = false, with_lmdb = false;
|
||||
int opt;
|
||||
str_array_t *sources, *images, *utils, *aya;
|
||||
|
|
@ -546,6 +492,13 @@ main_build(int argc, char *argv[])
|
|||
}
|
||||
|
||||
makefile = fopen("Makefile", "w");
|
||||
if (!makefile)
|
||||
{
|
||||
fprintf(stderr, "Couldn't create Makefile.\n");
|
||||
fprintf(stderr, "This isn't good, actually.\n");
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n");
|
||||
fprintf(makefile, "# Ideally do not touch, unless you have a very ");
|
||||
fprintf(makefile, "good reason to do it. \n\n");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
BIN
etc/man/man7/.parsee-bridge-guidebook.7.swp
Normal file
BIN
etc/man/man7/.parsee-bridge-guidebook.7.swp
Normal file
Binary file not shown.
BIN
etc/man/man7/.parsee-cmd-syntax.7.swp
Normal file
BIN
etc/man/man7/.parsee-cmd-syntax.7.swp
Normal file
Binary file not shown.
70
etc/man/man7/parsee-bridge-guidebook.7
Normal file
70
etc/man/man7/parsee-bridge-guidebook.7
Normal 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
1
guix.scm
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
.guix/modules/parsee.scm
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
48
src/Main.c
48
src/Main.c
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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); \
|
||||
} \
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
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);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
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. */
|
||||
Concat("(");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
UserID *id = MatrixParseIDFromMTO(pref);
|
||||
if (id)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(event, child, flags);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
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);
|
||||
}
|
||||
Concat(" points to ");
|
||||
else
|
||||
{
|
||||
Concat(real_id);
|
||||
}
|
||||
Free(real_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Concat(href);
|
||||
Concat(" )");
|
||||
}
|
||||
|
||||
Free(id);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
45
src/Unistr.c
45
src/Unistr.c
|
|
@ -192,6 +192,16 @@ UnistrGetch(Unistr *unistr, size_t i)
|
|||
return i < unistr->length ? unistr->codepoints[i] : 0;
|
||||
}
|
||||
bool
|
||||
UnistrIsASCII(uint32_t u)
|
||||
{
|
||||
if (u == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return u < 0x7F;
|
||||
}
|
||||
bool
|
||||
UnistrIsBMP(uint32_t u)
|
||||
{
|
||||
if (u == 0)
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("~"));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
154
src/XMPP/MUCServ.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +244,8 @@ ParseeXMPPThread(void *argp)
|
|||
}
|
||||
}
|
||||
|
||||
while (!args->halted)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
char *id;
|
||||
|
|
@ -292,10 +302,38 @@ ParseeXMPPThread(void *argp)
|
|||
if (!args->halted)
|
||||
{
|
||||
Log(LOG_WARNING, "XMPP server is closing stream...");
|
||||
Log(LOG_WARNING, "Stopping %s...", NAME);
|
||||
error = true;
|
||||
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);
|
||||
}
|
||||
|
||||
info.running = false;
|
||||
for (i = 0; i < info.available_dispatchers; i++)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)."
|
||||
|
|
|
|||
|
|
@ -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
39
src/include/MUCServ.h
Normal 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
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
* ---------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ typedef struct XMLEvent {
|
|||
XML_LEXER_ELEM, /* Empty attr */
|
||||
XML_LEXER_ENDELEM,
|
||||
|
||||
XML_ERROR,
|
||||
|
||||
XML_RELAX
|
||||
} type;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -89,8 +89,12 @@ 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)
|
||||
{
|
||||
|
|
@ -112,8 +116,3 @@ Main(Array *args, HashMap *env)
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ GetDB(char *config)
|
|||
{
|
||||
ret = DbOpenLMDB(db_path, lmdb_size);
|
||||
}
|
||||
if (!ret)
|
||||
else
|
||||
{
|
||||
ret = DbOpen(db_path, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
152
tools/plumb.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue