Compare commits

...

193 commits

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

Feel free to make a good XMPP bridge, I don't really even want to touch
either Matrix or XMPP anymore, you know?
2025-04-28 02:18:57 +02:00
LDA
e3749817d3 [MOD/FIX] Be even more strict with Unicode filtering
Now, only ASCII characters get to fit.
2025-04-13 10:49:40 +00:00
LDA
7b8ed08e88 [ADD] Optionally allow instance admins to ignore m.notice events 2025-04-13 10:39:15 +00:00
LDA
45250096ad [FIX] argh 2025-04-13 10:23:51 +00:00
LDA
f19ff2274b Merge branch 'master' of https://git.kappach.at/lda/Parsee 2025-03-09 11:06:42 +00:00
LDA
7454c8c597 [FIX] Fix a few bugs about edits and XEP-393 formatting 2025-03-09 11:05:55 +00:00
lda
e21ebed134 [META/ADD] Add Guix to the CHANGELOG
quick und dirty
2025-02-23 22:38:38 +00:00
lda
4eaec4687e [MERGE] Add Guix support (see #18)
Reviewed-on: https://git.kappach.at/lda/Parsee/pulls/18
2025-02-23 22:36:36 +00:00
Hank
680b4261c2 [ADD] small documentation paragraph 2025-02-23 21:48:15 +01:00
Hank
44473878d0 [ADD] add parsee guix package 2025-02-23 21:48:15 +01:00
Hank
b50b9bd615 [FIX] pin cytoplasm to the latest commit 2025-02-23 21:48:13 +01:00
Hank
a2c1de52dc [ADD] new guix.scm with cytoplasm 2025-02-23 21:33:42 +01:00
Hank
6762d1c1ce [FIX/WIP] remove .guix from gitignore 2025-02-23 21:33:42 +01:00
lda
40e3242465 [ADD] Try to renegociate an XMPP stream on failure 2025-02-19 16:14:30 +00:00
lda
b78f7b6ab3 [ADD/TOOL] Add -j flag for parsee-config 2025-02-19 15:38:22 +00:00
lda
fd1b3499b6 [ADD] Allow the main component to be used to issue commands 2025-02-19 15:32:52 +00:00
lda
fb44ad4bf6 [FIX] Go back to using room IDs
I already gave up on urandom anyways.
2025-02-18 22:52:44 +00:00
lda
0a4aa45de5 [FIX] Add proper includes 2025-02-17 20:24:06 +00:00
lda
cdbdc9345a [MOD] Don't always add two newlines 2025-02-17 17:44:09 +00:00
lda
c96f0486ff [FIX/WIP] Chrck URI before doing anything funny 2025-02-17 08:30:18 +00:00
lda
e2b014d000 [FIX/WIP] Fix replies and try fixing a bug with MUCs 2025-02-15 08:44:25 +00:00
lda
d12255b226 [FIX/WIP] Do not use room ID for plumbing 2025-02-14 21:58:42 +00:00
lda
43175e32e5 [FIX/WIP] Log out error info on ASSend 2025-02-14 20:28:49 +00:00
lda
3bef6afa5d [FIX/WIP] Try escaping room IDs
First attempt at dealing with #20
2025-02-14 19:10:26 +00:00
lda
71f3836ee1 [ADD] Use actual OS as information 2025-02-12 16:47:48 +00:00
lda
9a2d4188e2 [FIX] Do not put version in the OS field 2025-02-12 16:43:43 +00:00
lda
7f396a0379 [FIX] Write config to the proper field! 2025-02-12 08:33:05 +00:00
lda
1c51d57355 [ADD] Add accept_pings parameters
This is off by default, but highly encouraged for Synapse deployments
2025-02-12 08:24:18 +00:00
lda
176f390c4b [FIX] Use the puppet's name when pinged if possible 2025-02-11 17:18:01 +00:00
lda
f94db460ac [FIX/WIP] Fix annoying formatting bug
Fixes #16
2025-02-09 21:51:38 +00:00
lda
1936be0078 [FIX] Fix minor DB fuckup with tools
Used to open a flatfile database whenever it couldn't find an LMDB one,
which obviously caused *problems*
2025-02-08 08:45:21 +00:00
lda
c2536c2e84 [FIX] Log out some MUC information 2025-02-07 18:52:58 +00:00
lda
4f694129fc [FIX] Fix silly SEGV
Thanks, EP
2025-02-07 18:34:45 +00:00
LDA
110a1b695f [FIX] Fix the indenting 2025-02-05 08:58:25 +01:00
LDA
1e7d71f9f6 [ADD] Allow people to see component errors 2025-02-05 08:54:45 +01:00
LDA
b485298fbc [FIX] Log error if the config was not parsed 2025-01-29 19:15:23 +01:00
lda
f9de7f1750 [MOD/FIX] Be a bit more specific with component errors 2025-01-28 15:01:57 +00:00
lda
389870c5d3 [FIX] Ignore unavailable statuses
They aren't that useful, especially in MUCs.
2025-01-25 12:20:47 +00:00
lda
0facbaa5e5 [ADD/FIX/WIP] Allow reverting noflys, ignore MUC DMs (for now) 2025-01-07 15:14:47 +00:00
lda
5e1931a19f [MOD] Change media path, connect to component through another address 2025-01-06 17:09:36 +00:00
LDA
c433e31461 [DEL] Please enter a valid command
Please enter a valid command
2025-01-03 16:00:24 +01:00
LDA
c365681fcb [MOD] Notify MUCs about errors 2025-01-03 15:01:35 +01:00
LDA
86deab29af [FIX] Properly handle new contents with Matrix
No more ugly asterisks. It annoyed me enough.
2024-12-28 20:17:03 +01:00
LDA
5ddc5d3e5c [WIP/ADD] Try to separate discovery, make links nicer 2024-12-19 20:59:12 +01:00
LDA
2e566c73fc [MOD] Start not hardcoding discovery features
The eventual goal here is to have different disco replies for specific
kinds of JID, effectively allowing us to have MUCs. Yeah.
2024-11-29 13:58:49 +01:00
LDA
5ff92dda3d [ADD] Basic header check in configure
It doesn't exactly conform to C99 standards, but it is good enough for
Parsee as a whole.
2024-11-20 22:05:54 +01:00
LDA
56433fc69c [DEL] Remove 64KB avatar hardlimit 2024-11-18 21:46:59 +01:00
LDA
5d13410ac4 [ADD] Ping homeserver to see if Parsee works 2024-11-17 16:22:10 +01:00
LDA
7f41a85a8a [FIX] Oh, and set the version 2024-11-11 11:23:10 +01:00
LDA
954c588310 [FIX] Don't check for upgrades on start. 2024-11-11 11:00:26 +01:00
LDA
dddf1680a0 [RELEASE/FIX] Fix ban-unlinks, actually get 0.2. 2024-11-08 17:43:41 +01:00
LDA
960599d983 [FIX/ADD/WIP] Fix very silly printf, media flags 2024-10-30 09:21:20 +01:00
LDA
033dba6840 [META] Fix typo 2024-10-29 16:58:48 +01:00
LDA
cf3bb5260b [CI/FIX] Fix potentially uninitialised key-values 2024-10-29 14:51:58 +01:00
LDA
064040c18f [ADD/WIP] Chat settings
Right now, they are currently unused. Extensions, and Parsee itself
will be able to use those, though.
2024-10-29 14:44:55 +01:00
LDA
55ac682d26 [MOD] Timestamp Matrix messages 2024-10-28 13:26:25 +01:00
LDA
ca5f8fd5c5 [CI/FIX] Fix unused argument again (again) 2024-10-28 10:51:49 +01:00
LDA
947c2d48d2 [CI/FIX] Fix unused argument again 2024-10-28 10:47:54 +01:00
LDA
dcd31201ab [CI/FIX] Fix unused argument 2024-10-28 10:44:23 +01:00
LDA
6aef0349b8 [CI/FIX] Make CI happy again 2024-10-28 09:08:08 +01:00
LDA
7f7ab41afa [ADD] Add a few basic MUC commands 2024-10-28 08:57:17 +01:00
LDA
d4371ac30b [META] Update CHANGELOG 2024-10-27 20:40:25 +01:00
LDA
9a16d96323 [ADD] Filtering XMPP commands
This allows us to have commands apply to admins or MUCs only(which would
help with many things I want to implement).
2024-10-27 19:08:37 +01:00
LDA
7c60ab28cb [ADD] Proper stanza limits 2024-10-25 18:03:05 +02:00
LDA
ce8bbb5540 [MOD] Hide avatar code behind another function 2024-10-25 13:46:55 +02:00
LDA
4bc2d1606a Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-10-25 13:27:09 +02:00
LDA
fced112691 [FIX] Fix CI 2024-10-24 13:26:46 +02:00
LDA
4b5a759ce7 [FIX] Fix uninitialised variables
I'm not sure why Debian's GCC didn't catch this, but NetBSD's did.
Another reason to add CI checks for NetBSD, then, though I am not sure
how to do it safely through VMs(and I doubt Docker/Podman would help me
for weird architectures). Hm.
2024-10-22 10:26:38 +02:00
LDA
b0cf0961a3 [FIX] Make codebase *slightly* NetBSD-friendly
I need to make it so that NetBSD targets get their own
correctness/build checks in CI. I also had to do some hacks with
Cytoplasm, will make a PR for those soon.
2024-10-22 10:21:54 +02:00
LDA
44d6df3be8 [FIX/CI] Make the C compiler less stupid
(You may notice that the dates for these commits are off. I'm
currently writing them without an Internet connection, and the
device I'm writing this through doesn't have an RTC. Oops!)
2024-10-20 15:12:08 +02:00
LDA
02a89270c0 [FIX] Kill Parsee on unexcepted stream closure 2024-10-20 12:53:07 +02:00
LDA
ff5f085b7f [FIX/MOD] Stanza send function, fix some errors
Negociating stanza sizes seems to be real trouble. Also this code is now
`-fanalyzer'-safe!
Also kills the last GNUMakefile present. It wasn't even used...
2024-10-19 16:58:48 +02:00
LDA
d9b5141eb5 Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-10-17 17:13:08 +02:00
LDA
bb32eb48a6 Merge branch 'fix-configure' 2024-10-17 16:57:14 +02:00
Xavier Del Campo Romero
2074d8e768
[FIX] configure.c: Do not assume git
If parsee were downloaded from sources other than git-clone(1) (e.g.: a
tarball), perror(3) fails and/or git(1) is not available on the system,
"git remote get-url origin" would have unexpected results or even return
a NULL pointer, which would cause undefined behaviour on
strchr(repo, ...);

In such conditions, a placeholder string, namely "N/A", is strdup(3)ed
to `repo`.
2024-10-17 07:37:01 +02:00
Xavier Del Campo Romero
e0f76e7929
[FIX] configure.c: Add missing -I $(CYTO_INC)
Some tools depend on header files defined by Cytoplasm.
2024-10-17 07:00:59 +02:00
Xavier Del Campo Romero
a66021bf46
[FIX] configure.c: Improve recursion rule
So far, collect_sources was called recursively when the entry name did
not match a pre-defined list. However, this assumption broke with files
such as `etc/media/README`, that were not in the list.

Therefore, a deterministic way to distinguish files from directories is
to rely on the S_ISDIR macro. This avoids the need for a pre-defined
list.
2024-10-17 06:45:42 +02:00
Xavier Del Campo Romero
ecbc211003
[FIX] configure.c: Fix '.' and '..' detection
Otherwise, hidden files such as '.file.<ext>' would be ignored.
2024-10-17 06:44:18 +02:00
LDA
1be19d4b8d [CI/FIX] Proper casts 2024-10-11 20:03:41 +02:00
LDA
27223a5896 [FIX/CI] Listen to CI 2024-10-11 19:58:28 +02:00
LDA
147e430a47 [FIX] Do NOT use strlen in a loop
Never. Do. This. Never. It should never cross your mind. Doing so will
punish you. Basic computer science concepts will tell you it's O(n^2).
And it WILL actually matter. Never. Never. Never. Never. Never. Never.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
            NEVER FUCKING EVER DO THAT EVER AGAIN.
Anyways, also fixes licensing year. On est en octobre et il comprend
toujours pas qu'on est plus en 2023.
2024-10-11 19:49:19 +02:00
LDA
13a9d23fa0 [FIX/ADD] Fix YAML generation, keep track of time 2024-10-11 16:09:21 +02:00
LDA
a48c3ba126 [ADD] Have avatars on first-message too 2024-10-08 17:27:25 +02:00
LDA
94090a8c97 [MOD] Rewrite the status code slightly
Also handle error-based kicks
2024-10-05 09:43:12 +02:00
LDA
179fd405db [FIX] Do not listen for kick requests
They may be sent for scenarios where an unlink is NOT wanted.
2024-10-05 09:20:49 +02:00
LDA
b7c360d528 [FIX] Handle logic mistake in plumbing 2024-10-04 12:09:56 +02:00
LDA
17474bda0f [FIX] (Try to) fix the memory leak 2024-10-03 07:53:39 +02:00
LDA
d585134ce1 [ADD/WIP] Temporary vCard avatard
Gajim still reacts weirdly.... also it leaks memory related to avatars,
so that sucks...
2024-10-02 21:13:28 +02:00
LDA
9ea8f35c49 [ADD/WIP] Try dealing in VCards 2024-10-01 06:59:13 +02:00
LDA
d989331716 [MOD] Add width/height information when possible 2024-09-30 06:11:31 +02:00
LDA
09d38993bb [ADD] Add XMPP commands for managing whitelists 2024-09-29 09:37:46 +02:00
LDA
a38e14e029 [FIX] Fix unused variables 2024-09-28 19:04:23 +02:00
LDA
0b00a665bf [ADD] MUC whitelists, part II 2024-09-28 15:58:41 +02:00
LDA
af2d08a431 [ADD/WIP] Start introducing whitelist command
The whitelist system will also be managable from XMPP and Matrix, and
will be for MUC/room management.
2024-09-28 13:24:38 +02:00
LDA
3ceae7b053 I honestly don't know what to call it.
It's not a 0.2 release, btw.
2024-09-26 19:07:46 +02:00
LDA
e7ba1fa48d [MOD] Separate the unlink in another function 2024-09-26 11:11:39 +02:00
LDA
749df0feb1 [ADD/WIP] Basic support for opting-out of bridging 2024-09-25 22:09:11 +02:00
LDA
0f3253a385 [FIX] Fix unfreed value on renames 2024-09-24 07:08:25 +02:00
LDA
2324f9afc0 [MOD/META] Clarify LICENSE more, fix chatstates 2024-09-22 18:36:53 +02:00
LDA
63285ac24a [FIX] Fix a very silly mistake 2024-09-22 15:13:12 +02:00
LDA
dec0d1f3d9 [FIX/WIP] Start making Parsee go vroom 2024-09-22 11:18:41 +02:00
LDA
c2ea3807ec [MOD/FIX] Licensewerk, start counting by codepoint
XEP-0426 came out of the blue, and it *hit* me!
2024-09-21 18:26:35 +02:00
LDA
dba3dcc85f [FIX] Fix more unistr fuckups 2024-09-21 14:11:04 +02:00
LDA
b9954c06ce [FIX] Apparently 0b isn't valid C99 2024-09-21 13:48:09 +02:00
LDA
6167732e83 [FIX] BMP for MUC nicks, dip toes in vCard avatars
Can, your, Bifrost, Do, That. -lh
2024-09-21 13:18:59 +02:00
LDA
9b4d97b033 [FIX] Fix subscription hash's warns 2024-09-20 07:01:35 +02:00
LDA
6e9ba8f0ee [FIX] Fix media C99 fuckups 2024-09-19 23:30:59 +02:00
LDA
e54332e376 [MOD] Basic work to get XMPP avatars through PEP
Attaboy!
2024-09-19 23:24:46 +02:00
LDA
f31a9c37b6 Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-09-19 10:09:03 +02:00
lda
c607a99004 [FIX] Get rid of unused variable
It's joever...
2024-09-19 06:54:17 +00:00
LDA
7ee5c055f4 [MOD] Actually start noting down presence requests 2024-09-18 15:39:52 +02:00
LDA
f666f39b7c [MOD] Add CHANGELOG, start PEPing 2024-09-18 07:58:21 +02:00
LDA
420c05690f [FIX] Catch SIGPIPEs 2024-09-18 05:55:47 +02:00
LDA
f96e0a95bd [CI] Just generate ZIPs 2024-09-17 21:45:26 +02:00
LDA
5ffd413e67 [CI/FIX] Fix YAML issue 2024-09-17 10:33:46 +02:00
LDA
9d67cb6165 [CI] Silly me. 2024-09-17 10:24:26 +02:00
LDA
9ff70bc0ed [CI] Push my luck with strategies 2024-09-17 10:21:27 +02:00
LDA
05be7fe249 [MOD] Do some tricks to make formatting nicer
It just didn't make sense on basic messages and Element Android.
Fluffy behaved as excepted, though.
2024-09-16 09:32:44 +02:00
LDA
0cccef7194 [CI] I hate Docker, again 2024-09-15 16:22:03 +02:00
LDA
f8b834c652 [CI] Help 2024-09-15 15:40:50 +02:00
LDA
1ce2eea9d8 [CI] Use single runs-on 2024-09-15 15:24:54 +02:00
LDA
cb70d599bf [CI[ Try to get ARM64. 2024-09-15 14:15:10 +02:00
LDA
d99b827d5c [CI] Fix typo in generation 2024-09-15 13:47:51 +02:00
LDA
dd6dede2e8 [CI] Use the right URL 2024-09-15 13:43:56 +02:00
LDA
bdb1f46c77 [CI] Use custom configure 2024-09-15 13:40:08 +02:00
LDA
613822e40c [CI] Base on a Mbed release 2024-09-15 13:17:54 +02:00
LDA
2e4314be48 [CI] Grab the right branch 2024-09-15 12:33:29 +02:00
LDA
1f7a220a8f [CI] Build with submodules 2024-09-15 12:26:36 +02:00
LDA
7c4333f137 [CI] Python hjinks 2024-09-15 12:24:20 +02:00
LDA
901a88c638 [CI] Ow. 2024-09-15 12:12:25 +02:00
LDA
4a2160503b [CI] Try to Mbed things out 2024-09-15 12:07:30 +02:00
LDA
9f1c2046fd [FIX] Fix out media from Matrix 2024-09-13 14:29:51 +02:00
LDA
4164eb5ed2 [CI] Try compressing the compress 2024-09-13 12:00:46 +02:00
LDA
63772d50ab [CI] I hate Forgejo. 2024-09-13 06:10:25 +02:00
LDA
92db543d78 [CI] Bloody hell... 2024-09-13 00:02:35 +02:00
LDA
19cf159f21 [CI] Crank compression to 9 2024-09-12 23:41:01 +02:00
LDA
fc75dc52aa [CI] This ought to be it 2024-09-12 23:14:05 +02:00
LDA
0ba708e28f [CI] Is he stupid??? 2024-09-12 23:05:14 +02:00
LDA
75bbe69633 [CI/FIX] Use limits.h 2024-09-12 22:58:13 +02:00
LDA
b10f14d067 [CI] What. 2024-09-12 22:35:41 +02:00
LDA
eb25ce3e2b [CI] Silly me, I need to build configure 2024-09-12 22:29:07 +02:00
LDA
92d33fa641 [MOD/CI/BUILD] Static builds, via the native tools 2024-09-12 22:17:35 +02:00
LDA
a110b44ad6 [CI/FIX] 2024-09-12 20:11:54 +02:00
LDA
90ddce4757 [CI/FIX] Aya... 2024-09-12 20:09:35 +02:00
LDA
e0e398bfb5 [META/CI] Thanks CI for catching this! 2024-09-12 20:06:33 +02:00
LDA
9352bf9bba [META] Death to GNUMakefile and build.c
Soon bound to add a -s flag for static build.
2024-09-12 20:02:40 +02:00
LDA
355a9d9b99 [CI] Oops 2024-09-11 15:15:46 +02:00
LDA
0586e863e7 [CI] Try using directory 2024-09-11 15:10:03 +02:00
LDA
619f49f8c8 [CI] Don't actually suffix ZIP, I think 2024-09-11 12:59:20 +02:00
LDA
5c7189f518 [CI] Is he stupid? 2024-09-11 12:53:27 +02:00
LDA
b55457d5fe [CI] Actual tgz.zip 2024-09-11 12:43:11 +02:00
LDA
79e65f0822 [CI] Label artifact as tar.gz.zip 2024-09-11 12:42:31 +02:00
LDA
79c94759da [CI] TFW it's broken 2024-09-11 12:33:26 +02:00
LDA
5c5540ffa6 [CI] By Jove, I think I got it! 2024-09-11 12:25:46 +02:00
LDA
9e4348c6e3 [CI] AAA 2024-09-11 12:20:21 +02:00
LDA
0e746465c6 [CI] Help. 2024-09-11 12:13:14 +02:00
LDA
aef19d5eaf [CI] Should do it, I think 2024-09-11 12:03:15 +02:00
LDA
0c402d8032 [CI] it was, infact, not the last commit 2024-09-11 11:56:49 +02:00
LDA
e9b36730b5 [CI] C'est le commit final 2024-09-11 11:49:26 +02:00
LDA
7ab99982f5 [CI] Escape $s 2024-09-11 11:28:15 +02:00
LDA
2955787aa2 [CI] Please? 2024-09-11 11:17:31 +02:00
LDA
4496f377fd [CI] Even less prefix 2024-09-11 11:10:33 +02:00
LDA
4dcd989c8b [CI] Use the PREFIX less 2024-09-11 10:05:07 +02:00
LDA
eb9512cde5 [CI] Do not use checkout 2024-09-11 09:58:50 +02:00
LDA
89ab1b57f0 [CI] Please? 2024-09-11 09:40:05 +02:00
LDA
d9bea609dd [CI] This should(n't) do the trick 2024-09-11 09:33:02 +02:00
LDA
e8fb9b9641 [CI] Try to create a static CI job 2024-09-11 09:15:20 +02:00
LDA
eefd5f0389 [MOD] Don't make the text so large 2024-09-10 22:28:55 +02:00
LDA
907ac13da9 [FIX] Make Parsee build on GCC without warns 2024-09-10 22:20:13 +02:00
LDA
1d188069db [FIX] Fix other little CI thing 2024-09-10 21:51:09 +02:00
LDA
696def187c [MOD/FIX] Set history limit, change logo, CI 2024-09-10 21:47:08 +02:00
LDA
42f02010d4 [CI] Don't doublequote 2024-09-10 21:43:25 +02:00
LDA
a3dc90f93e [CI] What? 2024-09-10 21:41:21 +02:00
LDA
3ae262c452 [CI] Be stringent on flags 2024-09-10 21:39:08 +02:00
LDA
a7c06bdb5c [CI] Exit CI hell... for now. 2024-09-10 21:29:08 +02:00
LDA
e1b044b268 [CI] Set prefix to /usr 2024-09-10 21:13:05 +02:00
LDA
5149a37d2e [CI] Use local mirror of Cytoplasm 2024-09-10 21:10:23 +02:00
LDA
9cba44f69b [CI] Set a depth of 1 2024-09-10 20:39:45 +02:00
LDA
aa8faec486 [CI] Try getting in the right directory 2024-09-10 20:33:04 +02:00
LDA
7314be5aeb [CI] Silly me! 2024-09-10 20:29:38 +02:00
LDA
60f3dc12c1 [CI] Hopefully this works 2024-09-10 20:28:14 +02:00
LDA
4e22fb6f04 [CI] Install git too 2024-09-10 20:21:39 +02:00
LDA
c8925dbe00 [CI] checkout is stupid 2024-09-10 20:19:20 +02:00
LDA
1f31ce2c35 [CI] Use proper tag 2024-09-10 20:15:41 +02:00
LDA
f1d864c975 [CI] User input moment 2024-09-10 20:00:51 +02:00
LDA
fdc0dfbcb6 [CI] Try running in a container 2024-09-10 19:56:08 +02:00
LDA
971f8ba3aa [CI] Mess with waters 2024-09-10 19:22:01 +02:00
LDA
2a6a3300e6 [CI] Start entering CI hell 2024-09-10 19:07:12 +02:00
LDA
488ab92a90 [MOD] Modify retention periods 2024-09-10 10:52:40 +02:00
LDA
7593225e18 [MOD/META] Correct README, make sure edit is NULL 2024-09-10 08:01:32 +02:00
LDA
aec3c87aee Merge branch 'master' of https://git.kappach.at/lda/Parsee 2024-09-09 19:25:27 +02:00
LDA
65c6e79fb2 [MOD/META] Change README/CHANGELOG 2024-09-09 19:24:55 +02:00
114 changed files with 5491 additions and 1658 deletions

View file

@ -0,0 +1,85 @@
name: Builds static Parsee
on: [push]
jobs:
compile:
strategy:
matrix:
distro:
- debian
arch:
- amd64
- arm64
runs-on:
- ${{ matrix.distro }}
- ${{ matrix.arch }}
container:
image: ${{ matrix.distro }}
steps:
- name: Install LMDB+OpenSSL tools
run: |
echo $(uname -a) $(env | grep container)
apt update -y
apt install -y build-essential liblmdb-dev nodejs libssl-dev git wget python3 python3-pip bzip2
- name: Clone everything
run: |
mkdir -p repos
git clone --depth=1 https://git.musl-libc.org/git/musl repos/musl
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.1/mbedtls-3.6.1.tar.bz2
tar -xvf mbedtls-3.6.1.tar.bz2
mv mbedtls-3.6.1/ repos/mbed
git clone --depth=1 https://github.com/LMDB/lmdb repos/lmdb
git clone -b add-mbed --single-branch https://git.telodendria.io/lda/Cytoplasm repos/cyto
echo "PREFIX=$GITHUB_WORKSPACE/usr" >> $GITHUB_ENV
echo "PATH=$GITHUB_WORKSPACE/usr/bin:$PATH" >> $GITHUB_ENV
echo "INCLUDE_PATH=$GITHUB_WORKSPACE/usr/include" >> $GITHUB_ENV
echo "LIBRARY_PATH=$GITHUB_WORKSPACE/usr/lib" >> $GITHUB_ENV
- name: Build musl
run: |
cd repos/musl
./configure --prefix=${PREFIX}
make -j$(nproc)
make install -j$(nproc)
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
- name: Build MbedTLS
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/mbed
python3 -m pip install -r scripts/basic.requirements.txt --break-system-packages
make CC=musl-gcc -j$(nproc)
make install DESTDIR=${PREFIX} -j$(nproc)
- name: Build LMDB
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/lmdb/libraries/liblmdb
make CC=musl-gcc
make install prefix=${PREFIX}
- name: Build Cytoplasm
run: |
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
cd repos/cyto
git fetch
rm configure
wget https://kappach.at/configure
chmod +x configure
./configure --cc=musl-gcc --prefix=${PREFIX} --with-lmdb --with-mbed
make -j$(nproc)
make install
- name: Clone/Build Parsee
run: |
git clone https://git.kappach.at/${{ github.repository}} parsee
cd parsee
alias musl-gcc="musl-gcc -Wl,-Bstatic -L ${PREFIX}/lib"
musl-gcc -static configure.c -o configure && ./configure -s -l
make CC=musl-gcc CYTO_INC=${PREFIX}/include CYTO_LIB=${PREFIX}/lib LDFLAGS=-s
make PREFIX=bins install
- name: Create a final archive
run: |
cd parsee
echo "NAM=parsee-$(uname)-$(uname -m)" >> $GITHUB_ENV
echo "DIR=$PWD/bins" >> $GITHUB_ENV
- name: Upload it all(as a ZIP)
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: ${{ env.NAM }}
path: ${{ env.DIR }}
compression-level: 9

View file

@ -0,0 +1,41 @@
name: Checks Parsee's correctness on GCC+Debian
on: [push]
jobs:
compile:
strategy:
matrix:
distro:
- debian
arch:
- amd64
runs-on:
- ${{ matrix.distro }}
- ${{ matrix.arch }}
container:
image: ${{ matrix.distro }}
steps:
- name: Install LMDB+OpenSSL tools
run: |
echo $(uname -a) $(env | grep container)
apt update -y
apt install -y build-essential liblmdb-dev nodejs libssl-dev git
- name: Clone/Configure Cytoplasm
run: |
git clone https://git.kappach.at/KappaChat/Cytoplasm --depth=1
cd Cytoplasm
./configure --with-lmdb --prefix=/usr
- name: Build Cytoplasm
run: 'cd Cytoplasm && make -j$(nproc)'
- name: Install Cytoplasm
run: 'cd Cytoplasm && make install'
- name: Clone Parsee
uses: actions/checkout@v3
- name: Build Parsee
run: |
cc configure.c -o configure && ./configure
echo 'CFLAGS=-Werror -Wall -Wextra -pedantic -fanalyzer' >> build.conf
cat build.conf
make && make ayadoc
- name: Install Parsee
run: 'make install'

15
.gitignore vendored
View file

@ -4,8 +4,11 @@ parsee*
parsee parsee
*.swp *.swp
.* .*
data data*
data/* data*/*
Makefile
configure
gmon.out
tools/out tools/out
tools/out/* tools/out/*
@ -19,3 +22,11 @@ tags
# Whitelists # Whitelists
!etc/* !etc/*
!etc/** !etc/**
!.forgejo
!.forgejo/*
!.forgejo/**
!.guix
!.guix/*
!.guix/**

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

@ -0,0 +1,82 @@
(define-module (parsee)
#:use-module (guix packages)
#:use-module (guix git-download)
#:use-module (guix build-system gnu)
#:use-module (guix gexp)
#:use-module (guix utils)
#:use-module (gnu packages tls)
#:use-module (gnu packages databases)
#:use-module ((guix licenses) #:prefix license:))
(define vcs-file?
(or (git-predicate
(dirname (dirname (current-source-directory))))
(const #t)))
(define-public cytoplasm
(let ((commit "32f31fe6d61583630995d956ed7bd7566c4dc14f")
(revision "0"))
(package
(name "cytoplasm")
(version (git-version "0.4.1" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.telodendria.io/Telodendria/Cytoplasm")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "09x5xfswryf3wjs1synh972yr2fmpjmffi7pjyjdzb4asqh4whrv"))))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:configure-flags #~'("--with-lmdb")
#:phases #~(modify-phases %standard-phases
(add-before 'configure 'add-ld-flags
(lambda _
(substitute* "./configure"
(("(LDFLAGS=\"\\$\\{LIBS\\} \\$\\{LDFLAGS\\})\"" all flags)
(string-append flags " -Wl,-rpath=" #$output "/lib\"")))
(mkdir-p (string-append #$output "/lib"))))
(replace 'configure
(lambda* (#:key configure-flags #:allow-other-keys)
(apply invoke `("./configure"
,(string-append "--prefix=" #$output)
,@configure-flags)))))))
(inputs (list openssl lmdb))
(home-page "https://git.telodendria.io/Telodendria/Cytoplasm")
(synopsis "General-purpose high-level networked C library")
(description "Cytoplasm is a general-purpose C library for creating
high-level (particularly networked and multi-threaded) C applications.")
(license license:expat))))
(define-public parsee
(package
(name "parsee")
(version "0.0.1-git")
(source
(local-file "../.." "parsee-checkout"
#:recursive? #t
#:select? vcs-file?))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:make-flags #~(list "CC=gcc"
(string-append "CYTO_INC=" #$cytoplasm "/include")
(string-append "CYTO_LIB=" #$cytoplasm "/lib")
(string-append "PREFIX=" #$output))
#:phases #~(modify-phases %standard-phases
(replace 'configure
(lambda* (#:key inputs #:allow-other-keys)
(let ((gcc (string-append (assoc-ref inputs "gcc") "/bin/gcc")))
(invoke gcc "configure.c" "-o" "configure")
(invoke "./configure")))))))
(home-page "https://git.kappach.at/lda/Parsee")
(synopsis "Jealous Matrix to XMPP bridge")
(description "Parsee is a Matrix-XMPP bridge written in C99 with Cytoplasm.")
(license license:agpl3+)))
parsee

View file

@ -6,7 +6,73 @@ Dates are to be written as DD/MM/YYYY. Please update the
changelog as you go, no one wants to keep track of every changelog as you go, no one wants to keep track of every
commit done between releases. commit done between releases.
## Release
*There is currently no full releases of Parsee*
## Beta
*There is currently no beta releases of Parsee*
## Alpha ## 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
up Parsee just a bit. MbedTLS support is still highly unstable.
#### New things
- Start dealing with some basic PEP and vCard-based avatar
support from both sides.
- Banning Parsee from a XMPP MUC effectively unlinks it from
the MUC.
- Start adding basic documentation to Parsee, through the
wiki page.
- Add MUC whitelists for plumbing, alongside a `whitelist` tool
- Add parameter for setting the max stanza size allowed, with
the default being the XMPP minimum of 10000 bytes.
- Allows experimental MbedTLS through a specific Cytoplasm
patch (though still unstable AND slow).
- Does basic work towards NetBSD support(especially with DEC Alpha)
- Start contextualising XMPP commands(all/Parsee admins/MUCs).
#### Bugfixes
- Adds more information to media events so that clients can
behave.
- Fixes issues where SIGPIPE can actually just kill Parsee.
- "Lone" XMPP messages no longer render weirdly on Element
Android's weird rendering.
- Start fixing bug where Parsee takes several seconds to send
a message coming from XMPP with MbedTLS(it is still slow and
unstable)
- Fix issue where the XMPP server would kill Parsee if avatars
are too large.
- Refactor some of the code to abstract sending stanzas down
the wire to a specific function, thus allowing us to check
for certain conditions more easily(for example, verifying the
size on the fly, or having extensions being able to postprocess
the stanza).
- Parsee now stops when a stream error is received, instead of
being in a limbo state.
- The format for links has been changed to be slighlty *less*
annoying.
#### Deprecated features
- The old `build.c` and `Makefile`s used for building are removed,
and replaced by the `configure.c` C file(/script via TCC).
### v0.1.0[tomboyish-bridges-adventure] <9/9/2024> ### v0.1.0[tomboyish-bridges-adventure] <9/9/2024>
Nothing much to say, but this is the first alpha release Nothing much to say, but this is the first alpha release
of Parsee. May occasionally deadlock. of Parsee. May occasionally deadlock.

14
CONTRIBUTORS Normal file
View file

@ -0,0 +1,14 @@
# Lines starting with a '#' are ignored.
# This is a list of people who have contributed to Parsee.
# You *may* add some information about yourself after having made a change
# to the repository(or wiki!), with those fields:
# (N) - A nickname
# (L) - An approximate place of residence
# (X) - An XMPP URI
# (M) - A Matrix MXID
# (D) - A short description about yourself
(N): LDA
(L): France
(X): xmpp:lda@monocles.eu
(M): @fourier:ari.lt
(M): @fourier:ari.lt

View file

@ -1,7 +1,3 @@
Some dates for Parsee-related events. They mostly serve as LDA's TODOs with Some dates for Parsee-related events. They mostly serve as LDA's TODOs with
a strict deadline: a strict deadline:
- ~September 2024[tomboyish-bridges-adventure]: - Get star-of-hope out by November 8
Get Parsee into the _Phantasmagoria of Bug View_ stage (essentially
v0.0.1 for a public testing) once I can afford `yama`, and start
bridging the Matrix room alongside a shiny XMPP MUC, bridged by
yours truly.

View file

@ -1,6 +1,6 @@
For the files src/Parsee/HMAC.c, src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile, For the files src/include/Unistring.h, src/Unistr.h rc/Parsee/HMAC.c, src/XML/*, tools/*, src/include/XML.h, etc/*, and Makefile,
see COPYING.CC0. see COPYING.CC0.
For any other file in src/, see COPYING.AGPL as the primary license. For any other file in src/, see COPYING.AGPL as the primary license(AGPL-3.0-or-later)
As Parsee depends on Cytoplasm, its license is left here in COPYING.CYTO As Parsee depends on Cytoplasm, its license is left here in COPYING.CYTO

View file

@ -1,90 +0,0 @@
# (GNU)Makefile for building Parsee
# ================================
# TODO: Consider making something akin to a configure script that checks
# for dependencies, or maybe even use *autoconf* (the devil!)
# =========================== Parsee Flags =============================
include build.conf
REPOSITORY=$(shell git remote get-url origin)
# =========================== Compilation Flags ============================
CYTO_INC ?=/usr/local/include/ # Where Cytoplasm's include path is
# located.
CYTO_LIB ?=/usr/local/lib # Where's Cytoplasm's library is
# located.
PREFIX ?=/usr/local
AYAS=ayaya
ETC=etc
FCFLAGS=-I$(SOURCE) -I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -DCODE=\"$(CODE)\" $(CFLAGS)
FLDFLAGS=-L $(CYTO_LIB) -lCytoplasm $(LDFLAGS)
AFLAGS=-C "$(ETC)/ayadoc/style.css" -p "$(NAME)"
# ============================ Compilation =================================
SRC_FILES:=$(shell find $(SOURCE) -name '*.c') $(shell find $(ETC)/media -name '*.png')
OBJ_FILES:=${subst $(ETC)/media/,$(OBJECT)/,${subst $(SOURCE)/,$(OBJECT)/,$(patsubst %.png, %.o, $(patsubst %.c, %.o, $(SRC_FILES)))}}
CPP_FILES:=$(shell find $(INCLUDES) -name '*.h')
AYA_FILES:=${subst $(INCLUDES)/,$(AYAS)/,$(patsubst %.h, %.html, $(CPP_FILES))}
all: utils binary
binary: $(OBJ_FILES)
$(CC) $(FLDFLAGS) $(OBJ_FILES) -o $(BINARY)
tags: $(SRC_FILES)
@ctags --recurse $(SOURCE)/
clean:
rm -rf $(OBJECT) $(BINARY) $(AYAS)
$(OBJECT)/%.o: $(ETC)/media/%.png
@mkdir -p $(shell dirname "$@")
@echo "const char media_$(shell basename $< .png)[] =" > $@.c
@base64 $< | \
sed -e 's/^\(.*\)$$/ "\1"/' | \
sed -e '$$ s/^\(.*\)$$/\1;/' >> $@.c
$(CC) -c $(FCFLAGS) $@.c -o $@
$(OBJECT)/%.o: $(SOURCE)/%.c
@mkdir -p $(shell dirname "$@")
$(CC) -c $(FCFLAGS) $< -o $@
utils:
(cd tools && make)
ayadoc: utils $(AYA_FILES)
$(AYAS)/%.html: $(INCLUDES)/%.h
@mkdir -p $(shell dirname "$@")
tools/out/aya $(AFLAGS) -i $< -o $@
# Installs everything.
install: binary utils ayadoc install_setup install_parsee install_tools install_aya install_man
@echo Installed $(NAME) to $(PREFIX)!
install_setup:
install -dm755 "$(PREFIX)/bin"
install -dm755 "$(PREFIX)/share/doc"
install -dm755 "$(PREFIX)/man"
install_parsee:
install -Dm755 "$(BINARY)" "$(PREFIX)/bin/$(BINARY)"
TOOLS:=$(shell find 'tools/out' -name '*')
ITOOL:=${subst tools/out/,$(PREFIX)/bin/,$(patsubst tools/out/%, tools/out/$(BINARY)-%, $(TOOLS))}
install_tools: $(ITOOL)
$(PREFIX)/bin/$(BINARY)-%: tools/out/%
install -Dm755 "$<" "$@"
IHTML:=${subst $(AYAS)/,$(PREFIX)/share/doc/$(BINARY)/,$(AYA_FILES)}
install_aya: $(IHTML)
$(PREFIX)/share/doc/$(BINARY)/%: $(AYAS)/%
install -Dm644 "$<" "$@"
MPAGE:=$(shell find 'etc/man' -name '*.*')
PPAGE:=${subst etc/man/,$(PREFIX)/man/,$(MPAGE)}
install_man: $(PPAGE)
$(PREFIX)/man/%: etc/man/%
install -Dm644 "$<" "$@"

View file

@ -1,45 +1,55 @@
# Parsee - the jealous XMPP<=>Matrix bridge # Parsee - the jealous XMPP<=>Matrix bridge
Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is Parsee is a Matrix<=>XMPP bridge written in C99, with Cytoplasm, similar to Bifrost, but it is
NOT a drop-in replacment. NOT a drop-in replacment.
Currently, it is *alpha* stage, which means that I wouldn't recommend using this in production,
as I can change anything, at any time, and it may behave strangely at times.
## Why? ## Why?
### Naming ### Naming
The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi), The name 'Parsee' is actually a reference to [Parsee Mizuhashi](https://en.touhouwiki.net/wiki/Parsee_Mizuhashi),
a "*bridge* princess". a "*bridge* princess". The other name you actually can sometimes see explains itself, so I won't
be talking about it.
### Reasoning (personal to LDA) ### Reasoning (personal to LDA)
I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister I hate Bifrost. I also wanted to dip my toes in XMPP, XML, and bridges a bit. Also, as a sister
project to KappaChat, this means that I can integrate Parsee with KappaChat however I wish it project to KappaChat, this means that I can integrate Parsee with KappaChat however I wish it
to be, which allows me to mess around with a codebase I'm already familiar with. to be, which allows me to mess around with a codebase I'm already familiar with.
A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*. A more "up-to-date" reason may be to have a small, 'Just Werks' bridging solution *that is good*,
and maybe as a testing ground for Cytoplasm features I sometimes add.
Well, I'm *trying* to do that, at least. (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 Please scream at me if that fails(or just doesn't run on a overclocked Raspberry Pi 4B))
Pi 4B, which, by the way, is literally where Parsee+XMPP is running for now.)
### "Why not just use Matrix lol" ### "Why not just use Matrix lol"
### "Why not just use XMPP 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 One could also argue that both sides need to migrate(onboard) the other side, so
a bridge may be a good way to start. a bridge may be a good way to start.
## BUILDING ## BUILDING
```sh ```sh
$ make # This generates a 'parsee' executable. $ cc configure.c -o configure # that or use tcc -run to consolidate these two steps.
$ cd tools # If you want to build more tools $ ./configure # use -s if you want static Parsee+MbedTLS, use -s -l if LMDB is needed
$ make && cd .. $ make
$ make ayadoc # If you want to build HTML documentation $ make [PREFIX=...] install # run as root if on a protected dir like /usr
$ make [PREFIX=(install path)] install # To install Parsee.
``` ```
If there are any Cytoplasm-related build failures, you may want to check the Makefile to If there are any Cytoplasm-related build failures, you may want to check the Makefile to
change a few variables (you can set `CYTO_INC` and `CYTO_LIB` for Cytoplasm's include and change a few variables (you can set `CYTO_INC` and `CYTO_LIB` for Cytoplasm's include and
library paths specifically.) library paths specifically.)
If you build with MbedTLS, please mind setting the `CYTO_TLS_CA` env to Parsee.
### DEPENDENCIES ### DEPENDENCIES
Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm). Parsee tries to avoid dependencies aside from [Cytoplasm](https://git.telodendria.io/Telodendria/Cytoplasm).
Itself optionally depends on a good POSIX implementation, and optionally OpenSSL/LMDB (highly recommended, but 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). 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 ## RUNNING
First off, you may want to configure Parsee by running the `config` tool(generally named 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. `parsee-config` in most cases), with the correct flags, like here.
@ -51,7 +61,8 @@ parsee-config \
-H 'blow.hole' \ # Matrix homeserver name -H 'blow.hole' \ # Matrix homeserver name
-J 'parsee.blow.hole' \ # XMPP component host, must be reachable -J 'parsee.blow.hole' \ # XMPP component host, must be reachable
-s 'A very secure XMPP component secret' \ -s 'A very secure XMPP component secret' \
-p 5347 -p 5347 \
-M 65535 # Maximum stanza size. Stanzas larger than this from Parsee will be dropped to avoid stream closures. Leave this empty if you're unsure.
``` ```
If everything goes well, it should generate a `parsee.json` file. If everything goes well, it should generate a `parsee.json` file.
@ -64,33 +75,44 @@ returns with a landing page, then this side works. You can read it for some more
## DOCS ## DOCS
Currently, the main sources of documentation are the Ayadocs(for headers) and the manpages Currently, the main sources of documentation are the Ayadocs(for headers) and the manpages
(see `etc/man`) (see `etc/man`).
## TODOS before 1.0 rolls around ## TODOS before 1.0 rolls around
- Make Parsee actually go *vroooooooooommmmmmm*.
- Make sure Parsee can easily run on just about any reasonable POSIX
system.
- Avoid making 'back-puppets' from Matrix as much as possible
- Extension support. I'd need to design a good system, and maybe do it
with either shared libraries(`dlopen`/`dlclose` on POSIX) or use a
language like Janet or Lua.
- Add [libomemo](https://github.com/gkdr/libomemo) or something as an optional dependency. - Add [libomemo](https://github.com/gkdr/libomemo) or something as an optional dependency.
- It depends on more stuff anyways, and I don't want to weigh down the - It depends on more stuff anyways, and I don't want to weigh down the
dependency list of Parsee for that. dependency list of Parsee for that.
- Matrix's libolm is deprecated. They replaced it with a Rust version that - Matrix's libolm is deprecated. They replaced it with a Rust version that
pulls in *way too many* dependencies, and that lacks a C binding. We may pulls in *way too many* dependencies, and that lacks a C binding. We may
put in the work of either forking off libolm or making a binding to KappaChat. ~~put in the work of either forking off libolm or~~ be making a binding with
KappaChat(when I get around to remaking UI :p).
- Josh did infact tell me that maybe C bindings may happen. I'd be - Josh did infact tell me that maybe C bindings may happen. I'd be
willing to help out, but IDK. In any case, this will at best be an willing to help out, but IDK. In any case, this will at best be an
extension packagers may integrate properly. extension packagers may integrate properly.
- Get rid of the '?'-syntax and use another invalid Matrix char/valid XMPP char - Get rid of the '?'-syntax and use another invalid Matrix char/valid XMPP char
('$'?) for escaped? ('$'?) for escaped?
- PROPER FUCKING AVATARS
XMPP->Matrix is decent, Matrix->XMPP is effectiveny not done
- Consider making room/MUC admins/owners be able to plumb instead of it being - Consider making room/MUC admins/owners be able to plumb instead of it being
restricted to Parsee admins, with permission from MUC owners, too restricted to Parsee admins, with permission from MUC owners, too
- Limiting to admins may be a way to "control" consent for both, but this is - Limiting to admins may be a way to "control" consent for both, but this is
only if Parsee admins are good-willed, which we must assume such statment to only if Parsee admins are good-willed, which we must assume such statment to
be false by default. be false by default.
- Currently, MUC owners may kick Parsee out, with the effect of unlinking the
MUC.
- Look at XEPS-TBD.TXT for XEPs to be done - Look at XEPS-TBD.TXT for XEPs to be done
- Add a MUC server to Parsee, such that it may be able to hook onto it and therefore - Add a MUC server to Parsee, such that it may be able to hook onto it and therefore
support XMPP->Matrix bridging. support XMPP->Matrix bridging.
- Manage MUC DMs in a reasonable manner. Thanks `@freeoffers4u:matrix.org` for being - Manage MUC DMs in a reasonable manner. Thanks `@freeoffers4u:matrix.org` for being
a fucking annoyance and DMing an old Parsee semi-anon user for no clear reason. a fucking annoyance and DMing an old Parsee semi-anon user for no clear reason.
- Deadlocks. It's always deadlocks. - Make Parsee cope with stream closures(i.e: XMPP server turning off) better. As of
now, it just kills itself when that happens, instead of trying to negociate a new
connection, which would be a better method that would actually fit Parsee's own
principles.
## DONATING/CONTRIBUTING ## DONATING/CONTRIBUTING
If you know things about XMPP or Matrix, yet aren't familiar with C99, or just If you know things about XMPP or Matrix, yet aren't familiar with C99, or just
@ -107,7 +129,7 @@ You may also donate to [the LiberaPay](https://en.liberapay.com/Parsee), alongsi
currently maintaining Cytoplasm. currently maintaining Cytoplasm.
## IM chats ## IM chats
Please avoid asking for help/issues here. If you *really* want, you may just open Please avoid asking for help/issues there. If you *really* want, you may just open
an issue and link it over it. Basic respect for others/not being an asshat is required. an issue and link it over it. Basic respect for others/not being an asshat is required.
(Also, these are temporary room aliases.) (Also, these are temporary room aliases.)
- [#parsee:tedomum.net](https://matrix.to/#/%23parsee:tedomum.net) - [#parsee:tedomum.net](https://matrix.to/#/%23parsee:tedomum.net)

View file

@ -21,10 +21,14 @@ Somewhat implemented XEPs:
This allows reactions, which Matrix also has support to. The two This allows reactions, which Matrix also has support to. The two
systems don't seem *too* restrictive on one-another (unlike some systems don't seem *too* restrictive on one-another (unlike some
IM platforms I won't mention), so this doesn't sound too bad to do IM platforms I won't mention), so this doesn't sound too bad to do
HALF-IMPLEMENTED: Removing reacts won't work. TODO: Add support from Matrix.
~ https://xmpp.org/extensions/xep-0184.html ~ https://xmpp.org/extensions/xep-0184.html
Only Matrix->XMPP as of now. Requesting data from Matrix ASes without Only Matrix->XMPP as of now. Requesting data from Matrix ASes without
/sync seems like a non-option as of now, which _sucks_. /sync seems like a non-option as of now, which _sucks_.
~ https://xmpp.org/extensions/xep-0084.html
Avatar support would be extremely useful, if just a QoL improvment.
Matrix and XMPP both have support for these.
XEP-0084 is a pain in the ass to implement and seems generally just
For future XEPs: For future XEPs:
- https://xmpp.org/extensions/xep-0449.html - https://xmpp.org/extensions/xep-0449.html
@ -34,12 +38,7 @@ For future XEPs:
which is used along PEP, it seems, and meanwhile Matrix has ''support'' which is used along PEP, it seems, and meanwhile Matrix has ''support''
for packs too, tracking them is between "annoyance" and "yeah, no.". for packs too, tracking them is between "annoyance" and "yeah, no.".
ON STANDBY BECAUSE THESE HAVE BEEN TERRIBLE TO DEAL WITH AND WHO KEEPS WRITING On Standby:
THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT:
x https://xmpp.org/extensions/xep-0084.html
Avatar support would be extremely useful, if just a QoL improvment.
Matrix and XMPP both have support for these.
XEP-0084 is a pain in the ass to implement and seems generally just
unreliable, however. unreliable, however.
x https://xmpp.org/extensions/xep-0080.html x https://xmpp.org/extensions/xep-0080.html
Can't think of a good analogy to these... Can't think of a good analogy to these...
@ -48,7 +47,7 @@ THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT:
Not XEPs, but ideas that _needs_ to be added: Not XEPs, but ideas that _needs_ to be added:
~ "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d ~ "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d
Happens on Matrix. I'll need to handle that on XMPP as well. Happens on Matrix. I'll need to handle that on XMPP as well.
- Standalone/Static Parsee, ideally as small as it can be(if not as APE). ~ Standalone/Static Parsee, ideally as small as it can be(if not as APE).
- Kappa-like extension system(maybe bridging more than just Matrix-XMPP.) - Kappa-like extension system(maybe bridging more than just Matrix-XMPP.)
- https://www.youtube.com/watch?v=InL414iDZmY - https://www.youtube.com/watch?v=InL414iDZmY

863
build.c
View file

@ -1,863 +0,0 @@
/* build.c - Simple, POSIX, non-Cytoplasm utility to build out
* the entirety of Parsee from scratch, without any Makefiles.
*
* The main reason why this tool exists is merely because the
* current Make-based building is not POSIX compliant, and I
* am simply not porting it to be. The Makefile shall stay
* supported however, but if possible, use build.c.
* To run it, just build it with:
* cc build.c -o /tmp/build && /tmp/build
*
* TODO: Parallel jobs, CFLAGS and LDFLAGS.
* Note that this bit is formatted differently.
* --------------------------------
* LICENSE: CC0
* Written-By: LDA [lda@freetards.xyz] [@fourier:ari.lt] */
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#define DEFAULT_BUILD_PATH "build.conf"
#define DEFAULT_COMPILER "cc"
#define Compiler(info) (info.basic.cc ? info.basic.cc : DEFAULT_COMPILER)
static char *
string_rep_ext(char *in, char *ext1, char *ext2)
{
char *end;
size_t inLen;
if (!in || !ext1 || !ext2)
{
return NULL;
}
inLen = strlen(in);
end = inLen + in;
while (end >= in)
{
if (!strcmp(end, ext1))
{
size_t cpyLen = end - in;
size_t extLen = strlen(ext2);
char *ret = malloc(cpyLen + extLen + 1);
memcpy(ret, in, cpyLen);
memcpy(ret + cpyLen, ext2, extLen);
ret[cpyLen + extLen] = '\0';
return ret;
}
end--;
}
return NULL;
}
static char *
string_dup(char *in)
{
char *out;
size_t len;
if (!in)
{
return NULL;
}
len = strlen(in);
out = malloc(len + 1);
memcpy(out, in, len);
out[len] = '\0';
return out;
}
static char *
string_cat(char *in1, char *in2)
{
char *out;
size_t len1, len2;
if (!in1)
{
return string_dup(in2);
}
if (!in2)
{
return string_dup(in1);
}
len1 = strlen(in1);
len2 = strlen(in2);
out = malloc(len1 + len2 + 1);
memcpy(out, in1, len1);
memcpy(out + len1, in2, len2);
out[len1 + len2] = '\0';
return out;
}
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;
size_t quantity;
} str_array_t;
static str_array_t *
str_array_create(void)
{
str_array_t *ret = malloc(sizeof(*ret));
ret->values = NULL;
ret->quantity = 0;
return ret;
}
static void
str_array_free(str_array_t *arr)
{
size_t i;
if (!arr)
{
return;
}
for (i = 0; i < arr->quantity; i++)
{
if (arr->values[i]) free(arr->values[i]);
}
if (arr->values) free(arr->values);
free(arr);
}
static void
str_array_set(str_array_t *arr, size_t i, char *str)
{
if (!arr)
{
return;
}
if (i >= arr->quantity)
{
size_t size = (i+1) * sizeof(*arr->values);
size_t j;
arr->values = realloc(arr->values, size);
for (j = arr->quantity; j <= i; j++)
{
arr->values[j] = NULL;
}
arr->quantity = i + 1 ;
}
if (arr->values[i]) free(arr->values[i]);
arr->values[i] = string_dup(str);
}
static void
str_array_add(str_array_t *arr, char *str)
{
if (!arr)
{
return;
}
str_array_set(arr, arr->quantity, str);
}
static char *
str_array_get(str_array_t *arr, size_t i)
{
if (!arr)
{
return NULL;
}
return i < arr->quantity ? arr->values[i] : NULL;
}
static size_t
str_array_len(str_array_t *arr)
{
return arr ? arr->quantity : 0;
}
typedef struct buildinfo {
struct basic {
char *codename;
char *version;
char *name;
char *binary;
char *src;
char *inc;
char *obj;
char *cflags, *ldflags;
char *cc;
} basic;
char *repo;
} buildinfo_t;
static void
destroy_buildinfo(buildinfo_t *info)
{
if (!info)
{
return;
}
#define FreeIfExistent(v) do \
{ \
if (v) \
{ \
free(v); \
v = NULL; \
} \
} while (0)
FreeIfExistent(info->basic.codename);
FreeIfExistent(info->basic.version);
FreeIfExistent(info->basic.ldflags);
FreeIfExistent(info->basic.binary);
FreeIfExistent(info->basic.cflags);
FreeIfExistent(info->basic.name);
FreeIfExistent(info->basic.src);
FreeIfExistent(info->basic.inc);
FreeIfExistent(info->basic.obj);
FreeIfExistent(info->basic.cc);
FreeIfExistent(info->repo);
}
static char *
cmd_stdout(char *cmd)
{
FILE *f;
char *line = NULL;
size_t size;
if (!cmd)
{
return NULL;
}
if (!(f = popen(cmd, "r")))
{
return NULL;
}
getline(&line, &size, f);
pclose(f);
return line;
}
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)
{
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 void
mkdir_rec(char *dir)
{
char tmp[PATH_MAX];
char *start;
if (!dir || strlen(dir) >= PATH_MAX - 1)
{
return;
}
memset(tmp, '\0', sizeof(tmp));
for (start = dir; start && *start; start = strchrn(start, '/'))
{
char subtmp[PATH_MAX];
char *next = strchr(start, '/');
if (!next)
{
next = start + strlen(start);
}
memcpy(subtmp, start, next - start);
subtmp[next - start] = '\0';
{
memcpy(tmp, dir, start - dir);
tmp[strlen(tmp) - 1] = '\0';
mkdir(tmp, 0770);
}
}
}
static str_array_t *
split(char *dir)
{
str_array_t *ret;
char *start;
if (!dir || strlen(dir) >= PATH_MAX - 1)
{
return NULL;
}
ret = str_array_create();
for (start = dir; start; start = strchrn(start, ' '))
{
char subtmp[PATH_MAX];
char *next = strchr(start, ' ');
if (!next)
{
next = start + strlen(start);
}
memcpy(subtmp, start, next - start);
subtmp[next - start] = '\0';
str_array_add(ret, subtmp);
}
return ret;
}
static time_t
mod_date(char *file)
{
struct stat s;
if (stat(file, &s))
{
return (time_t) 0;
}
return s.st_mtime;
}
static bool
build_file(char *cSource, buildinfo_t info, bool isTool)
{
str_array_t *args, *cflags;
char *oFileName, *objPath, *oFile;
int ret, i = 0;
int srclen;
char *code, *name, *vers, *repo;
if (!cSource)
{
return false;
}
if (!isTool)
{
srclen = strncmp(cSource, "src", 3) ?
strlen(info.basic.obj) + 1 :
strlen(info.basic.src) + 1 ;
objPath = string_cat(info.basic.obj, "/");
oFile = string_rep_ext(cSource + srclen, ".c", ".o");
oFileName = string_cat(objPath, oFile);
}
else
{
srclen = 6;
objPath = string_dup("tools/out/");
oFile = string_rep_ext(cSource + srclen, ".c", "");
oFileName = string_cat(objPath, oFile);
}
mkdir_rec(oFileName);
if (!isTool && (mod_date(cSource) < mod_date(oFileName)))
{
free(objPath);
free(oFileName);
free(oFile);
return true;
}
args = str_array_create();
if (!isTool)
{
printf("\tCC %s...\n", cSource);
}
str_array_add(args, Compiler(info));
if (isTool)
{
str_array_add(args, "-lCytoplasm");
str_array_add(args, "-I.");
}
else
{
str_array_add(args, "-c");
}
str_array_add(args, cSource);
cflags = split(info.basic.cflags);
for (i = 0; i < str_array_len(cflags); i++)
{
str_array_add(args, str_array_get(cflags, i));
}
str_array_free(cflags);
str_array_add(args, "-o");
str_array_add(args, oFileName);
str_array_add(args, "-I");
str_array_add(args, info.basic.inc);
str_array_add(args, "-I");
str_array_add(args, info.basic.src);
{
char *pre = string_cat("\"", info.basic.version);
char *pos = string_cat(pre, "\"");
vers = string_cat("-DVERSION=", pos);
str_array_add(args, vers);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.basic.name);
char *pos = string_cat(pre, "\"");
name = string_cat("-DNAME=", pos);
str_array_add(args, name);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.basic.codename);
char *pos = string_cat(pre, "\"");
code = string_cat("-DCODE=", pos);
str_array_add(args, code);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.repo);
char *pos = string_cat(pre, "\"");
repo = string_cat("-DREPOSITORY=", pos);
str_array_add(args, repo);
free(pos);
free(pre);
}
str_array_add(args, NULL);
ret = exec_code(Compiler(info), args->values);
str_array_free(args);
free(objPath);
free(oFileName);
free(oFile);
free(vers);
free(name);
free(code);
free(repo);
return ret == EXIT_SUCCESS;
}
static bool
finalise_file(str_array_t *arr, buildinfo_t info)
{
str_array_t *flags, *ldflags;
size_t i;
bool ret = true;
if (!arr)
{
return false;
}
flags = str_array_create();
str_array_add(flags, Compiler(info));
ldflags = split(info.basic.cflags);
for (i = 0; i < str_array_len(ldflags); i++)
{
str_array_add(flags, str_array_get(ldflags, i));
}
str_array_free(ldflags);
str_array_add(flags, "-lCytoplasm");
for (i = 0; i < str_array_len(arr); i++)
{
char *file = str_array_get(arr, i);
if (file)
{
size_t srclen = strncmp(file, "src", 3) ?
strlen(info.basic.obj) + 1 :
strlen(info.basic.src) + 1 ;
char *objPath = string_cat(info.basic.obj, "/");
char *oFile = string_rep_ext(file + srclen, ".c", ".o");
char *oFileName = string_cat(objPath, oFile);
str_array_add(flags, oFileName);
free(oFileName);
free(oFile);
free(objPath);
}
}
str_array_add(flags, "-o");
str_array_add(flags, info.basic.binary);
str_array_add(flags, NULL);
ret = exec_code(Compiler(info), flags->values);
str_array_free(flags);
return ret;
}
static str_array_t *
collect_sources(char *dir, bool head, char *ext)
{
DIR *handle;
str_array_t *ret;
struct dirent *ent;
if (!dir)
{
return NULL;
}
ret = str_array_create();
handle = opendir(dir);
if (!handle)
{
printf("error: cannot open directory '%s'\n", dir);
return ret;
}
while ((ent = readdir(handle)))
{
char *name = ent->d_name;
if (*name == '.') continue;
if (strlen(name) > strlen(ext) &&
!strcmp(name + strlen(name) - strlen(ext), ext))
{
char *d1 = string_cat(dir, "/");
char *na = string_cat(d1, name);
str_array_add(ret, na);
free(d1);
free(na);
continue;
}
if (!strchr(name, '.') &&
strcmp(name, "out") &&
strcmp(name, "Makefile"))
{
str_array_t *sub;
char *d1 = string_cat(dir, "/");
char *d2 = string_cat(d1, name);
size_t i;
sub = collect_sources(d2, false, ext);
for (i = 0; i < str_array_len(sub); i++)
{
char *file = str_array_get(sub, i);
str_array_add(ret, file);
}
str_array_free(sub);
free(d2);
free(d1);
}
}
closedir(handle);
return ret;
}
static char *
process_png(char *png, buildinfo_t info)
{
size_t i = 0;
char *symbol;
char *cFile, *oFile;
char *pcFile, *poFile;
char *pcFile1, *poFile1;
char *filename, *symbol1;
char *arguments[8] = { 0 };
if (!png)
{
return NULL;
}
if (!(filename = strchrl(png, '/')))
{
return NULL;
}
filename++;
pcFile1= string_cat(info.basic.obj, "/");
pcFile = string_cat(pcFile1, filename);
cFile = string_rep_ext(pcFile, ".png", ".c");
free(pcFile);
free(pcFile1);
poFile1= string_cat(info.basic.obj, "/");
poFile = string_cat(poFile1, filename);
oFile = string_rep_ext(poFile, ".png", ".o");
free(poFile);
free(poFile1);
symbol1 = string_rep_ext(filename, ".png", "");
symbol = string_cat("media_", symbol1);
free(symbol1);
mkdir_rec(oFile);
mkdir_rec(cFile);
/* Build the image into Base64 */
arguments[i++] = "tools/out/b64";
arguments[i++] = png;
arguments[i++] = symbol;
arguments[i++] = cFile;
arguments[i++] = NULL;
if (exec_code(arguments[0], arguments))
{
free(symbol);
free(cFile);
free(oFile);
return NULL;
}
/* Compile it out */
i = 0;
arguments[i++] = Compiler(info);
arguments[i++] = "-c";
arguments[i++] = cFile;
arguments[i++] = "-o";
arguments[i++] = oFile;
arguments[i++] = NULL;
if (exec_code(arguments[0], arguments))
{
free(symbol);
free(cFile);
free(oFile);
return NULL;
}
free(symbol);
free(oFile);
return cFile;
}
/* Builds the entirety of Parsee. */
static int
main_build(int argc, char *argv[])
{
buildinfo_t info = { 0 };
FILE *buildinfo = NULL;
char *line = NULL;
size_t size, i;
ssize_t nread;
str_array_t *sources, *images;
/* Step 1: Get all basic information from build.conf */
if (!(buildinfo = fopen(DEFAULT_BUILD_PATH, "r")))
{
printf("error: cannot open '%s'\n", DEFAULT_BUILD_PATH);
goto fail;
}
while ((nread = getline(&line, &size, buildinfo)) != -1)
{
char *eq = strchr(line, '=');
char *end = strchr(line, '\n');
char *key, *val;
if (!eq)
{
free(line);
printf(
"error: line in '%s' does not contain '='\n",
DEFAULT_BUILD_PATH
);
goto fail;
}
/* Set delimiters */
*eq = '\0';
if (end) *end = '\0';
key = line;
val = eq + 1;
/* Now, we have KV mappings. */
#define If(k, v, d) do \
{ \
if (!strcmp(key, k) && !info.v) \
{ \
info.v = string_dup(val); \
printf("%s: %s\n", d, val); \
} \
} while (0)
If("CODE", basic.codename, "Codename");
If("NAME", basic.name, "Name");
If("VERSION", basic.version, "Version");
If("BINARY", basic.binary, "Binary name");
If("CFLAGS", basic.cflags, "C compiler arguments");
If("LDFLAGS", basic.ldflags, "Linker arguments");
If("INCLUDES", basic.inc, "Include path");
If("SOURCE", basic.src, "Source path");
If("OBJECT", basic.obj, "Object path");
If("CC", basic.cc, "Compiler");
}
if (line)
{
free(line);
line = NULL;
}
fclose(buildinfo);
buildinfo = NULL;
/* Step 2: Get all information from commands. */
if (!(info.repo = cmd_stdout("git remote get-url origin")))
{
printf("error: cannot find origins url\n");
goto fail;
}
info.repo = trim_nl(info.repo);
if (argc >= 2 && !strcmp(argv[1], "clean"))
{
char *args[8];
size_t i;
unlink(info.basic.binary);
args[i++] = "rm";
args[i++] = "-r";
args[i++] = info.basic.obj;
args[i++] = NULL;
exec_code(args[0], args);
goto end;
}
/* Step 3: Build all utilities. */
sources = collect_sources("tools", true, ".c");
for (i = 0; i < str_array_len(sources); i++)
{
char *file = str_array_get(sources, i);
printf("\tTOOL %s...\n", file);
if (!build_file(file, info, true))
{
str_array_free(sources);
goto fail;
}
}
str_array_free(sources);
/* Step 4: Build all media files. */
sources = collect_sources(info.basic.src, true, ".c");
images = collect_sources("etc/media", true, ".png");
for (i = 0; i < str_array_len(images); i++)
{
char *file = str_array_get(images, i);
char *out;
out = process_png(file, info);
if (!out)
{
str_array_free(images);
str_array_free(sources);
goto fail;
}
printf("\tPNG %s\n", file);
str_array_add(sources, out);
free(out);
}
str_array_free(images);
/* Step 5: Build all of Parsee itself */
for (i = 0; i < str_array_len(sources); i++)
{
char *file = str_array_get(sources, i);
if (!build_file(file, info, false))
{
str_array_free(sources);
goto fail;
}
}
printf("\tLINK\n");
if (!finalise_file(sources, info))
{
str_array_free(sources);
goto fail;
}
str_array_free(sources);
/* TODO: Step 6: Build every Ayadoc */
end:
destroy_buildinfo(&info);
return EXIT_SUCCESS;
fail:
if (buildinfo)
{
fclose(buildinfo);
buildinfo = NULL;
}
destroy_buildinfo(&info);
return EXIT_FAILURE;
}
int
main(int argc, char *argv[])
{
/* TODO: Multiple flags(build/install/ayadoc/...) */
if ((argc - 1) < 1)
{
/* No arguments, let's just build. */
return main_build(argc, argv);
}
if (!strcmp(argv[1], "build") ||
!strcmp(argv[1], "clean"))
{
return main_build(argc, argv);
}
printf("%s: unknown verb: %s\n", argv[0], argv[1]);
return EXIT_FAILURE;
}

View file

@ -1,8 +1,10 @@
CODE=tomboyish-bridges-adventure CODE=lunar-rainbow
NAME=Parsee NAME=Parsee
VERSION=0.1.0 VERSION=0.3.0
BINARY=parsee BINARY=parsee
SOURCE=src SOURCE=src
INCLUDES=src/include INCLUDES=src/include
OBJECT=build OBJECT=build
CC=cc CC=cc
CFLAGS=-O3
PREFIX=/usr

727
configure.c Normal file
View file

@ -0,0 +1,727 @@
/* configure.c - Simple, POSIX, non-Cytoplasm utility to build out
* a Parsee Makefile.
* To run it, just build it with:
* cc configure.c -o configure && configure
* --------------------------------
* LICENSE: CC0
* Written-By: LDA [lda@freetards.xyz] [@fourier:ari.lt] */
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <libgen.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
static char *
string_rep_ext(char *in, char *ext1, char *ext2)
{
char *end;
size_t inLen;
if (!in || !ext1 || !ext2)
{
return NULL;
}
inLen = strlen(in);
end = inLen + in;
while (end >= in)
{
if (!strcmp(end, ext1))
{
size_t cpyLen = end - in;
size_t extLen = strlen(ext2);
char *ret = malloc(cpyLen + extLen + 1);
memcpy(ret, in, cpyLen);
memcpy(ret + cpyLen, ext2, extLen);
ret[cpyLen + extLen] = '\0';
return ret;
}
end--;
}
return NULL;
}
static char *
string_dup(char *in)
{
char *out;
size_t len;
if (!in)
{
return NULL;
}
len = strlen(in);
out = malloc(len + 1);
memcpy(out, in, len);
out[len] = '\0';
return out;
}
static char *
string_cat(char *in1, char *in2)
{
char *out;
size_t len1, len2;
if (!in1)
{
return string_dup(in2);
}
if (!in2)
{
return string_dup(in1);
}
len1 = strlen(in1);
len2 = strlen(in2);
out = malloc(len1 + len2 + 1);
memcpy(out, in1, len1);
memcpy(out + len1, in2, len2);
out[len1 + len2] = '\0';
return out;
}
typedef struct str_array {
char **values;
size_t quantity;
} str_array_t;
static str_array_t *
str_array_create(void)
{
str_array_t *ret = malloc(sizeof(*ret));
if (!ret)
{
return NULL;
}
ret->values = NULL;
ret->quantity = 0;
return ret;
}
static void
str_array_free(str_array_t *arr)
{
size_t i;
if (!arr)
{
return;
}
for (i = 0; i < arr->quantity; i++)
{
if (arr->values[i]) free(arr->values[i]);
}
if (arr->values) free(arr->values);
free(arr);
}
static void
str_array_set(str_array_t *arr, size_t i, char *str)
{
if (!arr)
{
return;
}
if (i >= arr->quantity)
{
size_t size = (i+1) * sizeof(*arr->values);
size_t j;
arr->values = realloc(arr->values, size);
for (j = arr->quantity; j <= i; j++)
{
arr->values[j] = NULL;
}
arr->quantity = i + 1 ;
}
if (arr->values[i]) free(arr->values[i]);
arr->values[i] = string_dup(str);
}
static void
str_array_add(str_array_t *arr, char *str)
{
if (!arr)
{
return;
}
str_array_set(arr, arr->quantity, str);
}
static char *
str_array_get(str_array_t *arr, size_t i)
{
if (!arr)
{
return NULL;
}
return i < arr->quantity ? arr->values[i] : NULL;
}
static size_t
str_array_len(str_array_t *arr)
{
return arr ? arr->quantity : 0;
}
static char *
cmd_stdout(char *cmd)
{
FILE *f;
char *line = NULL;
size_t size;
int result;
if (!cmd)
{
goto failure;
}
if (!(f = popen(cmd, "r")))
{
goto failure;
}
getline(&line, &size, f);
result = pclose(f);
if (result < 0)
{
perror("pclose(3)");
goto failure;
}
else if (result)
{
fprintf(stderr, "command exited with status %d: %s\n", result, cmd);
goto failure;
}
return line;
failure:
free(line);
return NULL;
}
static char *
strchrn(char *s, char c)
{
s = strchr(s, c);
return s ? s+1 : NULL;
}
static str_array_t *
split(char *dir)
{
str_array_t *ret;
char *start;
if (!dir || strlen(dir) >= PATH_MAX - 1)
{
return NULL;
}
ret = str_array_create();
for (start = dir; start; start = strchrn(start, '/'))
{
char subtmp[PATH_MAX];
char *next = strchr(start, '/');
if (!next)
{
next = start + strlen(start);
}
memcpy(subtmp, start, next - start);
subtmp[next - start] = '\0';
str_array_add(ret, subtmp);
}
return ret;
}
static str_array_t *
collect_sources(char *dir, bool head, char *ext)
{
DIR *handle;
str_array_t *ret;
struct dirent *ent;
if (!dir)
{
return NULL;
}
ret = str_array_create();
handle = opendir(dir);
if (!handle)
{
printf("error: cannot open directory '%s'\n", dir);
return ret;
}
while ((ent = readdir(handle)))
{
char *name = ent->d_name;
if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
if (strlen(name) > strlen(ext) &&
!strcmp(name + strlen(name) - strlen(ext), ext))
{
char *d1 = string_cat(dir, "/");
char *na = string_cat(d1, name);
str_array_add(ret, na);
free(d1);
free(na);
continue;
}
{
char *d1 = string_cat(dir, "/");
char *d2 = string_cat(d1, name);
size_t i;
struct stat sb;
if (stat(d2, &sb))
{
fprintf(stderr, "stat(2) %s: %s\n", d2, strerror(errno));
free(d2);
free(d1);
}
else if (S_ISDIR(sb.st_mode))
{
str_array_t *sub = collect_sources(d2, false, ext);
for (i = 0; i < str_array_len(sub); i++)
{
char *file = str_array_get(sub, i);
str_array_add(ret, file);
}
str_array_free(sub);
}
free(d2);
free(d1);
}
}
closedir(handle);
return ret;
}
/* Builds the entirety of Parsee. */
static void
write_objects(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_images(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".png", ".o");
char *obj = string_cat("build", ofl + 3 + 1 + 5);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_executable(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
write_ayas(FILE *makefile, str_array_t *sources)
{
size_t i;
if (!makefile || !sources)
{
return;
}
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".h", ".html");
char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7);
fprintf(makefile, " %s", obj);
free(ofl);
free(obj);
}
}
static void
analyse_dependencies(FILE *makefile, char *src)
{
FILE *source = fopen(src, "r");
char *lineptr = NULL;
char *dn = strdup(src), *dirn;
size_t len = 0;
dirn = dirname(dn);
while (getline(&lineptr, &len, source) != -1)
{
char *corrptr = lineptr, *nl, *chevron, *quote, *cutoff;
char *basedir, *srcdir, *incdir, *tmp;
struct stat statinfo;
/* try to parse the line */
if ((nl = strchr(corrptr, '\n')))
{
*nl = '\0';
}
while (*corrptr && isblank(*corrptr))
{
corrptr++;
}
if (strncmp(corrptr, "#include \"", 10) &&
strncmp(corrptr, "#include <", 10))
{
continue;
}
corrptr += 10;
chevron = strchr(corrptr, '>');
chevron = chevron ? chevron : corrptr + strlen(corrptr);
quote = strchr(corrptr, '"');
quote = quote ? quote : corrptr + strlen(corrptr);
cutoff = chevron < quote ? chevron : quote;
*cutoff = '\0';
/* if we found something, try to resolve it */
tmp = string_cat(dirn, "/");
basedir = string_cat(tmp, corrptr);
free(tmp);
if (!stat(basedir, &statinfo))
{
fprintf(makefile, " %s", basedir);
free(basedir);
continue;
}
free(basedir);
srcdir = string_cat("src/", corrptr);
if (!stat(srcdir, &statinfo))
{
fprintf(makefile, " %s", srcdir);
free(srcdir);
continue;
}
free(srcdir);
incdir = string_cat("src/include/", corrptr);
if (!stat(incdir, &statinfo))
{
fprintf(makefile, " %s", incdir);
free(incdir);
continue;
}
free(incdir);
}
free(lineptr);
free(dn);
fclose(source);
}
static int
main_build(int argc, char *argv[])
{
FILE *makefile;
char *repo = cmd_stdout("git remote get-url origin");
size_t i;
bool with_static = false, with_lmdb = false;
int opt;
str_array_t *sources, *images, *utils, *aya;
if (repo)
{
char *lf = strchr(repo, '\n');
if (lf)
{
*lf = '\0';
}
}
else
{
repo = strdup("N/A");
}
while ((opt = getopt(argc, argv, "sl")) != -1)
{
switch (opt)
{
case 's':
with_static = true;
break;
case 'l':
with_lmdb = with_static;
with_static = true;
break;
}
}
makefile = fopen("Makefile", "w");
if (!makefile)
{
fprintf(stderr, "Couldn't create Makefile.\n");
fprintf(stderr, "This isn't good, actually.\n");
return EXIT_FAILURE;
}
fprintf(makefile, "# Autogenerated POSIX Makefile from Parsee\n");
fprintf(makefile, "# Ideally do not touch, unless you have a very ");
fprintf(makefile, "good reason to do it. \n\n");
fprintf(makefile, ".POSIX: \n");
fprintf(makefile, "include build.conf\n\n");
fprintf(makefile, "AYAYAS=ayaya\n");
fprintf(makefile, "CYTO_LIB=/usr/local/lib\n");
fprintf(makefile, "CYTO_INC=/usr/local/include\n");
fprintf(makefile, "ETC=etc\n\n");
fprintf(makefile, "all: utils binary ayadoc\n\n");
/* Create all objects */
sources = collect_sources("src", true, ".c");
images = collect_sources("etc/media", true, ".png");
fprintf(makefile, "binary:");
write_objects(makefile, sources);
write_images(makefile, images);
fprintf(makefile, "\n\t");
{
fprintf(makefile, "$(CC) -o $(BINARY)");
if (with_static)
{
fprintf(makefile, " -static");
}
fprintf(makefile, " -L $(CYTO_LIB)");
write_objects(makefile, sources);
write_images(makefile, images);
if (with_static)
{
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n");
}
/* Write rules for every source */
for (i = 0; i < str_array_len(sources); i++)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
fprintf(makefile, "%s: %s", obj, src);
analyse_dependencies(makefile, src);
fprintf(makefile, "\n");
{
str_array_t *s = split(obj);
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
str_array_free(s);
fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) ");
fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" ");
fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" ");
fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" ");
fprintf(makefile, "-DREPOSITORY=\"\\\"%s\\\"\" ", repo);
fprintf(makefile, "$(CFLAGS) %s -o %s\n", src, obj);
}
free(ofl);
free(obj);
}
for (i = 0; i < str_array_len(images); i++)
{
char *src = str_array_get(images, i);
char *ofl = string_rep_ext(src, ".png", ".o");
char *obj = string_cat("build", ofl + 3 + 1 + 5);
char *cfl = string_cat("build", src + 3 + 1 + 5);
fprintf(makefile, "%s: %s\n", obj, src);
{
str_array_t *s = split(obj);
char *sym;
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
sym = j != -1 ? str_array_get(s, j): NULL;
sym = string_rep_ext(sym, ".o", "");
str_array_free(s);
fprintf(makefile, "\ttools/out/b64 %s 'media_%s' '%s.c'\n", src, sym, obj);
fprintf(makefile, "\t$(CC) -c %s.c -o %s\n", obj, obj);
free(sym);
}
free(obj);
free(ofl);
free(cfl);
}
/* Build utilities */
utils = collect_sources("tools", true, ".c");
fprintf(makefile, "utils:");
write_executable(makefile, utils);
fprintf(makefile, "\n");
for (i = 0; i < str_array_len(utils); i++)
{
char *src = str_array_get(utils, i);
char *ofl = string_rep_ext(src, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
fprintf(makefile, "%s: %s\n", obj, src);
{
fprintf(makefile, "\t@mkdir -p tools/out\n");
fprintf(makefile, "\t$(CC) -o %s", obj);
if (with_static)
{
fprintf(makefile, " -static");
}
fprintf(makefile, " %s", src);
fprintf(makefile, " -I $(CYTO_INC)");
fprintf(makefile, " -L $(CYTO_LIB)");
if (with_static)
{
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto");
fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(CFLAGS)\n");
}
free(ofl);
free(obj);
}
/* Build Aya */
aya = collect_sources("src/include", true, ".h");
fprintf(makefile, "ayadoc:");
write_ayas(makefile, aya);
fprintf(makefile, "\n");
for (i = 0; i < str_array_len(aya); i++)
{
char *src = str_array_get(aya, i);
char *ofl = string_rep_ext(src, ".h", ".html");
char *obj = string_cat("$(AYAYAS)", ofl + 3 + 1 + 7);
fprintf(makefile, "%s: %s\n", obj, src);
{
str_array_t *s = split(obj);
ssize_t j;
fprintf(makefile, "\t@mkdir -p ");
for (j = 0; j < str_array_len(s) - 1; j++)
{
fprintf(makefile, "%s/", str_array_get(s, j));
}
fprintf(makefile, "\n");
str_array_free(s);
fprintf(makefile,
"\ttools/out/aya "
"-C etc/ayadoc/style.css "
"-p $(NAME) "
"-i %s -o %s\n", src, obj
);
}
free(ofl);
free(obj);
}
fprintf(makefile, "install-parsee: binary\n");
{
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n");
fprintf(makefile, "\tcp $(BINARY) $(PREFIX)/bin\n");
fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/$(BINARY)\n");
}
fprintf(makefile, "install-tools: utils\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/bin\n");
for (i = 0; i < str_array_len(utils); i++)
{
char *tool = str_array_get(utils, i);
char *ofl = string_rep_ext(tool, ".c", "");
char *obj = string_cat("tools/out", ofl + 5);
char *bna = basename(obj);
fprintf(makefile, "\tcp %s $(PREFIX)/bin/parsee-%s\n", obj, bna);
fprintf(makefile, "\tchmod 755 $(PREFIX)/bin/parsee-%s\n", bna);
free(ofl);
free(obj);
}
fprintf(makefile, "install-aya: ayadoc\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/aya/$(BINARY)\n");
fprintf(makefile, "\tcp -R $(AYAYAS)/* $(PREFIX)/aya/$(BINARY)\n");
fprintf(makefile, "install-man:\n");
fprintf(makefile, "\tmkdir -m 755 -p $(PREFIX)/share/man\n");
fprintf(makefile, "\tcp -R etc/man/* $(PREFIX)/share/man\n");
fprintf(makefile,
"install: "
"install-parsee "
"install-tools "
"install-aya "
"install-man\n"
);
fprintf(makefile, "clean:\n\trm -rf ayaya build $(BINARY) tools/out\n");
str_array_free(sources);
str_array_free(images);
str_array_free(utils);
str_array_free(aya);
fflush(makefile);
fclose(makefile);
free(repo);
return EXIT_SUCCESS;
}
int
main(int argc, char *argv[])
{
return main_build(argc, argv);
}

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-adminify 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee-adminify 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee-adminify - bootstrap an admin to a new Parsee server parsee-adminify - bootstrap an admin to a new Parsee server

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-aya 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee-aya 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee-aya - generate some nice Ayaya! documentation parsee-aya - generate some nice Ayaya! documentation

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-config 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee-config 1 "Parsee Utility" "lunar-rainbow"
.SH NAME .SH NAME
parsee-config - generate a basic configuration file parsee-config - generate a basic configuration file
@ -11,8 +11,10 @@ parsee-config
.B [-s SHARED_SECRET] .B [-s SHARED_SECRET]
.B [-m MEDIA_URL] .B [-m MEDIA_URL]
.B [-J JABBER_HOST] .B [-J JABBER_HOST]
.B [-j JABBER_ADDR]
.B [-p JABBER_PORT] .B [-p JABBER_PORT]
.B [-d DATABASE] .B [-d DATABASE]
.B [-M MAX_STANZA]
.B [-S DATABASE size] .B [-S DATABASE size]
.SH DESCRIPTION .SH DESCRIPTION
@ -33,6 +35,7 @@ $ parsee-config \\
-H 'blow.hole' \\ -H 'blow.hole' \\
-s 'The Dark Shared Secret' \\ -s 'The Dark Shared Secret' \\
-J 'xmpp.blow.hole' \\ -J 'xmpp.blow.hole' \\
-j 'localhost' \\
-S 128 -S 128
.fi .fi
.if n \{\ .if n \{\
@ -54,6 +57,10 @@ For example, if you except Parsee users to be on
.I SHARED_SECRET .I SHARED_SECRET
is a shared secret known by Parsee and the XMPP component to authenticate. is a shared secret known by Parsee and the XMPP component to authenticate.
.TP .TP
.BR -M MAX_STANZA
.I MAX_STANZA
is the maximum stanza size accepted by the XMPP host. If it is less than 10000 bytes, then it shall be set to that limit(the standardised value).
.TP
.BR -m MEDIA_URL .BR -m MEDIA_URL
.I MEDIA_URL .I MEDIA_URL
is an optional field used by Parsee as an address that points to Matrix is an optional field used by Parsee as an address that points to Matrix
@ -63,6 +70,12 @@ media. It must be publicly accessible (behind a reverse proxy to HTTP:7642)
.I JABBER_HOST .I JABBER_HOST
is used as the component host for Parsee. is used as the component host for Parsee.
.TP .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 .BR -p JABBER_PORT
.I JABBER_PORT .I JABBER_PORT
is used as the component post for Parsee. Parsee uses it alongside is used as the component post for Parsee. Parsee uses it alongside

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee - the jealous XMPP-Matrix bridge parsee - the jealous XMPP-Matrix bridge

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,70 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-bridge-guidebook 7 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-bridge-guidebook - A short guidebook on running a Parsee bridge
.SH INTRODUCTION
.P
This manpage is intended to be a guidebook for Parsee administrators. It
is meant to show how to create an instance with an XMPP-Matrix server
(though it cannot be specific, due to their ecosystem diversity), how to
plumb rooms, and moderate them through.
.P
It also assumes Parsee is properly built and installed, in which case you
are seeing this from
.I man
itself.
.SH CONVENTIONS
This page shall assume a few things that
.B must
be changed to fit your configuration. Please read those carefully, or it
will come and bite at you!
.P
First off, it assumes that you have a domain at
.I blow.hole
and that any other domains related to Parsee are it's subdomains, as
you'll see.
.P
We also assume you're planning on making the XMPP component available at
.I j.blow.hole ,
and that Parsee can reach it by using
.I localhost:1234 .
It is highly recommended that Parsee can reach the component locally, as
the stream cannot be encrypted!
.P
The Parsee HTTP server (which is used for media and the appservice) shall
be reached through
.I https://p.blow.hole/
via a reverse proxy. This manual shall only show you what port to make
available, as there are many reverse proxy options available.
.P
Finally, the Matrix server will be publicly known as
.I m.blow.hole
.
That is, if
.B bob
is in it, then they shall be known as
.I @bob:m.blow.hole .
.SH SETTING UP
Setting up Parsee mainly involves creating a valid configuration file
and the database. Most of this however is dealt with by
.I parsee-config(1)
TODO
.P
.SH LICENSE
This document is under public domain, or CC0 if not allowed by local law.
.SH SEE ALSO
.B parsee(1), parsee-cmd-syntax(7)

View file

@ -0,0 +1,50 @@
." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-cmd-syntax 7 "Parsee Utility" "star-of-hope"
.SH NAME
parsee-cmd-syntax - Basic syntax information with Parsee Matrix commands
.SH DESCRIPTION
Parsee uses a specific syntax for commands, which is generally different
from regular bots, but closer to
.B dd(1) 's
syntax.
.PP
A command is formatted as so.
.sp
.if n \{\
.RS 4
.\}
.nf
.B ![NAME] arg1=val1 arg2='val2' arg3="val\(rs\(dq3"
.fi
.if n \{\
.RE
.\}
.sp
.PP
The
.B arg1=val1
syntax is to be used for simple values, with values containing no spaces.
.PP
The
.B NAME
attribute defines the command to be called, and the
.B arg1='val1' ,
and
.B arg2="val2"
syntax is to be used for simple values, with values containing spaces. If
the value needs to contain quotes, they may be escaped with
.B \(rs'
and
.B \(rs"
respectively.
.SH LICENSE
This document is under public domain, or CC0 if not allowed by local law.
.SH SEE ALSO
.B parsee(1)

BIN
etc/media/unknown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

1
guix.scm Symbolic link
View file

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

View file

@ -30,8 +30,9 @@ ASType(const ParseeConfig *c, char *user, char *room, bool status)
json = HashMapCreate(); json = HashMapCreate();
HashMapSet(json, "typing", JsonValueBoolean(status)); HashMapSet(json, "typing", JsonValueBoolean(status));
/* If someone types for 10 minutes straight, they got something weird man. */ /* If someone types for 5 minutes straight, they got something
HashMapSet(json, "timeout", JsonValueBoolean(10 MINUTES)); * weird man. */
HashMapSet(json, "timeout", JsonValueInteger(5 MINUTES));
ctx = ParseeCreateRequest(c, HTTP_PUT, path); ctx = ParseeCreateRequest(c, HTTP_PUT, path);
Free(path); Free(path);
ASAuthenticateRequest(c, ctx); ASAuthenticateRequest(c, ctx);

View file

@ -4,6 +4,7 @@
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h> #include <Cytoplasm/Uri.h>
#include <Cytoplasm/Sha.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@ -14,7 +15,7 @@ char *
ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime) ASUpload(const ParseeConfig *c, Stream *from, unsigned int size, char *mime)
{ {
char *size_str, *path, *ret, *user; char *size_str, *path, *ret, *user;
int i; unsigned int i;
HttpClientContext *ctx; HttpClientContext *ctx;
HashMap *reply; HashMap *reply;
if (!c || !from) if (!c || !from)
@ -127,3 +128,93 @@ ASReupload(const ParseeConfig *c, char *from, char **mime)
return ret; return ret;
} }
bool
ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
unsigned char *sha1;
size_t len;
if (!c || !mxc || !mime || !sha)
{
return false;
}
*mime = NULL;
*sha = NULL;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, &len));
StreamCopy(stream, fake);
StreamClose(fake);
sha1 = Sha1Raw((unsigned char *) buf, len);
free(buf);
*sha = ShaToHex(sha1, HASH_SHA1);
Free(sha1);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}
bool
ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
if (!c || !mxc || !mime || !out || !len)
{
return false;
}
*mime = NULL;
*out = NULL;
*len = 0;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/client/v1/media/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, len));
StreamCopy(stream, fake);
StreamClose(fake);
*out = Malloc(*len);
memcpy(*out, buf, *len);
free(buf);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}

View file

@ -10,15 +10,16 @@
#include <Matrix.h> #include <Matrix.h>
void bool
ASPing(const ParseeConfig *conf) ASPing(const ParseeConfig *conf)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
HashMap *json = NULL; HashMap *json = NULL;
char *path; char *path;
bool ret;
if (!conf) if (!conf)
{ {
return; return false;
} }
path = StrConcat(3, path = StrConcat(3,
@ -33,7 +34,9 @@ ASPing(const ParseeConfig *conf)
Free(path); Free(path);
json = HashMapCreate(); json = HashMapCreate();
ASAuthenticateRequest(conf, ctx); ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, json); ret = ParseeSetRequestJSON(ctx, json) == HTTP_OK;
HttpClientContextFree(ctx); HttpClientContextFree(ctx);
JsonFree(json); JsonFree(json);
return ret;
} }

View file

@ -136,3 +136,71 @@ ASGetName(const ParseeConfig *c, char *room, char *user)
} }
return ret; return ret;
} }
char *
ASGetAvatar(const ParseeConfig *c, char *room, char *user)
{
HttpClientContext *ctx;
HashMap *reply;
char *path = NULL, *ret = NULL;
char *u2 = user;
if (!c || !user)
{
return NULL;
}
if (room)
{
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.member/", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
user = u2;
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from room, got %s", ret);
}
if (!ret)
{
user = HttpUrlEncode(user);
path = StrConcat(3,
"/_matrix/client/v3/profile/", user, "/avatar_url"
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
user = u2;
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from profile, got %s", ret);
}
return ret;
}

View file

@ -25,19 +25,20 @@ ASGetRelations(const ParseeConfig *c, size_t n, char *room, char *event, char *t
} }
user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base); user = StrConcat(4, "@", c->sender_localpart, ":", c->server_base);
if (event) if (type)
{ {
path = StrConcat(6, path = StrConcat(8,
"/_matrix/client/v1/rooms/", room, "/_matrix/client/v1/rooms/", room,
"/relations/", event, "/relations/", event, "/", type,
"?user_id=", user "?user_id=", user
); );
} }
else else
{ {
path = StrConcat(4, path = StrConcat(6,
"/_matrix/client/v1/rooms/", room, "/_matrix/client/v1/rooms/", room,
"/relations?user_id=", user "/relations/", event,
"?user_id=", user
); );
} }
Free(user); Free(user);

View file

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

View file

@ -1,21 +1,43 @@
#include <AS.h> #include <AS.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h> #include <Cytoplasm/Uri.h>
#include <inttypes.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <Matrix.h> #include <Matrix.h>
static char *
TSToStr(uint64_t ts)
{
size_t len;
char *str;
len = snprintf(NULL, 0, "%"PRIu64, ts);
str = Malloc(len+1);
snprintf(str, len+1, "%"PRIu64, ts);
return str;
}
char * char *
ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c) ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c, uint64_t ts)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
char *path; char *path;
char *txn, *ret; char *txn, *ret;
char *ts_str;
HttpStatus status;
if (!ret)
{
Log(LOG_ERR, "%", ret);
}
HashMap *reply; HashMap *reply;
if (!conf || !id || !type || !user || !c) if (!conf || !id || !type || !user || !c)
{ {
@ -23,18 +45,27 @@ ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
return NULL; return NULL;
} }
if (!ts)
{
ts = UtilTsMillis();
}
ts_str = TSToStr(ts);
txn = StrRandom(16); txn = StrRandom(16);
path = StrConcat(9, id = HttpUrlEncode(id);
path = StrConcat(11,
"/_matrix/client/v3/rooms/", "/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?", id, "/send/", type, "/", txn, "?",
"user_id=", user "user_id=", user, "&ts=", ts_str
); );
Free(id);
Free(txn); Free(txn);
Free(ts_str);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path); ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path); Free(path);
ASAuthenticateRequest(conf, ctx); ASAuthenticateRequest(conf, ctx);
ParseeSetRequestJSON(ctx, c); status = ParseeSetRequestJSON(ctx, c);
reply = JsonDecode(HttpClientStream(ctx)); reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id"))); ret = StrDuplicate(JsonValueAsString(HashMapGet(reply, "event_id")));

View file

@ -25,7 +25,7 @@ CommandParse(char *cmd)
} }
end_data = strchr(cmd, ' '); end_data = strchr(cmd, ' ');
if (!end_data) if (!end_data || (cmd > end_data))
{ {
ret = Malloc(sizeof(*ret)); ret = Malloc(sizeof(*ret));
ret->command = StrDuplicate(cmd); ret->command = StrDuplicate(cmd);
@ -53,7 +53,7 @@ CommandParse(char *cmd)
switch (state) switch (state)
{ {
case STATE_WHITE: case STATE_WHITE:
if (!isblank(c)) if (!isblank((int) c))
{ {
state = STATE_NAME; state = STATE_NAME;
namestart = cur; namestart = cur;
@ -84,7 +84,7 @@ CommandParse(char *cmd)
{ {
char c = *cur; char c = *cur;
char cb[2] = { c, '\0' }; char cb[2] = { c, '\0' };
if ((type && c == char_type) || (!type && isblank(c))) if ((type && c == char_type) || (!type && isblank((int) c)))
{ {
break; break;
} }

View file

@ -15,35 +15,47 @@ CommandCreateRouter(void)
void void
CommandAddCommand(CommandRouter *rter, char *c, CommandRoute rte) CommandAddCommand(CommandRouter *rter, char *c, CommandRoute rte)
{ {
CommandRoute *indirect;
if (!rter || !c || !rte) if (!rter || !c || !rte)
{ {
return; return;
} }
HashMapSet(rter->routes, c, rte);
/* Little dirty trick to force C99 into submission, and since
* some architectures may separate data/code. Still don't like it... */
indirect = Malloc(sizeof(rte));
*indirect = rte;
HashMapSet(rter->routes, c, (void *) indirect);
} }
void void
RouteCommand(CommandRouter *rter, Command *cmd, void *d) RouteCommand(CommandRouter *rter, Command *cmd, void *d)
{ {
CommandRoute route; CommandRoute *route;
if (!rter || !cmd) if (!rter || !cmd)
{ {
return; return;
} }
route = HashMapGet(rter->routes, cmd->command); route = HashMapGet(rter->routes, cmd->command);
if (route) if (route && *route)
{ {
route(cmd, d); (*route)(cmd, d);
} }
} }
void void
CommandFreeRouter(CommandRouter *rter) CommandFreeRouter(CommandRouter *rter)
{ {
char *key;
CommandRoute *val;
if (!rter) if (!rter)
{ {
return; return;
} }
while (HashMapIterate(rter->routes, &key, (void **) &val))
{
Free(val);
}
HashMapFree(rter->routes); HashMapFree(rter->routes);
Free(rter); Free(rter);
} }

View file

@ -22,11 +22,28 @@ CommandHead(CmdBanUser, cmd, argp)
BotDestroy(); BotDestroy();
return; return;
} }
ASBan(data->config, room, user);
ReplySprintf("Banning %s from '%s'...", ASBan(data->config, room, user);
user, room ReplySprintf("Banning %s from '%s'...", user, room);
);
BotDestroy();
}
CommandHead(CmdNoFlyListDel, cmd, argp)
{
ParseeCmdArg *args = argp;
ParseeData *data = args->data;
HashMap *event = args->event;
char *user = HashMapGet(cmd->arguments, "user");
BotInitialise();
if (!user)
{
BotDestroy();
return;
}
ReplySprintf("Unbanning %s", user);
ParseeGlobalUnban(data, user);
BotDestroy(); BotDestroy();
} }

View file

@ -25,4 +25,6 @@ CommandHead(CmdHelp, cmd, argp)
); );
ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*"); ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*");
BotDestroy(); BotDestroy();
(void) cmd;
} }

View file

@ -32,4 +32,6 @@ CommandHead(CmdListBans, cmd, argp)
DbUnlock(data->db, listed); DbUnlock(data->db, listed);
BotDestroy(); BotDestroy();
(void) cmd;
} }

View file

@ -24,7 +24,8 @@ CommandHead(CmdPlumb, cmd, argp)
BotRequired(room); BotRequired(room);
/* Check MUC viability */ /* Check MUC viability */
if (ParseeManageBan(args->data, muc, NULL)) if (ParseeManageBan(args->data, muc, NULL) ||
!ParseeIsMUCWhitelisted(args->data, muc))
{ {
ReplySprintf("MUC '%s' is not allowed on this bridge.", muc); ReplySprintf("MUC '%s' is not allowed on this bridge.", muc);
goto end; goto end;
@ -62,7 +63,7 @@ CommandHead(CmdPlumb, cmd, argp)
if (chat_id) if (chat_id)
{ {
char *rev = StrConcat(2, muc, "/parsee"); char *rev = StrConcat(2, muc, "/parsee");
XMPPJoinMUC(args->data->jabber, "parsee", rev, false); XMPPJoinMUC(args->data->jabber, "parsee", rev, NULL, -1, false);
Free(rev); Free(rev);
} }

View file

@ -40,4 +40,6 @@ CommandHead(CmdStats, cmd, argp)
ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*"); ReplyBasic("*Written with a shoelace and UHU glue by LDA <3 !*");
BotDestroy(); BotDestroy();
(void) cmd;
} }

View file

@ -9,43 +9,60 @@
#include <stdlib.h> #include <stdlib.h>
static bool
Grab(ParseeData *data, Command *cmd, char **muc, char **chat_id, char **room)
{
if (HashMapGet(cmd->arguments, "muc"))
{
*muc = HashMapGet(cmd->arguments, "muc");
*chat_id = ParseeGetFromMUCID(data, *muc);
*room = ParseeGetRoomID(data, *chat_id);
if (!chat_id || !room)
{
return false;
}
return true;
}
else if (HashMapGet(cmd->arguments, "room"))
{
*room = HashMapGet(cmd->arguments, "room");
*chat_id = ParseeGetFromRoomID(data, *room);
*muc = ParseeGetMUCID(data, *chat_id);
if (!chat_id || !muc)
{
return false;
}
return true;
}
return false;
}
CommandHead(CmdUnlinkMUC, cmd, argp) CommandHead(CmdUnlinkMUC, cmd, argp)
{ {
ParseeCmdArg *args = argp; ParseeCmdArg *args = argp;
ParseeData *data = args->data; ParseeData *data = args->data;
HashMap *json, *event = args->event, *mucs; HashMap *event = args->event;
DbRef *ref;
char *muc = NULL, *chat_id = NULL, *room = NULL; char *muc = NULL, *chat_id = NULL, *room = NULL;
BotInitialise(); BotInitialise();
muc = HashMapGet(cmd->arguments, "muc"); if (!Grab(data, cmd, &muc, &chat_id, &room))
if (!muc)
{ {
ReplyBasic("`muc` field REQUIRED."); ReplyBasic("`muc`|`room` REQUIRED");
goto end; goto end;
} }
ref = DbLock(data->db, 1, "chats"); chat_id = ParseeGetFromMUCID(data, muc);
json = DbJson(ref); room = ParseeGetRoomID(data, chat_id);
chat_id = StrDuplicate(GrabString(json, 2, "mucs", muc));
if (!chat_id) if (!chat_id)
{ {
ReplySprintf("No internal mapping to '%s'.", muc); ReplySprintf("No internal mapping to '%s'.", muc);
goto end; goto end;
} }
mucs = GrabObject(json, 1, "mucs");
JsonValueFree(HashMapDelete(mucs, muc));
DbUnlock(data->db, ref);
room = ParseeGetRoomID(data, chat_id); ParseeUnlinkRoom(data, chat_id);
ref = DbLock(data->db, 1, "chats");
json = DbJson(ref);
mucs = GrabObject(json, 1, "rooms");
JsonValueFree(HashMapDelete(mucs, room));
DbUnlock(data->db, ref);
DbDelete(data->db, 2, "chats", chat_id);
/* TODO: Do it automatically, if *not plumbed* */ /* TODO: Do it automatically, if *not plumbed* */
ReplySprintf("The MUC %s is now *unlinked*.", muc); ReplySprintf("The MUC %s is now *unlinked*.", muc);

View file

@ -79,7 +79,7 @@ MatrixCreateNickChange(char *nick)
return map; return map;
} }
HashMap * HashMap *
MatrixCreateMedia(char *mxc, char *body, char *mime) MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info)
{ {
HashMap *map; HashMap *map;
char *mime_type = NULL, *matrix_type = NULL; char *mime_type = NULL, *matrix_type = NULL;
@ -91,9 +91,10 @@ MatrixCreateMedia(char *mxc, char *body, char *mime)
matrix_type = "m.file"; matrix_type = "m.file";
if (mime) if (mime)
{ {
size_t i; size_t i, len;
mime_type = StrDuplicate(mime); mime_type = StrDuplicate(mime);
for (i = 0; i < strlen(mime); i++) len = strlen(mime);
for (i = 0; i < len; i++)
{ {
if (mime_type[i] == '/') if (mime_type[i] == '/')
{ {
@ -119,6 +120,12 @@ MatrixCreateMedia(char *mxc, char *body, char *mime)
} }
map = HashMapCreate(); map = HashMapCreate();
JsonSet(map, JsonValueString(mime), 2, "info", "mimetype");
if (info && info->width && info->height)
{
JsonSet(map, JsonValueInteger(info->width), 2, "info", "w");
JsonSet(map, JsonValueInteger(info->height), 2, "info", "h");
}
HashMapSet(map, "msgtype", JsonValueString(matrix_type)); HashMapSet(map, "msgtype", JsonValueString(matrix_type));
HashMapSet(map, "mimetype", JsonValueString(mime)); HashMapSet(map, "mimetype", JsonValueString(mime));
HashMapSet(map, "body", JsonValueString(body)); HashMapSet(map, "body", JsonValueString(body));

68
src/FileInfo.c Normal file
View file

@ -0,0 +1,68 @@
#include <FileInfo.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <string.h>
#include <stdlib.h>
static int
GetField(XMLElement *elem)
{
XMLElement *child;
if (!elem || ArraySize(elem->children) != 1)
{
return 0;
}
child = ArrayGet(elem->children, 0);
return strtol(child->data, NULL, 10);
}
FileInfo *
FileInfoFromXMPP(XMLElement *stanza)
{
FileInfo *info;
XMLElement *reference, *sims, *file;
if (!stanza)
{
return NULL;
}
reference = XMLookForTKV(stanza,
"reference", "xmlns", "urn:xmpp:reference:0"
);
sims = XMLookForTKV(reference,
"media-sharing", "xmlns", "urn:xmpp:sims:1"
);
file = XMLookForTKV(sims,
"file", "xmlns", "urn:xmpp:jingle:apps:file-transfer:5"
);
/* TODO: We'll definitely need MIME types to do things like
* WebXDC */
if (!file)
{
return NULL;
}
info = Malloc(sizeof(*info));
info->width = GetField(XMLookForUnique(file, "width"));
info->height = GetField(XMLookForUnique(file, "height"));
info->size = GetField(XMLookForUnique(file, "size"));
return info;
}
void
FileInfoFree(FileInfo *info)
{
if (!info)
{
return;
}
Free(info);
}

View file

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

View file

@ -1,5 +1,6 @@
#include <Cytoplasm/HttpServer.h> #include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Cytoplasm.h> #include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/Platform.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h> #include <Cytoplasm/Util.h>
#include <Cytoplasm/Cron.h> #include <Cytoplasm/Cron.h>
@ -12,6 +13,7 @@
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <StanzaBuilder.h>
#include <Parsee.h> #include <Parsee.h>
#include <XMPP.h> #include <XMPP.h>
#include <AS.h> #include <AS.h>
@ -46,7 +48,7 @@ static const Argument arguments[] =
"Generates a parsee.yaml AS file before exiting") "Generates a parsee.yaml AS file before exiting")
Arg('v', false, NULL, Arg('v', false, NULL,
"Forces Parsee to print in a more verbose fashion " "Forces Parsee to print in a more verbose fashion "
"(-vv prints stanzas to stderr)") "(-vvv prints stanzas to stderr)")
Arg('h', false, NULL, Arg('h', false, NULL,
"Generates an help screen(this one!)") "Generates an help screen(this one!)")
@ -56,6 +58,50 @@ static const Argument arguments[] =
#undef Argument #undef Argument
}; };
void
ParseeCheckMatrix(void *datp)
{
static volatile uint64_t streak = 0;
ParseeData *data = datp;
if (data->config->accept_pings && !ASPing(data->config))
{
Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host);
if (++streak == 10)
{
DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats");
HashMap *json = DbJson(ref);
HashMap *mucs = GrabObject(json, 1, "mucs");
char *muc;
void *ignored;
/* Notify any potential MUCs about this */
while (HashMapIterate(mucs, &muc, &ignored))
{
char *id = StrRandom(32);
char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee");
StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id);
SetStanzaType(b, "groupchat");
SetStanzaBody(b,
"This bridge hasn't been able to reach the Matrix host, and "
"as such, some messages may not have been sent over."
);
WriteoutStanza(b, data->jabber, 0);
DestroyStanzaBuilder(b);
Free(sender);
Free(id);
}
(void) ignored;
DbUnlock(data->db, ref);
}
return;
}
streak = 0;
}
int int
Main(Array *args, HashMap *env) Main(Array *args, HashMap *env)
{ {
@ -78,6 +124,15 @@ Main(Array *args, HashMap *env)
); );
ParseePrintASCII(); ParseePrintASCII();
Log(LOG_INFO, "======================="); Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
#ifdef PLATFORM_IPHONE
Log(LOG_WARNING, "Wait. Are you running this on an iPhone?");
Log(LOG_WARNING, "You *ought* to have spoofed this, haven't you?");
Log(LOG_WARNING, "Simply jealous of you for doing this.");
#endif
LogConfigIndent(LogConfigGlobal()); LogConfigIndent(LogConfigGlobal());
{ {
@ -103,6 +158,8 @@ Main(Array *args, HashMap *env)
/* Write out the config file to a YAML document */ /* Write out the config file to a YAML document */
Log(LOG_INFO, "Generating YAML..."); Log(LOG_INFO, "Generating YAML...");
yaml = StreamOpen("parsee.yaml", "w"); yaml = StreamOpen("parsee.yaml", "w");
ParseeConfigLoad(configuration);
ParseeConfigInit();
ParseeExportConfigYAML(yaml); ParseeExportConfigYAML(yaml);
StreamClose(yaml); StreamClose(yaml);
Free(opts); Free(opts);
@ -113,6 +170,9 @@ Main(Array *args, HashMap *env)
case PARSEE_VERBOSE_LOG: case PARSEE_VERBOSE_LOG:
LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG); LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG);
break; break;
case PARSEE_VERBOSE_TIMINGS:
Log(LOG_DEBUG, "Logging bench information.");
break;
case PARSEE_VERBOSE_STANZA: case PARSEE_VERBOSE_STANZA:
Log(LOG_DEBUG, "Enabling stanza printing."); Log(LOG_DEBUG, "Enabling stanza printing.");
break; break;
@ -145,7 +205,6 @@ Main(Array *args, HashMap *env)
} }
} }
Free(opts); Free(opts);
ParseeSetThreads(xmpp, http);
} }
if (verbose >= PARSEE_VERBOSE_COMICAL) if (verbose >= PARSEE_VERBOSE_COMICAL)
@ -159,13 +218,16 @@ Main(Array *args, HashMap *env)
{ {
goto end; goto end;
} }
ParseeSetThreads(xmpp, http);
Log(LOG_NOTICE, "Connecting to XMPP..."); Log(LOG_NOTICE, "Connecting to XMPP...");
jabber = XMPPInitialiseCompStream( jabber = XMPPInitialiseCompStream(
parsee_conf->component_addr,
parsee_conf->component_host, parsee_conf->component_host,
parsee_conf->component_port parsee_conf->component_port
); );
Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber);
if (!XMPPAuthenticateCompStream( if (!XMPPAuthenticateCompStream(
jabber, jabber,
parsee_conf->shared_comp_secret parsee_conf->shared_comp_secret
@ -206,6 +268,12 @@ Main(Array *args, HashMap *env)
} }
ParseeInitialiseNickTable(); ParseeInitialiseNickTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising affiliation table");
}
ParseeInitialiseAffiliationTable();
conf.port = parsee_conf->port; conf.port = parsee_conf->port;
conf.threads = parsee_conf->http_threads; conf.threads = parsee_conf->http_threads;
conf.maxConnections = conf.threads << 2; conf.maxConnections = conf.threads << 2;
@ -223,18 +291,21 @@ Main(Array *args, HashMap *env)
if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart))
{ {
char *parsee = ParseeMXID(conf.handlerArgs); char *parsee = ParseeMXID(conf.handlerArgs);
ASSetAvatar(parsee_conf, ASSetAvatar(parsee_conf,
parsee, parsee,
"mxc://tedomum.net/" "mxc://tedomum.net/"
"7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136"
); );
ASSetName(parsee_conf, parsee, "Parsee bridge"); ASSetName(parsee_conf, parsee, "Parsee bridge");
Free(parsee); Free(parsee);
} }
Log(LOG_NOTICE, "Starting up local cronjobs..."); Log(LOG_NOTICE, "Starting up local cronjobs...");
cron = CronCreate(10 SECONDS); cron = CronCreate(10 SECONDS);
CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs); CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs);
CronEvery(cron, 10 SECONDS, ParseeCheckMatrix, conf.handlerArgs);
ParseeCleanup(conf.handlerArgs); ParseeCleanup(conf.handlerArgs);
CronStart(cron); CronStart(cron);
@ -247,8 +318,9 @@ Main(Array *args, HashMap *env)
} }
server = HttpServerCreate(&conf); server = HttpServerCreate(&conf);
((ParseeData *) conf.handlerArgs)->server = server;
if (!ParseeInitialiseSignals(server, xmpp_thr, jabber)) if (!ParseeInitialiseSignals(conf.handlerArgs, xmpp_thr))
{ {
goto end; goto end;
} }
@ -272,9 +344,12 @@ end:
CronStop(cron); CronStop(cron);
CronFree(cron); CronFree(cron);
ParseeFreeData(conf.handlerArgs); ParseeFreeData(conf.handlerArgs);
ParseeDestroyAffiliationTable();
ParseeDestroyNickTable(); ParseeDestroyNickTable();
ParseeDestroyOIDTable(); ParseeDestroyOIDTable();
ParseeDestroyHeadTable(); ParseeDestroyHeadTable();
ParseeDestroyJIDTable(); ParseeDestroyJIDTable();
(void) env;
return 0; return 0;
} }

View file

@ -8,24 +8,29 @@
#include <stdlib.h> #include <stdlib.h>
#include <StanzaBuilder.h> #include <StanzaBuilder.h>
#include <Unistring.h>
#include <Matrix.h> #include <Matrix.h>
#include <AS.h> #include <AS.h>
#include <ctype.h>
static const char * static const char *
GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to); GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to);
static void static char *
JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name) JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name, char *hash)
{ {
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
char *nick = StrDuplicate(name); Unistr *uninick = UnistrCreate(name);
Unistr *filtered = UnistrFilter(uninick, UnistrIsASCII); /* I'm not even going to try messing with the BMP anymore. */
char *nick = UnistrC(filtered);
char *rev = StrConcat(3, muc, "/", nick); char *rev = StrConcat(3, muc, "/", nick);
int nonce = 0; int nonce = 0;
while (!XMPPJoinMUC(data->jabber, jid, rev, true) && nonce < 32) UnistrFree(uninick);
UnistrFree(filtered);
/* TODO: vCards! */
while (!XMPPJoinMUC(data->jabber, jid, rev, hash, -1, true) && nonce < 32)
{ {
char *nonce_str = StrInt(nonce); char *nonce_str = StrInt(nonce);
char *input = StrConcat(3, sender, name, nonce_str); char *input = StrConcat(3, sender, name, nonce_str);
@ -50,7 +55,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name)
ParseePushNickTable(muc, sender, nick); ParseePushNickTable(muc, sender, nick);
Free(nick); Free(nick);
Free(rev); return (rev);
} }
static void static void
@ -93,20 +98,95 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key)) else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key))
{ {
char *jid = ParseeEncodeMXID(state_key); char *jid = ParseeEncodeMXID(state_key);
char *sha = NULL, *mime = NULL;
char *avatar = ASGetAvatar(data->config, NULL, state_key);
char *url = ParseeToUnauth(data, avatar, NULL);
chat_id = ParseeGetFromRoomID(data, room_id); chat_id = ParseeGetFromRoomID(data, room_id);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
Free(avatar);
avatar = NULL;
if (chat_id) if (chat_id)
{ {
char *muc = ParseeGetMUCID(data, chat_id); char *muc = ParseeGetMUCID(data, chat_id);
char *name = ASGetName(data->config, room_id, state_key); char *name = ASGetName(data->config, room_id, state_key);
char *jabber = JoinMUC(data, event, jid, muc, name, sha);
avatar = ASGetAvatar(data->config, NULL, state_key);
Log(LOG_DEBUG, "MATRIX: Joining as '%s' (avatar=%s)", jabber, avatar);
JoinMUC(data, event, jid, muc, name); Free(jabber);
Free(avatar);
Free(name); Free(name);
Free(muc); Free(muc);
avatar = NULL;
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */ /* TODO: XEP-0084 magic to advertise a new avatar if possible. */
} }
Free(jid); else
{
char *full_jid = StrConcat(3,
jid, "@", data->config->component_host
);
XMLElement *elem, *pevent, *items, *item, *meta, *info;
Log(LOG_DEBUG, "MATRIX: Got local user '%s'(mxid=%s)", jid, state_key);
elem = XMLCreateTag("message");
{
#define PUBSUB "http://jabber.org/protocol/pubsub"
#define AVATAR "urn:xmpp:avatar:metadata"
pevent = XMLCreateTag("event");
XMLAddAttr(pevent, "xmlns", PUBSUB "#event");
{
items = XMLCreateTag("items");
item = XMLCreateTag("item");
XMLAddAttr(items, "node", AVATAR);
XMLAddAttr(item, "id", sha);
{
meta = XMLCreateTag("metadata");
info = XMLCreateTag("info");
XMLAddAttr(meta, "xmlns", AVATAR);
XMLAddAttr(info, "id", sha);
XMLAddAttr(info, "url", url);
XMLAddAttr(info, "type", mime);
XMLAddChild(meta, info);
XMLAddChild(item, meta);
}
XMLAddChild(items, item);
XMLAddChild(pevent, items);
}
XMLAddChild(elem, pevent);
#undef PUBSUB
}
/* TODO: Broadcast PEP avatar change */
ParseeBroadcastStanza(data, full_jid, elem);
XMLFreeElement(elem);
elem = XMLCreateTag("presence");
{
XMLElement *x = XMLCreateTag("x");
XMLElement *photo;
XMLAddAttr(x, "xmlns", "vcard-temp:x:update");
photo = XMLCreateTag("photo");
XMLAddChild(photo, XMLCreateText(sha));
XMLAddChild(x, photo);
XMLAddChild(elem, x);
}
ParseeBroadcastStanza(data, full_jid, elem);
XMLFreeElement(elem);
Free(full_jid);
}
Free(chat_id); Free(chat_id);
Free(avatar);
Free(mime);
Free(sha);
Free(jid);
Free(url);
} }
else if ((StrEquals(membership, "leave") || else if ((StrEquals(membership, "leave") ||
StrEquals(membership, "ban")) StrEquals(membership, "ban"))
@ -175,14 +255,9 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
return; return;
} }
if (*body != '!') if (!body || *body != '!')
{ {
/* All commands are to be marked with a ! */ /* All commands are to be marked with a ! */
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("Please enter a valid command")
));
Free(profile); Free(profile);
return; return;
} }
@ -192,7 +267,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
Free(ASSend( Free(ASSend(
data->config, id, profile, data->config, id, profile,
"m.room.message", "m.room.message",
MatrixCreateNotice("You are not authorised to do this.") MatrixCreateNotice("You are not authorised to do this."), 0
)); ));
Free(profile); Free(profile);
return; return;
@ -214,10 +289,10 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
char *room_id = GrabString(event, 1, "room_id"); char *room_id = GrabString(event, 1, "room_id");
char *matrix_sender = GrabString(event, 1, "sender"); char *matrix_sender = GrabString(event, 1, "sender");
char *chat_id = NULL, *muc_id = NULL; char *chat_id = NULL, *muc_id = NULL;
char *user; char *user = NULL;
DbRef *room_data; DbRef *room_data = NULL;
HashMap *data_json; HashMap *data_json = NULL;
bool direct = false; bool direct = false;
if (!data || !event || !from || !to) if (!data || !event || !from || !to)
@ -249,7 +324,8 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
} }
else else
{ {
char *matrix_name; char *matrix_name = NULL, *matrix_avatar = NULL;
char *mime = NULL, *sha = NULL;
muc_id = ParseeGetMUCID(data, chat_id); muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id) if (!chat_id)
@ -263,10 +339,16 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
} }
matrix_name = ASGetName(data->config, room_id, matrix_sender); matrix_name = ASGetName(data->config, room_id, matrix_sender);
JoinMUC(data, event, *from, muc_id, matrix_name); matrix_avatar = ASGetAvatar(data->config, NULL, matrix_sender);
ASGetMIMESHA(data->config, matrix_avatar, &mime, &sha);
Free(JoinMUC(data, event, *from, muc_id, matrix_name, sha));
*to = muc_id; *to = muc_id;
Free(matrix_avatar);
Free(matrix_name); Free(matrix_name);
Free(mime);
Free(sha);
} }
Free(chat_id); Free(chat_id);
@ -276,28 +358,47 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
static void static void
ParseeMessageHandler(ParseeData *data, HashMap *event) ParseeMessageHandler(ParseeData *data, HashMap *event)
{ {
if (!data || !event)
{
return;
}
XMPPComponent *jabber = data->jabber; XMPPComponent *jabber = data->jabber;
StanzaBuilder *builder; StanzaBuilder *builder = NULL;
DbRef *ref = 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 *body = GrabString(event, 2, "content", "body");
char *id = GrabString(event, 1, "room_id"); char *id = GrabString(event, 1, "room_id");
char *ev_id = GrabString(event, 1, "event_id"); char *ev_id = GrabString(event, 1, "event_id");
char *m_sender = GrabString(event, 1, "sender"); char *chat_id = NULL, *muc_id = NULL;
char *chat_id, *muc_id;
char *reply_id = MatrixGetReply(event); char *reply_id = MatrixGetReply(event);
char *xepd = ParseeXMPPify(event); char *xepd = ParseeXMPPify(data, event);
char *type, *user, *xmppified_user = NULL, *to = NULL; char *type, *user, *xmppified_user = NULL, *to = NULL;
char *unauth = NULL; char *unauth = NULL;
char *origin_id = NULL, *stanza = NULL; char *origin_id = NULL, *stanza = NULL;
char *sender = NULL; char *sender = NULL;
char *unedited_id = MatrixGetEdit(event);
char *url = GrabString(event, 2, "content", "url"); char *url = GrabString(event, 2, "content", "url");
char *encoded_from = NULL; char *encoded_from = NULL;
bool direct = false; 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) || if (ParseeIsPuppet(data->config, m_sender) ||
ParseeManageBan(data, m_sender, id)) ParseeManageBan(data, m_sender, id))
{ {
@ -328,7 +429,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
type = direct ? "chat" : "groupchat"; type = direct ? "chat" : "groupchat";
user = GrabString(json, 1, "xmpp_user"); user = GrabString(json, 1, "xmpp_user");
unauth = ParseeToUnauth(data, url); unauth = ParseeToUnauth(data, url, GrabString(event, 2, "content", "filename"));
encoded_from = ParseeEncodeMXID(m_sender); encoded_from = ParseeEncodeMXID(m_sender);
xmppified_user = StrConcat(3, xmppified_user = StrConcat(3,
@ -342,7 +443,8 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
} }
else else
{ {
char *name; char *name, *mime = NULL, *sha = NULL;
char *avatar;
/* Try to find the chat ID */ /* Try to find the chat ID */
muc_id = ParseeGetMUCID(data, chat_id); muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id) if (!chat_id)
@ -350,17 +452,21 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
goto end; goto end;
} }
/* TODO: Check the name's validity. /* TODO: Avoid using the AS endpoints */
* Is there a good way to check for that that isn't
* just "await on join and try again?" */
name = ASGetName(data->config, id, m_sender); name = ASGetName(data->config, id, m_sender);
avatar = ASGetAvatar(data->config, NULL, m_sender);
ASGetMIMESHA(data->config, avatar, &mime, &sha);
JoinMUC(data, event, encoded_from, muc_id, name); Free(JoinMUC(data, event, encoded_from, muc_id, name, sha));
to = muc_id; to = muc_id;
Free(sha);
Free(mime);
Free(name); Free(name);
Free(avatar);
} }
if (reply_id) if (reply_id)
{ {
/* TODO: Monocles chat DM users HATE this trick! /* TODO: Monocles chat DM users HATE this trick!
@ -394,7 +500,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
SetStanzaEdit(builder, origin_id); SetStanzaEdit(builder, origin_id);
SetStanzaXParsee(builder, event); SetStanzaXParsee(builder, event);
WriteoutStanza(builder, jabber); WriteoutStanza(builder, jabber, data->config->max_stanza_size);
DestroyStanzaBuilder(builder); DestroyStanzaBuilder(builder);
if (direct) if (direct)
@ -417,8 +523,8 @@ end:
Free(stanza); Free(stanza);
Free(sender); Free(sender);
Free(unauth); Free(unauth);
Free(unedited_id);
Free(encoded_from); Free(encoded_from);
Free(unedited_id);
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
ref = NULL; ref = NULL;
@ -449,8 +555,7 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
return; return;
} }
else if (StrEquals(event_type, "m.room.message") || else if (StrEquals(event_type, "m.room.message") ||
StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker StrEquals(event_type, "m.sticker"))
* support here... */
{ {
ParseeMessageHandler(data, event); ParseeMessageHandler(data, event);
Free(parsee); Free(parsee);

View file

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

View file

@ -30,12 +30,18 @@ ParseeConfigLoad(char *conf)
{ {
return; return;
} }
stream = StreamOpen("parsee.json", "r"); stream = StreamOpen(conf ? conf : "parsee.json", "r");
if (!stream) if (!stream)
{ {
return; return;
} }
json = JsonDecode(stream); json = JsonDecode(stream);
if (!json)
{
Log(LOG_ERR, "Could not parse config JSON");
StreamClose(stream);
return;
}
config = Malloc(sizeof(*config)); config = Malloc(sizeof(*config));
#define CopyToStr(to, str) config->to = StrDuplicate( \ #define CopyToStr(to, str) config->to = StrDuplicate( \
@ -44,6 +50,9 @@ ParseeConfigLoad(char *conf)
#define CopyToInt(to, str) config->to = (int) ( \ #define CopyToInt(to, str) config->to = (int) ( \
JsonValueAsInteger(HashMapGet(json, str)) \ JsonValueAsInteger(HashMapGet(json, str)) \
) )
#define CopyToBool(to, str) config->to = (int) ( \
JsonValueAsBoolean(HashMapGet(json, str)) \
)
config->http_threads = 8; config->http_threads = 8;
config->xmpp_threads = 8; config->xmpp_threads = 8;
@ -58,10 +67,25 @@ ParseeConfigLoad(char *conf)
CopyToStr(server_base, "hs_base"); CopyToStr(server_base, "hs_base");
CopyToStr(homeserver_host, "hs_host"); CopyToStr(homeserver_host, "hs_host");
CopyToInt(homeserver_port, "hs_port"); 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"); CopyToInt(component_port, "component_port");
CopyToStr(component_addr, "component_addr");
CopyToStr(component_host, "component_host"); CopyToStr(component_host, "component_host");
CopyToStr(shared_comp_secret, "shared_secret"); CopyToStr(shared_comp_secret, "shared_secret");
CopyToInt(max_stanza_size, "max_stanza_size");
if (!config->max_stanza_size)
{
/* Standard XMPP "minimum" maximum */
config->max_stanza_size = 10000;
}
CopyToBool(ignore_bots, "ignore_bots");
CopyToStr(media_base, "media_base"); CopyToStr(media_base, "media_base");
@ -77,6 +101,7 @@ ParseeSetThreads(int xmpp, int http)
{ {
if (!config) if (!config)
{ {
Achievement("THREAD COUNT REQUEST WITHOUT CONFIG", true);
return; return;
} }
config->http_threads = http; config->http_threads = http;
@ -88,6 +113,7 @@ ParseeExportConfigYAML(Stream *stream)
{ {
if (!stream || !config) if (!stream || !config)
{ {
Achievement("YAML EXPORT REQUEST WITHOUT CONFIG", true);
return; return;
} }
StreamPrintf(stream, "# Autogenerated YAML AS entry for %s\n", NAME); StreamPrintf(stream, "# Autogenerated YAML AS entry for %s\n", NAME);
@ -101,6 +127,7 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token); StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token);
StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart); StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart);
StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n"); StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n");
StreamPrintf(stream, "receive_ephemeral: true\n"); /* TODO: Actually use that field */
StreamPrintf(stream, "\n"); StreamPrintf(stream, "\n");
StreamPrintf(stream, "namespaces: \n"); StreamPrintf(stream, "namespaces: \n");
StreamPrintf(stream, " users:\n"); StreamPrintf(stream, " users:\n");
@ -109,6 +136,7 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, " aliases:\n"); StreamPrintf(stream, " aliases:\n");
StreamPrintf(stream, " - exclusive: true\n"); StreamPrintf(stream, " - exclusive: true\n");
StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base); StreamPrintf(stream, " regex: \"#%s_.*\"\n", config->namespace_base);
StreamFlush(stream);
} }
void void
@ -119,6 +147,7 @@ ParseeConfigFree(void)
return; return;
} }
Free(config->component_host); Free(config->component_host);
Free(config->component_addr);
Free(config->shared_comp_secret); Free(config->shared_comp_secret);
Free(config->db_path); Free(config->db_path);
Free(config->homeserver_host); Free(config->homeserver_host);

View file

@ -7,6 +7,7 @@
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <Routes.h> #include <Routes.h>
#include <AS.h> #include <AS.h>
@ -26,11 +27,15 @@ ParseeInitData(XMPPComponent *comp)
data->config = ParseeConfigGet(); data->config = ParseeConfigGet();
data->router = HttpRouterCreate(); data->router = HttpRouterCreate();
data->jabber = comp; data->jabber = comp;
data->muc = CreateMUCServer(data);
data->handler = CommandCreateRouter(); data->handler = CommandCreateRouter();
data->oid_servers = HashMapCreate(); data->oid_servers = HashMapCreate();
pthread_mutex_init(&data->oidl, NULL); pthread_mutex_init(&data->oidl, NULL);
data->halted = false;
pthread_mutex_init(&data->halt_lock, NULL);
if (data->config->db_size) if (data->config->db_size)
{ {
data->db = DbOpenLMDB(data->config->db_path, data->config->db_size); data->db = DbOpenLMDB(data->config->db_path, data->config->db_size);
@ -50,14 +55,16 @@ ParseeInitData(XMPPComponent *comp)
char *id = StrRandom(64); char *id = StrRandom(64);
ref = DbCreate(data->db, 1, "info"); ref = DbCreate(data->db, 1, "info");
HashMapSet(DbJson(ref), "identifier", JsonValueString(id)); HashMapSet(DbJson(ref), "identifier", JsonValueString(id));
HashMapSet(DbJson(ref), "version", JsonValueString(VERSION));
Free(id); Free(id);
} }
version = GrabString(DbJson(ref), 1, "version"); version = GrabString(DbJson(ref), 1, "version");
if (!ParseeIsCompatible(VERSION, version)) if (version && !ParseeIsCompatible(VERSION, version))
{ {
Log(LOG_WARNING, "Version mismatch(curr=%s db=%s).", VERSION, version); Log(LOG_WARNING, "Version mismatch(curr=%s db=%s).", VERSION, version);
Log(LOG_WARNING, "Yeah. You may want to _not_ do that."); Log(LOG_WARNING, "Yeah. You may want to _not_ do that.");
Log(LOG_WARNING, "(Parsee still needs an upgradepath mechanism.)");
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
DbClose(data->db); DbClose(data->db);
@ -107,8 +114,10 @@ ParseeFreeData(ParseeData *data)
} }
HashMapFree(data->oid_servers); HashMapFree(data->oid_servers);
pthread_mutex_destroy(&data->oidl); pthread_mutex_destroy(&data->oidl);
pthread_mutex_destroy(&data->halt_lock);
Free(data->id); Free(data->id);
XMPPEndCompStream(data->jabber); XMPPEndCompStream(data->jabber);
FreeMUCServer(data->muc);
DbClose(data->db); DbClose(data->db);
HttpRouterFree(data->router); HttpRouterFree(data->router);
CommandFreeRouter(data->handler); CommandFreeRouter(data->handler);
@ -123,9 +132,6 @@ ParseeCleanup(void *datp)
char *chat; char *chat;
size_t i; size_t i;
uint64_t ts = UtilTsMillis(); uint64_t ts = UtilTsMillis();
size_t entries = 0;
Log(LOG_DEBUG, "Cleaning up...");
chats = DbList(data->db, 1, "chats"); chats = DbList(data->db, 1, "chats");
@ -169,7 +175,6 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \ if (cleaned > threshold) \
{ \ { \
DbDelete(data->db, 4, "chats", chat, #field"s", field); \ DbDelete(data->db, 4, "chats", chat, #field"s", field); \
entries++; \
} \ } \
Free(field); \ Free(field); \
} \ } \
@ -177,9 +182,12 @@ ParseeCleanup(void *datp)
} \ } \
while (0) while (0)
CleanupField(stanza, 30 MINUTES, 50); /* TODO: Custom retention period for any 1.0 */
CleanupField(event, 30 MINUTES, 50); CleanupField(stanza, 30 MINUTES, 500);
CleanupField(id, 30 MINUTES, 50); CleanupField(event, 30 MINUTES, 500);
CleanupField(id, 30 MINUTES, 500);
/* TODO: Also cleanup user cache information */
#undef CleanupField #undef CleanupField
} }
DbListFree(chats); DbListFree(chats);
@ -223,7 +231,6 @@ ParseeCleanup(void *datp)
if (cleaned > threshold) \ if (cleaned > threshold) \
{ \ { \
JsonValueFree(HashMapDelete(field##s, field)); \ JsonValueFree(HashMapDelete(field##s, field)); \
entries++; \
} \ } \
Free(field); \ Free(field); \
} \ } \
@ -231,14 +238,13 @@ ParseeCleanup(void *datp)
} \ } \
while (0) while (0)
CleanupField(stanza, 3 HOURS, 50); CleanupField(stanza, 3 HOURS, 500);
CleanupField(event, 3 HOURS, 50); CleanupField(event, 3 HOURS, 500);
CleanupField(id, 3 HOURS, 50); CleanupField(id, 3 HOURS, 500);
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
} }
DbListFree(chats); DbListFree(chats);
Log(LOG_DEBUG, "Cleant up %d entries...", entries);
} }
void void
@ -333,6 +339,15 @@ ParseePushStanza(ParseeData *data, char *chat_id, char *stanza_id, char *id, cha
} }
/* TODO */ /* TODO */
{
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (j)
{
JsonValueFree(HashMapSet(j, "ts", JsonValueInteger(age)));
}
DbUnlock(data->db, ref);
}
{ {
ref = DbCreate(data->db, 4, "chats", chat_id, "stanzas", stanza_id); ref = DbCreate(data->db, 4, "chats", chat_id, "stanzas", stanza_id);
j = DbJson(ref); j = DbJson(ref);
@ -535,3 +550,178 @@ end:
return ret; return ret;
} }
void
ParseeUnlinkRoom(ParseeData *data, char *chat_id)
{
char *muc, *room;
DbRef *ref;
if (!data || !chat_id)
{
return;
}
muc = ParseeGetMUCID(data, chat_id);
room = ParseeGetRoomID(data, chat_id);
if (!muc || !room)
{
Free(muc);
Free(room);
return;
}
ref = DbLock(data->db, 1, "chats");
JsonValueFree(HashMapDelete(
GrabObject(DbJson(ref), 1, "rooms"),
room
));
JsonValueFree(HashMapDelete(
GrabObject(DbJson(ref), 1, "mucs"),
muc
));
DbUnlock(data->db, ref);
DbDelete(data->db, 2, "chats", chat_id);
Free(muc);
Free(room);
}
bool
ParseeIsMUCWhitelisted(ParseeData *data, char *muc)
{
char *server, *serv_start, *postserv;
DbRef *ref;
bool ret;
if (!data || !muc)
{
return false;
}
if (!DbExists(data->db, 1, "whitelist"))
{
return true;
}
serv_start = strchr(muc, '@');
serv_start = serv_start ? serv_start : muc;
server = StrDuplicate(serv_start + 1);
postserv = server ? strchr(server, '/') : NULL;
if (postserv) /* GCC doesn't know strchr is pure. */
{
*postserv = '\0';
}
ref = DbLockIntent(data->db,
DB_HINT_READONLY,
1, "whitelist"
);
ret = HashMapGet(DbJson(ref), server);
DbUnlock(data->db, ref);
Free(server);
return ret;
}
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat)
{
HashMap *ret, *json;
DbRef *ref;
char *key;
JsonValue *value;
if (!data || !chat)
{
return NULL;
}
ref = DbLockIntent(data->db, DB_HINT_READONLY,
3, "chats", chat, "settings"
);
json = DbJson(ref);
if (!ref)
{
return HashMapCreate();
}
ret = HashMapCreate();
while (HashMapIterate(json, &key, (void **) &value))
{
char *str = JsonValueAsString(value);
HashMapSet(ret, key, StrDuplicate(str));
}
DbUnlock(data->db, ref);
return ret;
}
void
ParseeFreeChatSettings(HashMap *settings)
{
char *key;
void *val;
if (!settings)
{
return;
}
while (HashMapIterate(settings, &key, &val))
{
Free(val);
}
HashMapFree(settings);
}
char *
ParseeGetChatSetting(ParseeData *data, char *chat, char *key)
{
HashMap *map;
char *ret;
if (!data || !chat || !key)
{
return NULL;
}
map = ParseeGetChatSettings(data, chat);
ret = StrDuplicate(HashMapGet(map, key));
ParseeFreeChatSettings(map);
return ret;
}
void
ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val)
{
DbRef *ref;
HashMap *json;
if (!data || !chat || !key || !val)
{
return;
}
ref = DbLockIntent(data->db, DB_HINT_WRITE,
3, "chats", chat, "settings"
);
if (!ref)
{
ref = DbCreate(data->db, 3, "chats", chat, "settings");
}
json = DbJson(ref);
JsonValueFree(HashMapSet(json, key, JsonValueString(val)));
DbUnlock(data->db, ref);
return;
}
bool
ParseeIsMediaEnabled(ParseeData *data, char *chat_id)
{
char *value;
bool ret;
if (!data || !chat_id)
{
return false;
}
ret = !StrEquals(
(value = ParseeGetChatSetting(data, chat_id, "p.media.enabled")),
"false"
);
Free(value);
return ret;
}

View file

@ -4,11 +4,10 @@
const char *parsee_ascii[PARSEE_ASCII_LINES] = const char *parsee_ascii[PARSEE_ASCII_LINES] =
{ {
"----------------------------",
" =+======", " =+======",
" || | _ _/__----", " || | _ _/__----",
" / || \\ ==+= _/_____\\_", " / || \\ ==+= _/_____\\_",
" | || | -|- L___J", " | || | -|- L___J ",
"_/ || \\_ ||| .______\\", "_/ || \\_ ||| .______\\",
" || | | | |.____.|", " || | | | |.____.|",
" || / | \\ |L____||", " || / | \\ |L____||",

View file

@ -0,0 +1,106 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t affi_lock;
static HashMap *affi_table = NULL;
typedef struct XMPPStatus {
char *role;
char *affiliation;
} XMPPStatus;
static XMPPStatus *
CreateStatus(char *role, char *affiliation)
{
XMPPStatus *ret;
if (!role || !affiliation)
{
return NULL;
}
ret = Malloc(sizeof(*ret));
ret->role = StrDuplicate(role);
ret->affiliation = StrDuplicate(affiliation);
return ret;
}
static void
FreeStatus(XMPPStatus *status)
{
if (!status)
{
return;
}
Free(status->affiliation);
Free(status->role);
Free(status);
}
void
ParseeInitialiseAffiliationTable(void)
{
if (affi_table)
{
return;
}
pthread_mutex_init(&affi_lock, NULL);
pthread_mutex_lock(&affi_lock);
affi_table = HashMapCreate();
pthread_mutex_unlock(&affi_lock);
}
void
ParseePushAffiliationTable(char *user, char *affi, char *role)
{
XMPPStatus *status;
if (!user || !affi || !role)
{
return;
}
pthread_mutex_lock(&affi_lock);
status = CreateStatus(role, affi);
FreeStatus(HashMapSet(affi_table, user, status));
pthread_mutex_unlock(&affi_lock);
}
bool
ParseeLookupAffiliation(char *user, char **affiliation, char **role)
{
XMPPStatus *status;
if (!user || !affiliation || !role)
{
return false;
}
pthread_mutex_lock(&affi_lock);
status = HashMapGet(affi_table, user);
*affiliation = StrDuplicate(status ? status->affiliation : NULL);
*role = StrDuplicate(status ? status->role : NULL);
pthread_mutex_unlock(&affi_lock);
return !!status;
}
void
ParseeDestroyAffiliationTable(void)
{
char *key;
void *val;
if (!affi_table)
{
return;
}
pthread_mutex_lock(&affi_lock);
while (HashMapIterate(affi_table, &key, &val))
{
FreeStatus(val);
}
HashMapFree(affi_table);
affi_table = NULL;
pthread_mutex_unlock(&affi_lock);
pthread_mutex_destroy(&affi_lock);
}

View file

@ -1,6 +1,7 @@
#include <Parsee.h> #include <Parsee.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Http.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h> #include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
@ -147,14 +148,15 @@ char *
ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim) ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
{ {
char *ret, *tmp; char *ret, *tmp;
size_t i; size_t i, len;
if (!c || !jid) if (!c || !jid)
{ {
return NULL; return NULL;
} }
ret = StrConcat(2, c->namespace_base, "_l_"); ret = StrConcat(2, c->namespace_base, "_l_");
for (i = 0; i < strlen(jid); i++) len = strlen(jid);
for (i = 0; i < len; i++)
{ {
char cpy = jid[i]; char cpy = jid[i];
char cs[4] = { 0 }; char cs[4] = { 0 };
@ -164,7 +166,7 @@ ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
/* RID: Break everything and die. */ /* RID: Break everything and die. */
break; break;
} }
if (islower(*cs) || isalnum(*cs) || *cs == '_' || if (islower((int) *cs) || isalnum((int) *cs) || *cs == '_' ||
*cs == '=' || *cs == '-' || *cs == '/' || *cs == '=' || *cs == '-' || *cs == '/' ||
*cs == '+' || *cs == '.') *cs == '+' || *cs == '.')
{ {
@ -193,7 +195,7 @@ char *
ParseeGetLocal(char *mxid) ParseeGetLocal(char *mxid)
{ {
char *cpy; char *cpy;
size_t i; size_t i, len;
if (!mxid) if (!mxid)
{ {
return NULL; return NULL;
@ -203,12 +205,14 @@ ParseeGetLocal(char *mxid)
return StrDuplicate(mxid); return StrDuplicate(mxid);
} }
mxid++; len = strlen(mxid);
cpy = Malloc(strlen(mxid) + 1);
memset(cpy, '\0', strlen(mxid) + 1);
memcpy(cpy, mxid, strlen(mxid));
for (i = 0; i < strlen(mxid); i++) mxid++;
cpy = Malloc(len + 1);
memset(cpy, '\0', len + 1);
memcpy(cpy, mxid, len);
for (i = 0; i < len; i++)
{ {
if (cpy[i] == ':') if (cpy[i] == ':')
{ {
@ -224,15 +228,16 @@ char *
ParseeEncodeMXID(char *mxid) ParseeEncodeMXID(char *mxid)
{ {
char *ret; char *ret;
size_t i, j; size_t i, j, len;
if (!mxid) if (!mxid)
{ {
return NULL; return NULL;
} }
/* Worst case scenario of 3-bytes the char */ /* Worst case scenario of 3-bytes the char */
ret = Malloc(strlen(mxid) * 3 + 1); len = strlen(mxid);
for (i = 0, j = 0; i < strlen(mxid); i++) ret = Malloc(len * 3 + 1);
for (i = 0, j = 0; i < len; i++)
{ {
char src = mxid[i]; char src = mxid[i];
@ -356,8 +361,6 @@ ParseePushDMRoom(ParseeData *d, char *mxid, char *jid, char *r)
void void
ParseeDeleteDM(ParseeData *d, char *mxid, char *jid) ParseeDeleteDM(ParseeData *d, char *mxid, char *jid)
{ {
DbRef *ref;
HashMap *j;
char *dmid; char *dmid;
if (!d || !mxid || !jid) if (!d || !mxid || !jid)
{ {
@ -374,14 +377,15 @@ char *
ParseeTrimJID(char *jid) ParseeTrimJID(char *jid)
{ {
char *ret; char *ret;
size_t i; size_t i, len;
if (!jid) if (!jid)
{ {
return NULL; return NULL;
} }
ret = StrDuplicate(jid); ret = StrDuplicate(jid);
for (i = 0; i < strlen(ret); i++) len = strlen(ret);
for (i = 0; i < len; i++)
{ {
if (ret[i] == '/') if (ret[i] == '/')
{ {
@ -535,7 +539,6 @@ ParseeGetMUCID(ParseeData *data, char *chat_id)
return ret; return ret;
} }
void void
ParseeSendPresence(ParseeData *data) ParseeSendPresence(ParseeData *data)
{ {
@ -554,10 +557,16 @@ ParseeSendPresence(ParseeData *data)
while (HashMapIterate(mucs, &muc, (void **) &val)) while (HashMapIterate(mucs, &muc, (void **) &val))
{ {
char *rev = StrConcat(2, muc, "/parsee"); char *rev = StrConcat(2, muc, "/parsee");
char *chat_id = ParseeGetFromMUCID(data, muc);
DbRef *chat = DbLockIntent(data->db, DB_HINT_READONLY, 2, "chats", chat_id);
uint64_t ts = GrabInteger(DbJson(chat), 1, "ts");
int diff = ts ? (int) ((UtilTsMillis() - ts) / 1000) : -1;
/* Make a fake user join the MUC */ /* Make a fake user join the MUC */
Log(LOG_NOTICE, "Sending presence to %s", rev); Log(LOG_NOTICE, "Sending presence to %s", rev);
XMPPJoinMUC(data->jabber, "parsee", rev, false); XMPPJoinMUC(data->jabber, "parsee", rev, NULL, diff, false);
DbUnlock(data->db, chat);
Free(chat_id);
Free(rev); Free(rev);
} }
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
@ -679,12 +688,13 @@ end:
#include <Cytoplasm/Uri.h> #include <Cytoplasm/Uri.h>
char * char *
ParseeToUnauth(ParseeData *data, char *mxc) ParseeToUnauth(ParseeData *data, char *mxc, char *filename)
{ {
Uri *url = NULL; Uri *url = NULL;
char *ret; char *ret;
char *key, *hmac; 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; size_t l;
if (!data || !mxc) if (!data || !mxc)
{ {
@ -705,19 +715,45 @@ ParseeToUnauth(ParseeData *data, char *mxc)
hmac = ParseeHMACS(data->id, key); hmac = ParseeHMACS(data->id, key);
Free(key); Free(key);
l = snprintf(NULL, 0, if (!filename)
PAT, {
data->config->media_base, l = snprintf(NULL, 0,
url->host, url->path, PAT,
hmac 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); ret = Malloc(l + 3);
snprintf(ret, l + 1, if (!filename)
PAT, {
data->config->media_base, snprintf(ret, l + 1,
url->host, url->path, PAT,
hmac 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); UriFree(url);
Free(hmac); Free(hmac);
return ret; return ret;

View file

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

View file

@ -6,6 +6,28 @@
#include <Glob.h> #include <Glob.h>
#include <AS.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 void
ParseeGlobalBan(ParseeData *data, char *glob, char *reason) ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
{ {

View file

@ -3,6 +3,8 @@
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Unistring.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
@ -37,6 +39,22 @@ ParseeFindDatastart(char *data)
return (int) (startline - data); return (int) (startline - data);
} }
int
ParseeFindDatastartU(char *data)
{
Unistr *str;
size_t ret;
if (!data)
{
return 0;
}
str = UnistrCreate(data);
ret = UnistrGetOffset(str, (uint32_t) '>');
UnistrFree(str);
return (int) ret;
}
char * char *
ParseeStringifyDate(uint64_t millis) ParseeStringifyDate(uint64_t millis)

View file

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

36
src/Routes/Ping.c Normal file
View file

@ -0,0 +1,36 @@
#include <Routes.h>
#include <Cytoplasm/Log.h>
#include <Matrix.h>
#include <AS.h>
RouteHead(RoutePing, arr, argp)
{
ParseeHttpArg *args = argp;
HashMap *request = NULL;
HashMap *response = NULL;
response = ASVerifyRequest(args);
if (response)
{
goto end;
}
if (HttpRequestMethodGet(args->ctx) != HTTP_POST)
{
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError(
"M_UNRECOGNIZED",
"Path /ping only accepts POST as a valid method."
);
goto end;
}
RequestJSON();
response = HashMapCreate();
end:
(void) arr;
JsonFree(request);
return response;
}

View file

@ -54,7 +54,13 @@ GetRandomQuote(void)
NAME ": the federated world's little little kobashi", NAME ": the federated world's little little kobashi",
"Go take a look at your stanzas!", "Go take a look at your stanzas!",
"Go take a look at your objects!" "Go take a look at your objects!",
"DEC Alpha AXP-Certified!",
"this is the moment parsee started parsing or smth idk"
" - another wise person",
"Ah, merde, mon TGV est en retard de 53 minutes !"
}; };
const size_t count = sizeof(quotes)/sizeof(*quotes); const size_t count = sizeof(quotes)/sizeof(*quotes);
@ -84,11 +90,13 @@ RouteHead(RouteRoot, arr, argp)
P("color: #eee;"); P("color: #eee;");
P("font-family: sans-serif;"); P("font-family: sans-serif;");
P("}"); P("}");
P("#cols {"); P("#ascii {");
P("column-count: 3;"); P("text-align: center;");
P("min-width: 100%;"); P("color: #be1337;");
P("max-width: 100%;"); P("}");
P("width: 100%;"); P("#ascii pre {");
P("display: inline-block;");
P("text-align: left;");
P("}"); P("}");
P("img {"); P("img {");
P("image-rendering: pixelated;"); P("image-rendering: pixelated;");
@ -115,6 +123,7 @@ RouteHead(RouteRoot, arr, argp)
P("<body>"); P("<body>");
{ {
size_t i;
P("<center>"); P("<center>");
P("<h1>Your %s is running, all with that %s!</h1>", NAME, CODE); P("<h1>Your %s is running, all with that %s!</h1>", NAME, CODE);
P("</center>"); P("</center>");
@ -124,6 +133,15 @@ RouteHead(RouteRoot, arr, argp)
P("%s", GetRandomQuote()); P("%s", GetRandomQuote());
} }
P("</i></blockquote>"); P("</i></blockquote>");
P("<pre id='ascii'><code>");
for (i = 0; i < PARSEE_ASCII_LINES; i++)
{
XMLElement *e = XMLCreateText((char *) parsee_ascii[i]);
XMLEncode(args->stream, e);
XMLFreeElement(e);
P("<br/>");
}
P("</code></pre>");
P("<p>"); P("<p>");
{ {
@ -161,7 +179,7 @@ RouteHead(RouteRoot, arr, argp)
P("<p>"); P("<p>");
{ {
P("More information available at "); P("More information available at ");
P("<a"); P("<a ");
P("href='https://kappach.at/parsee'"); P("href='https://kappach.at/parsee'");
P(">the actual page</a>."); P(">the actual page</a>.");
} }
@ -236,5 +254,6 @@ RouteHead(RouteRoot, arr, argp)
P("</html>"); P("</html>");
#undef P #undef P
(void) arr;
return NULL; return NULL;
} }

View file

@ -42,6 +42,7 @@ RouteHead(RouteTxns, arr, argp)
response = HashMapCreate(); response = HashMapCreate();
end: end:
(void) arr;
JsonFree(request); JsonFree(request);
return response; return response;
} }

View file

@ -67,7 +67,8 @@ RouteHead(RouteRoomAck, arr, argp)
} }
muc = ParseeDecodeLocalMUC(args->data->config, room); muc = ParseeDecodeLocalMUC(args->data->config, room);
if (ParseeManageBan(args->data, muc, NULL)) if (ParseeManageBan(args->data, muc, NULL) ||
ParseeIsMUCWhitelisted(args->data, muc))
{ {
HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED); HttpResponseStatus(args->ctx, HTTP_METHOD_NOT_ALLOWED);
response = MatrixCreateError( response = MatrixCreateError(
@ -130,7 +131,7 @@ RouteHead(RouteRoomAck, arr, argp)
{ {
char *rev = StrConcat(2, muc, "/parsee"); char *rev = StrConcat(2, muc, "/parsee");
Log(LOG_NOTICE, "Sending presence to %s", rev); Log(LOG_NOTICE, "Sending presence to %s", rev);
XMPPJoinMUC(args->data->jabber, "parsee", rev, false); XMPPJoinMUC(args->data->jabber, "parsee", rev, NULL, -1, false);
Free(rev); Free(rev);
} }

View file

@ -6,32 +6,35 @@
#include <XMPP.h> #include <XMPP.h>
static HttpServer *server = NULL; static ParseeData *data;
static pthread_t xmpp_thr; static pthread_t xmpp_thr;
static XMPPComponent *jabber = NULL;
static void static void
SignalHandler(int signal) SignalHandler(int signal)
{ {
if (server && (signal == SIGTERM || signal == SIGINT)) if (data->server && (signal == SIGTERM || signal == SIGINT))
{ {
Log(LOG_INFO, "Killing thread..."); Log(LOG_INFO, "Killing thread...");
XMPPFinishCompStream(jabber);
pthread_mutex_lock(&data->halt_lock);
data->halted = true;
pthread_mutex_unlock(&data->halt_lock);
XMPPFinishCompStream(data->jabber);
pthread_join(xmpp_thr, NULL); pthread_join(xmpp_thr, NULL);
Log(LOG_INFO, "Stopping server..."); Log(LOG_INFO, "Stopping server...");
HttpServerStop(server); HttpServerStop(data->server);
return; return;
} }
} }
bool bool
ParseeInitialiseSignals(HttpServer *s, pthread_t xmpp, XMPPComponent *j) ParseeInitialiseSignals(ParseeData *d, pthread_t xmpp)
{ {
struct sigaction sa; struct sigaction sa;
server = s; data = d;
xmpp_thr = xmpp; xmpp_thr = xmpp;
jabber = j;
sigfillset(&sa.sa_mask); sigfillset(&sa.sa_mask);
sa.sa_handler = SignalHandler; sa.sa_handler = SignalHandler;

View file

@ -40,6 +40,7 @@ CreateStanzaBuilder(char *from, char *to, char *id)
builder->replying_to_stanza = NULL; builder->replying_to_stanza = NULL;
builder->replying_to_sender = NULL; builder->replying_to_sender = NULL;
builder->editing = NULL;
builder->type = NULL; builder->type = NULL;
builder->body = NULL; builder->body = NULL;
builder->oob = NULL; builder->oob = NULL;
@ -184,7 +185,7 @@ ExportStanza(StanzaBuilder *builder)
builder->replying_to_sender && builder->replying_to_sender &&
builder->body) builder->body)
{ {
int off = ParseeFindDatastart(builder->body); int off = ParseeFindDatastartU(builder->body);
char *ostr = StrInt(off); char *ostr = StrInt(off);
XMLElement *reply = XMLCreateTag("reply"); XMLElement *reply = XMLCreateTag("reply");
XMLElement *fallback = XMLCreateTag("fallback"); XMLElement *fallback = XMLCreateTag("fallback");
@ -227,22 +228,21 @@ ExportStanza(StanzaBuilder *builder)
} }
void void
WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber) WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber, size_t max)
{ {
XMLElement *elem; XMLElement *elem;
if (!builder || !jabber) if (!builder || !jabber)
{ {
return; return;
} }
if (!max)
{
max = 10000; /* XMPP recommended limit */
}
elem = ExportStanza(builder); elem = ExportStanza(builder);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, elem, max);
XMLEncode(jabber->stream, elem);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(elem); XMLFreeElement(elem);
return;
} }
StanzaBuilder * StanzaBuilder *
@ -258,7 +258,6 @@ SetStanzaXParsee(StanzaBuilder *builder, HashMap *e)
{ {
XMLElement *parsee_version, *ver_elem; XMLElement *parsee_version, *ver_elem;
XMLElement *parsee_link, *link_elem; XMLElement *parsee_link, *link_elem;
XMLElement *parsee_text, *text_elem;
XMLElement *parsee_event, *event_elem; XMLElement *parsee_event, *event_elem;
XMLElement *parsee_json, *json_elem; XMLElement *parsee_json, *json_elem;
char *event_id = GrabString(e, 1, "event_id"); char *event_id = GrabString(e, 1, "event_id");
@ -285,16 +284,6 @@ SetStanzaXParsee(StanzaBuilder *builder, HashMap *e)
XMLAddChild(parsee_link, link_elem); XMLAddChild(parsee_link, link_elem);
XMLAddChild(parsee, parsee_link); XMLAddChild(parsee, parsee_link);
parsee_text = XMLCreateTag("zayds-note");
text_elem = XMLCreateText("\"LDA HANG YOURSELF\" - Zayd");
XMLAddChild(parsee_text, text_elem);
XMLAddChild(parsee, parsee_text);
parsee_text = XMLCreateTag("mcnebs-note");
text_elem = XMLCreateText("LDA will never beat the allegations");
XMLAddChild(parsee_text, text_elem);
XMLAddChild(parsee, parsee_text);
if (event_id) if (event_id)
{ {
parsee_event = XMLCreateTag("event-id"); parsee_event = XMLCreateTag("event-id");

View file

@ -7,7 +7,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h>
char ** char **
StrSplitLines(char *text) StrSplitLines(char *text)
@ -117,7 +116,7 @@ StrFullRect(char **split)
char char
StrGet(StringRect *rect, int line, int col) StrGet(StringRect *rect, int line, int col)
{ {
int actual_line, actual_col; size_t actual_line, actual_col;
char *linep; char *linep;
if (!rect || !rect->source_lines) if (!rect || !rect->source_lines)
{ {
@ -150,7 +149,7 @@ StrGet(StringRect *rect, int line, int col)
size_t size_t
StrViewChars(StringRect rect, int line) StrViewChars(StringRect rect, int line)
{ {
int actual_line; size_t actual_line;
char *linep; char *linep;
if (!rect.source_lines) if (!rect.source_lines)
{ {
@ -174,7 +173,7 @@ StrViewChars(StringRect rect, int line)
StringRect StringRect
StrGetl(StringRect *rect, int line, bool extend) StrGetl(StringRect *rect, int line, bool extend)
{ {
int actual_line; size_t actual_line;
StringRect ret; StringRect ret;
if (!rect->source_lines) if (!rect->source_lines)
{ {
@ -204,7 +203,7 @@ StrGetl(StringRect *rect, int line, bool extend)
StringRect StringRect
StrShift(StringRect rect, int n) StrShift(StringRect rect, int n)
{ {
int new = rect.start_char + n; size_t new = rect.start_char + n;
if (new > rect.end_char) if (new > rect.end_char)
{ {
new = rect.end_char; new = rect.end_char;

View file

@ -10,10 +10,10 @@
Stream * Stream *
StrStreamReaderN(char *buffer, int n) StrStreamReaderN(char *buffer, int n)
{ {
if (!buffer) if (!buffer || n < 0)
{ {
return NULL; return NULL;
} }
return StreamFile(fmemopen(buffer, n ? n : strlen(buffer), "rb")); return StreamFile(fmemopen(buffer, n ? (size_t) n : strlen(buffer), "rb"));
} }

View file

@ -11,6 +11,9 @@ static ssize_t
ReadStreamWriter(void *coop, void *to, size_t n) ReadStreamWriter(void *coop, void *to, size_t n)
{ {
/* Reading from a stream writer is silly. */ /* Reading from a stream writer is silly. */
(void) coop;
(void) to;
(void) n;
return 0; return 0;
} }
static ssize_t static ssize_t
@ -33,6 +36,9 @@ static off_t
SeekStreamWriter(void *coop, off_t mag, int sgn) SeekStreamWriter(void *coop, off_t mag, int sgn)
{ {
/* TODO: Seeking would be useful, though not supported yet. */ /* TODO: Seeking would be useful, though not supported yet. */
(void) coop;
(void) mag;
(void) sgn;
return 0; return 0;
} }
@ -40,10 +46,11 @@ static int
CloseStreamWriter(void *coop) CloseStreamWriter(void *coop)
{ {
/* Nothing to free as of now. */ /* Nothing to free as of now. */
(void) coop;
return 0; return 0;
} }
const static IoFunctions Functions = { static const IoFunctions Functions = {
.read = ReadStreamWriter, .read = ReadStreamWriter,
.seek = SeekStreamWriter, .seek = SeekStreamWriter,
.write = WriteStreamWriter, .write = WriteStreamWriter,

314
src/Unistr.c Normal file
View file

@ -0,0 +1,314 @@
#include <Unistring.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Log.h>
#include <string.h>
#include <stdarg.h>
struct Unistr {
size_t length;
uint32_t *codepoints;
};
void
UnistrAddch(Unistr *unistr, uint32_t u)
{
if (!unistr || !u)
{
return;
}
unistr->length++;
unistr->codepoints = Realloc(
unistr->codepoints,
unistr->length * sizeof(*unistr->codepoints)
);
unistr->codepoints[unistr->length - 1] = u;
}
static bool
UTFIsN(char *off, size_t available, size_t n, uint8_t pc)
{
size_t i;
uint8_t *offu = (uint8_t *) off;
if (((available < n) || ((*offu >> (8-n-1)) != pc)) && (n >= 1))
{
return false;
}
for (i = 0; i < n - 1; i++)
{
if ((offu[i+1] >> 6) != 0x2)
{
return false;
}
}
return true;
}
Unistr *
UnistrCreate(char *src)
{
size_t len, i;
Unistr *str;
if (!src)
{
return NULL;
}
len = strlen(src);
str = Malloc(sizeof(*str));
str->length = 0;
str->codepoints = NULL;
/* We can't just set the length to {len}. */
for (i = 0; i < len; i++)
{
char byte = src[i];
size_t available = len - i;
if ((byte & 0x80) == 0)
{
/* This is a regular codepoint */
UnistrAddch(str, byte & 0x7F);
continue;
}
else if (UTFIsN(&src[i], available, 2, 0x06))
{
char a = src[i+0] & 0x1F;
char b = src[i+1] & 0x3F;
uint32_t u = (a << (6 * 1)) | b;
/* Overlongs are errors. */
if (u < 0x0080 || u > 0x07FF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 2 - 1;
continue;
}
else if (UTFIsN(&src[i], available, 3, 0x0E))
{
char a = src[i+0] & 0x0F;
char b = src[i+1] & 0x3F;
char c = src[i+2] & 0x3F;
uint32_t u =
(a << (6 * 2)) |
(b << (6 * 1)) |
(c << (6 * 0)) ;
/* Overlongs are errors. */
if (u < 0x0800 || u > 0xFFFF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 3 - 1;
continue;
}
else if (UTFIsN(&src[i], available, 4, 0x1E))
{
char a = src[i+0] & 0x07;
char b = src[i+1] & 0x3F;
char c = src[i+2] & 0x3F;
char d = src[i+3] & 0x3F;
uint32_t u =
(a << (6 * 3)) |
(b << (6 * 2)) |
(c << (6 * 1)) |
(d << (6 * 0)) ;
/* Overlongs are errors. */
if (u < 0x10000 || u > 0x10FFFF)
{
UnistrFree(str);
return NULL;
}
UnistrAddch(str, u);
i += 4 - 1;
continue;
}
}
return str;
}
void
UnistrFree(Unistr *unistr)
{
if (!unistr)
{
return;
}
Free(unistr->codepoints);
Free(unistr);
}
char *
UnistrC(Unistr *unistr)
{
char *ret, *tmp, *utf;
size_t i;
if (!unistr)
{
return NULL;
}
ret = NULL;
for (i = 0; i < unistr->length; i++)
{
uint32_t code = unistr->codepoints[i];
utf = StrUtf8Encode(code);
tmp = ret;
ret = StrConcat(2, ret, utf);
Free(tmp);
Free(utf);
}
return ret;
}
size_t
UnistrSize(Unistr *unistr)
{
return unistr ? unistr->length : 0;
}
uint32_t
UnistrGetch(Unistr *unistr, size_t i)
{
if (!unistr)
{
return 0;
}
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)
{
return NULL;
}
return u <= 0xFFFF;
}
Unistr *
UnistrFilter(Unistr *str, UnistrFilterFunc filter)
{
Unistr *unistr;
size_t i;
if (!str || !filter)
{
return NULL;
}
unistr = UnistrCreate("");
for (i = 0; i < UnistrSize(str); i++)
{
uint32_t code = UnistrGetch(str, i);
if (!filter(code))
{
continue;
}
UnistrAddch(unistr, code);
}
return unistr;
}
Unistr *
UnistrConcat(size_t n, ...)
{
va_list list;
size_t i;
Unistr *ret = UnistrCreate("");
va_start(list, n);
for (i = 0; i < n; i++)
{
Unistr *to_concat = va_arg(list, Unistr *);
size_t j;
for (j = 0; j < UnistrSize(to_concat); j++)
{
UnistrAddch(ret, UnistrGetch(to_concat, j));
}
}
va_end(list);
return ret;
}
size_t
UnistrGetOffset(Unistr *str, uint32_t sep)
{
size_t i;
uint32_t prev = 0x0A;
if (!str || !sep)
{
return 0;
}
for (i = 0; i < str->length; i++)
{
uint32_t curr = str->codepoints[i];
if (prev == 0x0A && curr != sep)
{
return i;
}
prev = curr;
}
return 0;
}
size_t
UnistrGetUTFOffset(char *cstr, size_t unicode)
{
Unistr *tmp;
size_t ret = 0;
if (!cstr)
{
return 0;
}
tmp = UnistrCreate(cstr);
for (size_t i = 0; i < unicode && i < tmp->length; i++)
{
uint32_t codepoint = tmp->codepoints[i];
if (codepoint >= 0x0000 && codepoint <= 0x007F)
{
ret += 1;
}
else if (codepoint >= 0x0080 && codepoint <= 0x07FF)
{
ret += 2;
}
else if (codepoint >= 0x0800 && codepoint <= 0xFFFF)
{
ret += 3;
}
else if (codepoint >= 0x010000 && codepoint <= 0x10FFFF)
{
ret += 4;
}
}
end:
Free(tmp);
return ret;
}

View file

@ -87,7 +87,7 @@ DecodeQuote(StringRect rect, size_t *skip)
* > four but that's for Nerds * > four but that's for Nerds
* > seasons See, Touhou reference! * > seasons See, Touhou reference!
* concealing!) */ * concealing!) */
while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace(ch)) while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace((int) ch))
{ {
shift_by++; shift_by++;
} }
@ -132,7 +132,7 @@ DecodeSpan(StringRect rect, char del, size_t *skip)
{ {
return StrFullRect(NULL); return StrFullRect(NULL);
} }
if (!ret.source_lines && isspace(c)) if (!ret.source_lines && isspace((int) c))
{ {
return StrFullRect(NULL); return StrFullRect(NULL);
} }
@ -329,8 +329,8 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
break; break;
case XEP393_MONO: case XEP393_MONO:
head = XMLCreateTag("code"); head = XMLCreateTag("code");
XMLAddChild(xmlparent, XMLCreateText("`"));
XMLAddChild(xmlparent, head); XMLAddChild(xmlparent, head);
XMLAddChild(head, XMLCreateText("`"));
break; break;
case XEP393_SRKE: case XEP393_SRKE:
head = XMLCreateTag("s"); head = XMLCreateTag("s");
@ -372,7 +372,7 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
XMLAddChild(head, XMLCreateText("_")); XMLAddChild(head, XMLCreateText("_"));
break; break;
case XEP393_MONO: case XEP393_MONO:
XMLAddChild(head, XMLCreateText("`")); XMLAddChild(xmlparent, XMLCreateText("`"));
break; break;
case XEP393_SRKE: case XEP393_SRKE:
XMLAddChild(head, XMLCreateText("~")); XMLAddChild(head, XMLCreateText("~"));
@ -399,21 +399,37 @@ ShoveXML(XEP393Element *element, XMLElement *xmlparent)
char * char *
XEP393ToXMLString(XEP393Element *xepd) XEP393ToXMLString(XEP393Element *xepd)
{ {
XMLElement *root; XMLElement *root, *act_root;
XMLElement *child;
Stream *writer; Stream *writer;
char *ret = NULL; char *ret = NULL;
size_t i, children;
if (!xepd) if (!xepd)
{ {
return NULL; return NULL;
} }
root = XMLCreateTag("span"); root = XMLCreateTag("span");
act_root = root;
ShoveXML(xepd, root); ShoveXML(xepd, root);
writer = StrStreamWriter(&ret); writer = StrStreamWriter(&ret);
XMLEncode(writer, root); children = ArraySize(root->children);
XMLFreeElement(root);
child = ArrayGet(root->children, 0);
if (children == 1 && StrEquals(child->name, "p"))
{
children = ArraySize(child->children);
root = child;
}
for (i = 0; i < children; i++)
{
child = ArrayGet(root->children, i);
XMLEncode(writer, child);
}
XMLFreeElement(act_root);
StreamFlush(writer); StreamFlush(writer);
StreamClose(writer); StreamClose(writer);

View file

@ -31,6 +31,12 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
bool flag = false; bool flag = false;
switch (event->type) switch (event->type)
{ {
case XML_ERROR:
XMLFreeEvent(event);
XMLFreeElement(ret);
ArrayFree(stack);
XMLFreeLexer(lexer);
return NULL;
case XML_LEXER_STARTELEM: case XML_LEXER_STARTELEM:
/* Create a new element that will populated. */ /* Create a new element that will populated. */
top = XMLCreateTag(event->element); top = XMLCreateTag(event->element);
@ -114,9 +120,19 @@ XMLCDecode(Stream *stream, bool autofree, bool html)
void void
XMLEncodeString(Stream *stream, char *data) XMLEncodeString(Stream *stream, char *data)
{ {
size_t i; size_t i, len;
for (i = 0; i < strlen(data); i++) if (!stream || !data)
{
return;
}
/* TODO: I should write a "Parsee Best Practice" guideline and make sure
* people understand to NOT constantly recompute lengths parameter on
* these kinds of loops. ArraySize is fine(since its indirection), but
* operations like strlen take time! */
len = strlen(data);
for (i = 0; i < len; i++)
{ {
char c = data[i]; char c = data[i];
if (c == '<') if (c == '<')
@ -145,6 +161,9 @@ XMLEncodeString(Stream *stream, char *data)
continue; continue;
} }
StreamPrintf(stream, "%c", c); StreamPrintf(stream, "%c", c);
/* TODO: Maybe consider Unistrings and encode arbitrary Unicode
* codepoints * with special XML. Oughta make it printable, you know?
*/
} }
} }
void void

View file

@ -58,6 +58,7 @@ static char * XMLPopElement(XMLexer *lexer);
static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateEmptyElem(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs); static XMLEvent * XMLCreateStart(XMLexer *lexer, HashMap *attrs);
static XMLEvent * XMLCreateRelax(XMLexer *lexer); static XMLEvent * XMLCreateRelax(XMLexer *lexer);
static XMLEvent * XMLCreateError(XMLexer *lexer);
static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end); static XMLEvent * XMLCreateEnd(XMLexer *lexer, char *end);
static XMLEvent * XMLCreateData(XMLexer *lexer); static XMLEvent * XMLCreateData(XMLexer *lexer);
@ -198,7 +199,9 @@ XMLCrank(XMLexer *lexer)
else if (XMLookahead(lexer, "--", false)) else if (XMLookahead(lexer, "--", false))
{ {
/* Throw error */ /* Throw error */
return NULL; XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
} }
break; break;
case XML_STATE_PI: case XML_STATE_PI:
@ -215,6 +218,9 @@ XMLCrank(XMLexer *lexer)
if (!attrname) if (!attrname)
{ {
/* TODO: Throw error */ /* TODO: Throw error */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
} }
XMLPushElement(lexer, attrname); XMLPushElement(lexer, attrname);
@ -241,7 +247,10 @@ XMLCrank(XMLexer *lexer)
} }
else if (XMLookahead(lexer, "'", true)) else if (XMLookahead(lexer, "'", true))
{ {
while (true); //while (true); uh oh
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break;
} }
break; break;
case XML_STATE_ATTRTAIL: case XML_STATE_ATTRTAIL:
@ -250,6 +259,8 @@ XMLCrank(XMLexer *lexer)
if (!XMLookahead(lexer, ">", true)) if (!XMLookahead(lexer, ">", true))
{ {
/* TODO: Throw error. */ /* TODO: Throw error. */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break; break;
} }
lexer->state = XML_STATE_NONE; lexer->state = XML_STATE_NONE;
@ -258,6 +269,8 @@ XMLCrank(XMLexer *lexer)
break; break;
default: default:
/* TODO */ /* TODO */
XMLFreeEvent(event);
event = XMLCreateError(lexer);
break; break;
} }
/* TODO: Crank our XML parser. */ /* TODO: Crank our XML parser. */
@ -295,7 +308,7 @@ static bool
XMLookahead(XMLexer *lexer, const char *str, bool skip) XMLookahead(XMLexer *lexer, const char *str, bool skip)
{ {
int *stack; int *stack;
size_t top, i; size_t top, i, len;
ssize_t ntop; ssize_t ntop;
bool ret = false; bool ret = false;
if (!lexer || !str) if (!lexer || !str)
@ -304,9 +317,10 @@ XMLookahead(XMLexer *lexer, const char *str, bool skip)
} }
top = 0; top = 0;
stack = Malloc(strlen(str) * sizeof(*stack)); len = strlen(str);
stack = Malloc(len * sizeof(*stack));
for (i = 0; i < strlen(str); i++) for (i = 0; i < len; i++)
{ {
char c = str[i]; char c = str[i];
int getc = XMLGetc(lexer); int getc = XMLGetc(lexer);
@ -336,8 +350,8 @@ seekback:
return ret; return ret;
} }
#define IsNamestart(c) ((c == ':') || isalpha(c) || (c == '_')) #define IsNamestart(c) ((c == ':') || isalpha((int) c) || (c == '_'))
#define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit(c)) #define IsNamepart(c) (IsNamestart(c) || (c == '-') || isdigit((int) c))
static char * static char *
XMLParseName(XMLexer *lexer) XMLParseName(XMLexer *lexer)
{ {
@ -582,6 +596,8 @@ XMLCreateEnd(XMLexer *lexer, char *end)
event->col = 0; event->col = 0;
event->offset = 0; event->offset = 0;
(void) lexer;
return event; return event;
} }
static XMLEvent * static XMLEvent *
@ -690,6 +706,26 @@ XMLCreateData(XMLexer *lexer)
return event; return event;
} }
static XMLEvent * 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) XMLCreateRelax(XMLexer *lexer)
{ {
XMLEvent *event = Malloc(sizeof(*event)); XMLEvent *event = Malloc(sizeof(*event));

View file

@ -11,6 +11,7 @@
#include <errno.h> #include <errno.h>
#include <netdb.h> #include <netdb.h>
#include <StringStream.h>
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
@ -18,7 +19,7 @@
#define DEFAULT_PROSODY_PORT 5347 #define DEFAULT_PROSODY_PORT 5347
XMPPComponent * XMPPComponent *
XMPPInitialiseCompStream(char *host, int port) XMPPInitialiseCompStream(char *addr, char *host, int port)
{ {
int sd = -1; int sd = -1;
struct addrinfo hints, *res, *res0; struct addrinfo hints, *res, *res0;
@ -27,12 +28,17 @@ XMPPInitialiseCompStream(char *host, int port)
Stream *stream; Stream *stream;
XMPPComponent *comp; XMPPComponent *comp;
if (!addr)
{
addr = host;
}
snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT); snprintf(serv, sizeof(serv), "%hu", port ? port : DEFAULT_PROSODY_PORT);
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, serv, &hints, &res0); error = getaddrinfo(addr, serv, &hints, &res0);
if (error) if (error)
{ {
const char *error_str = gai_strerror(error); const char *error_str = gai_strerror(error);
@ -55,6 +61,10 @@ XMPPInitialiseCompStream(char *host, int port)
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0) 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); close(sd);
sd = -1; sd = -1;
continue; continue;
@ -65,6 +75,10 @@ XMPPInitialiseCompStream(char *host, int port)
if (sd < 0) if (sd < 0)
{ {
Log(LOG_ERR,
"%s: cannot connect to '%s': no socket available", __func__,
host
);
return NULL; return NULL;
} }
freeaddrinfo(res0); freeaddrinfo(res0);
@ -72,6 +86,10 @@ XMPPInitialiseCompStream(char *host, int port)
stream = StreamFd(sd); stream = StreamFd(sd);
if (!stream) if (!stream)
{ {
Log(LOG_ERR,
"%s: cannot connect to '%s': %s", __func__,
host, "couldn't create a Cytoplasm stream"
);
close(sd); close(sd);
return NULL; return NULL;
} }
@ -134,7 +152,7 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
} }
break; break;
} }
if (ev->type != XML_LEXER_STARTELEM || if (!ev || ev->type != XML_LEXER_STARTELEM ||
!StrEquals(ev->element, "stream:stream")) !StrEquals(ev->element, "stream:stream"))
{ {
Log(LOG_ERR, "Excepted stream:stream element."); Log(LOG_ERR, "Excepted stream:stream element.");
@ -159,12 +177,54 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
} }
break; break;
} }
if (ev->type != XML_LEXER_ELEM || if (!ev || ev->type != XML_LEXER_ELEM ||
!StrEquals(ev->element, "handshake")) !StrEquals(ev->element, "handshake"))
{ {
Log(LOG_DEBUG, "type=%d elem='%s'", ev->type, ev->element);
Log(LOG_ERR, "Excepted empty handshake reply, got nonsense."); Log(LOG_ERR, "Excepted empty handshake reply, got nonsense.");
Log(LOG_ERR, "Another service (possibly Parsee) may have taken over."); 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, "");
Log(LOG_ERR, "Simply jealous of that other service..."); Log(LOG_ERR, "Simply jealous of that other service...");
Free(stream_id); Free(stream_id);
@ -213,3 +273,35 @@ XMPPEndCompStream(XMPPComponent *comp)
Free(comp->host); Free(comp->host);
Free(comp); Free(comp);
} }
void
XMPPSendStanza(XMPPComponent *comp, XMLElement *stanza, size_t max)
{
size_t len;
char *c = NULL;
Stream *stringWriter;
if (!comp || !stanza)
{
return;
}
stringWriter = StrStreamWriter(&c);
XMLEncode(stringWriter, stanza);
StreamFlush(stringWriter);
StreamClose(stringWriter);
if (c && max && (len = strlen(c)) > max)
{
Log(LOG_WARNING,
"Unexceptedly large stanza received (len=%d max=%d).",
(int) len, (int) max
);
Free(c);
return;
}
pthread_mutex_lock(&comp->write_lock);
StreamPrintf(comp->stream, "%s", c);
StreamFlush(comp->stream);
pthread_mutex_unlock(&comp->write_lock);
Free(c);
}

View file

@ -8,6 +8,8 @@
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
#include "XMPPThread/internal.h"
bool bool
XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out) XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{ {
@ -18,8 +20,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
return false; return false;
} }
pthread_mutex_lock(&jabber->write_lock);
iq_query = XMLCreateTag("iq"); iq_query = XMLCreateTag("iq");
query = XMLCreateTag("query"); query = XMLCreateTag("query");
@ -35,8 +35,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
{ {
XMLElement *identity; XMLElement *identity;
XMLEncode(jabber->stream, iq_query); XMPPSendStanza(jabber, iq_query, 10000);
StreamFlush(jabber->stream);
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
/* Except an IQ reply. 10 seconds of timeout is pretty /* Except an IQ reply. 10 seconds of timeout is pretty
@ -45,8 +44,8 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
Free(uuid); Free(uuid);
if (!iq_query || !StrEquals(iq_query->name, "iq")) if (!iq_query || !StrEquals(iq_query->name, "iq"))
{ {
Log(LOG_ERR, "Didn't receive an <iq> stanza");
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
pthread_mutex_unlock(&jabber->write_lock);
return false; return false;
} }
query = XMLookForUnique(iq_query, "query"); query = XMLookForUnique(iq_query, "query");
@ -57,7 +56,11 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
"conference")) "conference"))
{ {
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
pthread_mutex_unlock(&jabber->write_lock); Log(LOG_DEBUG, "MUC INFO ERROR");
Log(LOG_DEBUG,
"identityp=%p category=%s", identity,
identity ? HashMapGet(identity->attrs, "category") : NULL
);
return false; return false;
} }
@ -73,7 +76,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
XMLFreeElement(iq_query); XMLFreeElement(iq_query);
} }
} }
pthread_mutex_unlock(&jabber->write_lock);
return true; return true;
} }
@ -116,7 +118,6 @@ XMPPRequestVoice(XMPPComponent *jabber, char *from, char *muc)
return; return;
} }
pthread_mutex_lock(&jabber->write_lock);
stanza = XMLCreateTag("message"); stanza = XMLCreateTag("message");
XMLAddAttr(stanza, "id", (identifier = StrRandom(32))); XMLAddAttr(stanza, "id", (identifier = StrRandom(32)));
XMLAddAttr(stanza, "from", from); XMLAddAttr(stanza, "from", from);
@ -160,44 +161,68 @@ XMPPRequestVoice(XMPPComponent *jabber, char *from, char *muc)
} }
XMLAddChild(stanza, x); XMLAddChild(stanza, x);
} }
XMLEncode(jabber->stream, stanza); XMPPSendStanza(jabber, stanza, 10000);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(stanza); XMLFreeElement(stanza);
Free(identifier); Free(identifier);
} }
bool bool
XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, bool care) XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc, char *hash, int time, bool ret)
{ {
XMLElement *presence, *x, *reply; XMLElement *presence, *x, *reply, *history, *photo;
char *from, *id; IQFeatures *features;
char *from, *id, *stime = "3600";
if (!comp || !fr || !muc) if (!comp || !fr || !muc)
{ {
return false; return false;
} }
pthread_mutex_lock(&comp->write_lock);
presence = XMLCreateTag("presence"); presence = XMLCreateTag("presence");
x = XMLCreateTag("x"); x = XMLCreateTag("x");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host))); XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc); XMLAddAttr(presence, "to", muc);
XMLAddAttr(presence, "id", (id = StrRandom(8))); XMLAddAttr(presence, "id", (id = StrRandom(8)));
XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc"); XMLAddAttr(x, "xmlns", "http://jabber.org/protocol/muc");
history = XMLCreateTag("history");
if (time > 0)
{
stime = StrInt(time);
}
XMLAddAttr(history, "seconds", stime);
if (time > 0)
{
Free(stime);
stime = NULL;
}
XMLAddChild(x, history);
XMLAddChild(presence, x); 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);
XMLEncode(comp->stream, presence); if (hash)
StreamFlush(comp->stream); {
x = XMLCreateTag("x");
XMLAddAttr(x, "xmlns", "vcard-temp:x:update");
photo = XMLCreateTag("photo");
XMLAddChild(photo, XMLCreateText(hash));
XMLAddChild(x, photo);
XMLAddChild(presence, x);
}
XMPPSendStanza(comp, presence, 10000);
XMLFreeElement(presence); XMLFreeElement(presence);
Free(from); Free(from);
pthread_mutex_unlock(&comp->write_lock); if (ret && (reply = ParseeAwaitStanza(id, 500)))
if (care && (reply = ParseeAwaitStanza(id, 500)))
{ {
bool exit_code = true; bool exit_code = true;
@ -217,14 +242,13 @@ void
XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason) XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
{ {
XMLElement *presence; XMLElement *presence;
IQFeatures *features;
char *from, *id; char *from, *id;
if (!comp || !fr || !muc) if (!comp || !fr || !muc)
{ {
return; return;
} }
pthread_mutex_lock(&comp->write_lock);
presence = XMLCreateTag("presence"); presence = XMLCreateTag("presence");
XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host))); XMLAddAttr(presence, "from", (from = StrConcat(3, fr, "@", comp->host)));
XMLAddAttr(presence, "to", muc); XMLAddAttr(presence, "to", muc);
@ -240,14 +264,20 @@ XMPPLeaveMUC(XMPPComponent *comp, char *fr, char *muc, char *reason)
XMLAddChild(presence, status); 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);
XMLEncode(comp->stream, presence);
StreamFlush(comp->stream); XMPPSendStanza(comp, presence, 10000);
XMLFreeElement(presence); XMLFreeElement(presence);
Free(from); Free(from);
Free(id); Free(id);
pthread_mutex_unlock(&comp->write_lock);
} }

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

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

View file

@ -7,6 +7,8 @@
#include <Parsee.h> #include <Parsee.h>
#include <XML.h> #include <XML.h>
#include <stdlib.h>
void void
XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact) XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
@ -66,13 +68,9 @@ XMPPRetract(XMPPComponent *comp, char *fr, char *to, char *type, char *redact)
} }
pthread_mutex_lock(&comp->write_lock); XMPPSendStanza(comp, message, 10000);
XMLEncode(comp->stream, message);
StreamFlush(comp->stream);
XMLFreeElement(message); XMLFreeElement(message);
pthread_mutex_unlock(&comp->write_lock);
Free(from); Free(from);
Free(ident); Free(ident);
} }
@ -85,6 +83,8 @@ XMPPIsParseeStanza(XMLElement *stanza)
return false; return false;
} }
/* TODO: Check if the user is a trustworthy Parsee puppet instead of some
* guy sending random stanzas */
return !!XMLookForUnique(stanza, "x-parsee"); return !!XMLookForUnique(stanza, "x-parsee");
} }
@ -161,27 +161,31 @@ XMPPGetReply(XMLElement *elem)
return HashMapGet(rep->attrs, "id"); return HashMapGet(rep->attrs, "id");
} }
void ssize_t
XMPPAnnotatePresence(XMLElement *presence) XMPPGetReplyOffset(XMLElement *elem)
{ {
XMLElement *c; if (!elem)
char *ver;
if (!presence)
{ {
return; return -1;
} }
for (size_t i = 0; i < ArraySize(elem->children); i++)
ver = XMPPGenerateVer(); {
c = XMLCreateTag("c"); XMLElement *child = ArrayGet(elem->children, i);
XMLAddAttr(c, "xmlns", "http://jabber.org/protocol/caps"); char *xmlns = HashMapGet(child->attrs, "xmlns");
XMLAddAttr(c, "hash", "sha-1"); char *xfor = HashMapGet(child->attrs, "for");
XMLAddAttr(c, "node", REPOSITORY); if (StrEquals(child->name, "fallback") &&
XMLAddAttr(c, "ver", ver); StrEquals(xmlns, "urn:xmpp:feature-fallback:0") &&
StrEquals(xfor, "urn:xmpp:reply:0"))
Free(ver); {
XMLAddChild(presence, c); XMLElement *body = XMLookForUnique(child, "body");
if (body && HashMapGet(body->attrs, "end"))
{
return strtol(HashMapGet(body->attrs, "end"), NULL, 10);
}
}
}
return -1;
} }
char * char *
XMPPGetModeration(XMLElement *stanza) XMPPGetModeration(XMLElement *stanza)
{ {
@ -266,10 +270,7 @@ XMPPSendDisco(ParseeData *data, char *from, char *to)
XMLAddChild(iq, query); XMLAddChild(iq, query);
} }
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq, 10000);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq); XMLFreeElement(iq);
ret = ParseeAwaitStanza(identifier, 1.25 SECONDS); ret = ParseeAwaitStanza(identifier, 1.25 SECONDS);

View file

@ -34,7 +34,17 @@ struct XMPPCommandManager {
HashMap *sessions; HashMap *sessions;
void *cookie; void *cookie;
XMPPCmdFilter filter;
}; };
static bool
XMPPDefaultFilter(XMPPCommandManager *manager, char *id, XMLElement *stanza)
{
(void) manager;
(void) stanza;
(void) id;
return true;
}
static void static void
XMPPDestroySession(XMPPSession *session) XMPPDestroySession(XMPPSession *session)
{ {
@ -118,6 +128,7 @@ XMPPCreateManager(void *cookie)
ret->commands = HashMapCreate(); ret->commands = HashMapCreate();
ret->sessions = HashMapCreate(); ret->sessions = HashMapCreate();
ret->cookie = cookie; ret->cookie = cookie;
ret->filter = XMPPDefaultFilter;
return ret; return ret;
} }
@ -161,12 +172,12 @@ XMPPFreeManager(XMPPCommandManager *manager)
Free(manager); Free(manager);
} }
void void
XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p) XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s)
{ {
char *node_name; char *node_name;
XMPPCommand *val; XMPPCommand *val;
XMLElement *item; XMLElement *item;
if (!m || !p || !jid) if (!m || !p || !jid || !s)
{ {
return; return;
} }
@ -174,11 +185,14 @@ XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p)
pthread_mutex_lock(&m->lock); pthread_mutex_lock(&m->lock);
while (HashMapIterate(m->commands, &node_name, (void **) &val)) while (HashMapIterate(m->commands, &node_name, (void **) &val))
{ {
item = XMLCreateTag("item"); if (m->filter(m, node_name, s))
XMLAddAttr(item, "jid", jid); {
XMLAddAttr(item, "node", node_name); item = XMLCreateTag("item");
XMLAddAttr(item, "name", XMPPGetCommandDesc(val)); XMLAddAttr(item, "jid", jid);
XMLAddChild(p, item); XMLAddAttr(item, "node", node_name);
XMLAddAttr(item, "name", XMPPGetCommandDesc(val));
XMLAddChild(p, item);
}
} }
pthread_mutex_unlock(&m->lock); pthread_mutex_unlock(&m->lock);
} }
@ -206,6 +220,16 @@ XMPPVerifySession(XMPPCommandManager *mgr, char *s_id, char *from, char *to)
return ret; return ret;
} }
void
XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter)
{
if (!manager || !filter)
{
return;
}
manager->filter = filter;
}
bool bool
XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data) XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
{ {
@ -242,7 +266,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
/* This is an execution. */ /* This is an execution. */
cmd = HashMapGet(m->commands, node); cmd = HashMapGet(m->commands, node);
if (!cmd) if (!cmd || !m->filter(m, node, stanza))
{ {
/* TODO: Set an error note */ /* TODO: Set an error note */
goto end; goto end;
@ -274,10 +298,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
x = XMPPFormifyCommand(m, cmd, from); x = XMPPFormifyCommand(m, cmd, from);
XMLAddChild(command_xml, x); XMLAddChild(command_xml, x);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq); XMLFreeElement(iq);
goto end; goto end;
@ -305,10 +326,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, NULL); XMPPExecuteCommand(m, cmd, from, command_xml, NULL);
XMLAddChild(iq, command_xml); XMLAddChild(iq, command_xml);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq); XMLFreeElement(iq);
InvalidateSession(m, session_id); InvalidateSession(m, session_id);
@ -348,10 +366,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMPPExecuteCommand(m, cmd, from, command_xml, x_form); XMPPExecuteCommand(m, cmd, from, command_xml, x_form);
XMLAddChild(iq, command_xml); XMLAddChild(iq, command_xml);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
XMLEncode(jabber->stream, iq);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq); XMLFreeElement(iq);
InvalidateSession(m, session_given); InvalidateSession(m, session_given);

View file

@ -15,19 +15,11 @@ void
AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{ {
ParseeData *data = XMPPGetManagerCookie(m); ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
size_t i; size_t i;
XMLElement *x; XMLElement *x;
XMLElement *title; XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt; XMLElement *reported, *item, *field, *value, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
x = XMLCreateTag("x"); x = XMLCreateTag("x");
title = XMLCreateTag("title"); title = XMLCreateTag("title");
@ -58,4 +50,7 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
} }
XMLAddChild(out, x); XMLAddChild(out, x);
(void) form;
(void) from;
} }

View file

@ -29,4 +29,6 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
ParseeCleanup(data); ParseeCleanup(data);
/* TODO: Cleanup old sessions? */ /* TODO: Cleanup old sessions? */
SetNote("info", "Parsee data was sucessfully cleant up."); SetNote("info", "Parsee data was sucessfully cleant up.");
(void) form;
} }

View file

@ -0,0 +1,49 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
void
MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
char *chat_id = ParseeGetFromMUCID(data, muc);
char *room_id = ParseeGetRoomID(data, chat_id);
char *msg = StrConcat(5,
"The MUC ", muc, " is bridged to ", room_id, "."
);
SetNote("info", msg);
Free(muc);
Free(msg);
Free(room_id);
Free(chat_id);
(void) form;
}
void
MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
char *chat_id = ParseeGetFromMUCID(data, muc);
char *msg = StrConcat(5,
"The MUC ", muc, "'s internal ID is ", chat_id, "."
);
SetNote("info", msg);
Free(muc);
Free(msg);
Free(chat_id);
(void) form;
}

117
src/XMPPCommands/MUCKV.c Normal file
View file

@ -0,0 +1,117 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
char *key = NULL, *val = NULL;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Setting MUC properties requires the 'owner' affiliation.");
Free(affiliation);
return;
}
GetFieldValue(key, "key", form);
GetFieldValue(val, "val", form);
if (!key || !val)
{
SetNote("error", "No keys or no value given.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
ParseeSetChatSetting(data, chat_id, key, val);
SetNote("info", "Set key-value pair!");
end:
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}
void
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
HashMap *settings = NULL;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Getting MUC roperties requires the 'owner' affiliation.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
settings = ParseeGetChatSettings(data, chat_id);
x = XMLCreateTag("x");
title = XMLCreateTag("title");
SetTitle(x, "MUC/room settings");
XMLAddChild(x, title);
XMLAddAttr(x, "xmlns", "jabber:x:data");
XMLAddAttr(x, "type", "result");
{
char *key, *val;
reported = XMLCreateTag("reported");
XMLAddChild(x, reported);
/* Report */
Report("key", "Setting's key");
Report("val", "Setting's value");
/* Set */
while (HashMapIterate(settings, &key, (void **) &val))
{
BeginItem();
SetField("key", key);
SetField("val", val);
EndItem();
}
}
XMLAddChild(out, x);
end:
ParseeFreeChatSettings(settings);
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}

View file

@ -0,0 +1,67 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *parsee;
char *room;
char *muc;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Unlinking a MUC requires the 'owner' affiliation.");
Free(affiliation);
return;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
room = ParseeGetRoomID(data, chat_id);
if (!chat_id)
{
SetNote("error", "Couldn't fetch chat ID.");
Free(muc);
return;
}
ParseeUnlinkRoom(data, chat_id);
parsee = ParseeMXID(data);
Free(ASSend(
data->config, room, parsee,
"m.room.message",
MatrixCreateNotice("This room has been unlinked."),
0
));
ASLeave(data->config, room, parsee);
XMPPLeaveMUC(data->jabber, "parsee", muc, "Unlinked by MUC admin.");
/* Setting an error here won't work, as we're communicating through
* the MUC, which we *left*. I guess we can try to defer the leave. */
SetNote("info", "Unlinked MUC.");
Free(affiliation);
Free(chat_id);
Free(parsee);
Free(room);
Free(muc);
(void) form;
}

View file

@ -87,4 +87,6 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
} }
DbUnlock(data->db, ref); DbUnlock(data->db, ref);
} }
(void) form;
} }

View file

@ -14,7 +14,6 @@
void void
StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out) StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{ {
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from); char *trimmed = ParseeTrimJID(from);
size_t alloc = MemoryAllocated(); size_t alloc = MemoryAllocated();
size_t kb = alloc >> 10; size_t kb = alloc >> 10;
@ -26,13 +25,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLElement *x; XMLElement *x;
XMLElement *title, *txt; XMLElement *title, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed); Free(trimmed);
x = XMLCreateTag("x"); x = XMLCreateTag("x");
title = XMLCreateTag("title"); title = XMLCreateTag("title");
@ -75,4 +67,7 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
EndItem(); EndItem();
} }
XMLAddChild(out, x); XMLAddChild(out, x);
(void) form;
(void) m;
} }

View file

@ -0,0 +1,142 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
void
ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
if (!DbDelete(data->db, 1, "whitelist"))
{
SetNote("error", "Parsee whitelist was non-existent or could not be removed.");
return;
}
/* TODO: Cleanup old sessions? */
SetNote("info", "Parsee whitelist was removed.");
(void) form;
}
void
AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
char *entity = NULL;
DbRef *ref;
GetFieldValue(entity, "entity", form);
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
if (!entity)
{
SetNote("error", "No entity found.");
Free(trimmed);
return;
}
Free(trimmed);
ref = DbLock(data->db, 1, "whitelist");
if (!ref)
{
ref = DbCreate(data->db, 1, "whitelist");
}
if (!ref)
{
SetNote("error", "Couldn't get a database entry. You're cooked.");
return;
}
JsonValueFree(HashMapSet(
DbJson(ref),
entity, JsonValueObject(HashMapCreate())
));
DbUnlock(data->db, ref);
SetNote("info", "Server successfully whitelisted.");
}
void
WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
x = XMLCreateTag("x");
XMLAddAttr(x, "xmlns", "jabber:x:data");
title = XMLCreateTag("title");
XMLAddChild(x, title);
XMLAddChild(out, x);
Free(trimmed);
SetTitle(x, NAME " chat whitelist");
XMLAddAttr(x, "type", "result");
{
DbRef *ref = DbLock(data->db, 1, "whitelist");
HashMap *obj;
char *serv;
JsonValue *obj_val;
reported = XMLCreateTag("reported");
XMLAddChild(x, reported);
if (!ref)
{
ref = DbCreate(data->db, 1, "global_bans");
}
obj = DbJson(ref);
/* Report */
Report("server", "Allowed servers");
/* Set */
while (HashMapIterate(obj, &serv, (void **) &obj_val))
{
BeginItem();
SetField("server", serv);
EndItem();
(void) obj_val;
}
DbUnlock(data->db, ref);
}
(void) form;
}

View file

@ -121,7 +121,7 @@ ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza)
bool bool
ServerHasXEP421(ParseeData *data, char *from) ServerHasXEP421(ParseeData *data, char *from)
{ {
char *server = NULL, *parsee; char *server = NULL, *postserv, *parsee;
XMLElement *disco; XMLElement *disco;
bool ret = false; bool ret = false;
if (!data || !from) if (!data || !from)
@ -140,9 +140,10 @@ ServerHasXEP421(ParseeData *data, char *from)
} }
server = StrDuplicate(server); server = StrDuplicate(server);
if (strchr(server, '/')) postserv = server ? strchr(server, '/') : NULL;
if (postserv)
{ {
*(strchr(server, '/')) = '\0'; *postserv = '\0';
} }
parsee = ParseeJID(data); parsee = ParseeJID(data);
@ -226,7 +227,6 @@ ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force)
{ {
ParseePushOIDTable(xmpp_from, occ_id); ParseePushOIDTable(xmpp_from, occ_id);
} }
Log(LOG_DEBUG, "Trying Occ ID for %s{%s}", xmpp_from, occ_id);
} }
if (!occ_id) if (!occ_id)

View file

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

View file

@ -10,6 +10,8 @@
#include <XMPP.h> #include <XMPP.h>
#include <XML.h> #include <XML.h>
#define PUBSUB "http://jabber.org/protocol/pubsub"
XMLElement * XMLElement *
CreatePubsubRequest(char *from, char *to, char *node) CreatePubsubRequest(char *from, char *to, char *node)
{ {
@ -22,7 +24,7 @@ CreatePubsubRequest(char *from, char *to, char *node)
XMLAddAttr(iq_req, "type", "set"); XMLAddAttr(iq_req, "type", "set");
pubsub = XMLCreateTag("pubsub"); pubsub = XMLCreateTag("pubsub");
XMLAddAttr(pubsub, "xmlns", "http://jabber.org/protocol/pubsub"); XMLAddAttr(pubsub, "xmlns", PUBSUB);
XMLAddChild(iq_req, pubsub); XMLAddChild(iq_req, pubsub);
sub = XMLCreateTag("subscribe"); sub = XMLCreateTag("subscribe");
@ -34,12 +36,40 @@ CreatePubsubRequest(char *from, char *to, char *node)
return iq_req; return iq_req;
} }
static bool
IsPubsubRequest(XMLElement *stanza)
{
char *type = HashMapGet(stanza ? stanza->attrs : NULL, "type");
XMLElement *pubsub;
if (!stanza)
{
return false;
}
if (!StrEquals(stanza->name, "iq") ||
!StrEquals(type, "set"))
{
return false;
}
pubsub = XMLookForTKV(stanza, "pubsub", "xmlns", PUBSUB);
if (!pubsub)
{
return false;
}
return XMLookForUnique(pubsub, "subscribe");
}
struct PEPManager { struct PEPManager {
pthread_mutex_t lock; pthread_mutex_t lock;
ParseeData *data; ParseeData *data;
HashMap *node_table; HashMap *node_table;
HashMap *followers;
void *cookie; void *cookie;
}; };
@ -56,6 +86,7 @@ CreatePEPManager(ParseeData *data, void *cookie)
ret->cookie = cookie; ret->cookie = cookie;
ret->data = data; ret->data = data;
ret->node_table = HashMapCreate(); ret->node_table = HashMapCreate();
ret->followers = HashMapCreate();
pthread_mutex_init(&ret->lock, NULL); pthread_mutex_init(&ret->lock, NULL);
return ret; return ret;
@ -68,29 +99,32 @@ PEPManagerCookie(PEPManager *manager)
void void
PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event) PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event)
{ {
PEPEvent *indirect;
if (!manager || !node || !event) if (!manager || !node || !event)
{ {
return; return;
} }
indirect = Malloc(sizeof(event));
*indirect = event;
pthread_mutex_lock(&manager->lock); pthread_mutex_lock(&manager->lock);
HashMapSet(manager->node_table, node, event); HashMapSet(manager->node_table, node, indirect);
pthread_mutex_unlock(&manager->lock); pthread_mutex_unlock(&manager->lock);
} }
static bool static bool
PEPManagerHandleEvent(PEPManager *manager, XMLElement *stanza) PEPManagerHandleEvent(PEPManager *manager, XMLElement *stanza)
{ {
PEPEvent call = NULL; PEPEvent *call = NULL;
XMLElement *event, *ps, *ev; XMLElement *event = NULL, *ps = NULL, *ev = NULL;
size_t i; size_t i;
if (!manager || !stanza) if (!manager || !stanza)
{ {
return false; return false;
} }
#define PEP_NS "http://jabber.org/protocol/pubsub"
if (!(ps = XMLookForTKV(stanza, "pubsub", "xmlns", PEP_NS)) && if (!(ps = XMLookForTKV(stanza, "pubsub", "xmlns", PUBSUB)) &&
!(ev = XMLookForTKV(stanza, "event", "xmlns", PEP_NS "#event"))) !(ev = XMLookForTKV(stanza, "event", "xmlns", PUBSUB "#event")))
{ {
return false; return false;
} }
@ -101,7 +135,7 @@ PEPManagerHandleEvent(PEPManager *manager, XMLElement *stanza)
XMLElement *items = ArrayGet(event->children, i); XMLElement *items = ArrayGet(event->children, i);
char *node = HashMapGet(items->attrs, "node"); char *node = HashMapGet(items->attrs, "node");
if ((call = HashMapGet(manager->node_table, node))) if ((call = HashMapGet(manager->node_table, node)) && *call)
{ {
size_t j; size_t j;
/* Use the callback over all items */ /* Use the callback over all items */
@ -109,13 +143,13 @@ PEPManagerHandleEvent(PEPManager *manager, XMLElement *stanza)
{ {
for (j = 0; j < ArraySize(items->children); j++) for (j = 0; j < ArraySize(items->children); j++)
{ {
call(manager, stanza, ArrayGet(items->children, j)); (*call)(manager, stanza, ArrayGet(items->children, j));
} }
return true; return true;
} }
/* ... or over "items" specifically. */ /* ... or over "items" specifically. */
call(manager, stanza, items); (*call)(manager, stanza, items);
return true; return true;
} }
} }
@ -132,6 +166,11 @@ PEPManagerHandle(PEPManager *manager, XMLElement *stanza)
} }
/* Check if it is a PEP stanza */ /* Check if it is a PEP stanza */
if (IsPubsubRequest(stanza))
{
Log(LOG_DEBUG, "UNIMPLEMENTED PUBSUB SUBSCRIPTION");
/* TODO */
}
if (PEPManagerHandleEvent(manager, stanza)) if (PEPManagerHandleEvent(manager, stanza))
{ {
return true; return true;
@ -142,12 +181,21 @@ PEPManagerHandle(PEPManager *manager, XMLElement *stanza)
void void
DestroyPEPManager(PEPManager *manager) DestroyPEPManager(PEPManager *manager)
{ {
char *key;
PEPEvent *val;
if (!manager) if (!manager)
{ {
return; return;
} }
pthread_mutex_destroy(&manager->lock); pthread_mutex_destroy(&manager->lock);
while (HashMapIterate(manager->node_table, &key, (void **) &val))
{
Free(val);
}
HashMapFree(manager->node_table); HashMapFree(manager->node_table);
HashMapFree(manager->followers);
Free(manager); Free(manager);
} }

View file

@ -72,11 +72,7 @@ PEPAvatarEvent(PEPManager *m, XMLElement *stanza, XMLElement *item)
char *url = HashMapGet(item->attrs, "url"); char *url = HashMapGet(item->attrs, "url");
XMLElement *request = CreateAvatarRequest(from, to, id); XMLElement *request = CreateAvatarRequest(from, to, id);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, request, args->config->max_stanza_size);
XMLEncode(jabber->stream, request);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(request); XMLFreeElement(request);
(void) url; /* TODO */ (void) url; /* TODO */

View file

@ -113,9 +113,7 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item)
} }
} }
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, reply, data->config->max_stanza_size);
XMLEncode(jabber->stream, reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(reply); XMLFreeElement(reply);
(void) item;
} }

View file

@ -0,0 +1,144 @@
#include "XMPPThread/internal.h"
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Base64.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h>
#include <string.h>
static char *
SubscriptionHash(char *from, char *to)
{
uint8_t *sum;
char *hash;
size_t len;
len = strlen(from) + 1 + strlen(to);
sum = Malloc(len);
memset(sum, 0x00, len);
memcpy(&sum[0], from, strlen(from));
memcpy(&sum[strlen(from) + 1], to, strlen(to));
hash = Base64Encode((const char *) sum, len);
Free(sum);
return hash;
}
static void
DecodeSubscription(ParseeData *data, char *hash, char **from, char **to)
{
char *sum;
if (!data || !hash || !from || !to)
{
return;
}
sum = Base64Decode(hash, strlen(hash));
*from = StrDuplicate(sum);
*to = StrDuplicate(sum + strlen(sum) + 1);
Free(sum);
}
void
AddPresenceSubscriber(ParseeData *data, char *from, char *to)
{
Db *database;
DbRef *ref;
char *hash;
if (!data || !from || !to)
{
return;
}
database = data->db;
hash = SubscriptionHash(from, to);
ref = DbCreate(database, 2, "subs", hash);
if (!ref)
{
goto end;
}
HashMapSet(DbJson(ref), "from", JsonValueString(from));
HashMapSet(DbJson(ref), "to", JsonValueString(to));
/* I don't think we need more information right now */
end:
DbUnlock(database, ref);
Free(hash);
}
bool
IsSubscribed(ParseeData *data, char *user, char *to)
{
Db *database;
char *hash;
bool ret;
if (!data || !user || !to)
{
return false;
}
database = data->db;
hash = SubscriptionHash(user, to);
ret = DbExists(database, 2, "subs", hash);
Free(hash);
return ret;
}
void
ParseeBroadcastStanza(ParseeData *data, char *from, XMLElement *stanza)
{
XMPPComponent *jabber = data ? data->jabber : NULL;
Array *entries;
size_t i;
if (!data || !from || !stanza)
{
return;
}
/* Copy our stanza so that we can freely modify it */
stanza = XMLCopy(stanza);
/* Start doing a storm on Mt. Subs. */
entries = DbList(data->db, 1, "subs");
for (i = 0; i < ArraySize(entries); i++)
{
char *entry = ArrayGet(entries, i);
char *entry_from = NULL, *entry_to = NULL;
char *storm_id; /* ooe */
XMLElement *sub;
DecodeSubscription(data, entry, &entry_from, &entry_to);
if (!StrEquals(entry_to, from))
{
goto end;
}
Log(LOG_DEBUG,
"PRESENCE SYSTEM: "
"We should be brotkasting straight to %s (from %s)",
entry_from, from
);
sub = XMLCopy(stanza);
XMLAddAttr(sub, "from", from);
XMLAddAttr(sub, "to", entry_from);
/* TODO: Should we store IDs somewhere? */
XMLAddAttr(sub, "id", (storm_id = StrRandom(16)));
XMPPSendStanza(jabber, sub, data->config->max_stanza_size);
XMLFreeElement(sub);
Free(storm_id);
end:
Free(entry_from);
Free(entry_to);
}
DbListFree(entries);
XMLFreeElement(stanza);
}

View file

@ -4,7 +4,6 @@
#include <pthread.h> #include <pthread.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h>
#include <errno.h> #include <errno.h>
#include <time.h> #include <time.h>
@ -57,6 +56,8 @@ XMPPDispatcher(void *argp)
if (!stanza) if (!stanza)
{ {
/* TODO: We shouldn't be busywaiting. Even with a sleep call.
*/
UtilSleepMillis(10); UtilSleepMillis(10);
continue; continue;
} }
@ -66,9 +67,15 @@ XMPPDispatcher(void *argp)
Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name); Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name);
} }
if (ManageMUCStanza(args->muc, stanza))
{
XMLFreeElement(stanza);
continue;
}
if (StrEquals(stanza->name, "presence")) if (StrEquals(stanza->name, "presence"))
{ {
PresenceStanza(args, stanza); PresenceStanza(args, stanza, thread);
XMLFreeElement(stanza); XMLFreeElement(stanza);
continue; continue;
} }
@ -127,6 +134,49 @@ ParseeCongestion(void)
return congestion; return congestion;
} }
bool
XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
{
ParseeData *args = XMPPGetManagerCookie(m);
char *trimmed_from;
char *from;
char *chat_id;
bool is_muc;
if (!m || !id || !stanza)
{
return false;
}
from = HashMapGet(stanza->attrs, "from");
trimmed_from = ParseeTrimJID(from);
is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from));
Free(trimmed_from);
Free(chat_id);
#define XMPP_COMMAND(f,l,n,t,s) \
if (StrEquals(n, id)) \
{ \
if (l == XMPPCMD_ALL) \
{ \
return true; \
} \
else if (l == XMPPCMD_MUC) \
{ \
return is_muc; \
} \
else if (l == XMPPCMD_ADMINS) \
{ \
bool is_admin; \
trimmed_from = ParseeTrimJID(from); \
is_admin = ParseeIsAdmin(args, trimmed_from); \
Free(trimmed_from); \
return is_admin; \
} \
}
XMPPCOMMANDS
#undef XMPP_COMMAND
return false;
}
void * void *
ParseeXMPPThread(void *argp) ParseeXMPPThread(void *argp)
@ -134,16 +184,20 @@ ParseeXMPPThread(void *argp)
ParseeData *args = argp; ParseeData *args = argp;
XMPPComponent *jabber = args->jabber; XMPPComponent *jabber = args->jabber;
XMLElement *stanza = NULL; XMLElement *stanza = NULL;
HashMap *await_table2;
size_t i; size_t i;
bool error = false;
/* Initialise the await table */ /* Initialise the await table */
await_table = HashMapCreate(); await_table = HashMapCreate();
/* Initialise the managers, and add all handlers. */ /* Initialise the managers, and add all handlers. */
info.m = XMPPCreateManager(args); info.m = XMPPCreateManager(args);
XMPPManagerSetFilter(info.m, XMPPCommandFilter);
{ {
XMPPCommand *cmd; XMPPCommand *cmd;
#define XMPP_COMMAND(f,n,t,s) \ #define XMPP_COMMAND(f,l,n,t,s) \
cmd = XMPPBasicCmd( \ cmd = XMPPBasicCmd( \
n, t, f \ n, t, f \
); \ ); \
@ -190,56 +244,95 @@ ParseeXMPPThread(void *argp)
} }
} }
while (true) while (!args->halted)
{ {
char *id; while (true)
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{ {
if (args->verbosity >= PARSEE_VERBOSE_COMICAL) char *id;
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{ {
Log(LOG_DEBUG, "RECEIVED EOF."); /* Try to check if an error is abound */
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "RECEIVED EOF.");
}
break;
} }
break;
}
if (args->verbosity >= PARSEE_VERBOSE_STANZA) if (args->verbosity >= PARSEE_VERBOSE_STANZA)
{
Stream *output = StreamStderr();
StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
XMLEncode(output, stanza);
StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
{ {
pthread_mutex_lock(&await->cond_lock); Stream *output = StreamStderr();
await->stanza = stanza; StreamPrintf(output, "-------STANZA BEGIN-------" "\n");
pthread_cond_signal(&await->condition); XMLEncode(output, stanza);
pthread_mutex_unlock(&await->cond_lock); StreamPrintf(output, "\n--------STANZA END--------" "\n");
StreamFlush(output);
}
HashMapDelete(await_table, id); id = HashMapGet(stanza->attrs, "id");
if (id)
{
XMPPAwait *await;
/* Lock out the table to see if we're awaiting. */
pthread_mutex_lock(&await_lock);
if ((await = HashMapGet(await_table, id)))
{
pthread_mutex_lock(&await->cond_lock);
await->stanza = stanza;
pthread_cond_signal(&await->condition);
pthread_mutex_unlock(&await->cond_lock);
HashMapDelete(await_table, id);
pthread_mutex_unlock(&await_lock);
continue;
}
pthread_mutex_unlock(&await_lock); pthread_mutex_unlock(&await_lock);
continue;
} }
pthread_mutex_unlock(&await_lock);
}
/* Push it into the stanza FIFO. A dispatcher thread should then /* Push it into the stanza FIFO. A dispatcher thread should then
* be able to freely grab a value(locked by a mutex). We can't make * be able to freely grab a value(locked by a mutex). We can't make
* dispatchers read stanzas on their own, since that's _not_ how * dispatchers read stanzas on their own, since that's _not_ how
* streams work, but this should mitigate some issues, and allow a * streams work, but this should mitigate some issues, and allow a
* few threads to be busy, while the rest of Parsee works. */ * few threads to be busy, while the rest of Parsee works. */
PushStanza(&info, stanza); PushStanza(&info, stanza);
}
pthread_mutex_lock(&args->halt_lock);
if (!args->halted)
{
Log(LOG_WARNING, "XMPP server is closing stream...");
for (size_t i = 0; i < 50; i++)
{
UtilSleepMillis(100); /* Wait a bit so that temporary failures don't fuck everything up */
Log(LOG_WARNING, "Restarting XMPP stream.");
/* This is the part where a new connection is being considered */
XMPPFinishCompStream(jabber);
XMPPEndCompStream(jabber);
args->jabber = XMPPInitialiseCompStream(
args->config->component_addr,
args->config->component_host,
args->config->component_port
);
jabber = args->jabber;
if (!jabber || !XMPPAuthenticateCompStream(jabber, args->config->shared_comp_secret))
{
/* Oops, there is something wrong! */
Log(LOG_ERR, "Couldn't authenticate to XMPP server");
XMPPEndCompStream(jabber);
args->jabber = NULL;
jabber = NULL;
if (i == 4)
{
pthread_mutex_unlock(&args->halt_lock);
break;
}
}
}
}
pthread_mutex_unlock(&args->halt_lock);
} }
info.running = false; info.running = false;
@ -268,12 +361,18 @@ ParseeXMPPThread(void *argp)
} }
ArrayFree(info.stanzas); ArrayFree(info.stanzas);
HashMapFree(await_table); await_table2 = await_table;
await_table = NULL;
HashMapFree(await_table2);
pthread_mutex_destroy(&info.lock); pthread_mutex_destroy(&info.lock);
DestroyPEPManager(info.pep_manager); DestroyPEPManager(info.pep_manager);
XMPPFreeManager(info.m); XMPPFreeManager(info.m);
if (error)
{
HttpServerStop(args->server); /* minor trolling */
}
return NULL; return NULL;
} }

View file

@ -9,6 +9,7 @@
#include <Matrix.h> #include <Matrix.h>
#include <AS.h> #include <AS.h>
#include <sys/utsname.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -32,7 +33,7 @@ TrimBase64(char *b64)
while (*b64) while (*b64)
{ {
char ch[2] = { *b64, 0 }; char ch[2] = { *b64, 0 };
if (isspace(*b64)) if (isspace((int) *b64))
{ {
b64++; b64++;
continue; continue;
@ -46,35 +47,106 @@ TrimBase64(char *b64)
return ret; return ret;
} }
static bool
AvatarGrab(ParseeData *data, char *mxc, char **mime, char **b64, size_t *len)
{
char *mimei = NULL, *outi = NULL, *b64i = NULL;
size_t sizei;
if (!data || !mxc || !mime || !b64 || !len)
{
return false;
}
if (!ASGrab(data->config, mxc, &mimei, &outi, &sizei))
{
Free(mimei);
Free(outi);
return false;
}
b64i = Base64Encode(outi, sizei);
Free(outi);
if (!b64i)
{
Free(mimei);
b64i = StrDuplicate(media_unknown); /* TODO: Different assets! */
mimei = StrDuplicate("image/png");
}
*mime = mimei;
*b64 = b64i;
*len = sizei;
return true;
}
XMLElement *
GenerateAvatarData(ParseeData *data, char *mxid)
{
char *mxc = NULL, *mime = NULL, *b64 = NULL;
XMLElement *elem = NULL, *type, *binval;
size_t len = 0;
if (!data || !mxid)
{
return NULL;
}
/* TODO: Use the right room for the avatar! */
mxc = ASGetAvatar(data->config, NULL, mxid);
if (!mxc || !AvatarGrab(data, mxc, &mime, &b64, &len))
{
goto end;
}
elem = XMLCreateTag("PHOTO");
type = XMLCreateTag("TYPE");
binval = XMLCreateTag("BINVAL");
XMLAddChild(type, XMLCreateText(mime));
XMLAddChild(binval, XMLCreateText(b64));
XMLAddChild(elem, type);
XMLAddChild(elem, binval);
end:
Free(mime);
Free(mxc);
Free(b64);
return elem;
}
#define DISCO "http://jabber.org/protocol/disco#info" #define DISCO "http://jabber.org/protocol/disco#info"
static XMLElement * static XMLElement *
IQGenerateQuery(void) IQGenerateQuery(IQFeatures *features)
{ {
XMLElement *query = XMLCreateTag("query"); XMLElement *query;
if (!features)
{
return NULL;
}
query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", DISCO); XMLAddAttr(query, "xmlns", DISCO);
{ {
XMLElement *feature; XMLElement *feature;
#define IdentitySimple(c,t,n) do \ size_t i;
{ \ for (i = 0; i < ArraySize(features->identity); i++)
feature = XMLCreateTag("identity"); \ {
XMLAddAttr(feature, "category", c); \ XMPPIdentity *identity = ArrayGet(features->identity, i);
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);
IQ_ADVERT feature = XMLCreateTag("identity");
#undef AdvertiseSimple 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; return query;
@ -84,6 +156,7 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
{ {
char *from, *to, *id; char *from, *to, *id;
XMLElement *iq_reply, *query; XMLElement *iq_reply, *query;
IQFeatures *features;
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to"); to = HashMapGet(stanza->attrs, "to");
@ -96,9 +169,10 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "id", id); 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); char *node = StrConcat(3, REPOSITORY, "#", ver);
XMLAddAttr(query, "node", node); XMLAddAttr(query, "node", node);
@ -106,13 +180,12 @@ IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
Free(ver); Free(ver);
} }
XMLAddChild(iq_reply, query); XMLAddChild(iq_reply, query);
FreeIQFeatures(features);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply); XMLFreeElement(iq_reply);
(void) args;
} }
void void
@ -301,7 +374,7 @@ IQResult(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
bool bool
IQIsCommandList(ParseeData *args, XMLElement *stanza) IQIsCommandList(ParseeData *args, XMLElement *stanza)
{ {
char *parsee = NULL; char *parsee = NULL, *to;
XMLElement *query = XMLookForTKV( XMLElement *query = XMLookForTKV(
stanza, "query", "xmlns", stanza, "query", "xmlns",
"http://jabber.org/protocol/disco#items" "http://jabber.org/protocol/disco#items"
@ -316,15 +389,27 @@ IQIsCommandList(ParseeData *args, XMLElement *stanza)
} }
parsee = ParseeJID(args); 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); Free(parsee);
return ret; return ret;
} }
static bool
IsInMUC(ParseeData *data, char *jid)
{
char *trimmed = ParseeTrimJID(jid);
char *chat_id = ParseeGetFromMUCID(data, trimmed);
Free(trimmed);
Free(chat_id);
return !!chat_id;
}
void void
IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
XMPPComponent *jabber = args->jabber; XMPPComponent *jabber = args->jabber;
XMLElement *pubsub;
char *from = HashMapGet(stanza->attrs, "from"); char *from = HashMapGet(stanza->attrs, "from");
char *to = HashMapGet(stanza->attrs, "to"); char *to = HashMapGet(stanza->attrs, "to");
char *id = HashMapGet(stanza->attrs, "id"); char *id = HashMapGet(stanza->attrs, "id");
@ -334,33 +419,38 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
if (IQIsCommandList(args, stanza)) if (IQIsCommandList(args, stanza))
{ {
XMLElement *iq_reply = XMLCreateTag("iq"); XMLElement *iq_reply = XMLCreateTag("iq");
char *trimmed = ParseeTrimJID(from);
XMLAddAttr(iq_reply, "type", "result"); XMLAddAttr(iq_reply, "type", "result");
XMLAddAttr(iq_reply, "from", to); XMLAddAttr(iq_reply, "from", to);
XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "to", from);
XMLAddAttr(iq_reply, "id", id); XMLAddAttr(iq_reply, "id", id);
{ {
XMLElement *q = XMLCreateTag("query"); XMLElement *q = XMLCreateTag("query");
char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee");
XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items"); XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items");
XMLAddAttr(q, "node", "http://jabber.org/protocol/commands"); XMLAddAttr(q, "node", "http://jabber.org/protocol/commands");
XMPPShoveCommandList(thr->info->m, to, q); XMPPShoveCommandList(thr->info->m,
IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza
);
XMLAddChild(iq_reply, q); XMLAddChild(iq_reply, q);
Free(parsee_muc_jid);
} }
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply); XMLFreeElement(iq_reply);
Free(trimmed);
} }
else if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp")) else if (XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp"))
{ {
Log(LOG_INFO, "vCard information GET for %s", to); char *to_matrix = ParseeGetBridgedUser(args, stanza);
char *name = ASGetName(args->config, NULL, to_matrix);
XMLElement *iqVCard;
Log(LOG_DEBUG, "vCard information GET for %s (%s)", to, to_matrix);
/* TODO: "a compliant server MUST respond on behalf of the
* requestor and not forward the IQ to the requestee's
* connected resource". */
if (!strncmp(to, "parsee@", 7)) if (!strncmp(to, "parsee@", 7))
{ {
XMLElement *iqVCard = XMLCreateTag("iq"); iqVCard = XMLCreateTag("iq");
XMLAddAttr(iqVCard, "from", to); XMLAddAttr(iqVCard, "from", to);
XMLAddAttr(iqVCard, "to", from); XMLAddAttr(iqVCard, "to", from);
XMLAddAttr(iqVCard, "id", id); XMLAddAttr(iqVCard, "id", id);
@ -386,13 +476,97 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
XMLAddChild(iqVCard, vCard); XMLAddChild(iqVCard, vCard);
} }
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size);
XMLEncode(jabber->stream, iqVCard);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iqVCard); XMLFreeElement(iqVCard);
Free(to_matrix);
Free(name);
return;
}
Free(to_matrix);
to_matrix = ParseeDecodeMXID(to);
iqVCard = XMLCreateTag("iq");
XMLAddAttr(iqVCard, "from", to);
XMLAddAttr(iqVCard, "to", from);
XMLAddAttr(iqVCard, "id", id);
XMLAddAttr(iqVCard, "type", "result");
{
XMLElement *vCard = XMLCreateTag("vCard");
char *mto_link = ParseeGenerateMTO(to_matrix);
XMLAddAttr(vCard, "xmlns", "vcard-temp");
{
XMLAddChild(vCard, GenerateAvatarData(args, to_matrix));
Free(mto_link);
}
XMLAddChild(iqVCard, vCard);
}
XMPPSendStanza(jabber, iqVCard, args->config->max_stanza_size);
XMLFreeElement(iqVCard);
Free(to_matrix);
Free(name);
}
#define PS "http://jabber.org/protocol/pubsub"
else if ((pubsub = XMLookForTKV(stanza, "pubsub", "xmlns", PS)))
{
/* TODO: Pass this through the PEP manager */
XMLElement *a_items = XMLookForTKV(pubsub,
"items", "node", "urn:xmpp:avatar:data"
);
if (a_items)
{
/* Do, without regret, start shoving an avatar out the bus.
* NOTE: I explicitely choose to not do any manipulation
* because messing with random user images is inherently a
* risk I do *not* want to take. */
char *to_matrix = ParseeDecodeMXID(to);
char *avatar = ASGetAvatar(args->config, NULL, to_matrix);
char *mime = NULL;
char *b64 = NULL;
size_t len = 0;
XMLElement *reply;
AvatarGrab(args, avatar, &mime, &b64, &len);
Log(LOG_DEBUG, "IQ-GET: PUBSUB AVATAR OF=%s", to_matrix);
/* Strike back with a response */
reply = XMLCreateTag("iq");
XMLAddAttr(reply, "type", "result");
XMLAddAttr(reply, "to", from);
XMLAddAttr(reply, "from", to);
XMLAddAttr(reply, "id", HashMapGet(stanza->attrs, "id"));
{
XMLElement *ps = XMLCreateTag("pubsub");
XMLElement *items = XMLCreateTag("items");
XMLAddAttr(ps, "xmlns", PS);
XMLAddAttr(items, "node", "urn:xmpp:avatar:data");
{
XMLElement *item = XMLCreateTag("item");
XMLElement *data = XMLCreateTag("data");
XMLAddAttr(item, "id", "TODO");
XMLAddAttr(data, "xmlns", "urn:xmpp:avatar:data");
XMLAddChild(data, XMLCreateText(b64));
XMLAddChild(item, data);
XMLAddChild(items, item);
}
XMLAddChild(ps, items);
XMLAddChild(reply, ps);
}
XMPPSendStanza(jabber, reply, args->config->max_stanza_size);
XMLFreeElement(reply);
Free(to_matrix);
Free(avatar);
Free(mime);
Free(b64);
} }
} }
#undef PS
else if (XMLookForTKV(stanza, "query", "xmlns", DISCO)) else if (XMLookForTKV(stanza, "query", "xmlns", DISCO))
{ {
IQDiscoGet(args, jabber, stanza); IQDiscoGet(args, jabber, stanza);
@ -400,7 +574,7 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version")) else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version"))
{ {
XMLElement *iq_reply, *query; XMLElement *iq_reply, *query;
XMLElement *name, *version; XMLElement *name, *version, *os;
iq_reply = XMLCreateTag("iq"); iq_reply = XMLCreateTag("iq");
XMLAddAttr(iq_reply, "to", from); XMLAddAttr(iq_reply, "to", from);
@ -411,28 +585,34 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
query = XMLCreateTag("query"); query = XMLCreateTag("query");
XMLAddAttr(query, "xmlns", "jabber:iq:version"); XMLAddAttr(query, "xmlns", "jabber:iq:version");
{ {
struct utsname info;
name = XMLCreateTag("name"); name = XMLCreateTag("name");
version = XMLCreateTag("version"); version = XMLCreateTag("version");
os = XMLCreateTag("os");
uname(&info);
XMLAddChild(name, XMLCreateText(NAME)); XMLAddChild(name, XMLCreateText(NAME));
XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]")); XMLAddChild(version, XMLCreateText(VERSION "[" CODE "]"));
XMLAddChild(os, XMLCreateText(info.sysname));
} }
XMLAddChild(query, name); XMLAddChild(query, name);
XMLAddChild(query, version); XMLAddChild(query, version);
XMLAddChild(query, os);
XMLAddChild(iq_reply, query); XMLAddChild(iq_reply, query);
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, iq_reply, args->config->max_stanza_size);
XMLEncode(jabber->stream, iq_reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(iq_reply); XMLFreeElement(iq_reply);
} }
else else
{ {
char *buf = NULL;
Stream *s = StrStreamWriter(&buf);
Log(LOG_WARNING, "Unknown I/Q received:"); Log(LOG_WARNING, "Unknown I/Q received:");
XMLEncode(StreamStdout(), stanza); XMLEncode(s, stanza);
StreamPrintf(StreamStdout(),"\n"); StreamFlush(s);
StreamFlush(StreamStdout()); StreamClose(s);
Log(LOG_WARNING, "%s", buf);
Free(buf);
} }
} }
@ -440,6 +620,9 @@ void
IQError(ParseeData *args, XMLElement *stanza, XMPPThread *thr) IQError(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
/* TODO */ /* TODO */
(void) args;
(void) stanza;
(void) thr;
} }
void void
IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr) IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)

View file

@ -1,14 +1,82 @@
#include "XMPPThread/internal.h" #include "XMPPThread/internal.h"
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Unistring.h>
#include <Matrix.h> #include <Matrix.h>
#include <AS.h> #include <AS.h>
#include <string.h> #include <string.h>
static void
LazyRegister(ParseeData *data, char *mxid, char *name)
{
DbRef *ref;
char *hash = ParseeHMACS(data->id, mxid);
char *dbname;
if (!(ref = DbLock(data->db, 2, "users", hash)))
{
ASRegisterUser(data->config, mxid);
ref = DbCreate(data->db, 2, "users", hash);
if (ref)
{
HashMapSet(DbJson(ref), "mxid", JsonValueString(mxid));
HashMapSet(DbJson(ref),
"ts", JsonValueInteger(UtilTsMillis())
);
}
}
dbname = GrabString(DbJson(ref), 1, "name");
if (name && !StrEquals(dbname, name))
{
ASSetName(data->config, mxid, name);
if (ref)
{
JsonValueFree(HashMapSet(
DbJson(ref), "name", JsonValueString(name)
));
}
}
DbUnlock(data->db, ref);
Free(hash);
}
static char *
LazySend(ParseeData *args, char *mxid, char *mroom_id, char *type, HashMap *ev, uint64_t ts)
{
HashMap *duplicate;
char *event;
if (!args || !mxid || !mroom_id || !ev)
{
return NULL;
}
if (!type)
{
type = "m.room.message";
}
duplicate = JsonDuplicate(ev);
event = ASSend(
args->config, mroom_id, mxid,
type, ev, ts
);
if (event)
{
JsonFree(duplicate);
return event;
}
ASInvite(args->config, mroom_id, mxid);
Free(ASJoin(args->config, mroom_id, mxid));
return ASSend(
args->config, mroom_id, mxid,
type, duplicate, ts
);
}
static void static void
ProcessChatstates(ParseeData *args, XMLElement *stanza) ProcessChatstates(ParseeData *args, XMLElement *stanza)
{ {
@ -44,7 +112,24 @@ ProcessChatstates(ParseeData *args, XMLElement *stanza)
from_matrix = NULL; from_matrix = NULL;
} }
if (XMLookForTKV(stanza, "paused", "xmlns", CHAT_STATES) || if (XMLookForTKV(stanza, "paused", "xmlns", CHAT_STATES) ||
XMLookForTKV(stanza, "received", "xmlns", "urn:xmpp:receipts") || XMLookForTKV(stanza, "inactive", "xmlns", CHAT_STATES))
{
char *latest = NULL;
from_matrix = ParseeGetBridgedUser(args, stanza);
mroom_id = ParseeGetBridgedRoom(args, stanza);
latest = ParseeLookupHead(mroom_id);
ASType(args->config, from_matrix, mroom_id, false);
ASPresence(args->config, from_matrix, mroom_id, latest);
Free(from_matrix);
Free(latest);
Free(mroom_id);
mroom_id = NULL;
from_matrix = NULL;
}
if (XMLookForTKV(stanza, "received", "xmlns", "urn:xmpp:receipts") ||
XMLookForTKV(stanza, "displayed", "xmlns", "urn:xmpp:chat-markers:0")) XMLookForTKV(stanza, "displayed", "xmlns", "urn:xmpp:chat-markers:0"))
{ {
/* TODO: Use stanza ID if possible */ /* TODO: Use stanza ID if possible */
@ -65,6 +150,16 @@ ProcessChatstates(ParseeData *args, XMLElement *stanza)
} }
#undef CHAT_STATES #undef CHAT_STATES
} }
static float
TimeElapsed(uint64_t *rectime, uint64_t v)
{
uint64_t time = UtilTsMillis();
float t = ((time-v)/1000.f);
*rectime = time;
return t;
}
bool bool
MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr) MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
@ -83,18 +178,29 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
char *type = HashMapGet(stanza->attrs, "type"); char *type = HashMapGet(stanza->attrs, "type");
bool chat = StrEquals(type, "chat"); bool chat = StrEquals(type, "chat");
size_t i; size_t i;
uint64_t time, rectime;
#define Elapsed(v) (TimeElapsed(&rectime, v))
to = NULL; to = NULL;
from = NULL; from = NULL;
decode_from = NULL; decode_from = NULL;
from_matrix = NULL; from_matrix = NULL;
Log(LOG_DEBUG, "<message> usage=%d", MemoryAllocated()); Log(LOG_DEBUG, "<message> usage=%d", MemoryAllocated());
time = UtilTsMillis();
rectime = time;
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
if (ParseeManageBan(args, from, NULL)) if (ParseeManageBan(args, from, NULL))
{ {
XMLFreeElement(stanza); XMLFreeElement(stanza);
Log(LOG_DEBUG, "<message/> usage=%d (%s:%d)", MemoryAllocated(), __FILE__, __LINE__); Log(LOG_DEBUG,
"<message/> time=%f "
"usage=%d (%s:%d)",
Elapsed(time),
MemoryAllocated(), __FILE__, __LINE__
);
return false; return false;
} }
@ -107,18 +213,25 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL; char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL;
if (occ_id) if (occ_id)
{ {
Log(LOG_DEBUG, if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
"'%s' has support for XEP-421, fetching OID=%s", {
from, occ_id Log(LOG_DEBUG,
); "'%s' has support for XEP-421, fetching OID=%s",
from, occ_id
);
}
ParseePushOIDTable(from, occ_id); ParseePushOIDTable(from, occ_id);
} }
} }
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
Log(LOG_DEBUG, "XEP-421: %fs", Elapsed(rectime));
}
if (StrEquals(type, "error")) if (StrEquals(type, "error"))
{ {
char *type, *text, *user, *parsee; char *type = NULL, *text = NULL, *user = NULL, *parsee = NULL;
char *message, *room; char *message = NULL, *room = NULL;
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
to = HashMapGet(stanza->attrs, "to"); to = HashMapGet(stanza->attrs, "to");
@ -139,10 +252,10 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
message = StrConcat(3, type, ": ", text); message = StrConcat(3, type, ": ", text);
room = ParseeGetBridgedRoom(args, stanza); room = ParseeGetBridgedRoom(args, stanza);
Free(ASSend( Free(LazySend(
args->config, room, parsee, args, parsee, room, NULL,
"m.room.message", MatrixCreateNotice(message),
MatrixCreateNotice(message) time
)); ));
end_error: end_error:
@ -151,11 +264,20 @@ end_error:
Free(parsee); Free(parsee);
Free(room); Free(room);
Free(user); Free(user);
Log(LOG_DEBUG, "<message/> usage=%d (%s:%d)", MemoryAllocated(), __FILE__, __LINE__); Log(LOG_DEBUG,
"<message/> time=%f "
"usage=%d (%s:%d)",
Elapsed(time),
MemoryAllocated(), __FILE__, __LINE__
);
return false; return false;
} }
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
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 */ /* TODO: Parsee MUST check if it is a valid MUC */
char *resource = ParseeGetResource(from); char *resource = ParseeGetResource(from);
@ -181,29 +303,39 @@ end_error:
body = XMLookForUnique(stanza, "body"); body = XMLookForUnique(stanza, "body");
PEPManagerHandle(thr->info->pep_manager, stanza); PEPManagerHandle(thr->info->pep_manager, stanza);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
ProcessChatstates(args, stanza); {
Log(LOG_DEBUG, "PEP management: %fs", Elapsed(rectime));
}
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to")); to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
decode_from = ParseeLookupJID(from); decode_from = ParseeLookupJID(from);
from_matrix = ParseeEncodeJID(args->config, decode_from, true); from_matrix = ParseeEncodeJID(args->config, decode_from, true);
room = ParseeFindDMRoom(args, to, from); room = ParseeFindDMRoom(args, to, from);
data = body ? ArrayGet(body->children, 0) : NULL; data = body ? ArrayGet(body->children, 0) : NULL;
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
Log(LOG_DEBUG, "Fetching user info: %fs", Elapsed(rectime));
}
/* TODO: CLEAN THAT UP INTO A CREATEDM FUNCTION */ /* TODO: CLEAN THAT UP INTO A CREATEDM FUNCTION */
mroom_id = ParseeGetBridgedRoom(args, stanza); mroom_id = ParseeGetBridgedRoom(args, stanza);
Log(LOG_DEBUG, "Bridging event to '%s'...", mroom_id); Log(LOG_DEBUG, "Bridging event to '%s'...", mroom_id);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
Log(LOG_DEBUG, "Fetching bridge: %fs", Elapsed(rectime));
}
char *trim = ParseeTrimJID(from);
if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) && if (!mroom_id && !room && !XMPPIsParseeStanza(stanza) &&
to && *to == '@') to && *to == '@' && !XMPPQueryMUC(jabber, trim, NULL))
{ {
DbRef *room_ref; DbRef *room_ref;
HashMap *room_json; HashMap *room_json;
char *from = HashMapGet(stanza->attrs, "from"); char *from = HashMapGet(stanza->attrs, "from");
ASRegisterUser(args->config, from_matrix); LazyRegister(args, from_matrix, NULL);
room = ASCreateDM(args->config, from_matrix, to); room = ASCreateDM(args->config, from_matrix, to);
mroom_id = StrDuplicate(room); mroom_id = StrDuplicate(room);
Log(LOG_INFO, "Creating a DM to '%s'(%s)...", to, mroom_id);
if (room) if (room)
{ {
room_ref = DbCreate(args->db, 3, "rooms", room, "data"); room_ref = DbCreate(args->db, 3, "rooms", room, "data");
@ -214,12 +346,15 @@ end_error:
ParseePushDMRoom(args, to, from, room); ParseePushDMRoom(args, to, from, room);
} }
} }
Free(trim);
/* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER /* TODO: THIS IS A HACK. THIS CODE IGNORES ALL MUC MESSAGES EVER
* SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED * SENT TO NON-PARSEE PUPPETS, AS A "FIX" TO THE MULTITHREADED
* ISSUE. * ISSUE.
* *
* I HATE THIS. I NEED TO FIND A BETTER WAY. */ * I HATE THIS. I NEED TO FIND A BETTER WAY.
*
* Actually, is it even _that_ bad? */
if (!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7)) if (!chat && strncmp(HashMapGet(stanza->attrs, "to"), "parsee@", 7))
{ {
goto end; goto end;
@ -238,20 +373,18 @@ end_error:
ps = CreatePubsubRequest( ps = CreatePubsubRequest(
parsee, decode_from, "urn:xmpp:avatar:metadata" parsee, decode_from, "urn:xmpp:avatar:metadata"
); );
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, ps, args->config->max_stanza_size);
XMLEncode(jabber->stream, ps);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(ps); XMLFreeElement(ps);
ps = CreatePubsubRequest( ps = CreatePubsubRequest(
parsee, trim, "urn:xmpp:avatar:metadata" parsee, trim, "urn:xmpp:avatar:metadata"
); );
pthread_mutex_lock(&jabber->write_lock); XMPPSendStanza(jabber, ps, args->config->max_stanza_size);
XMLEncode(jabber->stream, ps);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(ps); XMLFreeElement(ps);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
Log(LOG_DEBUG, "Subscription to XEP-0084: %fs", Elapsed(rectime));
}
Free(parsee); Free(parsee);
Free(trim); Free(trim);
@ -260,15 +393,26 @@ end_error:
if (ParseeVerifyAllStanza(args, stanza) && !replaced) if (ParseeVerifyAllStanza(args, stanza) && !replaced)
{ {
XMLElement *oob, *oob_data; XMLElement *oob, *oob_data;
char *chat_id = ParseeGetFromMUCID(args, from);
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); pthread_mutex_unlock(&thr->info->chk_lock);
ASRegisterUser(args->config, encoded);
if (!chat) parsee = ParseeJID(args);
is_parsee = StrEquals(to, parsee);
Free(parsee);
parsee = NULL;
LazyRegister(args, encoded, !chat ? res : NULL);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{ {
ASSetName(args->config, encoded, res); Log(LOG_DEBUG, "Matrix registration: %fs", Elapsed(rectime));
} }
ASInvite(args->config, mroom_id, encoded);
Free(ASJoin(args->config, mroom_id, encoded));
reactions = XMLookForTKV(stanza, reactions = XMLookForTKV(stanza,
"reactions", "xmlns", "urn:xmpp:reactions:0" "reactions", "xmlns", "urn:xmpp:reactions:0"
@ -276,7 +420,12 @@ end_error:
/* Check if it is a media link */ /* Check if it is a media link */
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob"); oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
if (oob && data) 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; char *mxc, *mime = NULL;
HashMap *content = NULL; HashMap *content = NULL;
@ -286,24 +435,27 @@ end_error:
if (oob_data) if (oob_data)
{ {
FileInfo *info = FileInfoFromXMPP(stanza);
mxc = ASReupload(args->config, oob_data->data, &mime); mxc = ASReupload(args->config, oob_data->data, &mime);
if (mxc) if (mxc)
{ {
content = MatrixCreateMedia(mxc, data->data, mime); content = MatrixCreateMedia(
mxc, data->data, mime, info
);
/* Yeah, no, I'm not modifying the media creation code. */
HashMapSet(content, HashMapSet(content,
"at.kappach.at.parsee.external", "at.kappach.at.parsee.external",
JsonValueString(oob_data->data) JsonValueString(oob_data->data)
); );
ShoveStanza(content, stanza); ShoveStanza(content, stanza);
event_id = ASSend( event_id = LazySend(
args->config, mroom_id, encoded, args, encoded, mroom_id, NULL,
"m.room.message", content content, time
); );
Free(mxc); Free(mxc);
} }
FileInfoFree(info);
Free(mime); Free(mime);
} }
} }
@ -340,13 +492,13 @@ end_error:
reaction = ArrayGet(react_child, i); reaction = ArrayGet(react_child, i);
react_data = ArrayGet(reaction->children, 0); react_data = ArrayGet(reaction->children, 0);
Free(ASSend( Free(LazySend(
args->config, mroom_id, encoded, args, encoded, mroom_id, "m.reaction",
"m.reaction",
ShoveStanza( ShoveStanza(
MatrixCreateReact(event_id, react_data->data), MatrixCreateReact(event_id, react_data->data),
stanza stanza
) ),
time
)); ));
} }
Free(event_id); Free(event_id);
@ -369,6 +521,15 @@ end_error:
* too. Go figure. */ * too. Go figure. */
size_t off = size_t off =
reply_to ? ParseeFindDatastart(data->data) : 0; 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); HashMap *ev = MatrixCreateMessage(data->data + off);
if (reply_to) if (reply_to)
{ {
@ -377,9 +538,9 @@ end_error:
Free(reply_id); Free(reply_id);
} }
ShoveStanza(ev, stanza); ShoveStanza(ev, stanza);
event_id = ASSend( event_id = LazySend(
args->config, mroom_id, encoded, args, encoded, mroom_id, NULL,
"m.room.message", ev ev, time
); );
} }
pthread_mutex_lock(&thr->info->chk_lock); pthread_mutex_lock(&thr->info->chk_lock);
@ -400,9 +561,9 @@ end_error:
); );
Log(LOG_DEBUG, "Replacing events in %s(%s)", mroom_id, event_id); Log(LOG_DEBUG, "Replacing events in %s(%s)", mroom_id, event_id);
Free(ASSend( Free(LazySend(
args->config, mroom_id, encoded, args, encoded, mroom_id, NULL,
"m.room.message", ev ev, time
)); ));
ParseePushAllStanza(args, stanza, event_id); ParseePushAllStanza(args, stanza, event_id);
pthread_mutex_unlock(&thr->info->chk_lock); pthread_mutex_unlock(&thr->info->chk_lock);
@ -428,6 +589,12 @@ end_error:
} }
} }
ProcessChatstates(args, stanza);
if (args->verbosity >= PARSEE_VERBOSE_TIMINGS)
{
Log(LOG_DEBUG, "Chatstate management: %fs", Elapsed(rectime));
}
end: end:
Free(mroom_id); Free(mroom_id);
mroom_id = NULL; mroom_id = NULL;
@ -435,7 +602,12 @@ end:
Free(decode_from); Free(decode_from);
Free(room); Free(room);
Free(to); Free(to);
Log(LOG_DEBUG, "<message/> usage=%d (%s:%d)", MemoryAllocated(), __FILE__, __LINE__); Log(LOG_DEBUG,
"<message/> time=%f "
"usage=%d (%s:%d)",
(UtilTsMillis()-time)/1000.f,
MemoryAllocated(), __FILE__, __LINE__
);
return true; return true;
} }

View file

@ -56,8 +56,62 @@ GuessStatus(XMLElement *stanza)
} }
return USER_STATUS_ONLINE; return USER_STATUS_ONLINE;
} }
static Array *
GetStatuses(XMLElement *info)
{
XMLElement *child;
Array *ret;
size_t i;
if (!info)
{
return NULL;
}
ret = ArrayCreate();
for (i = 0; i < ArraySize(info->children); i++)
{
child = ArrayGet(info->children, i);
if (StrEquals(child->name, "status"))
{
ArrayAdd(ret, HashMapGet(child->attrs, "code"));
}
}
return ret;
}
static void
FreeStatuses(Array *statuses)
{
if (!statuses)
{
return;
}
ArrayFree(statuses);
}
static bool
HasStatus(Array *statuses, const char *code)
{
size_t i;
if (!statuses || !code)
{
return false;
}
for (i = 0; i < ArraySize(statuses); i++)
{
if (StrEquals(ArrayGet(statuses, i), code))
{
return true;
}
}
return false;
}
void void
PresenceStanza(ParseeData *args, XMLElement *stanza) PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
#define MUC_USER_NS "http://jabber.org/protocol/muc#user" #define MUC_USER_NS "http://jabber.org/protocol/muc#user"
XMLElement *user_info; XMLElement *user_info;
@ -65,6 +119,20 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
XMLElement *status = XMLookForUnique(stanza, "status"); XMLElement *status = XMLookForUnique(stanza, "status");
char *oid = HashMapGet(stanza->attrs, "from"); char *oid = HashMapGet(stanza->attrs, "from");
char *dst = HashMapGet(stanza->attrs, "to");
char *type = HashMapGet(stanza->attrs, "type");
if (StrEquals(type, "subscribe"))
{
Log(LOG_WARNING, "!PRESENCE SUBSCRIPTION REQUEST! (%s:%s)", oid, dst);
AddPresenceSubscriber(args, oid, dst); /* TODO: Send presence updates
* whenever possible. */
}
if (PEPManagerHandle(thr->info->pep_manager, stanza))
{
return;
}
if (ServerHasXEP421(args, oid)) if (ServerHasXEP421(args, oid))
{ {
@ -75,10 +143,6 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL; char *occ_id = occupant ? HashMapGet(occupant->attrs, "id") : NULL;
if (occ_id) if (occ_id)
{ {
Log(LOG_DEBUG,
"'%s' has support for XEP-421, fetching OID=%s",
oid, occ_id
);
ParseePushOIDTable(oid, occ_id); ParseePushOIDTable(oid, occ_id);
} }
} }
@ -86,27 +150,33 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS))) if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS)))
{ {
XMLElement *item = XMLookForUnique(user_info, "item"); XMLElement *item = XMLookForUnique(user_info, "item");
XMLElement *status = XMLookForUnique(user_info, "status"); Array *statuses = GetStatuses(user_info);
#define IsStatus(code) (status && \ #define IsStatus(code) (HasStatus(statuses, #code))
StrEquals(HashMapGet(status->attrs, "code"), #code))
char *jid = item ? HashMapGet(item->attrs, "jid") : NULL; char *jid = item ? HashMapGet(item->attrs, "jid") : NULL;
char *trim = ParseeTrimJID(jid); char *trim = ParseeTrimJID(jid);
char *from = NULL; char *from = NULL;
char *type = HashMapGet(stanza->attrs, "type");
char *room = ParseeGetBridgedRoom(args, stanza); char *room = ParseeGetBridgedRoom(args, stanza);
char *decode_from, *real_matrix; char *decode_from = NULL, *real_matrix = NULL;
char *matrix_user_pl = ParseeEncodeJID(args->config, trim, false); char *matrix_user_pl = ParseeEncodeJID(args->config, trim, false);
char *affiliation = HashMapGet(item->attrs, "affiliation"); char *affiliation = item ? HashMapGet(item->attrs, "affiliation") : NULL;
char *role = HashMapGet(item->attrs, "role"); char *role = item ? HashMapGet(item->attrs, "role") : NULL;
int power_level = 0; int power_level = 0;
char *parsee = ParseeMXID(args); char *parsee = ParseeMXID(args);
char *parsee_j = ParseeJID(args);
char *muc = ParseeTrimJID(HashMapGet(stanza->attrs, "from"));
char *parsee_muc = StrConcat(3, muc, "/", "parsee");
Free(trim); Free(trim);
if (!item)
{
goto end_item;
}
if (jid) if (jid)
{ {
ParseePushJIDTable(oid, jid); ParseePushJIDTable(oid, jid);
} }
ParseePushAffiliationTable(oid, affiliation, role);
decode_from = ParseeLookupJID(oid); decode_from = ParseeLookupJID(oid);
real_matrix = ParseeDecodeMXID(decode_from); real_matrix = ParseeDecodeMXID(decode_from);
@ -135,22 +205,16 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
if (StrEquals(role, "visitor")) if (StrEquals(role, "visitor"))
{ {
char *parsee = ParseeJID(args); if (!StrEquals(HashMapGet(stanza->attrs, "to"), parsee_j) &&
if (!StrEquals(HashMapGet(stanza->attrs, "to"), parsee) &&
IsStatus(110)) IsStatus(110))
{ {
char *muc = ParseeTrimJID(HashMapGet(stanza->attrs, "from"));
char *usr = HashMapGet(stanza->attrs, "to"); char *usr = HashMapGet(stanza->attrs, "to");
/* Ask for voice in a visitor self-presence. We do not notify /* Ask for voice in a visitor self-presence. We do not notify
* the user, as an error MUST occur, which is handled and * the user, as an error MUST occur, which is handled and
* logged out. */ * logged out. */
XMPPRequestVoice(args->jabber, usr, muc); XMPPRequestVoice(args->jabber, usr, muc);
Free(muc);
} }
Free(parsee);
} }
/* Set the user's PL /* Set the user's PL
@ -190,7 +254,7 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
{ {
ASBan(args->config, room, real_matrix); ASBan(args->config, room, real_matrix);
} }
else if (IsStatus(307)) else if (IsStatus(307) && !IsStatus(333))
{ {
ASKick(args->config, room, real_matrix); ASKick(args->config, room, real_matrix);
} }
@ -204,16 +268,37 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
ASLeave(args->config, room, real_matrix); ASLeave(args->config, room, real_matrix);
} }
} }
if (StrEquals(type, "unavailable") &&
(StrEquals(jid, parsee_muc) || StrEquals(jid, parsee_j))
&& IsStatus(301))
{
char *chat_id = ParseeGetFromRoomID(args, room);
ParseeUnlinkRoom(args, chat_id);
Free(ASSend(
args->config, room, parsee,
"m.room.message",
MatrixCreateNotice("This room has been unlinked."),
0
));
ASLeave(args->config, room, parsee);
Free(chat_id);
}
end_item:
Free(muc);
Free(from); Free(from);
Free(parsee_muc);
Free(decode_from); Free(decode_from);
Free(real_matrix); Free(real_matrix);
Free(matrix_user_pl); Free(matrix_user_pl);
Free(parsee_j);
Free(parsee); Free(parsee);
Free(room); Free(room);
FreeStatuses(statuses);
} }
if (status) if (status && !StrEquals(type, "unavailable"))
{ {
XMLElement *status_data = ArrayGet(status->children, 0); XMLElement *status_data = ArrayGet(status->children, 0);
char *decode_from = ParseeLookupJID(oid); char *decode_from = ParseeLookupJID(oid);
@ -224,6 +309,8 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
status_str = status_data->data; status_str = status_data->data;
} }
/* TODO: "The server will automatically set a user's presence to /* TODO: "The server will automatically set a user's presence to
* unavailable if their last active time was over a threshold value * unavailable if their last active time was over a threshold value
* (e.g. 5 minutes)." * (e.g. 5 minutes)."
@ -299,11 +386,8 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
vcard_request = CreateVCardRequest( vcard_request = CreateVCardRequest(
from, HashMapGet(stanza->attrs, "from") from, HashMapGet(stanza->attrs, "from")
); );
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, vcard_request);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMPPSendStanza(jabber, vcard_request, args->config->max_stanza_size);
XMLFreeElement(vcard_request); XMLFreeElement(vcard_request);
Free(from); Free(from);
} }

View file

@ -32,13 +32,17 @@
IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \ IdentitySimple("client", "pc", NAME " v" VERSION " bridge") \
IdentitySimple("component", "generic", "Parsee's component") IdentitySimple("component", "generic", "Parsee's component")
typedef struct PEPManager PEPManager; typedef struct IQFeatures {
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item); Array *identity;
Array *adverts;
} IQFeatures;
typedef struct XMPPIdentity { typedef struct XMPPIdentity {
char *category, *type, *lang, *name; char *category, *type, *lang, *name;
} XMPPIdentity; } XMPPIdentity;
typedef struct PEPManager PEPManager;
typedef void (*PEPEvent)(PEPManager *m, XMLElement *stanza, XMLElement *item);
typedef struct XMPPThread XMPPThread; typedef struct XMPPThread XMPPThread;
typedef struct XMPPThreadInfo { typedef struct XMPPThreadInfo {
/* A FIFO of stanzas inbound, to be read by dispatcher /* 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 ICollate(unsigned char *cata, unsigned char *catb);
int IdentitySort(void *idap, void *idbp); 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 * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza);
char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id); char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id);
char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza); char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza);
@ -78,7 +105,7 @@ XMLElement * CreatePubsubRequest(char *from, char *to, char *node);
bool MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr); bool MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
void IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr); void IQStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
void PresenceStanza(ParseeData *args, XMLElement *stanza); void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
bool ServerHasXEP421(ParseeData *data, char *from); bool ServerHasXEP421(ParseeData *data, char *from);
@ -89,6 +116,7 @@ PEPManager * CreatePEPManager(ParseeData *data, void *cookie);
void * PEPManagerCookie(PEPManager *manager); void * PEPManagerCookie(PEPManager *manager);
void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event); void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event);
bool PEPManagerHandle(PEPManager *manager, XMLElement *stanza); bool PEPManagerHandle(PEPManager *manager, XMLElement *stanza);
void PEPManagerBroadcast(PEPManager *manager, char *as, XMLElement *item);
void DestroyPEPManager(PEPManager *manager); void DestroyPEPManager(PEPManager *manager);
/* PEP callbacks for the handler */ /* PEP callbacks for the handler */
@ -96,3 +124,7 @@ void PEPAvatarEvent(PEPManager *m, XMLElement *stanza, XMLElement *item);
void PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item); void PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item);
char * ScrambleOID(ParseeData *data, char *opaque_oid); char * ScrambleOID(ParseeData *data, char *opaque_oid);
/* Presence management */
void AddPresenceSubscriber(ParseeData *data, char *from, char *to);
bool IsSubscribed(ParseeData *data, char *user, char *to);

View file

@ -29,7 +29,7 @@ extern void ASAuthenticateRequest(const ParseeConfig *, HttpClientContext *);
extern bool ASRegisterUser(const ParseeConfig *, char *); extern bool ASRegisterUser(const ParseeConfig *, char *);
/* Pings the homeserver to get attention. */ /* Pings the homeserver to get attention. */
extern void ASPing(const ParseeConfig *); extern bool ASPing(const ParseeConfig *);
/** Joins a room from an {id} and a given {user} we want to masquerade /** Joins a room from an {id} and a given {user} we want to masquerade
* as. * as.
@ -67,7 +67,8 @@ extern HashMap * ASFind(const ParseeConfig *, char *, char *);
/* Sends a message event with a specific type and body. /* Sends a message event with a specific type and body.
* Said body is freed during the function's execution. */ * Said body is freed during the function's execution. */
extern char * ASSend(const ParseeConfig *, char *, char *, char *, HashMap *); extern char *
ASSend(const ParseeConfig *, char *, char *, char *, HashMap *, uint64_t ts);
extern void ASType(const ParseeConfig *, char *, char *, bool); extern void ASType(const ParseeConfig *, char *, char *, bool);
extern void ASPresence(const ParseeConfig *, char *, char *, char *); extern void ASPresence(const ParseeConfig *, char *, char *, char *);
@ -141,6 +142,14 @@ extern void ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, ch
* Modifies: NOTHING */ * Modifies: NOTHING */
extern char * ASGetName(const ParseeConfig *c, char *room, char *user); extern char * ASGetName(const ParseeConfig *c, char *room, char *user);
/** Returns the user's avatar in a room, or a the global user avatar, to be
* Free'd
* -------------
* Returns: The user's name in the [HEAP] | NULL
* Thrasher: Free
* Modifies: NOTHING */
extern char * ASGetAvatar(const ParseeConfig *c, char *room, char *user);
/** Uploads data to Matrix to be used later /** Uploads data to Matrix to be used later
* ---------------- * ----------------
* Returns: A valid MXC URI[HEAP] | NULL * Returns: A valid MXC URI[HEAP] | NULL
@ -170,4 +179,16 @@ extern Array * ASGetRelations(const ParseeConfig *c, size_t n, char *room, char
* Thrashes: {relations} * Thrashes: {relations}
* See-Also: ASGetRelations */ * See-Also: ASGetRelations */
extern void ASFreeRelations(Array *relations); extern void ASFreeRelations(Array *relations);
/** Returns the MIME and SHA-1 hash of a media entry, in one fell swoop.
* -----------------
* Returns: whenever the media exists
* Modifies: {mime}[HEAP], {sha}[HEAP] */
extern bool ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha);
/** Retrieves media off an MXC link.
* ------------
* Returns: whenever the media exists
* Modifies {mime}[HEAP], {out}[HEAP], {len} */
extern bool ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len);
#endif #endif

View file

@ -26,7 +26,7 @@
Free(ASSend( \ Free(ASSend( \
data->config, id, profile, \ data->config, id, profile, \
"m.room.message", \ "m.room.message", \
MatrixCreateNotice(rep) \ MatrixCreateNotice(rep), 0 \
)); \ )); \
} \ } \
while(0) while(0)
@ -45,7 +45,7 @@
Free(ASSend( \ Free(ASSend( \
data->config, id, profile, \ data->config, id, profile, \
"m.room.message", \ "m.room.message", \
MatrixCreateNotice(formatted) \ MatrixCreateNotice(formatted), 0 \
)); \ )); \
Free(formatted); \ Free(formatted); \
} \ } \

23
src/include/FileInfo.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef PARSEE_FILEINFO_H
#define PARSEE_FILEINFO_H
#include "XML.h"
typedef struct FileInfo {
int width;
int height;
int size;
} FileInfo;
/** Grab file metadata through SIMS.
* ----------------
* Returns: File information(SIMS)[HEAP]
* Thrasher: FileInfoFree */
extern FileInfo * FileInfoFromXMPP(XMLElement *stanza);
/** Frees all information related with file metadata.
* --------------
* Thrashes: info */
extern void FileInfoFree(FileInfo *info);
#endif

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

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

View file

@ -2,6 +2,9 @@
#define PARSEE_MATRIX_H #define PARSEE_MATRIX_H
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Uri.h>
#include "FileInfo.h"
/* A simple representation of everything. It is not as complex as /* A simple representation of everything. It is not as complex as
* Telodendria's CommonID parser, simply because Parsee does not * Telodendria's CommonID parser, simply because Parsee does not
@ -18,6 +21,12 @@ typedef struct UserID {
* Thrasher: Free */ * Thrasher: Free */
extern UserID * MatrixParseID(char *user); 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. */ /* Creates an error message JSON, with the specified code and message. */
extern HashMap * MatrixCreateError(char *err, char *msg); extern HashMap * MatrixCreateError(char *err, char *msg);
@ -33,7 +42,7 @@ extern HashMap * MatrixCreateReact(char *event, char *body);
extern HashMap * MatrixCreateReplace(char *event, char *body); extern HashMap * MatrixCreateReplace(char *event, char *body);
/* Creates the content for a media file. */ /* Creates the content for a media file. */
extern HashMap * MatrixCreateMedia(char *mxc, char *body, char *mime); extern HashMap * MatrixCreateMedia(char *mxc, char *body, char *mime, FileInfo *info);
/* Creates the content for a m.room.name state event */ /* Creates the content for a m.room.name state event */
extern HashMap * MatrixCreateNameState(char *name); extern HashMap * MatrixCreateNameState(char *name);

View file

@ -18,9 +18,12 @@ typedef struct ParseeData ParseeData;
#include <Command.h> #include <Command.h>
#include <XMPP.h> #include <XMPP.h>
#include <MUCServ.h>
#define PARSEE_VERBOSE_NONE 0 #define PARSEE_VERBOSE_NONE 0
#define PARSEE_VERBOSE_LOG 1 #define PARSEE_VERBOSE_LOG 1
#define PARSEE_VERBOSE_STANZA 2 #define PARSEE_VERBOSE_TIMINGS 2
#define PARSEE_VERBOSE_STANZA 3
#define PARSEE_VERBOSE_COMICAL 53 /* MINUTES */ #define PARSEE_VERBOSE_COMICAL 53 /* MINUTES */
typedef struct ParseeConfig { typedef struct ParseeConfig {
@ -38,12 +41,18 @@ typedef struct ParseeConfig {
/* Homeserver port info */ /* Homeserver port info */
char *homeserver_host; char *homeserver_host;
int homeserver_port; int homeserver_port;
int homeserver_tls;
bool accept_pings;
bool ignore_bots;
/* ------- JABBER -------- */ /* ------- JABBER -------- */
char *component_addr;
char *component_host; char *component_host;
char *shared_comp_secret; char *shared_comp_secret;
int component_port; int component_port;
size_t max_stanza_size;
/* ------- DB -------- */ /* ------- DB -------- */
char *db_path; char *db_path;
@ -58,7 +67,9 @@ typedef struct ParseeData {
HttpRouter *router; HttpRouter *router;
CommandRouter *handler; CommandRouter *handler;
HttpServer *server;
XMPPComponent *jabber; XMPPComponent *jabber;
MUCServer *muc;
Db *db; Db *db;
char *id; char *id;
@ -67,6 +78,10 @@ typedef struct ParseeData {
HashMap *oid_servers; HashMap *oid_servers;
pthread_mutex_t oidl; pthread_mutex_t oidl;
/* If Parsee was intentionally halted */
bool halted;
pthread_mutex_t halt_lock;
} ParseeData; } ParseeData;
typedef struct Argument { typedef struct Argument {
@ -79,6 +94,7 @@ typedef struct Argument {
const char *description; const char *description;
} Argument; } Argument;
/* A few helpful macros to make JSON less of a PITA */
#define GrabString(obj, ...) JsonValueAsString(JsonGet(obj, __VA_ARGS__)) #define GrabString(obj, ...) JsonValueAsString(JsonGet(obj, __VA_ARGS__))
#define GrabInteger(obj, ...) JsonValueAsInteger(JsonGet(obj, __VA_ARGS__)) #define GrabInteger(obj, ...) JsonValueAsInteger(JsonGet(obj, __VA_ARGS__))
#define GrabBoolean(obj, ...) JsonValueAsBoolean(JsonGet(obj, __VA_ARGS__)) #define GrabBoolean(obj, ...) JsonValueAsBoolean(JsonGet(obj, __VA_ARGS__))
@ -99,9 +115,12 @@ typedef struct Argument {
/* A base64-encoded Parsee logo */ /* A base64-encoded Parsee logo */
extern const char media_parsee_logo[]; extern const char media_parsee_logo[];
/* "Unknown avatar" */
extern const char media_unknown[];
/* An ASCII-art rendition of "小橋" */ /* An ASCII-art rendition of "小橋".
#define PARSEE_ASCII_LINES 9 * I'm sorry for its quality. If anyone wants to redraw it, feel free. */
#define PARSEE_ASCII_LINES 8
extern const char *parsee_ascii[PARSEE_ASCII_LINES]; extern const char *parsee_ascii[PARSEE_ASCII_LINES];
/** /**
@ -112,6 +131,9 @@ extern void ParseePrintASCII(void);
/** /**
* Checks if two versions of Parsee can be considered "compatible". * Checks if two versions of Parsee can be considered "compatible".
* This is mainly used for things like database operations. TODO:
* Make an auto-upgrade system to comply with the (undocumented)
* rule of "Don't Make The Sysadmin Think About Parsee Too Much".
* --------------- * ---------------
* Modifies: NOTHING */ * Modifies: NOTHING */
extern bool ParseeIsCompatible(char *ver1, char *ver2); extern bool ParseeIsCompatible(char *ver1, char *ver2);
@ -262,6 +284,47 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id);
/* Finds the MUC JID from a chat ID */ /* Finds the MUC JID from a chat ID */
extern char * ParseeGetMUCID(ParseeData *, char *chat_id); extern char * ParseeGetMUCID(ParseeData *, char *chat_id);
/** Fetches a configuration value from a key in a chat(given a Chat ID),
* as a string or NULL. Keys are to be stored like Java packages(reveres DNS).
* Parsee has the right over any key with the <code>'p.'</code> prefix.
* -----------------------------------
* Returns: a valid string[HEAP] | NULL
* Modifies: NOTHING
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting */
extern char *
ParseeGetChatSetting(ParseeData *data, char *chat, char *key);
/** Fetches the entire configuration in a chat(given a Chat ID), as an hashmap
* of strings.
* -----------------------------------
* Returns: a valid string[HEAP] | NULL
* Modifies: NOTHING
* Thrasher: ParseeFreeChatSettings
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting, ParseeGetChatSetting */
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat);
/** Destroys memory allocated from a call to {ParseeGetChatSettings}.
* -----------------------
* Returns: NOTHING
* Modifies: {settings}
* Thrashes: {settings}
* See-Also: ParseeGetChatSettings */
extern void
ParseeFreeChatSettings(HashMap *settings);
/** Replaces a configuration key-value pair within the chat's context, which
* can be read with {ParseeGetChatSetting}.
* -------------------------------------
* Returns: NOTHING
* Modifies: the chat context
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeGetChatSetting */
extern void
ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val);
extern bool
ParseeIsMediaEnabled(ParseeData *data, char *chat_id);
/* Pushes a stanza ID to a chat ID */ /* Pushes a stanza ID to a chat ID */
extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender);
extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender);
@ -278,7 +341,11 @@ extern bool ParseeGetDMOrigin(ParseeData *data, char *chat_id, char *ev, char **
/* Sends presence requests for every MUC around as a fake JID */ /* Sends presence requests for every MUC around as a fake JID */
extern void ParseeSendPresence(ParseeData *); extern void ParseeSendPresence(ParseeData *);
extern bool ParseeInitialiseSignals(HttpServer *, pthread_t, XMPPComponent *); /** Initialises signal-handling code within Parsee.
* --------------------
* Modifies: the signal handler
* Returns: whenever it has properly been setup */
extern bool ParseeInitialiseSignals(ParseeData *data, pthread_t xmpp);
/* Job used to cleanup Parsee data that isn't necessary anymore. */ /* Job used to cleanup Parsee data that isn't necessary anymore. */
extern void ParseeCleanup(void *data); extern void ParseeCleanup(void *data);
@ -286,9 +353,13 @@ extern void ParseeCleanup(void *data);
/* Finds the offset of the first line without a '>' at its start. */ /* Finds the offset of the first line without a '>' at its start. */
extern int ParseeFindDatastart(char *data); extern int ParseeFindDatastart(char *data);
/* Finds the offset of the first line without a '>' at its start(as
* Unicode codepoints). */
extern int ParseeFindDatastartU(char *data);
/* XMPP-ifies a message event to XEP-0393 if possible. */ /* 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 */ /* Finds an event ID from an ID in the stanza's attributes */
extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id); extern char * ParseeEventFromID(ParseeData *d, char *c_id, char *ori_id);
@ -298,13 +369,18 @@ extern char * ParseeDMEventFromID(ParseeData *d, char *r_id, char *ori_id);
extern char * ParseeDMEventFromSID(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 */ /* 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 ParseeInitialiseOIDTable(void);
extern void ParseePushOIDTable(char *muc, char *occupant); extern void ParseePushOIDTable(char *muc, char *occupant);
extern char *ParseeLookupOID(char *muc); extern char *ParseeLookupOID(char *muc);
extern void ParseeDestroyOIDTable(void); extern void ParseeDestroyOIDTable(void);
extern void ParseeInitialiseAffiliationTable(void);
extern void ParseePushAffiliationTable(char *user, char *affiliation, char *role);
extern bool ParseeLookupAffiliation(char *muc, char **affiliation, char **role);
extern void ParseeDestroyAffiliationTable(void);
extern void ParseeInitialiseJIDTable(void); extern void ParseeInitialiseJIDTable(void);
extern void ParseePushJIDTable(char *muc, char *bare); extern void ParseePushJIDTable(char *muc, char *bare);
extern char *ParseeLookupJID(char *muc); extern char *ParseeLookupJID(char *muc);
@ -324,10 +400,17 @@ extern void ParseeDestroyNickTable(void);
* to ban them from rooms where Parsee has the ability to do so ("noflying"). * to ban them from rooms where Parsee has the ability to do so ("noflying").
* --------------- * ---------------
* Returns: NOTHING * Returns: NOTHING
* See-Also: ParseeManageBan * See-Also: ParseeManageBan, ParseeGlobalUnban
* Modifies: the database */ * Modifies: the database */
extern void ParseeGlobalBan(ParseeData *, char *user, char *reason); 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), /** Verifies if a user was banned globally. If so (and if {room} is set),
* tries to ban the user from it. * tries to ban the user from it.
* --------------- * ---------------
@ -340,7 +423,8 @@ extern bool ParseeManageBan(ParseeData *, char *user, char *room);
extern bool ParseeVerifyDMStanza(ParseeData *data, char *room_id, char *id); extern bool ParseeVerifyDMStanza(ParseeData *data, char *room_id, char *id);
/** Checks if a Matrix/XMPP user is considered as "administrator" by Parsee. /** Checks if a Matrix/XMPP user is considered as "administrator" by
* Parsee.
* ---------------------- * ----------------------
* Returns: (whenever the user is an admin) * Returns: (whenever the user is an admin)
* Modifies: NOTHING */ * Modifies: NOTHING */
@ -401,4 +485,22 @@ extern void ParseeSetThreads(int xmpp, int http);
extern char * ParseeHMAC(char *key, uint8_t *msg, size_t msglen); extern char * ParseeHMAC(char *key, uint8_t *msg, size_t msglen);
#define ParseeHMACS(key, msg) ParseeHMAC(key, (uint8_t *) msg, strlen(msg)) #define ParseeHMACS(key, msg) ParseeHMAC(key, (uint8_t *) msg, strlen(msg))
/** Broadcasts a stanza from a user to any that may be interested by it
* (PEP or subscription)
* -------------------------------------
* Returns: NOTHING */
extern void ParseeBroadcastStanza(ParseeData *data, char *from, XMLElement *s);
/** Destroys the mapping between a MUC and a room from a chat ID.
* ----------------
* Modifies: the DB's room mappings
* Returns: NOTHING */
extern void ParseeUnlinkRoom(ParseeData *data, char *chat_id);
/** Verifies if there is no whitelists OR that a MUC's server is whitelisted.
* ----------------------
* Returns: if a MUC is to be allowed
* Modifies: NOTHING */
extern bool ParseeIsMUCWhitelisted(ParseeData *data, char *muc);
#endif #endif

View file

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

View file

@ -16,7 +16,7 @@ extern StanzaBuilder * SetStanzaID(StanzaBuilder *builder, char *id);
extern StanzaBuilder * SetStanzaXParsee(StanzaBuilder *builder, HashMap *e); extern StanzaBuilder * SetStanzaXParsee(StanzaBuilder *builder, HashMap *e);
extern StanzaBuilder * AddStanzaElem(StanzaBuilder *builder, XMLElement *item); extern StanzaBuilder * AddStanzaElem(StanzaBuilder *builder, XMLElement *item);
extern XMLElement * ExportStanza(StanzaBuilder *builder); extern XMLElement * ExportStanza(StanzaBuilder *builder);
extern void WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber); extern void WriteoutStanza(StanzaBuilder *builder, XMPPComponent *jabber, size_t max);
extern void DestroyStanzaBuilder(StanzaBuilder *builder); extern void DestroyStanzaBuilder(StanzaBuilder *builder);
#endif #endif

View file

@ -4,7 +4,7 @@
#include <Cytoplasm/Stream.h> #include <Cytoplasm/Stream.h>
/* Creates a string stream writer. The referenced buffer must be in the heap, /* Creates a string stream writer. The referenced buffer must be in the heap,
* or NULL. */ * or NULL for an empty string. */
extern Stream * StrStreamWriter(char **buffer); extern Stream * StrStreamWriter(char **buffer);
/* Creates a string stream reader. The referenced buffer may be everywhere. */ /* Creates a string stream reader. The referenced buffer may be everywhere. */

Some files were not shown because too many files have changed in this diff Show more